@cursor/july 0.1.26 → 0.1.28
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/dist/ab.d.ts +6 -0
- package/dist/ab.d.ts.map +1 -1
- package/dist/ab.js +1 -0
- package/dist/artifacts.d.ts +34 -0
- package/dist/artifacts.d.ts.map +1 -0
- package/dist/artifacts.js +57 -0
- package/dist/bin/agent-serve.js +43 -12
- package/dist/channels/github/github-channel.d.ts +1 -1
- package/dist/channels/github/github-channel.d.ts.map +1 -1
- package/dist/channels/github/github-channel.js +14 -1
- package/dist/channels/github/types.d.ts +6 -1
- package/dist/channels/github/types.d.ts.map +1 -1
- package/dist/channels/slack/api.d.ts +2 -1
- package/dist/channels/slack/api.d.ts.map +1 -1
- package/dist/channels/slack/api.js +1 -0
- package/dist/channels/slack/auth.d.ts +13 -13
- package/dist/channels/slack/auth.d.ts.map +1 -1
- package/dist/channels/slack/auth.js +19 -10
- package/dist/channels/slack/cursor-account.d.ts +2 -1
- package/dist/channels/slack/cursor-account.d.ts.map +1 -1
- package/dist/channels/slack/cursor-account.js +3 -1
- package/dist/channels/slack/defaults.d.ts +7 -4
- package/dist/channels/slack/defaults.d.ts.map +1 -1
- package/dist/channels/slack/defaults.js +14 -4
- package/dist/channels/slack/dispatch.d.ts +10 -1
- package/dist/channels/slack/dispatch.d.ts.map +1 -1
- package/dist/channels/slack/dispatch.js +59 -21
- package/dist/channels/slack/external-policy.d.ts +27 -0
- package/dist/channels/slack/external-policy.d.ts.map +1 -0
- package/dist/channels/slack/external-policy.js +53 -0
- package/dist/channels/slack/index.d.ts +1 -0
- package/dist/channels/slack/index.d.ts.map +1 -1
- package/dist/channels/slack/index.js +1 -0
- package/dist/channels/slack/interactive.d.ts +8 -1
- package/dist/channels/slack/interactive.d.ts.map +1 -1
- package/dist/channels/slack/interactive.js +43 -15
- package/dist/channels/slack/slack-channel.d.ts.map +1 -1
- package/dist/channels/slack/slack-channel.js +22 -8
- package/dist/channels/slack/types.d.ts +16 -1
- package/dist/channels/slack/types.d.ts.map +1 -1
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +3 -3
- package/dist/docs/assets/{app.DYcC9FY-.js → app.CqjrYaT2.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.VWQoPFJ1.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.o4N_knTV.js → VPLocalSearchBox.ag-MwUel.js} +1 -1
- package/dist/docs/assets/chunks/{theme.DQ-njyo0.js → theme.BS-DJWat.js} +2 -2
- package/dist/docs/assets/{guides_slack.md.D_xFKlnF.js → guides_slack.md.CjmJSvZS.js} +8 -4
- package/dist/docs/assets/guides_slack.md.CjmJSvZS.lean.js +1 -0
- package/dist/docs/assets/guides_webhooks.md.wSOYas3X.js +183 -0
- package/dist/docs/assets/{guides_webhooks.md.B1EswtUu.lean.js → guides_webhooks.md.wSOYas3X.lean.js} +1 -1
- package/dist/docs/assets/{index.md.Dfv5ic9t.js → index.md.CH_s5uZe.js} +1 -1
- package/dist/docs/assets/{reference_channels.md.DdmiKgqf.js → reference_channels.md.CDhTRfUz.js} +2 -2
- package/dist/docs/assets/{reference_channels.md.DdmiKgqf.lean.js → reference_channels.md.CDhTRfUz.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 +3 -3
- 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 +3 -3
- package/dist/docs/guides/slack.html +11 -7
- package/dist/docs/guides/webhooks.html +138 -5
- package/dist/docs/hashmap.json +1 -1
- package/dist/docs/hillclimbing.html +3 -3
- package/dist/docs/index.html +5 -5
- package/dist/docs/quickstart.html +3 -3
- package/dist/docs/reference/agent-config.html +3 -3
- package/dist/docs/reference/channels.html +5 -5
- package/dist/docs/reference/cli.html +3 -3
- package/dist/docs/reference/connections.html +3 -3
- 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 +10 -1
- package/dist/evals/assertions.d.ts.map +1 -1
- package/dist/evals/assertions.js +12 -0
- package/dist/evals.d.ts +1 -1
- package/dist/evals.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/internal/ab-fold.d.ts.map +1 -1
- package/dist/internal/ab-fold.js +4 -0
- package/dist/internal/ab-snapshot.d.ts.map +1 -1
- package/dist/internal/ab-snapshot.js +1 -0
- package/dist/internal/artifacts-store.d.ts +45 -0
- package/dist/internal/artifacts-store.d.ts.map +1 -0
- package/dist/internal/artifacts-store.js +250 -0
- package/dist/internal/builtin-tools/artifacts.d.ts +10 -0
- package/dist/internal/builtin-tools/artifacts.d.ts.map +1 -0
- package/dist/internal/builtin-tools/artifacts.js +73 -0
- package/dist/internal/builtin-tools/index.d.ts +8 -1
- package/dist/internal/builtin-tools/index.d.ts.map +1 -1
- package/dist/internal/builtin-tools/index.js +15 -0
- package/dist/internal/cli-ax.d.ts +19 -0
- package/dist/internal/cli-ax.d.ts.map +1 -1
- package/dist/internal/cli-ax.js +196 -57
- package/dist/internal/cli-slack.d.ts.map +1 -1
- package/dist/internal/cli-slack.js +14 -6
- package/dist/internal/cursor-slack-relay.d.ts +2 -0
- package/dist/internal/cursor-slack-relay.d.ts.map +1 -1
- package/dist/internal/cursor-slack-relay.js +1 -0
- package/dist/internal/deploy-client.d.ts +2 -0
- package/dist/internal/deploy-client.d.ts.map +1 -1
- package/dist/internal/deploy-client.js +1 -0
- package/dist/internal/discovery.d.ts.map +1 -1
- package/dist/internal/discovery.js +91 -3
- package/dist/internal/distribution.d.ts.map +1 -1
- package/dist/internal/distribution.js +1 -0
- package/dist/internal/model-pricing.d.ts +49 -0
- package/dist/internal/model-pricing.d.ts.map +1 -0
- package/dist/internal/model-pricing.js +377 -0
- package/dist/internal/reminder-runner.d.ts.map +1 -1
- package/dist/internal/reminder-runner.js +1 -0
- package/dist/internal/schedule-runner.d.ts.map +1 -1
- package/dist/internal/schedule-runner.js +1 -0
- package/dist/internal/sdk-runner.d.ts.map +1 -1
- package/dist/internal/sdk-runner.js +9 -1
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +49 -5
- package/dist/internal/session-cost.d.ts +49 -0
- package/dist/internal/session-cost.d.ts.map +1 -0
- package/dist/internal/session-cost.js +175 -0
- package/dist/internal/session-engine.d.ts +35 -1
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +128 -7
- package/dist/internal/storage-coordinator.d.ts +15 -0
- package/dist/internal/storage-coordinator.d.ts.map +1 -1
- package/dist/internal/storage-coordinator.js +52 -0
- package/dist/internal/trajectory.d.ts +9 -1
- package/dist/internal/trajectory.d.ts.map +1 -1
- package/dist/internal/trajectory.js +32 -5
- package/dist/playground/assets/index-Bul1WfrB.css +1 -0
- package/dist/playground/assets/index-CkN74U_L.js +87 -0
- package/dist/playground/index.html +2 -2
- package/dist/storage.d.ts +4 -0
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +4 -0
- package/dist/types.d.ts +164 -1
- package/dist/types.d.ts.map +1 -1
- package/docs/README.md +1 -0
- package/docs/guides/slack.md +32 -7
- package/docs/guides/webhooks.md +183 -0
- package/docs/reference/channels.md +2 -1
- package/package.json +26 -26
- package/skills/setup-slack/SKILL.md +3 -0
- package/src/ab.ts +7 -0
- package/src/bin/agent-serve.ts +42 -5
- package/src/channels/slack/auth.ts +29 -22
- package/src/channels/slack/cursor-account.ts +2 -0
- package/src/channels/slack/defaults.ts +24 -4
- package/src/channels/slack/dispatch.ts +42 -1
- package/src/channels/slack/external-policy.ts +62 -0
- package/src/channels/slack/index.ts +1 -0
- package/src/channels/slack/interactive.ts +42 -10
- package/src/channels/slack/slack-channel.ts +20 -3
- package/src/channels/slack/types.ts +12 -0
- package/src/internal/ab-fold.ts +4 -0
- package/src/internal/ab-snapshot.ts +1 -0
- package/src/internal/cli-ax.ts +205 -34
- package/src/internal/cursor-slack-relay.ts +3 -0
- package/src/internal/deploy-client.ts +3 -0
- package/src/internal/discovery.ts +8 -1
- package/src/internal/model-pricing.ts +426 -0
- package/src/internal/sdk-runner.ts +10 -2
- package/src/internal/server.ts +9 -5
- package/src/internal/session-cost.ts +231 -0
- package/src/internal/session-engine.ts +28 -0
- package/src/internal/trajectory.ts +45 -3
- package/src/types.ts +24 -1
- package/dist/channels/github/instrument.d.ts +0 -20
- package/dist/channels/github/instrument.d.ts.map +0 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.BQTzJjR_.js +0 -1
- package/dist/docs/assets/guides_slack.md.D_xFKlnF.lean.js +0 -1
- package/dist/docs/assets/guides_webhooks.md.B1EswtUu.js +0 -50
- package/dist/internal/json-dir-store.d.ts +0 -32
- package/dist/internal/json-dir-store.d.ts.map +0 -1
- package/dist/internal/json-dir-store.js +0 -100
- package/dist/playground/assets/index-CjOQ4hN9.css +0 -1
- package/dist/playground/assets/index-DqXdAFGa.js +0 -85
- package/src/bin/agent-serve.version.test.ts +0 -64
- package/src/channels/github/api.test.ts +0 -64
- package/src/channels/github/auth.test.ts +0 -105
- package/src/channels/github/cursor-account.test.ts +0 -204
- package/src/channels/github/forward.test.ts +0 -457
- package/src/channels/github/github.test.ts +0 -985
- package/src/channels/github/replay.test.ts +0 -179
- package/src/channels/slack/api.post-message.test.ts +0 -148
- package/src/channels/slack/approvals.test.ts +0 -328
- package/src/channels/slack/block-actions.test.ts +0 -469
- package/src/channels/slack/bot-mentions.test.ts +0 -267
- package/src/channels/slack/channel-watch.test.ts +0 -363
- package/src/channels/slack/cursor-account.test.ts +0 -301
- package/src/channels/slack/defaults.final-post.test.ts +0 -182
- package/src/channels/slack/dispatch.test.ts +0 -875
- package/src/channels/slack/eval-directive.test.ts +0 -273
- package/src/channels/slack/message-body.test.ts +0 -54
- package/src/channels/slack/nudge-store.test.ts +0 -143
- package/src/channels/slack/slack.test.ts +0 -391
- package/src/channels/slack/stop.test.ts +0 -23
- package/src/channels/slack/thread-context.test.ts +0 -202
- package/src/evals/assertions.test.ts +0 -617
- package/src/evals/expect.test.ts +0 -144
- package/src/evals/judge.test.ts +0 -181
- package/src/evals/loaders.test.ts +0 -132
- package/src/evals/matchers.test.ts +0 -95
- package/src/evals/reporters.test.ts +0 -303
- package/src/evals/run-facts.test.ts +0 -259
- package/src/internal/ab-snapshot.test.ts +0 -325
- package/src/internal/approval-gate.test.ts +0 -49
- package/src/internal/approvals.integration.test.ts +0 -383
- package/src/internal/artifacts-store.test.ts +0 -283
- package/src/internal/authored-loaders.test.ts +0 -31
- package/src/internal/builtin-tools/artifacts.test.ts +0 -247
- package/src/internal/builtin-tools/reminders.test.ts +0 -201
- package/src/internal/channel-route-schema.test.ts +0 -294
- package/src/internal/chat-attach.test.ts +0 -262
- package/src/internal/cli-deploy.test.ts +0 -1991
- package/src/internal/cli-docs.test.ts +0 -161
- package/src/internal/cli-mcp.test.ts +0 -789
- package/src/internal/cli-skills.test.ts +0 -133
- package/src/internal/cli-slack.test.ts +0 -1654
- package/src/internal/cloud-merge.test.ts +0 -74
- package/src/internal/cron.test.ts +0 -22
- package/src/internal/cursor/account-mcp.test.ts +0 -807
- package/src/internal/cursor/backend-client.test.ts +0 -591
- package/src/internal/cursor/credentials.test.ts +0 -351
- package/src/internal/cursor/github-credentials.test.ts +0 -136
- package/src/internal/cursor-account-mcp-auth.test.ts +0 -310
- package/src/internal/cursor-account.integration.test.ts +0 -441
- package/src/internal/cursor-event-relay.test.ts +0 -746
- package/src/internal/cursor-github-credentials.integration.test.ts +0 -271
- package/src/internal/cursor-slack-relay.test.ts +0 -525
- package/src/internal/deploy-source.test.ts +0 -111
- package/src/internal/discovery.artifact-tool.test.ts +0 -136
- package/src/internal/discovery.artifacts.test.ts +0 -119
- package/src/internal/discovery.builtin-tools.test.ts +0 -94
- package/src/internal/discovery.concurrency.test.ts +0 -60
- package/src/internal/discovery.cursor-account.test.ts +0 -133
- package/src/internal/discovery.cwd.test.ts +0 -83
- package/src/internal/discovery.hosting.test.ts +0 -80
- package/src/internal/discovery.identity.test.ts +0 -44
- package/src/internal/docs-site.test.ts +0 -66
- package/src/internal/duration.test.ts +0 -29
- package/src/internal/eval-judge-model.test.ts +0 -187
- package/src/internal/eval-run-store.cancel.test.ts +0 -142
- package/src/internal/eval-run-store.storage.test.ts +0 -211
- package/src/internal/eval-runner.http.test.ts +0 -403
- package/src/internal/eval-runner.run.test.ts +0 -928
- package/src/internal/evals-client.test.ts +0 -307
- package/src/internal/event-mapper.test.ts +0 -243
- package/src/internal/github-fanout.test.ts +0 -213
- package/src/internal/handleAgentServeTrigger.test.ts +0 -185
- package/src/internal/host-kv.test.ts +0 -82
- package/src/internal/host-platforms.test.ts +0 -126
- package/src/internal/http-channel.test.ts +0 -402
- package/src/internal/init-project.test.ts +0 -270
- package/src/internal/install-cursor-skills.test.ts +0 -262
- package/src/internal/local-env.test.ts +0 -120
- package/src/internal/log-ring.test.ts +0 -31
- package/src/internal/logs-client.test.ts +0 -350
- package/src/internal/mcp-endpoint.test.ts +0 -436
- package/src/internal/mcp-host.test.ts +0 -298
- package/src/internal/mcp-oauth.test.ts +0 -148
- package/src/internal/net.test.ts +0 -17
- package/src/internal/peer-connections.test.ts +0 -128
- package/src/internal/peer-mcp.integration.test.ts +0 -289
- package/src/internal/playground/toolchain.test.ts +0 -53
- package/src/internal/playground-cli.test.ts +0 -187
- package/src/internal/playground-proxy.test.ts +0 -376
- package/src/internal/prompt-context.integration.test.ts +0 -232
- package/src/internal/prompt-context.test.ts +0 -127
- package/src/internal/reminder-runner.test.ts +0 -432
- package/src/internal/reminder-store.test.ts +0 -53
- package/src/internal/request-headers.test.ts +0 -27
- package/src/internal/resolve-prod-target.test.ts +0 -787
- package/src/internal/resolved-connections.test.ts +0 -295
- package/src/internal/router.test.ts +0 -57
- package/src/internal/sdk-runner.test.ts +0 -290
- package/src/internal/server.artifacts.test.ts +0 -334
- package/src/internal/session-engine.artifacts.test.ts +0 -293
- package/src/internal/session-engine.coalesce.test.ts +0 -169
- package/src/internal/session-engine.concurrency.test.ts +0 -250
- package/src/internal/session-engine.host-oauth-mcp.test.ts +0 -110
- package/src/internal/session-engine.interrupt.test.ts +0 -577
- package/src/internal/session-engine.storage.test.ts +0 -547
- package/src/internal/session-urls.test.ts +0 -28
- package/src/internal/sessions-client.test.ts +0 -518
- package/src/internal/storage-coordinator.test.ts +0 -517
- package/src/internal/tool-call.test.ts +0 -458
- package/src/internal/tool-result.test.ts +0 -52
- package/src/internal/trajectory.approvals.test.ts +0 -83
- package/src/internal/trajectory.subagents.test.ts +0 -198
- package/src/internal/turn-governor.test.ts +0 -137
- package/src/internal/update-check.test.ts +0 -485
- package/src/internal/workspace.test.ts +0 -207
- package/src/storage-backends/cursor-hosted.test.ts +0 -121
- /package/dist/docs/assets/{index.md.Dfv5ic9t.lean.js → index.md.CH_s5uZe.lean.js} +0 -0
package/docs/guides/webhooks.md
CHANGED
|
@@ -177,6 +177,189 @@ swaps the default for `bearerAuth` on every channel that doesn't author
|
|
|
177
177
|
its own chain, and `--allow-anonymous` swaps it for `allowAll()`
|
|
178
178
|
(trusted networks only). Authored `auth` arrays always win over both.
|
|
179
179
|
|
|
180
|
+
## Example: Linear as the control plane
|
|
181
|
+
|
|
182
|
+
Everything above composes into a working ticket-driven agent. This
|
|
183
|
+
example wires Linear to the agent: new issues and comments start or
|
|
184
|
+
resume sessions, and replies land back on the issue as comments. The
|
|
185
|
+
same shape works for any tracker with signed webhooks.
|
|
186
|
+
|
|
187
|
+
Create the webhook in Linear under Settings → API → "New webhook",
|
|
188
|
+
pointed at `https://<your-host>/<slug>/v1/channels/linear`, and copy
|
|
189
|
+
the signing secret. Linear requires a public HTTPS URL, so use a
|
|
190
|
+
tunnel during local development or test with signed fixtures (below).
|
|
191
|
+
Set three environment variables:
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
LINEAR_WEBHOOK_SECRET=lin_wh_... # the webhook's signing secret
|
|
195
|
+
LINEAR_API_KEY=lin_api_... # posts replies as comments
|
|
196
|
+
LINEAR_AGENT_USER_ID=... # the API key's user: query { viewer { id } }
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
`LINEAR_AGENT_USER_ID` matters: replies posted with the API key
|
|
200
|
+
trigger the Comment webhook again, so the channel must recognize and
|
|
201
|
+
skip its own comments. Without the guard, every reply starts another
|
|
202
|
+
turn.
|
|
203
|
+
|
|
204
|
+
Author `agent/channels/linear.ts`:
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
import { Buffer } from "node:buffer";
|
|
208
|
+
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
209
|
+
import { allowAll, defineChannel, POST } from "@cursor/july/channels";
|
|
210
|
+
import { z } from "zod";
|
|
211
|
+
|
|
212
|
+
const secret = process.env.LINEAR_WEBHOOK_SECRET ?? "";
|
|
213
|
+
const apiKey = process.env.LINEAR_API_KEY ?? "";
|
|
214
|
+
const agentUserId = process.env.LINEAR_AGENT_USER_ID ?? "";
|
|
215
|
+
|
|
216
|
+
interface LinearWebhook {
|
|
217
|
+
action: string;
|
|
218
|
+
type: string;
|
|
219
|
+
url?: string;
|
|
220
|
+
webhookTimestamp: number;
|
|
221
|
+
data: {
|
|
222
|
+
id: string;
|
|
223
|
+
title?: string;
|
|
224
|
+
description?: string;
|
|
225
|
+
body?: string;
|
|
226
|
+
issueId?: string;
|
|
227
|
+
userId?: string;
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Linear signs the raw body: hex HMAC-SHA256 in `Linear-Signature`.
|
|
232
|
+
function verified(rawBody: string, header: string | null): boolean {
|
|
233
|
+
if (secret === "" || header === null) {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
const expected = createHmac("sha256", secret).update(rawBody).digest();
|
|
237
|
+
const received = Buffer.from(header, "hex");
|
|
238
|
+
return (
|
|
239
|
+
received.length === expected.length && timingSafeEqual(received, expected)
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async function postComment(issueId: string, body: string): Promise<void> {
|
|
244
|
+
const response = await fetch("https://api.linear.app/graphql", {
|
|
245
|
+
method: "POST",
|
|
246
|
+
headers: { "content-type": "application/json", authorization: apiKey },
|
|
247
|
+
body: JSON.stringify({
|
|
248
|
+
query:
|
|
249
|
+
"mutation($input: CommentCreateInput!) { commentCreate(input: $input) { success } }",
|
|
250
|
+
variables: { input: { issueId, body } },
|
|
251
|
+
}),
|
|
252
|
+
});
|
|
253
|
+
if (!response.ok) {
|
|
254
|
+
throw new Error(`commentCreate failed: ${response.status}`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function issueIdFromToken(token: string | null): string | null {
|
|
259
|
+
if (token === null || !token.startsWith("linear:")) {
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
return token.slice("linear:".length);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export default defineChannel({
|
|
266
|
+
// The HMAC check is the request auth, so admit everything at the route.
|
|
267
|
+
auth: [allowAll()],
|
|
268
|
+
routes: [
|
|
269
|
+
POST("/", {
|
|
270
|
+
description: "Linear webhook ingress",
|
|
271
|
+
// The payload shape varies by `Linear-Event`; parse after verifying.
|
|
272
|
+
bodySchema: z.unknown(),
|
|
273
|
+
handler: async (request, { send, waitUntil }) => {
|
|
274
|
+
const rawBody = await request.text();
|
|
275
|
+
if (!verified(rawBody, request.headers.get("linear-signature"))) {
|
|
276
|
+
return Response.json({ ok: false }, { status: 401 });
|
|
277
|
+
}
|
|
278
|
+
const event = JSON.parse(rawBody) as LinearWebhook;
|
|
279
|
+
// Reject stale deliveries to guard against replay.
|
|
280
|
+
if (Math.abs(Date.now() - event.webhookTimestamp) > 60_000) {
|
|
281
|
+
return Response.json({ ok: false }, { status: 401 });
|
|
282
|
+
}
|
|
283
|
+
// Skip the agent's own comments so replies don't re-trigger it.
|
|
284
|
+
if (event.type === "Comment" && event.data.userId === agentUserId) {
|
|
285
|
+
return Response.json({ ok: true });
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// New issue → new session. New comment → follow-up on the same
|
|
289
|
+
// session, keyed by issue id through the continuation token.
|
|
290
|
+
let issueId: string | undefined;
|
|
291
|
+
let message: string | undefined;
|
|
292
|
+
if (event.type === "Issue" && event.action === "create") {
|
|
293
|
+
issueId = event.data.id;
|
|
294
|
+
message = `New Linear issue: ${event.data.title}\n\n${
|
|
295
|
+
event.data.description ?? ""
|
|
296
|
+
}\n${event.url ?? ""}`;
|
|
297
|
+
} else if (event.type === "Comment" && event.action === "create") {
|
|
298
|
+
issueId = event.data.issueId;
|
|
299
|
+
message = event.data.body;
|
|
300
|
+
}
|
|
301
|
+
if (issueId === undefined || message === undefined) {
|
|
302
|
+
return Response.json({ ok: true });
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Linear retries on any non-200 and times out after five
|
|
306
|
+
// seconds: ACK now, run the turn in the background.
|
|
307
|
+
waitUntil(send(message, { continuationToken: `linear:${issueId}` }));
|
|
308
|
+
return Response.json({ ok: true });
|
|
309
|
+
},
|
|
310
|
+
}),
|
|
311
|
+
],
|
|
312
|
+
events: {
|
|
313
|
+
async "message.completed"(event, channel) {
|
|
314
|
+
if (event.data.finishReason === "tool_call" || event.data.text === "") {
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
const issueId = issueIdFromToken(channel.continuationToken);
|
|
318
|
+
if (issueId !== null) {
|
|
319
|
+
await postComment(issueId, event.data.text);
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
async "turn.failed"(event, channel) {
|
|
323
|
+
const issueId = issueIdFromToken(channel.continuationToken);
|
|
324
|
+
if (issueId !== null) {
|
|
325
|
+
await postComment(issueId, `Turn failed: ${event.data.message}`);
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
});
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
The channel handles ingress and reply delivery deterministically. To
|
|
333
|
+
let the model read and update Linear during the turn (search related
|
|
334
|
+
issues, change state, assign), add the Linear MCP connection alongside
|
|
335
|
+
it:
|
|
336
|
+
|
|
337
|
+
```ts
|
|
338
|
+
// agent/mcp-connections/linear.ts
|
|
339
|
+
import { defineConnection } from "@cursor/july/connections";
|
|
340
|
+
|
|
341
|
+
// Uses the signed-in Cursor account's Linear connector.
|
|
342
|
+
export default defineConnection({ cursorAccount: true, servers: ["Linear"] });
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
See [MCP connections](../reference/connections.md) for the direct
|
|
346
|
+
`https://mcp.linear.app/mcp` form when the host isn't signed in to
|
|
347
|
+
Cursor.
|
|
348
|
+
|
|
349
|
+
To test without a public URL, save a payload from the webhook's
|
|
350
|
+
delivery log (or the sample in
|
|
351
|
+
[Linear's webhook docs](https://linear.app/developers/webhooks)) under
|
|
352
|
+
`fixtures/`, refresh its `webhookTimestamp`, and sign it yourself:
|
|
353
|
+
|
|
354
|
+
```bash
|
|
355
|
+
SIG=$(node -e 'const {createHmac}=require("node:crypto");const fs=require("node:fs");
|
|
356
|
+
process.stdout.write(createHmac("sha256",process.env.LINEAR_WEBHOOK_SECRET)
|
|
357
|
+
.update(fs.readFileSync("fixtures/issue-create.json")).digest("hex"))')
|
|
358
|
+
curl -X POST http://127.0.0.1:3000/<slug>/v1/channels/linear/ \
|
|
359
|
+
-H 'content-type: application/json' -H "linear-signature: $SIG" \
|
|
360
|
+
--data-binary @fixtures/issue-create.json
|
|
361
|
+
```
|
|
362
|
+
|
|
180
363
|
## Test a channel
|
|
181
364
|
|
|
182
365
|
Start with curl and saved payloads under `fixtures/`. The playground's
|
|
@@ -174,7 +174,8 @@ declares `[localDevStrict()]` stays loopback-only even on an
|
|
|
174
174
|
## First class channels
|
|
175
175
|
|
|
176
176
|
**Slack** (`@cursor/july/channels/slack`): Socket Mode
|
|
177
|
-
transport, streaming replies, engagement rules, approval cards
|
|
177
|
+
transport, streaming replies, engagement rules, approval cards, and a
|
|
178
|
+
default block on Slack Connect / guest / other-workspace senders. Author
|
|
178
179
|
`agent/channels/slack.ts` with `slackChannel()`. Guide:
|
|
179
180
|
[Slack](../guides/slack.md).
|
|
180
181
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cursor/july",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.28",
|
|
4
4
|
"description": "(early alpha) Filesystem-first framework for defining Cursor agents as markdown and TypeScript and serving them over channels with the Cursor SDK.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
6
|
"repository": {
|
|
@@ -190,6 +190,24 @@
|
|
|
190
190
|
"default": "./package.json"
|
|
191
191
|
}
|
|
192
192
|
},
|
|
193
|
+
"scripts": {
|
|
194
|
+
"build": "tsgo -p tsconfig.build.json && pnpm run build:playground && pnpm run docs:build",
|
|
195
|
+
"build:playground": "vite build --config playground/vite.config.ts",
|
|
196
|
+
"build:playground:central": "vite build --mode central --config playground/vite.config.ts",
|
|
197
|
+
"dev:playground": "vite --config playground/vite.config.ts",
|
|
198
|
+
"check": "biome check src && tsgo --noEmit && tsgo --noEmit -p playground/tsconfig.json",
|
|
199
|
+
"docs:dev": "vitepress dev docs",
|
|
200
|
+
"docs:build": "vitepress build docs",
|
|
201
|
+
"docs:preview": "vitepress preview docs",
|
|
202
|
+
"fix": "biome check --write --unsafe src",
|
|
203
|
+
"test": "vitest run --passWithNoTests",
|
|
204
|
+
"type-check": "tsgo --noEmit && tsgo --noEmit -p playground/tsconfig.json",
|
|
205
|
+
"build-dev": "echo noop",
|
|
206
|
+
"build-types": "tsgo -p tsconfig.build.json --emitDeclarationOnly",
|
|
207
|
+
"verify-publish": "node scripts/verify-publish-artifacts.mjs",
|
|
208
|
+
"smoke-publish": "node scripts/smoke-publish-artifact.mjs",
|
|
209
|
+
"publish-check": "pnpm run build && pnpm run verify-publish && pnpm run smoke-publish"
|
|
210
|
+
},
|
|
193
211
|
"engines": {
|
|
194
212
|
"node": ">=22.13"
|
|
195
213
|
},
|
|
@@ -209,13 +227,14 @@
|
|
|
209
227
|
"zod-to-json-schema": "^3.24.5"
|
|
210
228
|
},
|
|
211
229
|
"devDependencies": {
|
|
212
|
-
"@
|
|
230
|
+
"@anysphere/ui": "workspace:*",
|
|
231
|
+
"@base-ui/react": "1.6.0",
|
|
213
232
|
"@stylexjs/stylex": "0.18.3",
|
|
214
|
-
"@stylexswc/rs-compiler": "^0.
|
|
233
|
+
"@stylexswc/rs-compiler": "^0.18.0",
|
|
215
234
|
"@tanstack/react-virtual": "3.13.23",
|
|
216
235
|
"@types/js-yaml": "4.0.9",
|
|
217
236
|
"@types/ms": "^2.1.0",
|
|
218
|
-
"@types/node": "
|
|
237
|
+
"@types/node": "catalog:",
|
|
219
238
|
"@types/pg": "^8.16.0",
|
|
220
239
|
"@types/react": "^19.0.0",
|
|
221
240
|
"@types/react-dom": "^19.0.0",
|
|
@@ -228,29 +247,10 @@
|
|
|
228
247
|
"react-markdown": "^10.1.0",
|
|
229
248
|
"rehype-highlight": "^7.0.2",
|
|
230
249
|
"remark-gfm": "^4.0.1",
|
|
231
|
-
"typescript": "
|
|
250
|
+
"typescript": "catalog:",
|
|
232
251
|
"vite": "^7.3.0",
|
|
233
252
|
"vitepress": "1.6.4",
|
|
234
253
|
"vitest": "^4.0.18",
|
|
235
|
-
"zustand": "^5.0.9"
|
|
236
|
-
"@anysphere/ui": "1.0.0"
|
|
237
|
-
},
|
|
238
|
-
"scripts": {
|
|
239
|
-
"build": "tsgo -p tsconfig.build.json && pnpm run build:playground && pnpm run docs:build",
|
|
240
|
-
"build:playground": "vite build --config playground/vite.config.ts",
|
|
241
|
-
"build:playground:central": "vite build --mode central --config playground/vite.config.ts",
|
|
242
|
-
"dev:playground": "vite --config playground/vite.config.ts",
|
|
243
|
-
"check": "biome check src && tsgo --noEmit && tsgo --noEmit -p playground/tsconfig.json",
|
|
244
|
-
"docs:dev": "vitepress dev docs",
|
|
245
|
-
"docs:build": "vitepress build docs",
|
|
246
|
-
"docs:preview": "vitepress preview docs",
|
|
247
|
-
"fix": "biome check --write --unsafe src",
|
|
248
|
-
"test": "vitest run --passWithNoTests",
|
|
249
|
-
"type-check": "tsgo --noEmit && tsgo --noEmit -p playground/tsconfig.json",
|
|
250
|
-
"build-dev": "echo noop",
|
|
251
|
-
"build-types": "tsgo -p tsconfig.build.json --emitDeclarationOnly",
|
|
252
|
-
"verify-publish": "node scripts/verify-publish-artifacts.mjs",
|
|
253
|
-
"smoke-publish": "node scripts/smoke-publish-artifact.mjs",
|
|
254
|
-
"publish-check": "pnpm run build && pnpm run verify-publish && pnpm run smoke-publish"
|
|
254
|
+
"zustand": "^5.0.9"
|
|
255
255
|
}
|
|
256
|
-
}
|
|
256
|
+
}
|
|
@@ -97,6 +97,9 @@ export default slackChannel({
|
|
|
97
97
|
single token — string literals with spaces fail typecheck; runtime
|
|
98
98
|
values throw at construction/serve. Names must be unique on the host;
|
|
99
99
|
an unmatched `@Cursor <name>` stays with Cursor's normal Slack agent.
|
|
100
|
+
External senders (Slack Connect / guests / other workspaces) are
|
|
101
|
+
blocked by default; see `policy.blockExternals` in
|
|
102
|
+
[docs/guides/slack.md](../../docs/guides/slack.md).
|
|
100
103
|
|
|
101
104
|
### A2 — Sign in + serve (agent)
|
|
102
105
|
|
package/src/ab.ts
CHANGED
|
@@ -78,6 +78,12 @@ export interface ABMetrics {
|
|
|
78
78
|
outputTokens: number;
|
|
79
79
|
cacheReadTokens: number;
|
|
80
80
|
cacheWriteTokens: number;
|
|
81
|
+
/**
|
|
82
|
+
* Sum of estimated turn costs in USD (the `cost.totalUsd` recorded on
|
|
83
|
+
* `turn.completed` events). Turns whose model has no known rates
|
|
84
|
+
* contribute 0.
|
|
85
|
+
*/
|
|
86
|
+
costUsd: number;
|
|
81
87
|
/** Sum of turn wall times (turn.started → turn.completed/failed). */
|
|
82
88
|
wallTimeMs: number;
|
|
83
89
|
/** Custom counters from {@link ABConfig.derive}. */
|
|
@@ -315,6 +321,7 @@ export function emptyABMetrics(): ABMetrics {
|
|
|
315
321
|
outputTokens: 0,
|
|
316
322
|
cacheReadTokens: 0,
|
|
317
323
|
cacheWriteTokens: 0,
|
|
324
|
+
costUsd: 0,
|
|
318
325
|
wallTimeMs: 0,
|
|
319
326
|
custom: {},
|
|
320
327
|
};
|
package/src/bin/agent-serve.ts
CHANGED
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
type AxCliOptions,
|
|
30
30
|
cmdCall,
|
|
31
31
|
cmdChat,
|
|
32
|
+
cmdCost,
|
|
32
33
|
cmdEval,
|
|
33
34
|
cmdEvalCancel,
|
|
34
35
|
cmdEvalStatus,
|
|
@@ -55,6 +56,7 @@ import {
|
|
|
55
56
|
} from "../internal/cli-deploy.js";
|
|
56
57
|
import {
|
|
57
58
|
CLI_COMMAND_NAME as CLI,
|
|
59
|
+
PACKAGE_NAME,
|
|
58
60
|
packageVersion,
|
|
59
61
|
} from "../internal/distribution.js";
|
|
60
62
|
import { loadAgentLocalEnv } from "../internal/local-env.js";
|
|
@@ -83,7 +85,9 @@ Usage:
|
|
|
83
85
|
${CLI} sessions [--url <baseUrl> | --prod] [--json]
|
|
84
86
|
[--slug <slug>] [--team <id>] [--dir <path>]
|
|
85
87
|
${CLI} session <sessionId> [--url <baseUrl> | --prod]
|
|
86
|
-
[--json | --text | --events]
|
|
88
|
+
[--json | --text | --events] [--out <file.ndjson>]
|
|
89
|
+
[--slug <slug>] [--team <id>] [--dir <path>]
|
|
90
|
+
${CLI} cost [sessionId] [--url <baseUrl> | --prod] [--json]
|
|
87
91
|
[--slug <slug>] [--team <id>] [--dir <path>]
|
|
88
92
|
${CLI} playground [--url <baseUrl> | --prod] [--session <id>] [--print]
|
|
89
93
|
[--slug <slug>] [--team <id>] [--dir <path>]
|
|
@@ -106,11 +110,12 @@ Usage:
|
|
|
106
110
|
[--tag <tag>]... [--verbose] [--timeout-ms <n>]
|
|
107
111
|
[--strict] [--max-concurrency <n>]
|
|
108
112
|
[--junit <path>] [--artifacts <dir>|--no-artifacts]
|
|
113
|
+
[--out <file.json>]
|
|
109
114
|
[--skip-report] [--no-stream] [--no-wait]
|
|
110
115
|
[--prod | --url <baseUrl>] [--slug <slug>]
|
|
111
116
|
[--team <id>]
|
|
112
117
|
${CLI} eval status [runId] --prod|--url <baseUrl> [--dir <path>] [--json]
|
|
113
|
-
[--slug <slug>] [--team <id>]
|
|
118
|
+
[--out <file.json>] [--slug <slug>] [--team <id>]
|
|
114
119
|
${CLI} eval cancel <runId> --prod|--url <baseUrl> [--dir <path>] [--json]
|
|
115
120
|
[--slug <slug>] [--team <id>]
|
|
116
121
|
${CLI} info [--dir <path>] [--json]
|
|
@@ -161,6 +166,10 @@ Commands:
|
|
|
161
166
|
session Show one session. Default: text trajectory. --json for trajectory
|
|
162
167
|
JSON; --events for the raw event list. To continue interactively,
|
|
163
168
|
use \`${CLI} resume [id]\`.
|
|
169
|
+
cost Pull cost metrics. With a session id: per-turn token usage and
|
|
170
|
+
estimated cost for that session. Without one: one row per
|
|
171
|
+
session plus a total. Costs are the engine's recorded estimates
|
|
172
|
+
from turn.completed events; --json for machine-readable reports.
|
|
164
173
|
playground Open the web playground. --prod starts a loopback proxy that
|
|
165
174
|
injects hosted engine credentials (browsers cannot set them).
|
|
166
175
|
--print skips opening a browser. Ctrl-C stops the proxy.
|
|
@@ -384,7 +393,11 @@ Options:
|
|
|
384
393
|
--comment github replay: comment body for comment events.
|
|
385
394
|
--context github replay: commit-status context.
|
|
386
395
|
--dry-run github replay: print synthesized payloads, don't POST.
|
|
387
|
-
--out
|
|
396
|
+
--out eval / eval status <runId>: also write the full results
|
|
397
|
+
JSON to this path. session: write the raw NDJSON trace
|
|
398
|
+
there instead of printing (same format as run --events;
|
|
399
|
+
playground-openable). github replay: write synthesized
|
|
400
|
+
payloads as fixtures.
|
|
388
401
|
`;
|
|
389
402
|
|
|
390
403
|
async function main(argv: string[]): Promise<number> {
|
|
@@ -767,6 +780,7 @@ async function main(argv: string[]): Promise<number> {
|
|
|
767
780
|
apiKey: values["api-key"],
|
|
768
781
|
slug: values.slug,
|
|
769
782
|
bearerToken: values["bearer-token"],
|
|
783
|
+
out: values.out,
|
|
770
784
|
})
|
|
771
785
|
);
|
|
772
786
|
}
|
|
@@ -811,6 +825,7 @@ async function main(argv: string[]): Promise<number> {
|
|
|
811
825
|
artifacts: values["no-artifacts"] ? false : values.artifacts,
|
|
812
826
|
skipReport: values["skip-report"],
|
|
813
827
|
maxConcurrency: parseMaxConcurrency(values["max-concurrency"]),
|
|
828
|
+
out: values.out,
|
|
814
829
|
})
|
|
815
830
|
);
|
|
816
831
|
}
|
|
@@ -904,6 +919,23 @@ async function main(argv: string[]): Promise<number> {
|
|
|
904
919
|
json: values.json,
|
|
905
920
|
text: values.text,
|
|
906
921
|
rawEvents: values["raw-events"],
|
|
922
|
+
out: values.out,
|
|
923
|
+
})
|
|
924
|
+
);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
case "cost": {
|
|
928
|
+
return cmdCost(
|
|
929
|
+
positionals[0] ?? values["session-id"] ?? values.session,
|
|
930
|
+
axFromOptions({
|
|
931
|
+
dir,
|
|
932
|
+
url: values.url,
|
|
933
|
+
prod: values.prod,
|
|
934
|
+
team: values.team,
|
|
935
|
+
apiKey: values["api-key"],
|
|
936
|
+
slug: values.slug,
|
|
937
|
+
bearerToken: values["bearer-token"],
|
|
938
|
+
json: values.json,
|
|
907
939
|
})
|
|
908
940
|
);
|
|
909
941
|
}
|
|
@@ -1267,6 +1299,7 @@ function axFromOptions(
|
|
|
1267
1299
|
artifacts: partial.artifacts,
|
|
1268
1300
|
skipReport: partial.skipReport,
|
|
1269
1301
|
maxConcurrency: partial.maxConcurrency,
|
|
1302
|
+
out: partial.out,
|
|
1270
1303
|
};
|
|
1271
1304
|
}
|
|
1272
1305
|
|
|
@@ -1383,11 +1416,15 @@ function printServeBanner(
|
|
|
1383
1416
|
): void {
|
|
1384
1417
|
const c = stdoutPalette();
|
|
1385
1418
|
const agents = handle.agents;
|
|
1419
|
+
// Name the framework build in the boot line: hosted engines install the
|
|
1420
|
+
// published package at provision time, so the serve log is the only
|
|
1421
|
+
// durable record of which build a deployment's turns actually ran on.
|
|
1422
|
+
const versionTag = c.dim(`(${PACKAGE_NAME} ${packageVersion()})`);
|
|
1386
1423
|
if (agents.length > 1) {
|
|
1387
|
-
process.stdout.write(`${agents.length} agents listening\n`);
|
|
1424
|
+
process.stdout.write(`${agents.length} agents listening ${versionTag}\n`);
|
|
1388
1425
|
} else {
|
|
1389
1426
|
const info = projectInfo(agents[0]?.project ?? handle.project);
|
|
1390
|
-
process.stdout.write(`${info.name} listening\n`);
|
|
1427
|
+
process.stdout.write(`${info.name} listening ${versionTag}\n`);
|
|
1391
1428
|
}
|
|
1392
1429
|
|
|
1393
1430
|
const playgroundUrl = resolvePlaygroundBannerUrl(handle, opts.noPlayground);
|
|
@@ -4,29 +4,39 @@
|
|
|
4
4
|
|
|
5
5
|
import { samePrincipal } from "../../internal/auth.js";
|
|
6
6
|
import type { AuthContext } from "../../types.js";
|
|
7
|
-
import type {
|
|
7
|
+
import type {
|
|
8
|
+
SlackBinding,
|
|
9
|
+
SlackBlockAction,
|
|
10
|
+
SlackMessage,
|
|
11
|
+
SlackRespondTo,
|
|
12
|
+
} from "./types.js";
|
|
8
13
|
|
|
9
|
-
|
|
10
|
-
* Build a thread-scoped principal so any participant in the same Slack
|
|
11
|
-
* thread can continue the session. The triggering user is preserved in
|
|
12
|
-
* attributes for message context.
|
|
13
|
-
*/
|
|
14
|
-
export function buildSlackAuthContext(args: {
|
|
14
|
+
export type BuildSlackAuthArgs = {
|
|
15
15
|
channelId: string;
|
|
16
16
|
threadTs: string;
|
|
17
17
|
teamId?: string;
|
|
18
18
|
userId?: string;
|
|
19
19
|
userName?: string;
|
|
20
20
|
isBot?: boolean;
|
|
21
|
-
/**
|
|
22
|
-
* Engagement surface that started the session (e.g. `"channel_post"` for
|
|
23
|
-
* the opt-in channel watch). Recorded as an attribute; the principal stays
|
|
24
|
-
* thread-scoped so an @mention in the same thread continues the session.
|
|
25
|
-
*/
|
|
26
21
|
engagement?: string;
|
|
27
|
-
}
|
|
22
|
+
/** Defaults to `"thread"`. `"author"` appends `:{userId}` to the principal. */
|
|
23
|
+
respondTo?: SlackRespondTo;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Build a Slack session principal.
|
|
28
|
+
*
|
|
29
|
+
* - `"thread"` (default): any participant can continue.
|
|
30
|
+
* - `"author"`: only the starter (`…:{userId}`), same shape approvals already honor.
|
|
31
|
+
*/
|
|
32
|
+
export function buildSlackAuthContext(args: BuildSlackAuthArgs): AuthContext {
|
|
28
33
|
const team = args.teamId ?? "unknown";
|
|
29
|
-
const
|
|
34
|
+
const respondTo = args.respondTo ?? "thread";
|
|
35
|
+
const base = `slack:${team}:${args.channelId}:${args.threadTs}`;
|
|
36
|
+
const authorScoped =
|
|
37
|
+
respondTo === "author" && args.userId !== undefined && args.userId !== "";
|
|
38
|
+
const principalId = authorScoped ? `${base}:${args.userId}` : base;
|
|
39
|
+
|
|
30
40
|
const attributes: Record<string, string> = {
|
|
31
41
|
channel_id: args.channelId,
|
|
32
42
|
thread_ts: args.threadTs,
|
|
@@ -106,7 +116,7 @@ export function blockActionAuth(action: SlackBlockAction): AuthContext {
|
|
|
106
116
|
export function defaultSlackAuth(
|
|
107
117
|
message: SlackMessage,
|
|
108
118
|
ctx: SlackBinding,
|
|
109
|
-
options?: { engagement?: string }
|
|
119
|
+
options?: { engagement?: string; respondTo?: SlackRespondTo }
|
|
110
120
|
): AuthContext | null {
|
|
111
121
|
if (message.author === undefined) {
|
|
112
122
|
return null;
|
|
@@ -114,14 +124,11 @@ export function defaultSlackAuth(
|
|
|
114
124
|
return buildSlackAuthContext({
|
|
115
125
|
channelId: ctx.slack.channelId,
|
|
116
126
|
threadTs: ctx.slack.threadTs,
|
|
117
|
-
|
|
127
|
+
teamId: message.teamId,
|
|
118
128
|
userId: message.author.userId,
|
|
119
|
-
|
|
120
|
-
? {}
|
|
121
|
-
: { userName: message.author.userName }),
|
|
129
|
+
userName: message.author.userName,
|
|
122
130
|
isBot: message.author.isBot,
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
: { engagement: options.engagement }),
|
|
131
|
+
engagement: options?.engagement,
|
|
132
|
+
respondTo: options?.respondTo,
|
|
126
133
|
});
|
|
127
134
|
}
|
|
@@ -172,6 +172,8 @@ export async function dispatchCursorSlackEnvelope(args: {
|
|
|
172
172
|
log,
|
|
173
173
|
evalCommandsEnabled: runtime.options.evalCommands !== false,
|
|
174
174
|
cursorAgentName: envelope.agentName,
|
|
175
|
+
// Backend router already refused externals for this envelope.
|
|
176
|
+
blockExternals: false,
|
|
175
177
|
...(args.hasContinuationSession === undefined
|
|
176
178
|
? {}
|
|
177
179
|
: { hasContinuationSession: args.hasContinuationSession }),
|
|
@@ -20,9 +20,11 @@ import { createStreamDeliveryStrategy } from "./live-delivery.js";
|
|
|
20
20
|
import { defaultSlackLog, type SlackLog } from "./log.js";
|
|
21
21
|
import { buildTaskChunkForResult, buildTaskChunksForCalls } from "./stream.js";
|
|
22
22
|
import type {
|
|
23
|
+
SlackAccessPolicy,
|
|
23
24
|
SlackChannelState,
|
|
24
25
|
SlackCredentials,
|
|
25
26
|
SlackMessageHandler,
|
|
27
|
+
SlackRespondTo,
|
|
26
28
|
SlackSuggestedPrompt,
|
|
27
29
|
} from "./types.js";
|
|
28
30
|
|
|
@@ -30,6 +32,19 @@ const SLACK_TURN_FAILURE_FALLBACK =
|
|
|
30
32
|
"I hit an error while handling your request. Please try again or rephrase.";
|
|
31
33
|
const SLACK_TURN_FAILURE_MAX_CHARS = 400;
|
|
32
34
|
|
|
35
|
+
export function resolveSlackRespondTo(
|
|
36
|
+
policy: SlackAccessPolicy | undefined
|
|
37
|
+
): SlackRespondTo {
|
|
38
|
+
return policy?.respondTo === "author" ? "author" : "thread";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Defaults to true when omitted. */
|
|
42
|
+
export function resolveSlackBlockExternals(
|
|
43
|
+
policy: SlackAccessPolicy | undefined
|
|
44
|
+
): boolean {
|
|
45
|
+
return policy?.blockExternals !== false;
|
|
46
|
+
}
|
|
47
|
+
|
|
33
48
|
/** Scrubbed turn.failed body for Slack (secrets / paths / URLs stripped). */
|
|
34
49
|
export function formatSlackTurnFailureMessage(raw: string | undefined): string {
|
|
35
50
|
const scrubbed = scrubSlackErrorDetail(raw?.trim() ?? "");
|
|
@@ -68,29 +83,34 @@ function resolveLoadingMessages(
|
|
|
68
83
|
|
|
69
84
|
/** Default mention/DM inbound handler (optional custom loading rotation). */
|
|
70
85
|
export function createDefaultOnInbound(
|
|
71
|
-
loadingMessages?: readonly string[]
|
|
86
|
+
loadingMessages?: readonly string[],
|
|
87
|
+
policy?: SlackAccessPolicy
|
|
72
88
|
): SlackMessageHandler {
|
|
73
89
|
const rotating = resolveLoadingMessages(loadingMessages);
|
|
90
|
+
const respondTo = resolveSlackRespondTo(policy);
|
|
74
91
|
return async (ctx, message) => {
|
|
75
92
|
await ctx.thread.startTyping("Thinking…", rotating);
|
|
76
|
-
const auth = defaultSlackAuth(message, ctx);
|
|
93
|
+
const auth = defaultSlackAuth(message, ctx, { respondTo });
|
|
77
94
|
return auth === null ? null : { auth };
|
|
78
95
|
};
|
|
79
96
|
}
|
|
80
97
|
|
|
81
98
|
/**
|
|
82
99
|
* Default watched-channel-post handler (optional custom loading rotation).
|
|
83
|
-
* Same
|
|
100
|
+
* Same principal policy as mentions, tagged with
|
|
84
101
|
* `engagement: "channel_post"` for auth policies.
|
|
85
102
|
*/
|
|
86
103
|
export function createDefaultOnChannelPost(
|
|
87
|
-
loadingMessages?: readonly string[]
|
|
104
|
+
loadingMessages?: readonly string[],
|
|
105
|
+
policy?: SlackAccessPolicy
|
|
88
106
|
): SlackMessageHandler {
|
|
89
107
|
const rotating = resolveLoadingMessages(loadingMessages);
|
|
108
|
+
const respondTo = resolveSlackRespondTo(policy);
|
|
90
109
|
return async (ctx, message) => {
|
|
91
110
|
await ctx.thread.startTyping("Thinking…", rotating);
|
|
92
111
|
const auth = defaultSlackAuth(message, ctx, {
|
|
93
112
|
engagement: "channel_post",
|
|
113
|
+
respondTo,
|
|
94
114
|
});
|
|
95
115
|
return auth === null ? null : { auth };
|
|
96
116
|
};
|