@cursor/july 0.1.16 → 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -4
- package/dist/bin/agent-serve.js +60 -2
- package/dist/channels/slack/api.d.ts.map +1 -1
- package/dist/channels/slack/api.js +13 -3
- package/dist/channels/slack/auth.d.ts +7 -1
- package/dist/channels/slack/auth.d.ts.map +1 -1
- package/dist/channels/slack/auth.js +14 -0
- package/dist/channels/slack/blocks.d.ts +61 -0
- package/dist/channels/slack/blocks.d.ts.map +1 -0
- package/dist/channels/slack/blocks.js +90 -0
- package/dist/channels/slack/cursor-account.d.ts.map +1 -1
- package/dist/channels/slack/cursor-account.js +1 -1
- package/dist/channels/slack/defaults.d.ts +2 -0
- package/dist/channels/slack/defaults.d.ts.map +1 -1
- package/dist/channels/slack/defaults.js +29 -3
- package/dist/channels/slack/dispatch.d.ts +3 -17
- package/dist/channels/slack/dispatch.d.ts.map +1 -1
- package/dist/channels/slack/dispatch.js +4 -1
- package/dist/channels/slack/inbound.d.ts.map +1 -1
- package/dist/channels/slack/inbound.js +6 -1
- package/dist/channels/slack/index.d.ts +2 -0
- package/dist/channels/slack/index.d.ts.map +1 -1
- package/dist/channels/slack/index.js +2 -0
- package/dist/channels/slack/init.d.ts.map +1 -1
- package/dist/channels/slack/init.js +4 -1
- package/dist/channels/slack/interactive.d.ts +39 -3
- package/dist/channels/slack/interactive.d.ts.map +1 -1
- package/dist/channels/slack/interactive.js +153 -7
- package/dist/channels/slack/manifest.d.ts +13 -0
- package/dist/channels/slack/manifest.d.ts.map +1 -1
- package/dist/channels/slack/manifest.js +36 -14
- package/dist/channels/slack/message-body.d.ts +13 -0
- package/dist/channels/slack/message-body.d.ts.map +1 -0
- package/dist/channels/slack/message-body.js +82 -0
- package/dist/channels/slack/setup.d.ts +4 -1
- package/dist/channels/slack/setup.d.ts.map +1 -1
- package/dist/channels/slack/setup.js +16 -2
- package/dist/channels/slack/slack-channel.d.ts.map +1 -1
- package/dist/channels/slack/slack-channel.js +24 -3
- package/dist/channels/slack/thread-context.d.ts +0 -2
- package/dist/channels/slack/thread-context.d.ts.map +1 -1
- package/dist/channels/slack/thread-context.js +1 -65
- package/dist/channels/slack/types.d.ts +93 -1
- package/dist/channels/slack/types.d.ts.map +1 -1
- package/dist/connections.d.ts +4 -1
- package/dist/connections.d.ts.map +1 -1
- package/dist/connections.js +4 -1
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +3 -3
- package/dist/docs/assets/{app.DjHC-Ggf.js → app.BR5EdVSq.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.BOzkeUi8.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.DwviwmIq.js → VPLocalSearchBox.CNCzfUKC.js} +1 -1
- package/dist/docs/assets/chunks/{theme.BIpeYSon.js → theme.CwKvMSWv.js} +2 -2
- package/dist/docs/assets/{example-agents_security-reviewer.md.BZSNycMf.js → example-agents_security-reviewer.md.Dhj_m7_B.js} +1 -1
- package/dist/docs/assets/{guides_mcp-oauth.md.DqmEOQY1.js → guides_mcp-oauth.md.Dd8EgSem.js} +3 -2
- package/dist/docs/assets/{guides_mcp-oauth.md.DqmEOQY1.lean.js → guides_mcp-oauth.md.Dd8EgSem.lean.js} +1 -1
- package/dist/docs/assets/{guides_slack.md.FWale6Ip.js → guides_slack.md.D_xFKlnF.js} +1 -1
- package/dist/docs/assets/{reference_connections.md.CsSr3rDz.js → reference_connections.md.B9Q3TOve.js} +12 -5
- package/dist/docs/assets/{reference_connections.md.CsSr3rDz.lean.js → reference_connections.md.B9Q3TOve.lean.js} +1 -1
- package/dist/docs/building-with-agents.html +3 -3
- package/dist/docs/concepts.html +3 -3
- package/dist/docs/deployment.html +3 -3
- package/dist/docs/evals.html +3 -3
- package/dist/docs/example-agents/approval-buddy.html +3 -3
- package/dist/docs/example-agents/benny.html +3 -3
- package/dist/docs/example-agents/bugbot.html +3 -3
- package/dist/docs/example-agents/codebase-wiki.html +3 -3
- package/dist/docs/example-agents/codeowners-review.html +3 -3
- package/dist/docs/example-agents/concierge.html +3 -3
- package/dist/docs/example-agents/fsd.html +3 -3
- package/dist/docs/example-agents/index.html +3 -3
- package/dist/docs/example-agents/knowledge-base.html +3 -3
- package/dist/docs/example-agents/oncall.html +3 -3
- package/dist/docs/example-agents/security-reviewer.html +5 -5
- package/dist/docs/example-agents/slack-agent.html +3 -3
- package/dist/docs/example-agents/weather-agent.html +3 -3
- package/dist/docs/guides/agent-to-agent.html +3 -3
- package/dist/docs/guides/cloud-runtime.html +3 -3
- package/dist/docs/guides/github.html +3 -3
- package/dist/docs/guides/human-in-the-loop.html +3 -3
- package/dist/docs/guides/mcp-oauth.html +6 -5
- package/dist/docs/guides/slack.html +5 -5
- package/dist/docs/guides/webhooks.html +3 -3
- package/dist/docs/hashmap.json +1 -1
- package/dist/docs/hillclimbing.html +3 -3
- package/dist/docs/index.html +3 -3
- package/dist/docs/quickstart.html +3 -3
- package/dist/docs/reference/agent-config.html +3 -3
- package/dist/docs/reference/channels.html +3 -3
- package/dist/docs/reference/cli.html +3 -3
- package/dist/docs/reference/connections.html +14 -7
- package/dist/docs/reference/hooks.html +3 -3
- package/dist/docs/reference/http-api.html +3 -3
- package/dist/docs/reference/instructions.html +3 -3
- package/dist/docs/reference/playground.html +3 -3
- package/dist/docs/reference/project-layout.html +3 -3
- package/dist/docs/reference/prompt.html +3 -3
- package/dist/docs/reference/schedules.html +3 -3
- package/dist/docs/reference/sessions.html +3 -3
- package/dist/docs/reference/skills.html +3 -3
- package/dist/docs/reference/subagents.html +3 -3
- package/dist/docs/reference/tools.html +3 -3
- package/dist/docs/scaffolding-agents.html +3 -3
- package/dist/docs/storage.html +3 -3
- package/dist/docs/troubleshooting.html +3 -3
- package/dist/evals/assertions.d.ts +190 -0
- package/dist/evals/assertions.d.ts.map +1 -0
- package/dist/evals/assertions.js +461 -0
- package/dist/evals/expect.d.ts +95 -0
- package/dist/evals/expect.d.ts.map +1 -0
- package/dist/evals/expect.js +194 -0
- package/dist/evals/judge.d.ts +133 -0
- package/dist/evals/judge.d.ts.map +1 -0
- package/dist/evals/judge.js +228 -0
- package/dist/evals/loaders.d.ts +51 -0
- package/dist/evals/loaders.d.ts.map +1 -0
- package/dist/evals/loaders.js +124 -0
- package/dist/evals/matchers.d.ts +95 -0
- package/dist/evals/matchers.d.ts.map +1 -0
- package/dist/evals/matchers.js +145 -0
- package/dist/evals/reporters.d.ts +70 -0
- package/dist/evals/reporters.d.ts.map +1 -0
- package/dist/evals/reporters.js +221 -0
- package/dist/evals/results.d.ts +103 -0
- package/dist/evals/results.d.ts.map +1 -0
- package/dist/evals/results.js +28 -0
- package/dist/evals/run-facts.d.ts +77 -0
- package/dist/evals/run-facts.d.ts.map +1 -0
- package/dist/evals/run-facts.js +187 -0
- package/dist/evals.d.ts +118 -31
- package/dist/evals.d.ts.map +1 -1
- package/dist/evals.js +46 -12
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/internal/builtin-tools/index.d.ts +10 -0
- package/dist/internal/builtin-tools/index.d.ts.map +1 -0
- package/dist/internal/builtin-tools/index.js +27 -0
- package/dist/internal/builtin-tools/reminders.d.ts +9 -0
- package/dist/internal/builtin-tools/reminders.d.ts.map +1 -0
- package/dist/internal/builtin-tools/reminders.js +138 -0
- package/dist/internal/cli-ax.d.ts +27 -0
- package/dist/internal/cli-ax.d.ts.map +1 -1
- package/dist/internal/cli-ax.js +90 -12
- package/dist/internal/cli-mcp.d.ts +8 -0
- package/dist/internal/cli-mcp.d.ts.map +1 -1
- package/dist/internal/cli-mcp.js +98 -0
- package/dist/internal/cli-slack.d.ts +29 -1
- package/dist/internal/cli-slack.d.ts.map +1 -1
- package/dist/internal/cli-slack.js +799 -13
- package/dist/internal/cursor-event-relay.d.ts +30 -0
- package/dist/internal/cursor-event-relay.d.ts.map +1 -1
- package/dist/internal/cursor-event-relay.js +37 -0
- package/dist/internal/deploy-client.d.ts +7 -0
- package/dist/internal/deploy-client.d.ts.map +1 -1
- package/dist/internal/deploy-client.js +9 -0
- package/dist/internal/discovery.d.ts.map +1 -1
- package/dist/internal/discovery.js +70 -9
- package/dist/internal/distribution.d.ts.map +1 -1
- package/dist/internal/distribution.js +2 -0
- package/dist/internal/eval-judge-model.d.ts +59 -0
- package/dist/internal/eval-judge-model.d.ts.map +1 -0
- package/dist/internal/eval-judge-model.js +131 -0
- package/dist/internal/eval-run-store.d.ts.map +1 -1
- package/dist/internal/eval-run-store.js +12 -20
- package/dist/internal/eval-runner.d.ts +31 -25
- package/dist/internal/eval-runner.d.ts.map +1 -1
- package/dist/internal/eval-runner.js +329 -126
- package/dist/internal/evals-client.d.ts.map +1 -1
- package/dist/internal/evals-client.js +4 -1
- package/dist/internal/local-env.d.ts +8 -0
- package/dist/internal/local-env.d.ts.map +1 -0
- package/dist/internal/local-env.js +47 -0
- package/dist/internal/playground/static.d.ts +3 -0
- package/dist/internal/playground/static.d.ts.map +1 -1
- package/dist/internal/playground/static.js +3 -0
- package/dist/internal/reminder-runner.d.ts +7 -0
- package/dist/internal/reminder-runner.d.ts.map +1 -1
- package/dist/internal/reminder-runner.js +21 -0
- package/dist/internal/run-client.d.ts +15 -0
- package/dist/internal/run-client.d.ts.map +1 -1
- package/dist/internal/run-client.js +3 -2
- package/dist/internal/sdk-runner.d.ts +51 -1
- package/dist/internal/sdk-runner.d.ts.map +1 -1
- package/dist/internal/sdk-runner.js +176 -36
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +11 -1
- package/dist/internal/session-engine.d.ts +18 -5
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +81 -19
- package/dist/internal/slack-provision-client.d.ts +133 -0
- package/dist/internal/slack-provision-client.d.ts.map +1 -0
- package/dist/internal/slack-provision-client.js +286 -0
- package/dist/playground/assets/cursor-icons-16-f_W_ogc-.woff2 +0 -0
- package/dist/playground/assets/index-DRjS5klQ.js +85 -0
- package/dist/playground/assets/index-TCDs-UhZ.css +1 -0
- package/dist/playground/index.html +2 -2
- package/dist/types.d.ts +98 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +26 -0
- package/docs/example-agents/security-reviewer.md +1 -1
- package/docs/guides/mcp-oauth.md +10 -5
- package/docs/guides/slack.md +3 -2
- package/docs/reference/connections.md +28 -18
- package/package.json +20 -2
- package/skills/create-agent/SKILL.md +15 -7
- package/skills/debug/SKILL.md +3 -1
- package/skills/mcp-auth/SKILL.md +1 -1
- package/skills/setup-slack/SKILL.md +177 -11
- package/src/bin/agent-serve.ts +63 -2
- package/src/channels/slack/api.ts +16 -3
- package/src/channels/slack/auth.ts +16 -1
- package/src/channels/slack/blocks.ts +139 -0
- package/src/channels/slack/cursor-account.ts +1 -0
- package/src/channels/slack/defaults.ts +32 -4
- package/src/channels/slack/dispatch.ts +6 -19
- package/src/channels/slack/inbound.ts +6 -1
- package/src/channels/slack/index.ts +2 -0
- package/src/channels/slack/init.ts +3 -0
- package/src/channels/slack/interactive.ts +184 -19
- package/src/channels/slack/manifest.ts +37 -6
- package/src/channels/slack/message-body.ts +90 -0
- package/src/channels/slack/setup.ts +16 -2
- package/src/channels/slack/slack-channel.ts +37 -6
- package/src/channels/slack/thread-context.ts +1 -69
- package/src/channels/slack/types.ts +105 -1
- package/src/connections.ts +4 -1
- package/src/evals/assertions.ts +704 -0
- package/src/evals/expect.ts +272 -0
- package/src/evals/judge.ts +346 -0
- package/src/evals/loaders.ts +113 -0
- package/src/evals/matchers.ts +246 -0
- package/src/evals/reporters.ts +274 -0
- package/src/evals/results.ts +132 -0
- package/src/evals/run-facts.ts +274 -0
- package/src/evals.ts +205 -42
- package/src/index.ts +2 -0
- package/src/internal/builtin-tools/index.ts +40 -0
- package/src/internal/builtin-tools/reminders.ts +158 -0
- package/src/internal/cli-ax.ts +134 -15
- package/src/internal/cli-mcp.ts +118 -5
- package/src/internal/cli-slack.ts +1017 -15
- package/src/internal/cursor-event-relay.ts +67 -0
- package/src/internal/deploy-client.ts +10 -0
- package/src/internal/discovery.ts +83 -5
- package/src/internal/distribution.ts +2 -0
- package/src/internal/eval-judge-model.ts +148 -0
- package/src/internal/eval-run-store.ts +10 -14
- package/src/internal/eval-runner.ts +460 -214
- package/src/internal/evals-client.ts +5 -0
- package/src/internal/local-env.ts +53 -0
- package/src/internal/playground/static.ts +3 -0
- package/src/internal/reminder-runner.ts +30 -0
- package/src/internal/run-client.ts +17 -1
- package/src/internal/sdk-runner.ts +234 -35
- package/src/internal/server.ts +12 -0
- package/src/internal/session-engine.ts +103 -21
- package/src/internal/slack-provision-client.ts +442 -0
- package/src/types.ts +125 -4
- package/dist/channels/slack/post-update-delivery.d.ts +0 -85
- package/dist/channels/slack/post-update-delivery.d.ts.map +0 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.DZtu0ZIc.js +0 -1
- package/dist/internal/json-dir-store.d.ts +0 -32
- package/dist/internal/json-dir-store.d.ts.map +0 -1
- package/dist/internal/persistence-coordinator.d.ts +0 -127
- package/dist/internal/persistence-coordinator.d.ts.map +0 -1
- package/dist/persistence.d.ts +0 -184
- package/dist/persistence.d.ts.map +0 -1
- package/dist/playground/assets/cursor-icons-16-CQ50JpfO.woff2 +0 -0
- package/dist/playground/assets/index-Bwl4Onx4.js +0 -85
- package/dist/playground/assets/index-ByV4nfch.css +0 -1
- /package/dist/docs/assets/{example-agents_security-reviewer.md.BZSNycMf.lean.js → example-agents_security-reviewer.md.Dhj_m7_B.lean.js} +0 -0
- /package/dist/docs/assets/{guides_slack.md.FWale6Ip.lean.js → guides_slack.md.D_xFKlnF.lean.js} +0 -0
package/skills/mcp-auth/SKILL.md
CHANGED
|
@@ -22,7 +22,7 @@ Docs: `docs/guides/mcp-oauth.md`, `docs/reference/connections.md`,
|
|
|
22
22
|
|
|
23
23
|
| Need | Use |
|
|
24
24
|
| --- | --- |
|
|
25
|
-
| Connector already in Cursor dashboard (Linear, Notion, …) | `defineConnection({ cursorAccount: true
|
|
25
|
+
| Connector already in Cursor dashboard (Linear, Notion, …) | `defineConnection({ cursorAccount: true })` or `servers: "*"` for every connected server; `servers: […]` to allowlist |
|
|
26
26
|
| Remote URL that speaks OAuth; host must hold tokens | `defineConnection({ url, oauth: true })` + this skill |
|
|
27
27
|
| Static bearer / API key | `headers` / env on a plain `{ url }` connection |
|
|
28
28
|
|
|
@@ -1,16 +1,138 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: agentkit-setup-slack
|
|
3
3
|
description: >-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
Put an agentkit agent in Slack. Covers the required connection decision
|
|
5
|
+
(Cursor-hosted connection vs dedicated Socket Mode Slack app) with an
|
|
6
|
+
AskQuestion gate, then both setup paths end to end — cursorAccount
|
|
7
|
+
sign-in + smoke, or manifests, tokens, doctor, smoke test, and opt-in
|
|
8
|
+
Slack tool approvals (HITL). Read this skill when wiring Slack for an
|
|
9
|
+
agentkit project.
|
|
7
10
|
---
|
|
8
11
|
|
|
9
12
|
# Setup Slack for agentkit
|
|
10
13
|
|
|
11
14
|
Use this skill when a human asks to wire Slack to an agentkit agent.
|
|
12
15
|
|
|
13
|
-
|
|
16
|
+
There are **two ways to connect**, and they differ mostly in setup cost:
|
|
17
|
+
|
|
18
|
+
- **Path A — Cursor-hosted connection**
|
|
19
|
+
(`slackChannel({ cursorAccount: true })`): rides the Cursor Slack
|
|
20
|
+
integration already installed in the workspace. No Slack app, no
|
|
21
|
+
manifests, no tokens, no Slack admin. Live in minutes.
|
|
22
|
+
- **Path B — Dedicated Slack app** (Socket Mode): the agent's own bot
|
|
23
|
+
user, created from generated manifests with an `xoxb-…` + `xapp-…`
|
|
24
|
+
token pair. Required for channel watching, approval buttons, or a
|
|
25
|
+
distinct bot handle. When the workspace is enrolled in Cursor's
|
|
26
|
+
**Add to Slack** (feature gate + a workspace-manager consent),
|
|
27
|
+
`agentkit slack create` provisions all of it in one command.
|
|
28
|
+
|
|
29
|
+
**Do not generate manifests or ask for tokens until the decision below
|
|
30
|
+
is made.** Defaulting into Path B buries the user in Slack admin work
|
|
31
|
+
(two app creations, token minting, env wiring) they may not need.
|
|
32
|
+
|
|
33
|
+
## Step 0 — Choose the connection (required gate)
|
|
34
|
+
|
|
35
|
+
Walk the user through the tradeoffs before touching files:
|
|
36
|
+
|
|
37
|
+
| | A: Cursor-hosted | B: Dedicated Slack app |
|
|
38
|
+
| --- | --- | --- |
|
|
39
|
+
| Setup | Zero Slack admin — sign the host in (`agentkit login` / `CURSOR_API_KEY`) and serve | Enrolled in Cursor's Add to Slack: one `agentkit slack create` per env (app + install + tokens, signed-in host). Otherwise: a Slack admin creates **two** apps (dev + prod) from manifests, installs them, mints `xoxb-…` + `xapp-…` for each |
|
|
40
|
+
| Prereq | Cursor Slack integration installed in the workspace and linked to the Cursor account | Permission to create Slack apps; token storage/rotation is on you |
|
|
41
|
+
| How users reach it | `@Cursor <AgentName> …`, DMs to @Cursor, replies in claimed threads | Its own bot user: @mention, DM, channel invites |
|
|
42
|
+
| Identity | Posts as the Cursor app under the agent's name + icon | Own app name, avatar, and handle |
|
|
43
|
+
| Channel watching (dispatch without a mention) | Not available | `engagement.channelPosts` allowlist |
|
|
44
|
+
| Tool approvals / Block Kit interactivity | Not available (construction throws) | `toolApprovals: true` Approve/Deny cards |
|
|
45
|
+
| Reply delivery | Status while working, then one final post (no streaming edits) | Streaming replies with postMessage fallback |
|
|
46
|
+
| Multi-agent hosts | Any number of agents, unique `agentName` each, no extra Slack apps | One Slack app + token pair **per agent** |
|
|
47
|
+
|
|
48
|
+
Decision shortcuts — skip the question when the request already decides:
|
|
49
|
+
|
|
50
|
+
- Needs channel watching, Approve/Deny buttons, `onBlockAction`, or its
|
|
51
|
+
own bot user / handle → **Path B**. Say which requirement forced it.
|
|
52
|
+
- Workspace has no Cursor Slack integration and won't install it →
|
|
53
|
+
**Path B**.
|
|
54
|
+
- "Just get it into Slack", a first bring-up, or the user already said
|
|
55
|
+
to use the Cursor Slack integration, with none of the above → **Path A**.
|
|
56
|
+
|
|
57
|
+
### Collect the decision with AskQuestion
|
|
58
|
+
|
|
59
|
+
Otherwise ask — one single-select question via the `AskQuestion` tool,
|
|
60
|
+
recommended option first so one click accepts it:
|
|
61
|
+
|
|
62
|
+
- **Cursor-hosted Slack connection (recommended)** — zero Slack setup;
|
|
63
|
+
sign in and mention `@Cursor <AgentName>`. No channel watching or
|
|
64
|
+
approval buttons.
|
|
65
|
+
- **Dedicated Slack app (Socket Mode)** — own bot user, channel
|
|
66
|
+
watching, approval buttons; needs a Slack admin to create two apps
|
|
67
|
+
and mint tokens.
|
|
68
|
+
|
|
69
|
+
If `AskQuestion` is unavailable in your environment, or the user does
|
|
70
|
+
not answer, **default to Path A (Cursor-hosted)**: state the assumption
|
|
71
|
+
in one line and proceed. It is the reversible choice — a dedicated app
|
|
72
|
+
can be added later as a *second* channel file without losing anything.
|
|
73
|
+
Never mix both in one channel definition: `cursorAccount: true` rejects
|
|
74
|
+
`credentials` / `envPrefix` at construction.
|
|
75
|
+
|
|
76
|
+
## Path A — Cursor-hosted connection
|
|
77
|
+
|
|
78
|
+
Goal: mention `@Cursor <AgentName>` in the linked workspace and get a
|
|
79
|
+
threaded reply. No Slack app, no tokens, no manifests.
|
|
80
|
+
|
|
81
|
+
### A1 — Author the channel (agent)
|
|
82
|
+
|
|
83
|
+
Write `agent/channels/slack.ts` by hand — do **not** run
|
|
84
|
+
`agentkit slack init` (that scaffolds the Path B manifests + env):
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import { slackChannel } from "@cursor/july/channels/slack";
|
|
88
|
+
|
|
89
|
+
export default slackChannel({
|
|
90
|
+
cursorAccount: true,
|
|
91
|
+
agentName: "MyAgent", // single token, no whitespace; prefer PascalCase
|
|
92
|
+
agentIcon: { emoji: ":robot_face:" },
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`agentName` defaults from the mount slug (PascalCase) and must be a
|
|
97
|
+
single token — string literals with spaces fail typecheck; runtime
|
|
98
|
+
values throw at construction/serve. Names must be unique on the host;
|
|
99
|
+
an unmatched `@Cursor <name>` stays with Cursor's normal Slack agent.
|
|
100
|
+
|
|
101
|
+
### A2 — Sign in + serve (agent)
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
agentkit login # or export CURSOR_API_KEY
|
|
105
|
+
agentkit dev .
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Confirm log: `[agentkit] cursor-slack: relaying Slack for <AgentName> …`.
|
|
109
|
+
An unsigned host fails `serve` with a sign-in hint naming the channel.
|
|
110
|
+
|
|
111
|
+
### A3 — Smoke (human)
|
|
112
|
+
|
|
113
|
+
1. In the Slack workspace linked to the Cursor account, post
|
|
114
|
+
`@Cursor MyAgent <question>` (or DM @Cursor)
|
|
115
|
+
2. Confirm a working status while the turn runs, then a threaded reply
|
|
116
|
+
posted under the agent's name and icon
|
|
117
|
+
3. Reply in the thread without re-mentioning — it stays with the agent
|
|
118
|
+
|
|
119
|
+
### Path A limits (enforced at construction)
|
|
120
|
+
|
|
121
|
+
- `credentials` / `envPrefix` → throw (author a second channel file for
|
|
122
|
+
a dedicated app instead)
|
|
123
|
+
- `engagement.channelPosts`, `toolApprovals`, `interactivity`,
|
|
124
|
+
`onBlockAction` → throw (Socket Mode only)
|
|
125
|
+
- `engagement.mentions: false` / `directMessages: false` → throw (the
|
|
126
|
+
relay always delivers those)
|
|
127
|
+
- Streaming is forced off: rotating status while thinking, one final
|
|
128
|
+
reply post
|
|
129
|
+
|
|
130
|
+
When one of these limits becomes a real requirement, keep the
|
|
131
|
+
cursor-account channel and add a Path B channel file beside it.
|
|
132
|
+
|
|
133
|
+
## Path B — Dedicated Slack app (Socket Mode)
|
|
134
|
+
|
|
135
|
+
Goal:
|
|
14
136
|
|
|
15
137
|
- Importable **dev** and **prod** Slack app manifests (Socket Mode on)
|
|
16
138
|
- App-level Token (`xapp-…`, `connections:write`) + Bot token (`xoxb-…`)
|
|
@@ -18,10 +140,40 @@ Use this skill when a human asks to wire Slack to an agentkit agent.
|
|
|
18
140
|
- Thread ↔ session via `continuationToken = channelId:threadTs`
|
|
19
141
|
- One Slack app (token pair) **per agent** when serving multiple demos
|
|
20
142
|
|
|
21
|
-
## Phases
|
|
22
|
-
|
|
23
143
|
Update `.agent-serve/slack/setup-status.json` as you complete phases.
|
|
24
144
|
|
|
145
|
+
### Provisioned variant — `agentkit slack create` (primary when enrolled)
|
|
146
|
+
|
|
147
|
+
When the Slack workspace is enrolled in Cursor's **Add to Slack**
|
|
148
|
+
(the provisioning feature gate plus a workspace-manager consent), one
|
|
149
|
+
command collapses Phases 0–3 — manifest import, app creation, install,
|
|
150
|
+
and token minting:
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
agentkit slack create --dir <agent> # dev app (default)
|
|
154
|
+
agentkit slack create --dir <agent> --prod # prod app
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
`create` scaffolds `agent/channels/slack.ts` + the `.agent-serve/slack`
|
|
158
|
+
files when missing (never overwriting existing ones), runs the browser
|
|
159
|
+
consent flow when the workspace is not connected yet, provisions +
|
|
160
|
+
installs the app, writes `<PREFIX>_SLACK_BOT_TOKEN` /
|
|
161
|
+
`<PREFIX>_SLACK_APP_TOKEN` into `<dir>/.env.local` (replacing stale
|
|
162
|
+
lines), and finishes with `doctor`. It needs a signed-in host
|
|
163
|
+
(`agentkit login` / `CURSOR_API_KEY`). Pass `--slack-team <id>` when
|
|
164
|
+
several workspaces are connected. If the install lands in
|
|
165
|
+
`pending_admin_approval`, a Slack workspace admin must approve the
|
|
166
|
+
printed link — then re-run `create` to finish and mint the tokens.
|
|
167
|
+
`agentkit slack destroy` deletes the provisioned app (tokens left in
|
|
168
|
+
`.env.local` simply stop working). Skip to Phase 4 when `create`
|
|
169
|
+
succeeds — `dev`/`serve` load the agent's `.env.local` (real env wins).
|
|
170
|
+
Note: re-running `create` rebuilds the app manifest from the command's
|
|
171
|
+
flags, so any customization made outside the CLI resets to it.
|
|
172
|
+
|
|
173
|
+
If the command reports the feature gate is not enabled (the workspace
|
|
174
|
+
is not enrolled), fall back to the manual manifest-import flow below
|
|
175
|
+
(Phases 0–3).
|
|
176
|
+
|
|
25
177
|
### Phase 0 — Generate (agent)
|
|
26
178
|
|
|
27
179
|
```bash
|
|
@@ -38,6 +190,10 @@ Creates:
|
|
|
38
190
|
Use `--no-prefix` for the shared `SLACK_BOT_TOKEN` / `SLACK_APP_TOKEN` globals
|
|
39
191
|
(single-agent only). Use `--prefix CUSTOM` to override the default prefix.
|
|
40
192
|
|
|
193
|
+
These files deliberately omit `settings.managed_app_settings` — Slack accepts
|
|
194
|
+
that block only from a manager app's user token, so it belongs to the
|
|
195
|
+
`slack create` path, not to a manifest a human pastes in.
|
|
196
|
+
|
|
41
197
|
### Phase 1 — Create apps (human)
|
|
42
198
|
|
|
43
199
|
1. Open https://api.slack.com/apps → **Create New App** → **From a manifest**
|
|
@@ -92,7 +248,7 @@ Human:
|
|
|
92
248
|
|
|
93
249
|
## Features (defaults ON)
|
|
94
250
|
|
|
95
|
-
- Chat streaming (`chat.startStream` / `appendStream` / `stopStream`) with postMessage fallback
|
|
251
|
+
- Chat streaming (`chat.startStream` / `appendStream` / `stopStream`) with postMessage fallback (Path B; Path A posts one final reply)
|
|
96
252
|
- Thinking / task cards for tool calls
|
|
97
253
|
- `assistant.threads.setStatus` with rotating `loading_messages`
|
|
98
254
|
- Thread title + suggested prompts
|
|
@@ -101,7 +257,7 @@ Human:
|
|
|
101
257
|
|
|
102
258
|
If a Slack API is unavailable (`method_not_found` / `missing_scope`), the pack falls back to postMessage + setStatus.
|
|
103
259
|
|
|
104
|
-
## Engagement: mentions by default, channel watch opt-in
|
|
260
|
+
## Engagement: mentions by default, channel watch opt-in (Path B only)
|
|
105
261
|
|
|
106
262
|
The pack dispatches only on `app_mention` + DMs unless told otherwise —
|
|
107
263
|
summoned, never proactive. To watch channels (dispatch on new posts without
|
|
@@ -128,7 +284,7 @@ bot to each watched channel. Posts mentioning the bot stay on the
|
|
|
128
284
|
thread. `engagement.mentions` / `engagement.directMessages` can be set to
|
|
129
285
|
`false` to turn those surfaces off.
|
|
130
286
|
|
|
131
|
-
## Tool approvals over Slack (opt-in HITL)
|
|
287
|
+
## Tool approvals over Slack (opt-in HITL, Path B only)
|
|
132
288
|
|
|
133
289
|
Tools with `needsApproval` park until a human approves or denies. To route
|
|
134
290
|
that through Slack, set `toolApprovals: true` on `slackChannel` — it posts
|
|
@@ -163,15 +319,25 @@ Requirements and caveats:
|
|
|
163
319
|
|
|
164
320
|
## Notes
|
|
165
321
|
|
|
166
|
-
- Dev and prod are **separate Slack apps** (separate tokens).
|
|
167
|
-
|
|
322
|
+
- Dev and prod are **separate Slack apps** (separate tokens) on Path B.
|
|
323
|
+
Path A has no per-env app split — the host's Cursor account decides
|
|
324
|
+
where it relays.
|
|
325
|
+
- Socket Mode / the Cursor relay are outbound only — no Events HTTP /
|
|
326
|
+
Request URL path on either connection.
|
|
168
327
|
- Do not `await waitForCompletion()` inside Slack dispatch handlers — the pack uses `waitUntil`.
|
|
169
328
|
- Do not share one token pair across multiple agents in the same process.
|
|
170
329
|
|
|
171
330
|
## CLI reference
|
|
172
331
|
|
|
332
|
+
`slack create` / `destroy` / `init` / `manifest` / `doctor` are Path B
|
|
333
|
+
tooling; Path A needs only `login`.
|
|
334
|
+
|
|
173
335
|
```bash
|
|
174
336
|
agentkit slack setup
|
|
337
|
+
agentkit slack create --dir . # provision via Add to Slack (dev app)
|
|
338
|
+
agentkit slack create --dir . --prod # prod app
|
|
339
|
+
agentkit slack create --dir . --slack-team T0123ABCD
|
|
340
|
+
agentkit slack destroy --dir . [--prod] [--slack-team T0123ABCD]
|
|
175
341
|
agentkit slack init --dir . --name "My Agent"
|
|
176
342
|
agentkit slack init --dir . --no-prefix
|
|
177
343
|
agentkit slack init --dir . --channel-posts # + message.channels/groups events
|
package/src/bin/agent-serve.ts
CHANGED
|
@@ -56,6 +56,7 @@ import {
|
|
|
56
56
|
CLI_COMMAND_NAME as CLI,
|
|
57
57
|
packageVersion,
|
|
58
58
|
} from "../internal/distribution.js";
|
|
59
|
+
import { loadAgentLocalEnv } from "../internal/local-env.js";
|
|
59
60
|
import { clearTerminal, stdoutPalette } from "../internal/terminal-style.js";
|
|
60
61
|
import { cmdUpdate, warnIfOutdated } from "../internal/update-check.js";
|
|
61
62
|
import type { AgentProject, AgentServeHandle } from "../types.js";
|
|
@@ -100,7 +101,9 @@ Usage:
|
|
|
100
101
|
${CLI} mcp oauth <connection> [--dir <path>] [--store] [--slug <slug>]
|
|
101
102
|
${CLI} eval [--dir <path>] [evalId...] [--list] [--json]
|
|
102
103
|
[--tag <tag>]... [--verbose] [--timeout-ms <n>]
|
|
103
|
-
[--
|
|
104
|
+
[--strict] [--max-concurrency <n>]
|
|
105
|
+
[--junit <path>] [--artifacts <dir>|--no-artifacts]
|
|
106
|
+
[--skip-report] [--no-stream] [--no-wait]
|
|
104
107
|
[--prod | --url <baseUrl>] [--slug <slug>]
|
|
105
108
|
[--team <id>]
|
|
106
109
|
${CLI} eval status [runId] --prod|--url <baseUrl> [--dir <path>] [--json]
|
|
@@ -223,7 +226,8 @@ Commands:
|
|
|
223
226
|
secrets Manage deployment secrets: set (upsert by name; NAMEs without
|
|
224
227
|
=VALUE are prompted for with hidden input, or read from piped
|
|
225
228
|
stdin), list (names + createdAt; never values), unset.
|
|
226
|
-
slack Slack channel pack:
|
|
229
|
+
slack Slack channel pack: create/destroy/icon (Cursor Add to Slack
|
|
230
|
+
provisioning), init, manifests, doctor (see slack help).
|
|
227
231
|
github GitHub webhook pack: forward live deliveries, events, doctor
|
|
228
232
|
(wraps \`gh webhook forward\`; see github help).
|
|
229
233
|
|
|
@@ -320,6 +324,13 @@ Options:
|
|
|
320
324
|
--tag eval: only run cases with this tag (repeatable).
|
|
321
325
|
--list eval: print discovered cases without running.
|
|
322
326
|
--verbose eval: stream t.log lines and reply snippets.
|
|
327
|
+
--strict eval: a missed soft threshold (SCORED) also exits 1.
|
|
328
|
+
--max-concurrency eval: override evals.config.ts maxConcurrency.
|
|
329
|
+
--junit eval: write JUnit XML here for CI annotations.
|
|
330
|
+
--artifacts eval: run-artifact directory (default a timestamped dir
|
|
331
|
+
under <dir>/.agent-serve/evals/).
|
|
332
|
+
--no-artifacts eval: do not write run artifacts.
|
|
333
|
+
--skip-report eval: ignore reporters from evals.config.ts / eval files.
|
|
323
334
|
--no-stream run/eval: silence live progress on stderr (JSON still prints).
|
|
324
335
|
--json JSON output (init/info/chat/eval/logs/sessions/session/
|
|
325
336
|
github; run defaults to JSON). logs: NDJSON progress/log
|
|
@@ -433,10 +444,14 @@ async function main(argv: string[]): Promise<number> {
|
|
|
433
444
|
"base-url": { type: "string" },
|
|
434
445
|
print: { type: "boolean", default: false },
|
|
435
446
|
store: { type: "boolean", default: false },
|
|
447
|
+
remote: { type: "boolean", default: false },
|
|
436
448
|
transport: { type: "string" },
|
|
437
449
|
prefix: { type: "string" },
|
|
438
450
|
"no-prefix": { type: "boolean", default: false },
|
|
439
451
|
"channel-posts": { type: "boolean", default: false },
|
|
452
|
+
"slack-team": { type: "string" },
|
|
453
|
+
icon: { type: "string" },
|
|
454
|
+
force: { type: "boolean", default: false },
|
|
440
455
|
repo: { type: "string", multiple: true },
|
|
441
456
|
org: { type: "string" },
|
|
442
457
|
"cursor-events": { type: "boolean", default: false },
|
|
@@ -456,6 +471,12 @@ async function main(argv: string[]): Promise<number> {
|
|
|
456
471
|
all: { type: "boolean", default: false },
|
|
457
472
|
"cursor-events-repo": { type: "string", multiple: true },
|
|
458
473
|
"allow-domain": { type: "string", multiple: true },
|
|
474
|
+
strict: { type: "boolean", default: false },
|
|
475
|
+
junit: { type: "string" },
|
|
476
|
+
artifacts: { type: "string" },
|
|
477
|
+
"no-artifacts": { type: "boolean", default: false },
|
|
478
|
+
"skip-report": { type: "boolean", default: false },
|
|
479
|
+
"max-concurrency": { type: "string" },
|
|
459
480
|
},
|
|
460
481
|
});
|
|
461
482
|
const dir = values.dir ?? ".";
|
|
@@ -483,6 +504,13 @@ async function main(argv: string[]): Promise<number> {
|
|
|
483
504
|
prefix: values.prefix,
|
|
484
505
|
noPrefix: values["no-prefix"],
|
|
485
506
|
channelPosts: values["channel-posts"],
|
|
507
|
+
slackTeam: values["slack-team"],
|
|
508
|
+
prod: values.prod,
|
|
509
|
+
team: values.team,
|
|
510
|
+
apiKey: values["api-key"],
|
|
511
|
+
icon: values.icon,
|
|
512
|
+
force: values.force,
|
|
513
|
+
iconSource: positionals[0] === "icon" ? positionals[1] : undefined,
|
|
486
514
|
});
|
|
487
515
|
}
|
|
488
516
|
case "github": {
|
|
@@ -545,6 +573,7 @@ async function main(argv: string[]): Promise<number> {
|
|
|
545
573
|
print: values.print,
|
|
546
574
|
json: values.json,
|
|
547
575
|
store: values.store,
|
|
576
|
+
remote: values.remote,
|
|
548
577
|
oauthConnection: mcpSub === "oauth" ? positionals[1] : undefined,
|
|
549
578
|
});
|
|
550
579
|
}
|
|
@@ -754,6 +783,11 @@ async function main(argv: string[]): Promise<number> {
|
|
|
754
783
|
stateRoot: values["state-root"],
|
|
755
784
|
timeoutMs: parseTimeoutMs(values["timeout-ms"]),
|
|
756
785
|
stream: values["no-stream"] ? false : undefined,
|
|
786
|
+
strict: values.strict,
|
|
787
|
+
junit: values.junit,
|
|
788
|
+
artifacts: values["no-artifacts"] ? false : values.artifacts,
|
|
789
|
+
skipReport: values["skip-report"],
|
|
790
|
+
maxConcurrency: parseMaxConcurrency(values["max-concurrency"]),
|
|
757
791
|
})
|
|
758
792
|
);
|
|
759
793
|
}
|
|
@@ -1016,6 +1050,16 @@ async function main(argv: string[]): Promise<number> {
|
|
|
1016
1050
|
}
|
|
1017
1051
|
const dev = command === "dev" || values.dev === true;
|
|
1018
1052
|
const style = stdoutPalette();
|
|
1053
|
+
// Real env wins (loadEnvFile never overrides). Runs inside start()
|
|
1054
|
+
// so dev's Enter-restart picks up tokens written since boot.
|
|
1055
|
+
const loadLocalEnv = (): void => {
|
|
1056
|
+
const loadedEnvFiles = loadAgentLocalEnv(serveDir);
|
|
1057
|
+
if (loadedEnvFiles.length > 0) {
|
|
1058
|
+
process.stdout.write(
|
|
1059
|
+
`${style.dim(`env: loaded ${loadedEnvFiles.join(", ")}`)}\n`
|
|
1060
|
+
);
|
|
1061
|
+
}
|
|
1062
|
+
};
|
|
1019
1063
|
const serveOptions = {
|
|
1020
1064
|
port,
|
|
1021
1065
|
host: values.host,
|
|
@@ -1055,6 +1099,7 @@ async function main(argv: string[]): Promise<number> {
|
|
|
1055
1099
|
};
|
|
1056
1100
|
|
|
1057
1101
|
const start = async (): Promise<AgentServeHandle> => {
|
|
1102
|
+
loadLocalEnv();
|
|
1058
1103
|
const next = await serve(serveDir, serveOptions);
|
|
1059
1104
|
printServeBanner(next, bannerOpts);
|
|
1060
1105
|
if (enterToRestart) {
|
|
@@ -1179,6 +1224,11 @@ function axFromOptions(
|
|
|
1179
1224
|
once: partial.once,
|
|
1180
1225
|
rawEvents: partial.rawEvents,
|
|
1181
1226
|
print: partial.print,
|
|
1227
|
+
strict: partial.strict,
|
|
1228
|
+
junit: partial.junit,
|
|
1229
|
+
artifacts: partial.artifacts,
|
|
1230
|
+
skipReport: partial.skipReport,
|
|
1231
|
+
maxConcurrency: partial.maxConcurrency,
|
|
1182
1232
|
};
|
|
1183
1233
|
}
|
|
1184
1234
|
|
|
@@ -1217,6 +1267,17 @@ function parseTimeoutMs(value: string | undefined): number | undefined {
|
|
|
1217
1267
|
return n;
|
|
1218
1268
|
}
|
|
1219
1269
|
|
|
1270
|
+
function parseMaxConcurrency(value: string | undefined): number | undefined {
|
|
1271
|
+
if (value === undefined) {
|
|
1272
|
+
return undefined;
|
|
1273
|
+
}
|
|
1274
|
+
const n = Number.parseInt(value, 10);
|
|
1275
|
+
if (Number.isNaN(n) || n <= 0) {
|
|
1276
|
+
throw new Error(`invalid --max-concurrency: ${value}`);
|
|
1277
|
+
}
|
|
1278
|
+
return n;
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1220
1281
|
/**
|
|
1221
1282
|
* Load mounts the same way `serve` does (multi-agent layout by default).
|
|
1222
1283
|
*/
|
|
@@ -350,7 +350,14 @@ export async function postMessage(args: {
|
|
|
350
350
|
base.thread_ts = args.threadTs;
|
|
351
351
|
}
|
|
352
352
|
const hasBlocks = args.blocks !== undefined && args.blocks.length > 0;
|
|
353
|
-
|
|
353
|
+
// User-group mentions (`<!subteam^…>`) and Slack mrkdwn links
|
|
354
|
+
// (`<https://…|label>`) only render correctly via mrkdwn `text`,
|
|
355
|
+
// not `markdown_text`.
|
|
356
|
+
const usePlainText =
|
|
357
|
+
args.plainText === true ||
|
|
358
|
+
hasBlocks ||
|
|
359
|
+
args.text.includes("<!subteam^") ||
|
|
360
|
+
/<https?:\/\/[^|>]+\|/.test(args.text);
|
|
354
361
|
|
|
355
362
|
let response: Record<string, unknown>;
|
|
356
363
|
if (usePlainText) {
|
|
@@ -451,8 +458,14 @@ export async function updateMessage(args: {
|
|
|
451
458
|
channel: args.channel,
|
|
452
459
|
ts: args.ts,
|
|
453
460
|
};
|
|
454
|
-
if (
|
|
455
|
-
|
|
461
|
+
if (
|
|
462
|
+
args.blocks !== undefined ||
|
|
463
|
+
args.plainText === true ||
|
|
464
|
+
args.text.includes("<!subteam^") ||
|
|
465
|
+
/<https?:\/\/[^|>]+\|/.test(args.text)
|
|
466
|
+
) {
|
|
467
|
+
// Blocks / plainText / user-group mentions / mrkdwn links force the
|
|
468
|
+
// legacy text path (same as postMessage).
|
|
456
469
|
return callSlackApiSoft({
|
|
457
470
|
botToken: args.botToken,
|
|
458
471
|
operation: "chat.update",
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { samePrincipal } from "../../internal/auth.js";
|
|
6
6
|
import type { AuthContext } from "../../types.js";
|
|
7
|
-
import type { SlackBinding, SlackMessage } from "./types.js";
|
|
7
|
+
import type { SlackBinding, SlackBlockAction, SlackMessage } from "./types.js";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Build a thread-scoped principal so any participant in the same Slack
|
|
@@ -88,6 +88,21 @@ export function buildSlackInteractiveApprovalAuth(args: {
|
|
|
88
88
|
return clickerAuth;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Default auth for an `onBlockAction` dispatch: the click's thread-scoped
|
|
93
|
+
* principal with the clicker in attributes, so replies in the same thread
|
|
94
|
+
* continue the session and the clicker is the attributable user.
|
|
95
|
+
*/
|
|
96
|
+
export function blockActionAuth(action: SlackBlockAction): AuthContext {
|
|
97
|
+
return buildSlackAuthContext({
|
|
98
|
+
channelId: action.channelId,
|
|
99
|
+
threadTs: action.threadTs,
|
|
100
|
+
teamId: action.teamId,
|
|
101
|
+
userId: action.userId,
|
|
102
|
+
userName: action.userName,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
91
106
|
export function defaultSlackAuth(
|
|
92
107
|
message: SlackMessage,
|
|
93
108
|
ctx: SlackBinding,
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Block Kit building helpers shared by card-building agents.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/** Slack cap for one section block's mrkdwn text. */
|
|
6
|
+
export const SLACK_SECTION_MRKDWN_MAX = 3000;
|
|
7
|
+
|
|
8
|
+
/** Slack caps for a native `table` block (one table per message). */
|
|
9
|
+
export const SLACK_TABLE_MAX_ROWS = 100;
|
|
10
|
+
export const SLACK_TABLE_MAX_COLUMNS = 20;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Pack lines into as few mrkdwn section blocks as fit the 3000-char cap,
|
|
14
|
+
* preserving order. Long lists (a full service fleet, a file inventory)
|
|
15
|
+
* overflow a single section and make `chat.postMessage` refuse the whole
|
|
16
|
+
* card with `invalid_blocks`.
|
|
17
|
+
*/
|
|
18
|
+
export function mrkdwnSections(lines: readonly string[]): unknown[] {
|
|
19
|
+
const texts: string[] = [];
|
|
20
|
+
let current = "";
|
|
21
|
+
for (const raw of lines) {
|
|
22
|
+
// A single oversized line (one huge blob) hard-splits at the cap.
|
|
23
|
+
const parts: string[] = [];
|
|
24
|
+
for (
|
|
25
|
+
let i = 0;
|
|
26
|
+
i < Math.max(raw.length, 1);
|
|
27
|
+
i += SLACK_SECTION_MRKDWN_MAX
|
|
28
|
+
) {
|
|
29
|
+
parts.push(raw.slice(i, i + SLACK_SECTION_MRKDWN_MAX));
|
|
30
|
+
}
|
|
31
|
+
for (const line of parts) {
|
|
32
|
+
const next = current === "" ? line : `${current}\n${line}`;
|
|
33
|
+
if (next.length > SLACK_SECTION_MRKDWN_MAX && current !== "") {
|
|
34
|
+
texts.push(current);
|
|
35
|
+
current = line;
|
|
36
|
+
} else {
|
|
37
|
+
current = next;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (current !== "") {
|
|
42
|
+
texts.push(current);
|
|
43
|
+
}
|
|
44
|
+
return texts.map((text) => ({
|
|
45
|
+
type: "section",
|
|
46
|
+
text: { type: "mrkdwn", text },
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type SlackTableAlign = "left" | "center" | "right";
|
|
51
|
+
|
|
52
|
+
export type SlackTableColumnSetting = {
|
|
53
|
+
align?: SlackTableAlign;
|
|
54
|
+
is_wrapped?: boolean;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/** Cell types accepted by Slack's native `table` block. */
|
|
58
|
+
export type SlackTableCell =
|
|
59
|
+
| { type: "raw_text"; text: string }
|
|
60
|
+
| {
|
|
61
|
+
type: "rich_text";
|
|
62
|
+
elements: Array<{
|
|
63
|
+
type: "rich_text_section";
|
|
64
|
+
elements: Array<
|
|
65
|
+
| { type: "text"; text: string }
|
|
66
|
+
| { type: "link"; url: string; text: string }
|
|
67
|
+
>;
|
|
68
|
+
}>;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export type SlackTableBlock = {
|
|
72
|
+
type: "table";
|
|
73
|
+
rows: SlackTableCell[][];
|
|
74
|
+
column_settings?: SlackTableColumnSetting[];
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/** Plain text cell (header or body). */
|
|
78
|
+
export function slackRawTextCell(text: string): SlackTableCell {
|
|
79
|
+
return { type: "raw_text", text };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Linked cell via rich_text (preferred over mrkdwn `<url|label>` in tables). */
|
|
83
|
+
export function slackLinkCell(args: {
|
|
84
|
+
url: string;
|
|
85
|
+
text: string;
|
|
86
|
+
}): SlackTableCell {
|
|
87
|
+
return {
|
|
88
|
+
type: "rich_text",
|
|
89
|
+
elements: [
|
|
90
|
+
{
|
|
91
|
+
type: "rich_text_section",
|
|
92
|
+
elements: [{ type: "link", url: args.url, text: args.text }],
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Build a native Slack `table` block. First row is the header. Slack allows
|
|
100
|
+
* at most one table block per message, 100 rows, and 20 columns — callers
|
|
101
|
+
* should keep tables short.
|
|
102
|
+
*/
|
|
103
|
+
export function slackTableBlock(args: {
|
|
104
|
+
headers: readonly string[];
|
|
105
|
+
rows: readonly (readonly SlackTableCell[])[];
|
|
106
|
+
columnSettings?: readonly SlackTableColumnSetting[];
|
|
107
|
+
}): SlackTableBlock | undefined {
|
|
108
|
+
if (args.headers.length === 0) {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
if (args.headers.length > SLACK_TABLE_MAX_COLUMNS) {
|
|
112
|
+
throw new Error(
|
|
113
|
+
`slackTableBlock: ${args.headers.length} columns exceeds Slack cap of ${SLACK_TABLE_MAX_COLUMNS}`
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
// Header + body rows share the 100-row cap.
|
|
117
|
+
if (args.rows.length + 1 > SLACK_TABLE_MAX_ROWS) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
`slackTableBlock: ${args.rows.length + 1} rows exceeds Slack cap of ${SLACK_TABLE_MAX_ROWS}`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
const columnCount = args.headers.length;
|
|
123
|
+
for (const [index, row] of args.rows.entries()) {
|
|
124
|
+
if (row.length !== columnCount) {
|
|
125
|
+
throw new Error(
|
|
126
|
+
`slackTableBlock: row ${index} has ${row.length} cells; expected ${columnCount}`
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const headerRow = args.headers.map((header) => slackRawTextCell(header));
|
|
131
|
+
const block: SlackTableBlock = {
|
|
132
|
+
type: "table",
|
|
133
|
+
rows: [headerRow, ...args.rows.map((row) => [...row])],
|
|
134
|
+
};
|
|
135
|
+
if (args.columnSettings !== undefined && args.columnSettings.length > 0) {
|
|
136
|
+
block.column_settings = [...args.columnSettings];
|
|
137
|
+
}
|
|
138
|
+
return block;
|
|
139
|
+
}
|
|
@@ -137,6 +137,7 @@ export async function dispatchCursorSlackEnvelope(args: {
|
|
|
137
137
|
|
|
138
138
|
const message: SlackMessage = {
|
|
139
139
|
text: envelope.text,
|
|
140
|
+
body: envelope.text,
|
|
140
141
|
markdown: slackTextToMarkdown(envelope.text),
|
|
141
142
|
ts: envelope.messageTs,
|
|
142
143
|
threadTs: envelope.threadTs,
|
|
@@ -26,6 +26,37 @@ import type {
|
|
|
26
26
|
SlackSuggestedPrompt,
|
|
27
27
|
} from "./types.js";
|
|
28
28
|
|
|
29
|
+
const SLACK_TURN_FAILURE_FALLBACK =
|
|
30
|
+
"I hit an error while handling your request. Please try again or rephrase.";
|
|
31
|
+
const SLACK_TURN_FAILURE_MAX_CHARS = 400;
|
|
32
|
+
|
|
33
|
+
/** Scrubbed turn.failed body for Slack (secrets / paths / URLs stripped). */
|
|
34
|
+
export function formatSlackTurnFailureMessage(raw: string | undefined): string {
|
|
35
|
+
const scrubbed = scrubSlackErrorDetail(raw?.trim() ?? "");
|
|
36
|
+
if (scrubbed === "") {
|
|
37
|
+
return SLACK_TURN_FAILURE_FALLBACK;
|
|
38
|
+
}
|
|
39
|
+
if (/^I hit an error/i.test(scrubbed)) {
|
|
40
|
+
return scrubbed;
|
|
41
|
+
}
|
|
42
|
+
return `I hit an error while handling your request: ${scrubbed}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function scrubSlackErrorDetail(message: string): string {
|
|
46
|
+
const scrubbed = message
|
|
47
|
+
.replace(/https?:\/\/\S+/gi, "[url]")
|
|
48
|
+
.replace(/\b(crsr_|xox[baprs]-|sk-|Bearer\s+)\S+/gi, "[redacted]")
|
|
49
|
+
.replace(/\/(?:home|Users|var\/folders)\/\S+/g, "[path]")
|
|
50
|
+
.replace(/\b[A-Za-z]:(?:\\|\/)[^\s]+/g, "[path]")
|
|
51
|
+
.replace(/\\\\[^\s]+/g, "[path]")
|
|
52
|
+
.replace(/\s+/g, " ")
|
|
53
|
+
.trim();
|
|
54
|
+
if (scrubbed.length <= SLACK_TURN_FAILURE_MAX_CHARS) {
|
|
55
|
+
return scrubbed;
|
|
56
|
+
}
|
|
57
|
+
return `${scrubbed.slice(0, SLACK_TURN_FAILURE_MAX_CHARS - 1)}…`;
|
|
58
|
+
}
|
|
59
|
+
|
|
29
60
|
function resolveLoadingMessages(
|
|
30
61
|
loadingMessages: readonly string[] | undefined
|
|
31
62
|
): string[] {
|
|
@@ -434,10 +465,7 @@ export function buildDefaultEvents(
|
|
|
434
465
|
if (state.channelId === null || state.threadTs === null) {
|
|
435
466
|
return;
|
|
436
467
|
}
|
|
437
|
-
|
|
438
|
-
// paths, tokens, or other sensitive detail to workspace members.
|
|
439
|
-
const errorText =
|
|
440
|
-
"I hit an error while handling your request. Please try again or rephrase.";
|
|
468
|
+
const errorText = formatSlackTurnFailureMessage(event.data.message);
|
|
441
469
|
|
|
442
470
|
let delivered = false;
|
|
443
471
|
if (state.streamTs !== null && state.streamTs !== "") {
|