@cursor/july 0.1.27 → 0.1.29
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/bin/agent-serve.js +32 -5
- 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.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 +6 -0
- 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 +6 -0
- package/dist/channels/slack/interactive.d.ts.map +1 -1
- package/dist/channels/slack/interactive.js +42 -15
- package/dist/channels/slack/slack-channel.d.ts +19 -1
- package/dist/channels/slack/slack-channel.d.ts.map +1 -1
- package/dist/channels/slack/slack-channel.js +48 -6
- package/dist/channels/slack/types.d.ts +10 -0
- 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.BICLoJYF.js → app.QdunVQKg.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.DGe61XHA.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.wN4Ko9dT.js → VPLocalSearchBox.B6EpUXYf.js} +1 -1
- package/dist/docs/assets/chunks/{theme.dnnTE-Co.js → theme.BOTJVqh7.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/internal/cli-ax.d.ts +8 -0
- package/dist/internal/cli-ax.d.ts.map +1 -1
- package/dist/internal/cli-ax.js +116 -57
- package/dist/internal/cloud-turn-cost.d.ts +34 -0
- package/dist/internal/cloud-turn-cost.d.ts.map +1 -0
- package/dist/internal/cloud-turn-cost.js +106 -0
- 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 +9 -2
- package/dist/internal/event-log.d.ts +6 -0
- package/dist/internal/event-log.d.ts.map +1 -1
- package/dist/internal/event-log.js +16 -0
- package/dist/internal/host-platforms.d.ts +13 -0
- package/dist/internal/host-platforms.d.ts.map +1 -1
- package/dist/internal/host-platforms.js +25 -16
- package/dist/internal/http-channel.d.ts.map +1 -1
- package/dist/internal/http-channel.js +3 -1
- package/dist/internal/local-control-plane.d.ts +25 -0
- package/dist/internal/local-control-plane.d.ts.map +1 -0
- package/dist/internal/local-control-plane.js +172 -0
- package/dist/internal/platform-schedule-sync.d.ts +17 -0
- package/dist/internal/platform-schedule-sync.d.ts.map +1 -0
- package/dist/internal/platform-schedule-sync.js +72 -0
- package/dist/internal/platform-timers.d.ts +28 -0
- package/dist/internal/platform-timers.d.ts.map +1 -0
- package/dist/internal/platform-timers.js +120 -0
- package/dist/internal/reminder-runner.d.ts +16 -12
- package/dist/internal/reminder-runner.d.ts.map +1 -1
- package/dist/internal/reminder-runner.js +112 -61
- package/dist/internal/reminder-store.d.ts +5 -4
- package/dist/internal/reminder-store.d.ts.map +1 -1
- package/dist/internal/reminder-store.js +5 -4
- package/dist/internal/sdk-runner.d.ts.map +1 -1
- package/dist/internal/sdk-runner.js +44 -1
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +115 -16
- package/dist/internal/session-engine.d.ts +22 -1
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +110 -4
- package/dist/internal/storage-coordinator.d.ts +4 -15
- package/dist/internal/storage-coordinator.d.ts.map +1 -1
- package/dist/internal/storage-coordinator.js +4 -30
- package/dist/playground/assets/index-CquRB-l0.js +85 -0
- package/dist/playground/assets/{index-Bul1WfrB.css → index-Dj0bWpkn.css} +1 -1
- package/dist/playground/index.html +2 -2
- package/dist/storage.d.ts +0 -3
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +0 -3
- package/dist/types.d.ts +11 -2
- 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 +1 -1
- package/skills/setup-slack/SKILL.md +3 -0
- package/src/bin/agent-serve.ts +33 -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 +66 -3
- package/src/channels/slack/types.ts +12 -0
- package/src/internal/cli-ax.ts +110 -34
- package/src/internal/cloud-turn-cost.ts +158 -0
- package/src/internal/cursor-slack-relay.ts +3 -0
- package/src/internal/deploy-client.ts +3 -0
- package/src/internal/discovery.ts +9 -1
- package/src/internal/event-log.ts +17 -0
- package/src/internal/host-platforms.ts +40 -18
- package/src/internal/http-channel.ts +3 -1
- package/src/internal/local-control-plane.ts +188 -0
- package/src/internal/platform-schedule-sync.ts +81 -0
- package/src/internal/platform-timers.ts +173 -0
- package/src/internal/reminder-runner.ts +131 -67
- package/src/internal/reminder-store.ts +5 -4
- package/src/internal/sdk-runner.ts +49 -1
- package/src/internal/server.ts +130 -11
- package/src/internal/session-engine.ts +138 -3
- package/src/internal/storage-coordinator.ts +4 -41
- package/src/storage.ts +0 -5
- package/src/types.ts +11 -2
- package/dist/docs/assets/chunks/@localSearchIndexroot.CQJytDwU.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/playground/assets/index-BDCeW4uc.js +0 -86
- /package/dist/docs/assets/{index.md.Dfv5ic9t.lean.js → index.md.CH_s5uZe.lean.js} +0 -0
package/src/bin/agent-serve.ts
CHANGED
|
@@ -85,7 +85,7 @@ Usage:
|
|
|
85
85
|
${CLI} sessions [--url <baseUrl> | --prod] [--json]
|
|
86
86
|
[--slug <slug>] [--team <id>] [--dir <path>]
|
|
87
87
|
${CLI} session <sessionId> [--url <baseUrl> | --prod]
|
|
88
|
-
[--json | --text | --events]
|
|
88
|
+
[--json | --text | --events] [--out <file.ndjson>]
|
|
89
89
|
[--slug <slug>] [--team <id>] [--dir <path>]
|
|
90
90
|
${CLI} cost [sessionId] [--url <baseUrl> | --prod] [--json]
|
|
91
91
|
[--slug <slug>] [--team <id>] [--dir <path>]
|
|
@@ -110,11 +110,12 @@ Usage:
|
|
|
110
110
|
[--tag <tag>]... [--verbose] [--timeout-ms <n>]
|
|
111
111
|
[--strict] [--max-concurrency <n>]
|
|
112
112
|
[--junit <path>] [--artifacts <dir>|--no-artifacts]
|
|
113
|
+
[--out <file.json>]
|
|
113
114
|
[--skip-report] [--no-stream] [--no-wait]
|
|
114
115
|
[--prod | --url <baseUrl>] [--slug <slug>]
|
|
115
116
|
[--team <id>]
|
|
116
117
|
${CLI} eval status [runId] --prod|--url <baseUrl> [--dir <path>] [--json]
|
|
117
|
-
[--slug <slug>] [--team <id>]
|
|
118
|
+
[--out <file.json>] [--slug <slug>] [--team <id>]
|
|
118
119
|
${CLI} eval cancel <runId> --prod|--url <baseUrl> [--dir <path>] [--json]
|
|
119
120
|
[--slug <slug>] [--team <id>]
|
|
120
121
|
${CLI} info [--dir <path>] [--json]
|
|
@@ -299,6 +300,9 @@ Options:
|
|
|
299
300
|
connections (defineConnection({ agent })) on cloud-runtime
|
|
300
301
|
turns; local turns always use loopback.
|
|
301
302
|
--no-schedules Disable the cron runner outside dev mode.
|
|
303
|
+
--no-control-plane serve: do not start the bundled control plane (schedule
|
|
304
|
+
and reminder clocks). Cursor hosting passes this so the
|
|
305
|
+
kit uses the Cursor backend instead.
|
|
302
306
|
--team deploy/deployments/deployment/stop/rotate-token and
|
|
303
307
|
chat/resume/run/call/eval/logs/sessions/session/playground
|
|
304
308
|
--prod: Cursor team id. Defaults to the account's team when
|
|
@@ -392,9 +396,25 @@ Options:
|
|
|
392
396
|
--comment github replay: comment body for comment events.
|
|
393
397
|
--context github replay: commit-status context.
|
|
394
398
|
--dry-run github replay: print synthesized payloads, don't POST.
|
|
395
|
-
--out
|
|
399
|
+
--out eval / eval status <runId>: also write the full results
|
|
400
|
+
JSON to this path. session: write the raw NDJSON trace
|
|
401
|
+
there instead of printing (same format as run --events;
|
|
402
|
+
playground-openable). github replay: write synthesized
|
|
403
|
+
payloads as fixtures.
|
|
396
404
|
`;
|
|
397
405
|
|
|
406
|
+
function writeStdout(text: string): Promise<void> {
|
|
407
|
+
return new Promise((resolve, reject) => {
|
|
408
|
+
process.stdout.write(text, (error) => {
|
|
409
|
+
if (error) {
|
|
410
|
+
reject(error);
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
resolve();
|
|
414
|
+
});
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
|
|
398
418
|
async function main(argv: string[]): Promise<number> {
|
|
399
419
|
const [command, ...restIn] = argv;
|
|
400
420
|
if (
|
|
@@ -403,11 +423,13 @@ async function main(argv: string[]): Promise<number> {
|
|
|
403
423
|
command === "--help" ||
|
|
404
424
|
command === "-h"
|
|
405
425
|
) {
|
|
406
|
-
process.
|
|
426
|
+
// Await the write: HELP is larger than a pipe buffer, and process.exit
|
|
427
|
+
// after main() would otherwise truncate --help under execFile / CI.
|
|
428
|
+
await writeStdout(HELP);
|
|
407
429
|
return command === undefined ? 1 : 0;
|
|
408
430
|
}
|
|
409
431
|
if (command === "version" || command === "--version" || command === "-V") {
|
|
410
|
-
|
|
432
|
+
await writeStdout(`${CLI} ${packageVersion()}\n`);
|
|
411
433
|
return 0;
|
|
412
434
|
}
|
|
413
435
|
|
|
@@ -436,6 +458,7 @@ async function main(argv: string[]): Promise<number> {
|
|
|
436
458
|
"allow-anonymous-cursor-account-mcp": { type: "boolean", default: false },
|
|
437
459
|
"cursor-github-proxy": { type: "boolean", default: false },
|
|
438
460
|
"no-schedules": { type: "boolean", default: false },
|
|
461
|
+
"no-control-plane": { type: "boolean", default: false },
|
|
439
462
|
"no-playground": { type: "boolean", default: false },
|
|
440
463
|
"no-docs": { type: "boolean", default: false },
|
|
441
464
|
url: { type: "string" },
|
|
@@ -775,6 +798,7 @@ async function main(argv: string[]): Promise<number> {
|
|
|
775
798
|
apiKey: values["api-key"],
|
|
776
799
|
slug: values.slug,
|
|
777
800
|
bearerToken: values["bearer-token"],
|
|
801
|
+
out: values.out,
|
|
778
802
|
})
|
|
779
803
|
);
|
|
780
804
|
}
|
|
@@ -819,6 +843,7 @@ async function main(argv: string[]): Promise<number> {
|
|
|
819
843
|
artifacts: values["no-artifacts"] ? false : values.artifacts,
|
|
820
844
|
skipReport: values["skip-report"],
|
|
821
845
|
maxConcurrency: parseMaxConcurrency(values["max-concurrency"]),
|
|
846
|
+
out: values.out,
|
|
822
847
|
})
|
|
823
848
|
);
|
|
824
849
|
}
|
|
@@ -912,6 +937,7 @@ async function main(argv: string[]): Promise<number> {
|
|
|
912
937
|
json: values.json,
|
|
913
938
|
text: values.text,
|
|
914
939
|
rawEvents: values["raw-events"],
|
|
940
|
+
out: values.out,
|
|
915
941
|
})
|
|
916
942
|
);
|
|
917
943
|
}
|
|
@@ -1140,6 +1166,7 @@ async function main(argv: string[]): Promise<number> {
|
|
|
1140
1166
|
cursorGithubProxy:
|
|
1141
1167
|
values["cursor-github-proxy"] === true ? true : undefined,
|
|
1142
1168
|
schedules: values["no-schedules"] === true ? false : undefined,
|
|
1169
|
+
noControlPlane: values["no-control-plane"] === true ? true : undefined,
|
|
1143
1170
|
playground: values["no-playground"] === true ? false : undefined,
|
|
1144
1171
|
docs: values["no-docs"] === true ? false : undefined,
|
|
1145
1172
|
publicUrl: values["public-url"],
|
|
@@ -1291,6 +1318,7 @@ function axFromOptions(
|
|
|
1291
1318
|
artifacts: partial.artifacts,
|
|
1292
1319
|
skipReport: partial.skipReport,
|
|
1293
1320
|
maxConcurrency: partial.maxConcurrency,
|
|
1321
|
+
out: partial.out,
|
|
1294
1322
|
};
|
|
1295
1323
|
}
|
|
1296
1324
|
|
|
@@ -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
|
};
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
parseSlackEvalDirective,
|
|
18
18
|
} from "./eval-directive.js";
|
|
19
19
|
import type { EventIdDedupe } from "./event-id-dedupe.js";
|
|
20
|
+
import { isExternalSlackSender } from "./external-policy.js";
|
|
20
21
|
import {
|
|
21
22
|
formatSlackContextBlock,
|
|
22
23
|
parseAppMentionEvent,
|
|
@@ -91,6 +92,11 @@ export interface SlackInboundDispatchDeps extends SlackContinuationProbes {
|
|
|
91
92
|
evalCommandsEnabled?: boolean;
|
|
92
93
|
/** cursorAccount agent name stripped before `eval` directive parsing. */
|
|
93
94
|
cursorAgentName?: string;
|
|
95
|
+
/**
|
|
96
|
+
* Drop external senders before the handler. Defaults to `true`; pass
|
|
97
|
+
* `false` to admit them (or when an upstream gate already refused them).
|
|
98
|
+
*/
|
|
99
|
+
blockExternals?: boolean;
|
|
94
100
|
}
|
|
95
101
|
|
|
96
102
|
function triggeringMergeMessage(
|
|
@@ -132,6 +138,7 @@ export async function dispatchInboundMessage(args: {
|
|
|
132
138
|
* (`OncallBuddy eval list`).
|
|
133
139
|
*/
|
|
134
140
|
cursorAgentName?: string;
|
|
141
|
+
blockExternals?: boolean;
|
|
135
142
|
}): Promise<void> {
|
|
136
143
|
const { message, kind } = args;
|
|
137
144
|
const log = args.log ?? defaultSlackLog;
|
|
@@ -142,7 +149,7 @@ export async function dispatchInboundMessage(args: {
|
|
|
142
149
|
host: args.host,
|
|
143
150
|
artifacts: args.artifacts,
|
|
144
151
|
log,
|
|
145
|
-
|
|
152
|
+
teamId: message.teamId,
|
|
146
153
|
});
|
|
147
154
|
|
|
148
155
|
log.info("inbound", {
|
|
@@ -155,6 +162,38 @@ export async function dispatchInboundMessage(args: {
|
|
|
155
162
|
team: message.teamId,
|
|
156
163
|
});
|
|
157
164
|
|
|
165
|
+
if (args.blockExternals !== false && message.author?.isBot !== true) {
|
|
166
|
+
const userId = message.author?.userId ?? "";
|
|
167
|
+
const teamId = message.teamId;
|
|
168
|
+
if (teamId === undefined || teamId === "") {
|
|
169
|
+
log.info("inbound dropped (missing team)", {
|
|
170
|
+
kind,
|
|
171
|
+
channel: message.channelId,
|
|
172
|
+
thread_ts: message.threadTs,
|
|
173
|
+
user: userId,
|
|
174
|
+
event_id: args.eventId,
|
|
175
|
+
});
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (
|
|
179
|
+
await isExternalSlackSender({
|
|
180
|
+
botToken: args.credentials?.botToken,
|
|
181
|
+
slackTeamId: teamId,
|
|
182
|
+
userId,
|
|
183
|
+
})
|
|
184
|
+
) {
|
|
185
|
+
log.info("inbound dropped (external sender)", {
|
|
186
|
+
kind,
|
|
187
|
+
channel: message.channelId,
|
|
188
|
+
thread_ts: message.threadTs,
|
|
189
|
+
user: userId,
|
|
190
|
+
team: teamId,
|
|
191
|
+
event_id: args.eventId,
|
|
192
|
+
});
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
158
197
|
const continuation = slackContinuationKey(
|
|
159
198
|
message.channelId,
|
|
160
199
|
message.threadTs
|
|
@@ -463,6 +502,7 @@ export function dispatchInboundFromEventBody(
|
|
|
463
502
|
log,
|
|
464
503
|
evalCommandsEnabled: deps.evalCommandsEnabled,
|
|
465
504
|
cursorAgentName: deps.cursorAgentName,
|
|
505
|
+
blockExternals: deps.blockExternals,
|
|
466
506
|
...(deps.hasContinuationSession === undefined
|
|
467
507
|
? {}
|
|
468
508
|
: { hasContinuationSession: deps.hasContinuationSession }),
|
|
@@ -504,6 +544,7 @@ export function dispatchInboundFromEventBody(
|
|
|
504
544
|
log,
|
|
505
545
|
evalCommandsEnabled: deps.evalCommandsEnabled,
|
|
506
546
|
cursorAgentName: deps.cursorAgentName,
|
|
547
|
+
blockExternals: deps.blockExternals,
|
|
507
548
|
...(deps.hasContinuationSession === undefined
|
|
508
549
|
? {}
|
|
509
550
|
: { hasContinuationSession: deps.hasContinuationSession }),
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace-membership checks for AgentKit Slack agents.
|
|
3
|
+
* Mirrors backend `isExternalSlackUser` (cloud-agent/slack/externalChannelPolicy).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { callSlackApi } from "./api.js";
|
|
7
|
+
import type { SlackBotToken } from "./types.js";
|
|
8
|
+
|
|
9
|
+
type SlackUserInfo = {
|
|
10
|
+
team_id?: string;
|
|
11
|
+
is_stranger?: boolean;
|
|
12
|
+
is_restricted?: boolean;
|
|
13
|
+
is_ultra_restricted?: boolean;
|
|
14
|
+
enterprise_user?: { teams?: string[] };
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/** Pure classifier over a `users.info` user object. Fails open to external. */
|
|
18
|
+
export function classifySlackUserExternality(args: {
|
|
19
|
+
slackTeamId: string;
|
|
20
|
+
user: SlackUserInfo;
|
|
21
|
+
}): boolean {
|
|
22
|
+
const { slackTeamId, user } = args;
|
|
23
|
+
if (
|
|
24
|
+
user.is_stranger === true ||
|
|
25
|
+
user.is_restricted === true ||
|
|
26
|
+
user.is_ultra_restricted === true
|
|
27
|
+
) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
return (
|
|
31
|
+
user.team_id !== undefined &&
|
|
32
|
+
user.team_id !== slackTeamId &&
|
|
33
|
+
!(user.enterprise_user?.teams ?? []).includes(slackTeamId)
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Lookup + classify. Empty userId / API failure → external. */
|
|
38
|
+
export async function isExternalSlackSender(args: {
|
|
39
|
+
botToken: SlackBotToken | undefined;
|
|
40
|
+
slackTeamId: string;
|
|
41
|
+
userId: string;
|
|
42
|
+
}): Promise<boolean> {
|
|
43
|
+
if (args.userId === "") {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const result = await callSlackApi({
|
|
48
|
+
botToken: args.botToken,
|
|
49
|
+
operation: "users.info",
|
|
50
|
+
body: { user: args.userId },
|
|
51
|
+
});
|
|
52
|
+
if (result.ok !== true || result.user === undefined) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
return classifySlackUserExternality({
|
|
56
|
+
slackTeamId: args.slackTeamId,
|
|
57
|
+
user: result.user as SlackUserInfo,
|
|
58
|
+
});
|
|
59
|
+
} catch {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -33,6 +33,7 @@ export * from "./dispatch.js";
|
|
|
33
33
|
export * from "./doctor.js";
|
|
34
34
|
export * from "./eval-directive.js";
|
|
35
35
|
export * from "./event-id-dedupe.js";
|
|
36
|
+
export * from "./external-policy.js";
|
|
36
37
|
export * from "./inbound.js";
|
|
37
38
|
export * from "./init.js";
|
|
38
39
|
export * from "./interactive.js";
|
|
@@ -20,6 +20,7 @@ import { updateMessage } from "./api.js";
|
|
|
20
20
|
import { decisionFromActionId, parseApprovalActionValue } from "./approvals.js";
|
|
21
21
|
import { buildSlackInteractiveApprovalAuth } from "./auth.js";
|
|
22
22
|
import { dispatchInboundMessage } from "./dispatch.js";
|
|
23
|
+
import { isExternalSlackSender } from "./external-policy.js";
|
|
23
24
|
import { defaultSlackLog, type SlackLog } from "./log.js";
|
|
24
25
|
import type {
|
|
25
26
|
SlackBlockAction,
|
|
@@ -41,6 +42,8 @@ export interface SlackInteractiveDispatchDeps {
|
|
|
41
42
|
) => Promise<PendingApproval>;
|
|
42
43
|
waitUntil: (promise: Promise<unknown>) => void;
|
|
43
44
|
log?: SlackLog;
|
|
45
|
+
/** Defaults to true. */
|
|
46
|
+
blockExternals?: boolean;
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
/**
|
|
@@ -116,11 +119,35 @@ export async function dispatchInteractiveFromPayload(
|
|
|
116
119
|
}
|
|
117
120
|
|
|
118
121
|
const teamId = attrs.team_id;
|
|
122
|
+
if (deps.blockExternals !== false && parsed.userId !== "") {
|
|
123
|
+
if (teamId === undefined || teamId === "") {
|
|
124
|
+
log.info("interactive approval dropped (missing team)", {
|
|
125
|
+
session: value.sessionId,
|
|
126
|
+
user: parsed.userId,
|
|
127
|
+
});
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
if (
|
|
131
|
+
await isExternalSlackSender({
|
|
132
|
+
botToken: deps.credentials?.botToken,
|
|
133
|
+
slackTeamId: teamId,
|
|
134
|
+
userId: parsed.userId,
|
|
135
|
+
})
|
|
136
|
+
) {
|
|
137
|
+
log.info("interactive approval dropped (external sender)", {
|
|
138
|
+
session: value.sessionId,
|
|
139
|
+
user: parsed.userId,
|
|
140
|
+
team: teamId,
|
|
141
|
+
});
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
119
146
|
const clickingAuth = buildSlackInteractiveApprovalAuth({
|
|
120
147
|
sessionAuth,
|
|
121
148
|
channelId: parsed.channelId || sessionChannel,
|
|
122
149
|
threadTs: parsed.threadTs || sessionThread,
|
|
123
|
-
|
|
150
|
+
teamId: teamId === undefined || teamId === "" ? undefined : teamId,
|
|
124
151
|
clickingUserId: parsed.userId,
|
|
125
152
|
});
|
|
126
153
|
|
|
@@ -204,12 +231,9 @@ export function parseSlackBlockAction(
|
|
|
204
231
|
: typeof user.name === "string"
|
|
205
232
|
? user.name
|
|
206
233
|
: undefined;
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
: typeof user.team_id === "string"
|
|
211
|
-
? user.team_id
|
|
212
|
-
: undefined;
|
|
234
|
+
// Payload `team.id` only — never `user.team_id` (Connect home team).
|
|
235
|
+
// Membership gates must baseline the install workspace separately.
|
|
236
|
+
const teamId = typeof team.id === "string" ? team.id : undefined;
|
|
213
237
|
const label =
|
|
214
238
|
typeof action.text === "object" &&
|
|
215
239
|
action.text !== null &&
|
|
@@ -278,6 +302,10 @@ export interface SlackBlockActionDispatchDeps extends SlackContinuationProbes {
|
|
|
278
302
|
host: HostContext;
|
|
279
303
|
artifacts: ArtifactsApi;
|
|
280
304
|
log?: SlackLog;
|
|
305
|
+
/** Defaults to true. */
|
|
306
|
+
blockExternals?: boolean;
|
|
307
|
+
/** Install workspace from `auth.test`; baseline for the external gate. */
|
|
308
|
+
installTeamId?: string;
|
|
281
309
|
}
|
|
282
310
|
|
|
283
311
|
/**
|
|
@@ -285,7 +313,10 @@ export interface SlackBlockActionDispatchDeps extends SlackContinuationProbes {
|
|
|
285
313
|
* `action_ts` (unique per click, never a real thread message), so ask-dedupe
|
|
286
314
|
* claims and `<slack_thread>` exclusions stay per-click.
|
|
287
315
|
*/
|
|
288
|
-
function blockActionAsMessage(
|
|
316
|
+
function blockActionAsMessage(
|
|
317
|
+
action: SlackBlockAction,
|
|
318
|
+
installTeamId: string | undefined
|
|
319
|
+
): SlackMessage {
|
|
289
320
|
const text = `[clicked: ${action.label ?? action.actionId}]`;
|
|
290
321
|
return {
|
|
291
322
|
text,
|
|
@@ -294,7 +325,7 @@ function blockActionAsMessage(action: SlackBlockAction): SlackMessage {
|
|
|
294
325
|
ts: action.actionTs,
|
|
295
326
|
threadTs: action.threadTs,
|
|
296
327
|
channelId: action.channelId,
|
|
297
|
-
teamId:
|
|
328
|
+
teamId: installTeamId,
|
|
298
329
|
author: {
|
|
299
330
|
userId: action.userId,
|
|
300
331
|
userName: action.userName,
|
|
@@ -344,7 +375,7 @@ export async function dispatchBlockAction(
|
|
|
344
375
|
|
|
345
376
|
await dispatchInboundMessage({
|
|
346
377
|
kind: "block_action",
|
|
347
|
-
message: blockActionAsMessage(action),
|
|
378
|
+
message: blockActionAsMessage(action, deps.installTeamId),
|
|
348
379
|
handler: (ctx) => deps.handler(ctx, action),
|
|
349
380
|
send: deps.send,
|
|
350
381
|
credentials: deps.credentials,
|
|
@@ -353,6 +384,7 @@ export async function dispatchBlockAction(
|
|
|
353
384
|
log,
|
|
354
385
|
// Click text is synthetic — never treat it as a stop/eval directive.
|
|
355
386
|
evalCommandsEnabled: false,
|
|
387
|
+
blockExternals: deps.blockExternals,
|
|
356
388
|
hasContinuationSession: deps.hasContinuationSession,
|
|
357
389
|
isContinuationBusy: deps.isContinuationBusy,
|
|
358
390
|
getContinuationLastBotMessageTs: deps.getContinuationLastBotMessageTs,
|