Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion docs/explanation.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,6 @@ readers who know Vercel Chat SDK, this is the concept-by-concept status map:
| Observability metrics/tracing | Optional `Observer` seam, no-op default, no OTel dependency in core |
| Message history persistence | App-owned (Thread Application State); thin live read-through via the `HistoryReader` Optional Capability (Slack, Linear) |
| AI-message conversion helpers | Not implemented |
| Multiple production adapters | Slack is the only `supported` adapter; Linear is `experimental` |
| Middleware | Not implemented |

The behavioral differences that matter when porting handler code — single-slot
Expand Down
3 changes: 2 additions & 1 deletion docs/how-to/linear-agent-sessions.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ through typed adapter access (see [ADR 0001](../adr/0001-linear-app-actor-slice.
Start from the runnable example:
[`examples/linear-agent-hello-world`](../../examples/linear-agent-hello-world/README.md)
walks through the Linear OAuth app setup (app-actor client credentials,
webhook configuration, public HTTPS URL) and includes dogfooding notes.
webhook configuration, public HTTPS URL), lists the expected behavior, and
notes common setup problems.

## Construct The Adapter

Expand Down
18 changes: 6 additions & 12 deletions docs/linear-agent-capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,18 +59,12 @@ the core adapter.

**Status:** Inherent limitation; needs an application-owned pattern.

The `stop` signal arrives as a prompted event on the same thread, so it is
serialized behind the thread lock like any other event: it cannot preempt a
handler that is already running (`ConcurrencyDrop` discards it during a
conflict; `ConcurrencyQueue` delivers it only after the in-flight handler
returns). There is no pre-lock interception hook, so Linear's Stop control
cannot drive active cancellation through this adapter today. Workable
patterns: split sessions into short handler turns that check `StopRequested`
at each turn boundary, or deliver the stop out-of-band (an application-owned
webhook/endpoint outside the runtime's serialized dispatch that sets a
cancellation flag handlers poll). The turn-boundary pattern is worked through
in [`docs/how-to/linear-agent-sessions.md`](how-to/linear-agent-sessions.md)
(`confirmStop`); the serialization limitation itself remains.
The `stop` signal arrives as a prompted event on the same thread, so the
thread lock serializes it like any other event and it cannot preempt a
running handler. The
[Linear agent sessions guide](how-to/linear-agent-sessions.md#use-the-full-activity-surface)
explains the limitation and the workable patterns (short handler turns that
check `StopRequested`, or an out-of-band cancellation flag).

### 3. Best-Practice Webhook Categories

Expand Down
3 changes: 3 additions & 0 deletions docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,9 @@ public reply. Fallback is explicit:
operation returns no delivered message.
- If fallback is requested but impossible, the operation returns an error.

On Slack, DM fallback opens the conversation with `conversations.open`, so the
app needs the `im:write` bot scope.

Ephemeral delivery is an optional adapter capability expressed through a
small Go interface (`EphemeralPoster`), not a string capability flag. The
Slack adapter implements it; on adapters that do not, `PostEphemeral` returns
Expand Down
30 changes: 23 additions & 7 deletions docs/tutorials/slack-bot.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,12 @@ Then configure it:
| `im:history` | Only needed if you also want direct messages to reach the bot. |

2. In **App Home**, under **Show Tabs**, enable the **Messages Tab** and allow
users to send messages from it (Slack labels this "Allow users to send
Slash commands and messages from the messages tab"). This matters only for
direct messages; mentions in channels work without it.
users to send messages from it (depending on Slack's current UI, this is
labeled "Allow users to send Slash commands and messages from the messages
tab" or appears as turning off read-only mode). This matters only for
direct messages; mentions in channels work without it. If Slack later
shows **"Sending messages to this app has been turned off"** in the app's
DM, this setting is still off.

3. In **OAuth & Permissions**, click **Install to Workspace** and approve the
app.
Expand Down Expand Up @@ -104,13 +107,17 @@ on the first message. When the bot is up you should see a log line like:

Slack must be able to reach your machine over public HTTPS. In a second
terminal, expose port 8080 with your tunnel of choice. For example, with
Tailscale Funnel:
Tailscale Funnel (not `tailscale serve`, which is visible only inside your
tailnet; Funnel must be enabled for your tailnet):

```sh
tailscale funnel --bg --https=443 localhost:8080
tailscale funnel status
```

Run `tailscale funnel reset` when you are done to turn the public endpoint
off.

or with ngrok:

```sh
Expand Down Expand Up @@ -209,6 +216,15 @@ Two things to notice:
- Read the [architecture explanation](../explanation.md) to understand the
model behind what you just built.

The example's own [README](../../examples/slack-hello-world/README.md) repeats
the Slack app setup with more detail (including Tailscale Funnel specifics)
if you need to revisit it later.
Slack's own documentation for the settings used here:

- App dashboard: <https://api.slack.com/apps>
- Events API request URLs: <https://docs.slack.dev/apis/events-api/>
- Events: [`app_mention`](https://docs.slack.dev/reference/events/app_mention/),
[`message.channels`](https://docs.slack.dev/reference/events/message.channels),
[`message.im`](https://docs.slack.dev/reference/events/message.im)
- App Home Messages tab: <https://docs.slack.dev/surfaces/app-home>
- Bot tokens (`xoxb-`): <https://docs.slack.dev/authentication/tokens>
- Scopes: [`chat:write`](https://docs.slack.dev/reference/scopes/chat.write);
[`conversations.open`](https://docs.slack.dev/reference/methods/conversations.open/)
lists the scopes DM fallback needs
60 changes: 3 additions & 57 deletions examples/linear-agent-hello-world/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,53 +72,9 @@ assignment-created sessions started arriving.
## Expose Localhost

Linear requires a public HTTPS endpoint. For local development, expose port
`8080` with a tunnel such as `ngrok`, `cloudflared`, Tailscale Funnel, or another
HTTPS forwarding tool, then use that public HTTPS URL as `YOUR_PUBLIC_HOST`.

### Expose Localhost With Tailscale Funnel

Tailscale has two similar commands:

- `tailscale serve` shares a local service only inside your tailnet.
- `tailscale funnel` shares a local service on the public internet.

Linear needs a public HTTPS URL, so use Funnel rather than Serve.

Start the example on port `8080`:

```sh
export LINEAR_WEBHOOK_SECRET="..."
export LINEAR_CLIENT_CREDENTIALS_CLIENT_ID="..."
export LINEAR_CLIENT_CREDENTIALS_CLIENT_SECRET="..."
export PORT=8080

go run ./examples/linear-agent-hello-world
```

In another terminal, expose it with Funnel:

```sh
tailscale funnel --bg --https=443 localhost:8080
tailscale funnel status
```

The status output should show a public HTTPS URL similar to:

```text
https://your-machine.your-tailnet.ts.net
```

Use that URL in Linear's webhook settings:

```text
https://your-machine.your-tailnet.ts.net/webhooks/linear
```

When you are done, turn the public endpoint off:

```sh
tailscale funnel reset
```
`8080` with a tunnel such as Tailscale Funnel, `ngrok`, or `cloudflared` (see
[Step 5 of the Slack tutorial](../../docs/tutorials/slack-bot.md#step-5-expose-the-bot-to-slack)
for Funnel commands), then use that public HTTPS URL as `YOUR_PUBLIC_HOST`.

## Run

Expand Down Expand Up @@ -170,16 +126,6 @@ Expected behavior:

A follow-up carrying Linear's stop signal gets a stop confirmation instead.

## Dogfooding Evidence

Before claiming a live Linear dogfood passed, capture screenshots or video of:

- the Linear app actor settings;
- the webhook configuration with agent session events enabled;
- the first app mention and created agent session;
- the ephemeral thought and final response;
- a follow-up prompt and follow-up thought/response.

## Notes
Comment thread
ThomasK33 marked this conversation as resolved.

- State is in memory, so subscriptions and dedupe data are lost when the process
Expand Down
129 changes: 11 additions & 118 deletions examples/slack-hello-world/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,116 +10,20 @@ restart. Use `examples/slack-redis-state`, `examples/slack-postgres-state`, or

## Slack App Setup

Create or open a Slack app from the Slack app dashboard:
Set up a Slack app as in the [tutorial](../../docs/tutorials/slack-bot.md):
Steps 2–3 create the app and collect credentials, Step 5 exposes your
machine, and Step 6 subscribes to events.

https://api.slack.com/apps

In **OAuth & Permissions**, add these **Bot Token Scopes**:

| Scope | Why this example needs it |
| Setting | Value |
| --- | --- |
| `chat:write` | Send the portable Markdown reply with `chat.postMessage`. |
| `app_mentions:read` | Receive `app_mention` events when the bot is mentioned. |
| `im:history` | Optional for channel-only testing, required if you subscribe to `message.im` so direct messages reach the bot. |

This example does not use ephemeral DM fallback. If you change it to call
`PostEphemeral` with `FallbackToDM: true`, also add `im:write` so the adapter can
open a DM with `conversations.open`.

In **App Home**:

1. Under **Show Tabs**, enable the **Messages Tab**.
2. Make the Messages tab writable. Depending on Slack's current UI, this appears
as disabling read-only mode or enabling **Allow users to send Slash commands
and messages from the messages tab**.

If Slack shows **"Sending messages to this app has been turned off"** in the app
DM, this App Home Messages tab setting is still off or read-only.

In **Event Subscriptions**:

1. Enable events.
2. Set the request URL to:

```text
https://YOUR_PUBLIC_HOST/webhooks/slack
```

3. Subscribe to the `app_mention` **Bot User Event**. Also add `message.im`
if you want direct messages to reach the bot (it needs the `im:history`
scope).

4. Save changes.

After changing scopes, App Home settings, or event subscriptions, reinstall the
app from **OAuth & Permissions** so Slack applies the new configuration to your
workspace.

The app must be reachable from Slack. For local development, expose port `8080`
with a tunnel such as `ngrok`, `cloudflared`, or another HTTPS forwarding tool,
then use that public HTTPS URL as `YOUR_PUBLIC_HOST`.

### Expose Localhost With Tailscale Funnel

Tailscale has two similar commands:

- `tailscale serve` shares a local service only inside your tailnet.
- `tailscale funnel` shares a local service on the public internet.

Slack needs a public HTTPS URL, so use Funnel rather than Serve.

First, make sure Tailscale is installed, running, and signed in. Funnel must be
enabled for your tailnet; the CLI may prompt you to approve this in the Tailscale
admin console.

Start the example on port `8080`:

```sh
export SLACK_SIGNING_SECRET="..."
export SLACK_BOT_TOKEN="xoxb-..."
export CHAT_DEMO_IN_MEMORY_STATE=1
export PORT=8080

go run ./examples/slack-hello-world
```

In another terminal, expose it with Funnel:

```sh
tailscale funnel --bg --https=443 localhost:8080
tailscale funnel status
```

The status output should show a public HTTPS URL similar to:

```text
https://your-machine.your-tailnet.ts.net
```
| Bot token scopes | `chat:write`, `app_mentions:read` (add `im:history` for direct messages) |
| Bot events | `app_mention` (add `message.im` for direct messages) |
| Request URL | `https://YOUR_PUBLIC_HOST/webhooks/slack` |
| `SLACK_BOT_TOKEN` | **OAuth & Permissions** → Bot User OAuth Token (`xoxb-…`) |
| `SLACK_SIGNING_SECRET` | **Basic Information** → App Credentials → Signing Secret |

Use that URL in Slack's **Event Subscriptions** request URL:

```text
https://your-machine.your-tailnet.ts.net/webhooks/slack
```

When you are done, turn the public endpoint off:

```sh
tailscale funnel reset
```

## Install And Get Credentials

1. Go to **OAuth & Permissions**.
2. Click **Install to Workspace** or **Reinstall to Workspace** after changing
scopes, App Home settings, or event subscriptions.
3. Copy **Bot User OAuth Token**. It starts with `xoxb-`; use it as
`SLACK_BOT_TOKEN`.
4. Go to **Basic Information**.
5. In **App Credentials**, copy **Signing Secret**; use it as
`SLACK_SIGNING_SECRET`.

Treat both values like passwords.
Reinstall the app from **OAuth & Permissions** after changing scopes or
events.

## Run

Expand Down Expand Up @@ -156,14 +60,3 @@ If you enabled `message.im`, you can also send a direct message to the bot.
- Slack request signatures are verified with `SLACK_SIGNING_SECRET`.
- The bot token is used for `auth.test` during adapter startup and
`chat.postMessage` when replying.

Relevant Slack docs:

- App dashboard: https://api.slack.com/apps
- Events API request URLs: https://docs.slack.dev/apis/events-api/
- `app_mention` event: https://docs.slack.dev/reference/events/app_mention/
- `message.im` event: https://docs.slack.dev/reference/events/message.im
- App Home Messages tab: https://docs.slack.dev/surfaces/app-home
- `chat:write` scope: https://docs.slack.dev/reference/scopes/chat.write
- `conversations.open` scopes: https://docs.slack.dev/reference/methods/conversations.open/
- Bot tokens and `xoxb-` tokens: https://docs.slack.dev/authentication/tokens
Loading
Loading