@cursor/july 0.1.55 → 0.1.61
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 +30 -23
- package/dist/channels/github/github-channel.d.ts +12 -0
- package/dist/channels/github/github-channel.d.ts.map +1 -1
- package/dist/channels/github/github-channel.js +67 -15
- package/dist/channels/github/types.d.ts +27 -1
- package/dist/channels/github/types.d.ts.map +1 -1
- package/dist/channels/origin/cursor-account.d.ts +22 -0
- package/dist/channels/origin/cursor-account.d.ts.map +1 -0
- package/dist/channels/origin/cursor-account.js +35 -0
- package/dist/channels/origin/events.d.ts +10 -0
- package/dist/channels/origin/events.d.ts.map +1 -0
- package/dist/channels/origin/events.js +27 -0
- package/dist/channels/origin/index.d.ts +29 -0
- package/dist/channels/origin/index.d.ts.map +1 -0
- package/dist/channels/origin/index.js +28 -0
- package/dist/channels/origin/origin-channel.d.ts +68 -0
- package/dist/channels/origin/origin-channel.d.ts.map +1 -0
- package/dist/channels/origin/origin-channel.js +567 -0
- package/dist/channels/origin/origin-webhook.d.ts +38 -0
- package/dist/channels/origin/origin-webhook.d.ts.map +1 -0
- package/dist/channels/origin/origin-webhook.js +151 -0
- package/dist/channels/origin/types.d.ts +152 -0
- package/dist/channels/origin/types.d.ts.map +1 -0
- package/dist/channels/origin/types.js +1 -0
- package/dist/channels/slack/doctor.d.ts +17 -1
- package/dist/channels/slack/doctor.d.ts.map +1 -1
- package/dist/channels/slack/doctor.js +186 -10
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +4 -4
- package/dist/docs/assets/{app.CVQxhV85.js → app.CW3KJh72.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.BuymfBAo.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.QAPzJjql.js → VPLocalSearchBox.Bk6EWpPj.js} +1 -1
- package/dist/docs/assets/chunks/{theme.CSxTCOFu.js → theme.Bz9AodBA.js} +2 -2
- package/dist/docs/assets/{example-agents_fsd.md.DPz9ezO4.js → example-agents_fsd.md.ZeGEpAw_.js} +2 -2
- package/dist/docs/assets/{example-agents_fsd.md.DPz9ezO4.lean.js → example-agents_fsd.md.ZeGEpAw_.lean.js} +1 -1
- package/dist/docs/assets/{example-agents_security-reviewer.md.Dkf1gyo6.js → example-agents_security-reviewer.md.DBL1TwtP.js} +2 -2
- package/dist/docs/assets/{example-agents_security-reviewer.md.Dkf1gyo6.lean.js → example-agents_security-reviewer.md.DBL1TwtP.lean.js} +1 -1
- package/dist/docs/assets/guides_convert-automation.md.BhMzTkE5.js +12 -0
- package/dist/docs/assets/guides_convert-automation.md.BhMzTkE5.lean.js +1 -0
- package/dist/docs/assets/{guides_github.md.B8QKtIpw.js → guides_github.md.CLLQJKRB.js} +4 -4
- package/dist/docs/assets/{guides_github.md.B8QKtIpw.lean.js → guides_github.md.CLLQJKRB.lean.js} +1 -1
- package/dist/docs/assets/index.md.DhRHS_-L.js +5 -0
- package/dist/docs/assets/{index.md.COeoSkcF.lean.js → index.md.DhRHS_-L.lean.js} +1 -1
- package/dist/docs/assets/{reference_cli.md.D_CCfG8R.js → reference_cli.md.Cl14Jfo3.js} +8 -2
- package/dist/docs/assets/{reference_cli.md.D_CCfG8R.lean.js → reference_cli.md.Cl14Jfo3.lean.js} +1 -1
- package/dist/docs/assets/scaffolding-agents.md.CrWSA0Ko.js +1 -0
- package/dist/docs/assets/{scaffolding-agents.md.mkc3B_ZW.lean.js → scaffolding-agents.md.CrWSA0Ko.lean.js} +1 -1
- package/dist/docs/assets/templates_agentic-owners.md.BkTLORaU.js +14 -0
- package/dist/docs/assets/templates_agentic-owners.md.BkTLORaU.lean.js +1 -0
- package/dist/docs/assets/templates_demo.md.Bgd6MBaZ.js +4 -0
- package/dist/docs/assets/templates_demo.md.Bgd6MBaZ.lean.js +1 -0
- package/dist/docs/assets/templates_pr-autofixer.md.DcmoeUNZ.js +8 -0
- package/dist/docs/assets/templates_pr-autofixer.md.DcmoeUNZ.lean.js +1 -0
- package/dist/docs/assets/templates_security-reviewer.md.C0yIUaYs.js +2 -0
- package/dist/docs/assets/templates_security-reviewer.md.C0yIUaYs.lean.js +1 -0
- package/dist/docs/building-with-agents.html +4 -4
- package/dist/docs/concepts.html +5 -5
- package/dist/docs/deployment.html +4 -4
- package/dist/docs/evals.html +4 -4
- package/dist/docs/example-agents/approval-buddy.html +4 -4
- package/dist/docs/example-agents/benny.html +4 -4
- package/dist/docs/example-agents/bugbot.html +4 -4
- package/dist/docs/example-agents/codebase-wiki.html +4 -4
- package/dist/docs/example-agents/codeowners-review.html +4 -4
- package/dist/docs/example-agents/concierge.html +4 -4
- package/dist/docs/example-agents/fsd.html +5 -5
- package/dist/docs/example-agents/index.html +4 -4
- package/dist/docs/example-agents/knowledge-base.html +4 -4
- package/dist/docs/example-agents/oncall.html +4 -4
- package/dist/docs/example-agents/security-reviewer.html +5 -5
- package/dist/docs/example-agents/slack-agent.html +4 -4
- package/dist/docs/example-agents/weather-agent.html +4 -4
- package/dist/docs/guides/agent-to-agent.html +4 -4
- package/dist/docs/guides/cloud-runtime.html +4 -4
- package/dist/docs/guides/convert-automation.html +36 -0
- package/dist/docs/guides/github.html +7 -7
- package/dist/docs/guides/human-in-the-loop.html +4 -4
- package/dist/docs/guides/mcp-oauth.html +4 -4
- package/dist/docs/guides/opentelemetry.html +4 -4
- package/dist/docs/guides/slack.html +4 -4
- package/dist/docs/guides/webhooks.html +4 -4
- package/dist/docs/hashmap.json +1 -1
- package/dist/docs/hillclimbing.html +4 -4
- package/dist/docs/index.html +6 -6
- package/dist/docs/quickstart.html +4 -4
- package/dist/docs/reference/agent-config.html +4 -4
- package/dist/docs/reference/artifacts.html +4 -4
- package/dist/docs/reference/channels.html +4 -4
- package/dist/docs/reference/cli.html +12 -6
- package/dist/docs/reference/connections.html +4 -4
- package/dist/docs/reference/hooks.html +4 -4
- package/dist/docs/reference/http-api.html +4 -4
- package/dist/docs/reference/instructions.html +4 -4
- package/dist/docs/reference/playground.html +4 -4
- package/dist/docs/reference/project-layout.html +4 -4
- package/dist/docs/reference/prompt.html +4 -4
- package/dist/docs/reference/schedules.html +4 -4
- package/dist/docs/reference/sessions.html +4 -4
- package/dist/docs/reference/skills.html +4 -4
- package/dist/docs/reference/subagents.html +4 -4
- package/dist/docs/reference/tools.html +4 -4
- package/dist/docs/scaffolding-agents.html +5 -5
- package/dist/docs/storage.html +4 -4
- package/dist/docs/templates/agentic-owners.html +38 -0
- package/dist/docs/templates/demo.html +28 -0
- package/dist/docs/templates/pr-autofixer.html +32 -0
- package/dist/docs/templates/security-reviewer.html +26 -0
- package/dist/docs/templates/triage.html +5 -5
- package/dist/docs/troubleshooting.html +4 -4
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/internal/cli-ax.d.ts +2 -0
- package/dist/internal/cli-ax.d.ts.map +1 -1
- package/dist/internal/cli-ax.js +20 -0
- package/dist/internal/cli-slack.d.ts +2 -0
- package/dist/internal/cli-slack.d.ts.map +1 -1
- package/dist/internal/cli-slack.js +11 -2
- package/dist/internal/cursor-event-relay.d.ts +59 -11
- package/dist/internal/cursor-event-relay.d.ts.map +1 -1
- package/dist/internal/cursor-event-relay.js +133 -26
- package/dist/internal/discovery.d.ts.map +1 -1
- package/dist/internal/discovery.js +11 -2
- package/dist/internal/distribution.d.ts.map +1 -1
- package/dist/internal/distribution.js +1 -0
- package/dist/internal/handleAgentServeTrigger.d.ts +33 -6
- package/dist/internal/handleAgentServeTrigger.d.ts.map +1 -1
- package/dist/internal/handleAgentServeTrigger.js +97 -19
- package/dist/internal/init-interview.d.ts +67 -0
- package/dist/internal/init-interview.d.ts.map +1 -0
- package/dist/internal/init-interview.js +300 -0
- package/dist/internal/init-project.d.ts +4 -0
- package/dist/internal/init-project.d.ts.map +1 -1
- package/dist/internal/init-project.js +40 -16
- package/dist/internal/mcp-endpoint.d.ts +7 -0
- package/dist/internal/mcp-endpoint.d.ts.map +1 -1
- package/dist/internal/mcp-endpoint.js +7 -0
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +159 -57
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +20 -21
- package/dist/internal/session-links.d.ts +14 -0
- package/dist/internal/session-links.d.ts.map +1 -0
- package/dist/internal/session-links.js +69 -0
- package/dist/internal/sessions-client.d.ts.map +1 -1
- package/dist/internal/sessions-client.js +1 -0
- package/dist/playground/assets/index-ClbuaLpm.css +1 -0
- package/dist/playground/assets/{index-0oqXUkrR.js → index-SqkR13k_.js} +39 -39
- package/dist/playground/index.html +2 -2
- package/dist/types.d.ts +19 -12
- package/dist/types.d.ts.map +1 -1
- package/docs/README.md +15 -0
- package/docs/example-agents/fsd.md +3 -0
- package/docs/example-agents/security-reviewer.md +3 -0
- package/docs/guides/convert-automation.md +176 -0
- package/docs/guides/github.md +6 -2
- package/docs/reference/cli.md +34 -10
- package/docs/scaffolding-agents.md +3 -0
- package/docs/templates/agentic-owners.md +97 -0
- package/docs/templates/demo.md +84 -0
- package/docs/templates/pr-autofixer.md +136 -0
- package/docs/templates/security-reviewer.md +89 -0
- package/package.json +9 -1
- package/skills/github/SKILL.md +4 -5
- package/src/bin/agent-serve.ts +13 -6
- package/src/channels/github/github-channel.ts +95 -18
- package/src/channels/github/types.ts +31 -0
- package/src/channels/origin/cursor-account.ts +64 -0
- package/src/channels/origin/events.ts +41 -0
- package/src/channels/origin/index.ts +29 -0
- package/src/channels/origin/origin-channel.ts +784 -0
- package/src/channels/origin/origin-webhook.ts +202 -0
- package/src/channels/origin/types.ts +186 -0
- package/src/channels/slack/doctor.ts +218 -5
- package/src/index.ts +3 -1
- package/src/internal/cli-ax.ts +23 -0
- package/src/internal/cli-slack.ts +11 -1
- package/src/internal/cursor-event-relay.ts +193 -31
- package/src/internal/discovery.ts +15 -5
- package/src/internal/distribution.ts +1 -0
- package/src/internal/handleAgentServeTrigger.ts +129 -19
- package/src/internal/init-interview.ts +384 -0
- package/src/internal/init-project.ts +70 -15
- package/src/internal/mcp-endpoint.ts +7 -0
- package/src/internal/server.ts +188 -58
- package/src/internal/session-engine.ts +5 -21
- package/src/internal/session-links.ts +88 -0
- package/src/internal/sessions-client.ts +1 -0
- package/src/types.ts +19 -12
- package/templates/agentic-owners/README.md +73 -0
- package/templates/agentic-owners/agent/agent.ts +15 -0
- package/templates/agentic-owners/agent/channels/github.ts +85 -0
- package/templates/agentic-owners/agent/instructions.md +34 -0
- package/templates/agentic-owners/agent/lib/config.ts +13 -0
- package/templates/agentic-owners/agent/lib/github.ts +241 -0
- package/templates/agentic-owners/agent/lib/owners/README.md +50 -0
- package/templates/agentic-owners/agent/lib/owners/constants.ts +22 -0
- package/templates/agentic-owners/agent/lib/owners/decision.ts +485 -0
- package/templates/agentic-owners/agent/lib/owners/handles.ts +54 -0
- package/templates/agentic-owners/agent/lib/owners/match.ts +177 -0
- package/templates/agentic-owners/agent/lib/owners/matching.ts +95 -0
- package/templates/agentic-owners/agent/lib/owners/parse.ts +589 -0
- package/templates/agentic-owners/agent/lib/owners/policyMode.ts +65 -0
- package/templates/agentic-owners/agent/lib/owners/prompt.ts +66 -0
- package/templates/agentic-owners/agent/lib/owners/prompts/base-review.md +105 -0
- package/templates/agentic-owners/agent/lib/owners/types.ts +45 -0
- package/templates/agentic-owners/agent/lib/review.ts +777 -0
- package/templates/agentic-owners/agent/tools/record_review.ts +69 -0
- package/templates/agentic-owners/evals/evals.config.ts +5 -0
- package/templates/agentic-owners/evals/owners.eval.ts +116 -0
- package/templates/agentic-owners/package.json +20 -0
- package/templates/agentic-owners/tsconfig.json +12 -0
- package/templates/demo/README.md +33 -0
- package/templates/demo/agent/agent.ts +15 -0
- package/templates/demo/agent/channels/github.ts +50 -0
- package/templates/demo/agent/channels/queue.ts +30 -0
- package/templates/demo/agent/instructions.md +8 -0
- package/templates/demo/agent/lib/collect.ts +78 -0
- package/templates/demo/agent/tools/record_demo.ts +40 -0
- package/templates/demo/evals/demo.eval.ts +33 -0
- package/templates/demo/evals/evals.config.ts +5 -0
- package/templates/demo/package.json +18 -0
- package/templates/demo/tsconfig.json +12 -0
- package/templates/pr-autofixer/README.md +44 -0
- package/templates/pr-autofixer/agent/agent.ts +22 -0
- package/templates/pr-autofixer/agent/channels/drive.ts +40 -0
- package/templates/pr-autofixer/agent/channels/github.ts +167 -0
- package/templates/pr-autofixer/agent/channels/slack.ts +17 -0
- package/templates/pr-autofixer/agent/hooks/bind-agent.ts +19 -0
- package/templates/pr-autofixer/agent/instructions.md +83 -0
- package/templates/pr-autofixer/agent/lib/affinity.ts +33 -0
- package/templates/pr-autofixer/agent/lib/drive-schemas.ts +15 -0
- package/templates/pr-autofixer/agent/lib/drive.ts +79 -0
- package/templates/pr-autofixer/agent/lib/merge-watch.ts +123 -0
- package/templates/pr-autofixer/agent/lib/pr-state.ts +54 -0
- package/templates/pr-autofixer/agent/lib/repos.ts +19 -0
- package/templates/pr-autofixer/agent/lib/start-drive.ts +122 -0
- package/templates/pr-autofixer/agent/lib/webhook-buffer.ts +225 -0
- package/templates/pr-autofixer/agent/lib/webhook-routing.ts +231 -0
- package/templates/pr-autofixer/agent/storage.ts +7 -0
- package/templates/pr-autofixer/agent/tools/drive_pr.ts +22 -0
- package/templates/pr-autofixer/evals/coordinator.eval.ts +16 -0
- package/templates/pr-autofixer/evals/evals.config.ts +5 -0
- package/templates/pr-autofixer/init.json +25 -0
- package/templates/pr-autofixer/package.json +18 -0
- package/templates/pr-autofixer/tsconfig.json +12 -0
- package/templates/security-reviewer/README.md +31 -0
- package/templates/security-reviewer/agent/agent.ts +15 -0
- package/templates/security-reviewer/agent/channels/github.ts +46 -0
- package/templates/security-reviewer/agent/instructions.md +22 -0
- package/templates/security-reviewer/agent/lib/repos.ts +5 -0
- package/templates/security-reviewer/agent/tools/post_findings.ts +72 -0
- package/templates/security-reviewer/evals/evals.config.ts +5 -0
- package/templates/security-reviewer/evals/review.eval.ts +53 -0
- package/templates/security-reviewer/init.json +25 -0
- package/templates/security-reviewer/package.json +18 -0
- package/templates/security-reviewer/tsconfig.json +12 -0
- package/dist/docs/assets/chunks/@localSearchIndexroot.BFpWSo6D.js +0 -1
- package/dist/docs/assets/index.md.COeoSkcF.js +0 -5
- package/dist/docs/assets/scaffolding-agents.md.mkc3B_ZW.js +0 -1
- package/dist/playground/assets/index-tHCjJxv4.css +0 -1
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export declare const V1ED_SIGNATURE_VERSION = "v1ed";
|
|
2
|
+
export declare const DEFAULT_ORIGIN_API_BASE_URL = "https://origin.cursor.com";
|
|
3
|
+
export type OriginJwk = Record<string, unknown>;
|
|
4
|
+
export type OriginWebhookHeaders = {
|
|
5
|
+
"webhook-id"?: string;
|
|
6
|
+
"webhook-timestamp"?: string;
|
|
7
|
+
"webhook-signature"?: string;
|
|
8
|
+
};
|
|
9
|
+
export declare function buildV1edSignedContentDigest(args: {
|
|
10
|
+
webhookId: string;
|
|
11
|
+
timestampSeconds: number;
|
|
12
|
+
body: string | Buffer;
|
|
13
|
+
}): string;
|
|
14
|
+
/**
|
|
15
|
+
* Verify a delivery's Standard Webhooks headers against the Origin signing
|
|
16
|
+
* JWKS. Owns the whole check: header presence, timestamp parsing, the replay
|
|
17
|
+
* window, and Ed25519 verification against any provided key.
|
|
18
|
+
*/
|
|
19
|
+
export declare function verifyV1edWebhookPayload(args: {
|
|
20
|
+
publicJwks: readonly OriginJwk[];
|
|
21
|
+
body: string | Buffer;
|
|
22
|
+
headers: OriginWebhookHeaders;
|
|
23
|
+
nowMs?: number;
|
|
24
|
+
}): boolean;
|
|
25
|
+
/** Fetch (and cache for 5 minutes) the Origin signing JWKS. */
|
|
26
|
+
export declare function fetchOriginJwks(apiBaseUrl: string, fetchImpl?: typeof fetch): Promise<readonly OriginJwk[]>;
|
|
27
|
+
export interface VerifyOriginWebhookOptions {
|
|
28
|
+
apiBaseUrl?: string;
|
|
29
|
+
fetchImpl?: typeof fetch;
|
|
30
|
+
nowMs?: number;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Verify a Standard-Webhooks v1ed Origin payload. Throws on failure.
|
|
34
|
+
*/
|
|
35
|
+
export declare function verifyOriginWebhook(body: string | Buffer, headers: OriginWebhookHeaders, options?: VerifyOriginWebhookOptions): Promise<void>;
|
|
36
|
+
/** Test helper: drop the in-process JWKS cache. */
|
|
37
|
+
export declare function resetOriginWebhookKeysCache(): void;
|
|
38
|
+
//# sourceMappingURL=origin-webhook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"origin-webhook.d.ts","sourceRoot":"","sources":["../../../src/channels/origin/origin-webhook.ts"],"names":[],"mappings":"AAsBA,eAAO,MAAM,sBAAsB,SAAS,CAAC;AAC7C,eAAO,MAAM,2BAA2B,8BAA8B,CAAC;AAavE,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEhD,MAAM,MAAM,oBAAoB,GAAG;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B,CAAC;AAEF,wBAAgB,4BAA4B,CAAC,IAAI,EAAE;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;CACvB,GAAG,MAAM,CAUT;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE;IAC7C,UAAU,EAAE,SAAS,SAAS,EAAE,CAAC;IACjC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,OAAO,EAAE,oBAAoB,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CA6DV;AAMD,+DAA+D;AAC/D,wBAAsB,eAAe,CACnC,UAAU,EAAE,MAAM,EAClB,SAAS,GAAE,OAAO,KAAa,GAC9B,OAAO,CAAC,SAAS,SAAS,EAAE,CAAC,CA2B/B;AAED,MAAM,WAAW,0BAA0B;IACzC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,MAAM,GAAG,MAAM,EACrB,OAAO,EAAE,oBAAoB,EAC7B,OAAO,CAAC,EAAE,0BAA0B,GACnC,OAAO,CAAC,IAAI,CAAC,CAaf;AAED,mDAAmD;AACnD,wBAAgB,2BAA2B,IAAI,IAAI,CAElD"}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Origin webhook verification (Standard Webhooks `v1ed`).
|
|
12
|
+
*
|
|
13
|
+
* Direct Origin HTTP deliveries must verify the Ed25519 signature
|
|
14
|
+
* (`webhook-id`, `webhook-timestamp`, `webhook-signature`) against Origin's
|
|
15
|
+
* JWKS. Cursor-events relay deliveries skip this — the platform already
|
|
16
|
+
* verified the webhook before enqueueing.
|
|
17
|
+
*
|
|
18
|
+
* The signed content matches the Standard Webhooks `v1` scheme
|
|
19
|
+
* (`${webhook-id}.${webhook-timestamp}.${body}`) but is hashed to a lowercase
|
|
20
|
+
* hex SHA-256 before signing. Lifted from
|
|
21
|
+
* `factory/agentic-owners/agent/lib/origin-webhook.ts` /
|
|
22
|
+
* `backend/server/src/origin/webhooks/asymmetricWebhookSignature.ts`.
|
|
23
|
+
*/
|
|
24
|
+
import { createHash, createPublicKey, verify as verifyEd25519, } from "node:crypto";
|
|
25
|
+
export const V1ED_SIGNATURE_VERSION = "v1ed";
|
|
26
|
+
export const DEFAULT_ORIGIN_API_BASE_URL = "https://origin.cursor.com";
|
|
27
|
+
/** ±5 minutes, the Standard Webhooks replay-window convention. */
|
|
28
|
+
const V1ED_TIMESTAMP_TOLERANCE_SECONDS = 5 * 60;
|
|
29
|
+
/**
|
|
30
|
+
* The sender emits exactly one v1ed entry; key rotation is handled by trying
|
|
31
|
+
* multiple public keys, never multiple signatures.
|
|
32
|
+
*/
|
|
33
|
+
const MAX_V1ED_CANDIDATE_SIGNATURES = 1;
|
|
34
|
+
const JWKS_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
35
|
+
export function buildV1edSignedContentDigest(args) {
|
|
36
|
+
const prefix = Buffer.from(`${args.webhookId}.${args.timestampSeconds}.`, "utf8");
|
|
37
|
+
const body = typeof args.body === "string" ? Buffer.from(args.body, "utf8") : args.body;
|
|
38
|
+
return createHash("sha256")
|
|
39
|
+
.update(Buffer.concat([prefix, body]))
|
|
40
|
+
.digest("hex");
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Verify a delivery's Standard Webhooks headers against the Origin signing
|
|
44
|
+
* JWKS. Owns the whole check: header presence, timestamp parsing, the replay
|
|
45
|
+
* window, and Ed25519 verification against any provided key.
|
|
46
|
+
*/
|
|
47
|
+
export function verifyV1edWebhookPayload(args) {
|
|
48
|
+
var _a;
|
|
49
|
+
const webhookId = args.headers["webhook-id"];
|
|
50
|
+
const webhookTimestamp = args.headers["webhook-timestamp"];
|
|
51
|
+
const signatureHeader = args.headers["webhook-signature"];
|
|
52
|
+
if (webhookId === undefined ||
|
|
53
|
+
webhookTimestamp === undefined ||
|
|
54
|
+
signatureHeader === undefined) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
const timestampSeconds = Number.parseInt(webhookTimestamp, 10);
|
|
58
|
+
if (Number.isNaN(timestampSeconds)) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
const nowSeconds = Math.floor(((_a = args.nowMs) !== null && _a !== void 0 ? _a : Date.now()) / 1000);
|
|
62
|
+
if (Math.abs(nowSeconds - timestampSeconds) > V1ED_TIMESTAMP_TOLERANCE_SECONDS) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
const digestBytes = Buffer.from(buildV1edSignedContentDigest({
|
|
66
|
+
webhookId,
|
|
67
|
+
timestampSeconds,
|
|
68
|
+
body: args.body,
|
|
69
|
+
}), "utf8");
|
|
70
|
+
const candidateSignatures = signatureHeader
|
|
71
|
+
.split(/\s+/)
|
|
72
|
+
.filter((entry) => entry.startsWith(`${V1ED_SIGNATURE_VERSION},`))
|
|
73
|
+
.map((entry) => entry.slice(V1ED_SIGNATURE_VERSION.length + 1))
|
|
74
|
+
.slice(0, MAX_V1ED_CANDIDATE_SIGNATURES);
|
|
75
|
+
if (candidateSignatures.length === 0) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
for (const jwk of args.publicJwks) {
|
|
79
|
+
let publicKey;
|
|
80
|
+
try {
|
|
81
|
+
publicKey = createPublicKey({
|
|
82
|
+
key: jwk,
|
|
83
|
+
format: "jwk",
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
catch (_b) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
for (const candidate of candidateSignatures) {
|
|
90
|
+
const signature = Buffer.from(candidate, "base64");
|
|
91
|
+
try {
|
|
92
|
+
if (verifyEd25519(null, digestBytes, publicKey, signature)) {
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch (_c) {
|
|
97
|
+
// Malformed signature bytes or a non-Ed25519 key: try the next pair.
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
let jwksCache;
|
|
104
|
+
/** Fetch (and cache for 5 minutes) the Origin signing JWKS. */
|
|
105
|
+
export function fetchOriginJwks(apiBaseUrl_1) {
|
|
106
|
+
return __awaiter(this, arguments, void 0, function* (apiBaseUrl, fetchImpl = fetch) {
|
|
107
|
+
const now = Date.now();
|
|
108
|
+
if (jwksCache !== undefined &&
|
|
109
|
+
jwksCache.apiBaseUrl === apiBaseUrl &&
|
|
110
|
+
now - jwksCache.fetchedAtMs < JWKS_CACHE_TTL_MS) {
|
|
111
|
+
return jwksCache.keys;
|
|
112
|
+
}
|
|
113
|
+
const response = yield fetchImpl(`${apiBaseUrl.replace(/\/+$/, "")}/v1/origin/keys`);
|
|
114
|
+
if (!response.ok) {
|
|
115
|
+
throw new Error(`Origin JWKS fetch failed: status=${response.status}`);
|
|
116
|
+
}
|
|
117
|
+
const body = yield response.json().catch(() => undefined);
|
|
118
|
+
const keys = typeof body === "object" &&
|
|
119
|
+
body !== null &&
|
|
120
|
+
Array.isArray(body.keys)
|
|
121
|
+
? body.keys
|
|
122
|
+
: [];
|
|
123
|
+
if (keys.length === 0) {
|
|
124
|
+
throw new Error("Origin JWKS fetch returned no keys");
|
|
125
|
+
}
|
|
126
|
+
jwksCache = { apiBaseUrl, keys, fetchedAtMs: now };
|
|
127
|
+
return keys;
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Verify a Standard-Webhooks v1ed Origin payload. Throws on failure.
|
|
132
|
+
*/
|
|
133
|
+
export function verifyOriginWebhook(body, headers, options) {
|
|
134
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
135
|
+
var _a, _b;
|
|
136
|
+
const apiBaseUrl = (_a = options === null || options === void 0 ? void 0 : options.apiBaseUrl) !== null && _a !== void 0 ? _a : DEFAULT_ORIGIN_API_BASE_URL;
|
|
137
|
+
const keys = yield fetchOriginJwks(apiBaseUrl, (_b = options === null || options === void 0 ? void 0 : options.fetchImpl) !== null && _b !== void 0 ? _b : fetch);
|
|
138
|
+
if (!verifyV1edWebhookPayload({
|
|
139
|
+
publicJwks: keys,
|
|
140
|
+
body,
|
|
141
|
+
headers,
|
|
142
|
+
nowMs: options === null || options === void 0 ? void 0 : options.nowMs,
|
|
143
|
+
})) {
|
|
144
|
+
throw new Error("Origin webhook signature verification failed");
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
/** Test helper: drop the in-process JWKS cache. */
|
|
149
|
+
export function resetOriginWebhookKeysCache() {
|
|
150
|
+
jwksCache = undefined;
|
|
151
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import type { AgentCloudOptions, ArtifactsApi, AuthContext, AuthPolicy, ChannelStartArgs, HostContext } from "../../types.js";
|
|
2
|
+
export interface OriginActor {
|
|
3
|
+
id: string;
|
|
4
|
+
kind?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface OriginRepositoryRef {
|
|
7
|
+
owner: string;
|
|
8
|
+
name: string;
|
|
9
|
+
fullName: string;
|
|
10
|
+
}
|
|
11
|
+
export interface OriginPullRequestEvent {
|
|
12
|
+
type: string;
|
|
13
|
+
number: number;
|
|
14
|
+
url: string;
|
|
15
|
+
title?: string;
|
|
16
|
+
body?: string | null;
|
|
17
|
+
draft?: boolean;
|
|
18
|
+
merged?: boolean;
|
|
19
|
+
authorId?: string;
|
|
20
|
+
headRef?: string;
|
|
21
|
+
headSha?: string;
|
|
22
|
+
baseRef?: string;
|
|
23
|
+
baseSha?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface OriginComment {
|
|
26
|
+
id: string;
|
|
27
|
+
body: string;
|
|
28
|
+
author?: OriginActor;
|
|
29
|
+
}
|
|
30
|
+
export interface OriginReview {
|
|
31
|
+
id?: string;
|
|
32
|
+
verdict?: string;
|
|
33
|
+
author?: OriginActor;
|
|
34
|
+
}
|
|
35
|
+
export interface OriginEventContext {
|
|
36
|
+
deliveryId?: string;
|
|
37
|
+
eventType: string;
|
|
38
|
+
repository: OriginRepositoryRef;
|
|
39
|
+
pullRequestNumber: number;
|
|
40
|
+
pullRequestUrl: string;
|
|
41
|
+
sender?: OriginActor;
|
|
42
|
+
host: HostContext;
|
|
43
|
+
artifacts: ArtifactsApi;
|
|
44
|
+
}
|
|
45
|
+
export type OriginTurnDispatch = {
|
|
46
|
+
auth: AuthContext;
|
|
47
|
+
message?: string;
|
|
48
|
+
title?: string;
|
|
49
|
+
context?: readonly string[];
|
|
50
|
+
};
|
|
51
|
+
export type OriginTaskDispatch = {
|
|
52
|
+
task: () => Promise<void>;
|
|
53
|
+
};
|
|
54
|
+
export type OriginDispatch = OriginTurnDispatch | OriginTaskDispatch;
|
|
55
|
+
export type OriginDispatchResult = OriginDispatch | null | Promise<OriginDispatch | null>;
|
|
56
|
+
export type OriginPullRequestHook = (ctx: OriginEventContext, pullRequest: OriginPullRequestEvent) => OriginDispatchResult;
|
|
57
|
+
export type OriginCommentHook = (ctx: OriginEventContext, comment: OriginComment) => OriginDispatchResult;
|
|
58
|
+
export type OriginReviewHook = (ctx: OriginEventContext, review: OriginReview) => OriginDispatchResult;
|
|
59
|
+
export type OriginEventHook = (ctx: OriginEventContext, event: {
|
|
60
|
+
eventType: string;
|
|
61
|
+
payload: unknown;
|
|
62
|
+
}) => OriginDispatchResult;
|
|
63
|
+
export type OriginCloudOptionsResolver = (ctx: OriginEventContext, pullRequest: OriginPullRequestEvent) => AgentCloudOptions | undefined | Promise<AgentCloudOptions | undefined>;
|
|
64
|
+
/**
|
|
65
|
+
* Origin delivery envelope. Public payloads are proto3 camelCase
|
|
66
|
+
* (`pullRequest`, `repository.owner.slug`).
|
|
67
|
+
*/
|
|
68
|
+
export interface OriginWebhookEnvelope {
|
|
69
|
+
deliveryId?: string;
|
|
70
|
+
appId?: string;
|
|
71
|
+
installationId?: string;
|
|
72
|
+
sender?: OriginActor;
|
|
73
|
+
event: {
|
|
74
|
+
id?: string;
|
|
75
|
+
type: string;
|
|
76
|
+
eventTime?: string;
|
|
77
|
+
payload?: unknown;
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
export interface OriginChannelOptions {
|
|
81
|
+
/**
|
|
82
|
+
* Use the signed-in Cursor account for inbound Origin events via
|
|
83
|
+
* `/v0/scm-events` (`provider: "origin"`). `true` takes repository scope
|
|
84
|
+
* from `serve --cursor-events --repo`; `{ repos }` makes the channel
|
|
85
|
+
* self-contained. Origin identity is the Cursor user — there is no
|
|
86
|
+
* separate OAuth principal, and this does not mint a GitHub token.
|
|
87
|
+
*/
|
|
88
|
+
cursorAccount?: boolean | {
|
|
89
|
+
repos: readonly string[];
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Origin app id this channel accepts (`envelope.appId`). v1ed only proves
|
|
93
|
+
* Origin signed the body with the platform JWKS; it does not bind the
|
|
94
|
+
* delivery to one app (any Origin app can aim signed POSTs at a public
|
|
95
|
+
* URL). Required for non-loopback HTTP, same role as GitHub's per-channel
|
|
96
|
+
* webhook secret. Falls back to `AGENT_SERVE_ORIGIN_APP_ID`.
|
|
97
|
+
*/
|
|
98
|
+
appId?: string | readonly string[];
|
|
99
|
+
/**
|
|
100
|
+
* Optional Origin installation id bind (`envelope.installationId`). When
|
|
101
|
+
* set, HTTP deliveries for any other installation are rejected.
|
|
102
|
+
*/
|
|
103
|
+
installationId?: string | readonly string[];
|
|
104
|
+
/**
|
|
105
|
+
* Optional HTTP/dispatch repo allowlist (`owner/name`). `{ cursorAccount:
|
|
106
|
+
* { repos } }` is also an allowlist. Events outside the set are acked
|
|
107
|
+
* without starting a turn. Omit to accept every repo the bound app sends.
|
|
108
|
+
*/
|
|
109
|
+
repos?: readonly string[];
|
|
110
|
+
/**
|
|
111
|
+
* Route auth chain. Defaults to allow-all when {@link appId} (or
|
|
112
|
+
* `AGENT_SERVE_ORIGIN_APP_ID`) is set, because v1ed plus that app-id bind
|
|
113
|
+
* is the request auth. Without an app id, production stays loopback-strict
|
|
114
|
+
* (GitHub's no-secret posture); `--dev` / skip-verify admits loopback only.
|
|
115
|
+
*/
|
|
116
|
+
auth?: AuthPolicy[];
|
|
117
|
+
/** Route path under `/v1/channels/<name>`. Defaults to `/`. */
|
|
118
|
+
path?: string;
|
|
119
|
+
/** Origin API base used to fetch `/v1/origin/keys` for v1ed verify. */
|
|
120
|
+
apiBaseUrl?: string | (() => string | undefined);
|
|
121
|
+
/**
|
|
122
|
+
* Pin the Origin event types this channel dispatches on (published as
|
|
123
|
+
* `meta.originWebhookEvents`). Defaults to the set derived from hooks.
|
|
124
|
+
*/
|
|
125
|
+
webhookEvents?: readonly string[];
|
|
126
|
+
/**
|
|
127
|
+
* Decide whether an inbound pull-request event starts a turn. Return
|
|
128
|
+
* `{ auth: defaultOriginAuth(ctx) }` to dispatch, `null` to ignore.
|
|
129
|
+
* Without this hook the channel dispatches on created / published /
|
|
130
|
+
* head_ref.pushed / base_ref.updated.
|
|
131
|
+
*/
|
|
132
|
+
onPullRequest?: OriginPullRequestHook;
|
|
133
|
+
/** Opt-in `pull_request.comment.*` handler. No default dispatch. */
|
|
134
|
+
onComment?: OriginCommentHook;
|
|
135
|
+
/** Opt-in `pull_request.review.*` handler. No default dispatch. */
|
|
136
|
+
onReview?: OriginReviewHook;
|
|
137
|
+
/**
|
|
138
|
+
* Catch-all for Origin event types without a typed hook. NOT a fallback:
|
|
139
|
+
* events with a typed hook never reach this one.
|
|
140
|
+
*/
|
|
141
|
+
onEvent?: OriginEventHook;
|
|
142
|
+
/**
|
|
143
|
+
* Per-session cloud options. Pass `true` to attach the PR repo using the
|
|
144
|
+
* pull request URL, or a resolver for custom cloud options.
|
|
145
|
+
*/
|
|
146
|
+
cloud?: boolean | AgentCloudOptions | OriginCloudOptionsResolver;
|
|
147
|
+
/** Called once when the server mounts this channel. */
|
|
148
|
+
onStart?: (args: ChannelStartArgs) => void | Promise<void>;
|
|
149
|
+
/** Called when the server shuts down. */
|
|
150
|
+
onStop?: () => void | Promise<void>;
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/channels/origin/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Slack channel doctor — verify env, Socket Mode app token, auth.test
|
|
2
|
+
* Slack channel doctor — verify env, Socket Mode app token, auth.test, and
|
|
3
|
+
* (when watched channels are supplied) that the bot token's workspace can
|
|
4
|
+
* actually see, join, and read those channels.
|
|
3
5
|
*/
|
|
6
|
+
import { callSlackApi } from "./api.js";
|
|
4
7
|
import type { SlackCredentials } from "./types.js";
|
|
5
8
|
export interface SlackDoctorCheck {
|
|
6
9
|
id: string;
|
|
@@ -14,11 +17,24 @@ export interface SlackDoctorResult {
|
|
|
14
17
|
bot: string;
|
|
15
18
|
app: string;
|
|
16
19
|
};
|
|
20
|
+
/**
|
|
21
|
+
* Things a bot token cannot prove. Event subscriptions are app-level
|
|
22
|
+
* config, so a fully green doctor can still be a silent agent.
|
|
23
|
+
*/
|
|
24
|
+
notes?: string[];
|
|
17
25
|
}
|
|
18
26
|
export interface SlackDoctorOptions {
|
|
19
27
|
credentials?: SlackCredentials;
|
|
20
28
|
/** Per-agent env prefix (e.g. `MY_AGENT`). */
|
|
21
29
|
envPrefix?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Watched channel ids (`engagement.channelPosts.allow`). Each is checked
|
|
32
|
+
* for visibility, bot membership, and history scope.
|
|
33
|
+
*/
|
|
34
|
+
channels?: readonly string[];
|
|
35
|
+
/** Test seams. */
|
|
36
|
+
callImpl?: typeof callSlackApi;
|
|
37
|
+
fetchImpl?: typeof fetch;
|
|
22
38
|
}
|
|
23
39
|
export declare function runSlackDoctor(options?: SlackDoctorOptions): Promise<SlackDoctorResult>;
|
|
24
40
|
//# sourceMappingURL=doctor.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../../src/channels/slack/doctor.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../../src/channels/slack/doctor.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,YAAY,EAAoC,MAAM,UAAU,CAAC;AAE1E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAC3B,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IACtC;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAC/B,8CAA8C;IAC9C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7B,kBAAkB;IAClB,QAAQ,CAAC,EAAE,OAAO,YAAY,CAAC;IAC/B,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AA4ID,wBAAsB,cAAc,CAClC,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,iBAAiB,CAAC,CAsK5B"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Slack channel doctor — verify env, Socket Mode app token, auth.test
|
|
2
|
+
* Slack channel doctor — verify env, Socket Mode app token, auth.test, and
|
|
3
|
+
* (when watched channels are supplied) that the bot token's workspace can
|
|
4
|
+
* actually see, join, and read those channels.
|
|
3
5
|
*/
|
|
4
6
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
5
7
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
@@ -12,17 +14,136 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
12
14
|
};
|
|
13
15
|
import { callSlackApi, resolveAppToken, resolveBotToken } from "./api.js";
|
|
14
16
|
import { credentialsFromEnv, slackEnvKeys } from "./credentials.js";
|
|
17
|
+
/**
|
|
18
|
+
* Socket Mode only delivers bot events the app subscribes to. Missing
|
|
19
|
+
* subscriptions produce total silence with valid tokens, and cannot be read
|
|
20
|
+
* back from a bot token.
|
|
21
|
+
*/
|
|
22
|
+
const REQUIRED_BOT_EVENTS = {
|
|
23
|
+
app_mention: "@mentions of the bot",
|
|
24
|
+
"message.im": "direct messages",
|
|
25
|
+
"message.channels": "public channel posts",
|
|
26
|
+
"message.groups": "private channel posts",
|
|
27
|
+
reaction_added: "reaction handlers (onReactionAdded)",
|
|
28
|
+
};
|
|
29
|
+
function eventSubscriptionNote() {
|
|
30
|
+
const lines = Object.entries(REQUIRED_BOT_EVENTS).map(([event, purpose]) => ` ${event} — ${purpose}`);
|
|
31
|
+
return [
|
|
32
|
+
"A bot token cannot read Event Subscriptions. If every check above passes",
|
|
33
|
+
"but the bot is silent, verify these bot events are subscribed on the app",
|
|
34
|
+
"(Slack app config -> Event Subscriptions -> Subscribe to bot events):",
|
|
35
|
+
...lines,
|
|
36
|
+
].join("\n");
|
|
37
|
+
}
|
|
38
|
+
function asRecord(value) {
|
|
39
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
function errorOf(response) {
|
|
45
|
+
return typeof response.error === "string" ? response.error : "unknown";
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Visibility, membership, and history-scope checks for one watched channel.
|
|
49
|
+
* `channel_not_found` here is the classic wrong-workspace bot token: the
|
|
50
|
+
* token authenticates fine, it just belongs to a different installation.
|
|
51
|
+
*/
|
|
52
|
+
function checkChannel(args) {
|
|
53
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
54
|
+
const { call, botToken, channelId } = args;
|
|
55
|
+
const checks = [];
|
|
56
|
+
let info;
|
|
57
|
+
try {
|
|
58
|
+
info = yield call({
|
|
59
|
+
botToken,
|
|
60
|
+
operation: "conversations.info",
|
|
61
|
+
body: { channel: channelId },
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
checks.push({
|
|
66
|
+
id: `channel_visible:${channelId}`,
|
|
67
|
+
ok: false,
|
|
68
|
+
message: `conversations.info threw: ${error instanceof Error ? error.message : String(error)}`,
|
|
69
|
+
});
|
|
70
|
+
return checks;
|
|
71
|
+
}
|
|
72
|
+
if (info.ok !== true) {
|
|
73
|
+
const error = errorOf(info);
|
|
74
|
+
checks.push({
|
|
75
|
+
id: `channel_visible:${channelId}`,
|
|
76
|
+
ok: false,
|
|
77
|
+
message: error === "channel_not_found"
|
|
78
|
+
? `${channelId} not found by this bot token — the token is probably installed on a different workspace, or the channel id is wrong`
|
|
79
|
+
: `conversations.info failed: ${error}`,
|
|
80
|
+
});
|
|
81
|
+
return checks;
|
|
82
|
+
}
|
|
83
|
+
const channel = asRecord(info.channel);
|
|
84
|
+
const name = typeof (channel === null || channel === void 0 ? void 0 : channel.name) === "string" ? channel.name : "?";
|
|
85
|
+
checks.push({
|
|
86
|
+
id: `channel_visible:${channelId}`,
|
|
87
|
+
ok: true,
|
|
88
|
+
message: `${channelId} visible (#${name})`,
|
|
89
|
+
});
|
|
90
|
+
const isMember = (channel === null || channel === void 0 ? void 0 : channel.is_member) === true;
|
|
91
|
+
checks.push({
|
|
92
|
+
id: `channel_member:${channelId}`,
|
|
93
|
+
ok: isMember,
|
|
94
|
+
message: isMember
|
|
95
|
+
? `bot is a member of ${channelId}`
|
|
96
|
+
: `bot is NOT a member of ${channelId} — invite it to the channel`,
|
|
97
|
+
});
|
|
98
|
+
let history;
|
|
99
|
+
try {
|
|
100
|
+
history = yield call({
|
|
101
|
+
botToken,
|
|
102
|
+
operation: "conversations.history",
|
|
103
|
+
body: { channel: channelId, limit: 1 },
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
checks.push({
|
|
108
|
+
id: `channel_history:${channelId}`,
|
|
109
|
+
ok: false,
|
|
110
|
+
message: `conversations.history threw: ${error instanceof Error ? error.message : String(error)}`,
|
|
111
|
+
});
|
|
112
|
+
return checks;
|
|
113
|
+
}
|
|
114
|
+
if (history.ok === true) {
|
|
115
|
+
checks.push({
|
|
116
|
+
id: `channel_history:${channelId}`,
|
|
117
|
+
ok: true,
|
|
118
|
+
message: `conversations.history ok for ${channelId}`,
|
|
119
|
+
});
|
|
120
|
+
return checks;
|
|
121
|
+
}
|
|
122
|
+
const error = errorOf(history);
|
|
123
|
+
const needed = typeof history.needed === "string" ? history.needed : undefined;
|
|
124
|
+
checks.push({
|
|
125
|
+
id: `channel_history:${channelId}`,
|
|
126
|
+
ok: false,
|
|
127
|
+
message: error === "missing_scope"
|
|
128
|
+
? `conversations.history missing scope${needed === undefined ? "" : ` ${needed}`} — add it and reinstall the app`
|
|
129
|
+
: `conversations.history failed: ${error}`,
|
|
130
|
+
});
|
|
131
|
+
return checks;
|
|
132
|
+
});
|
|
133
|
+
}
|
|
15
134
|
export function runSlackDoctor() {
|
|
16
135
|
return __awaiter(this, arguments, void 0, function* (options = {}) {
|
|
17
|
-
var _a, _b, _c, _d, _e;
|
|
136
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
18
137
|
const credentials = (_a = options.credentials) !== null && _a !== void 0 ? _a : credentialsFromEnv(options.envPrefix);
|
|
19
138
|
const envKeys = slackEnvKeys(options.envPrefix);
|
|
20
139
|
const checks = [];
|
|
140
|
+
const call = (_b = options.callImpl) !== null && _b !== void 0 ? _b : callSlackApi;
|
|
141
|
+
const fetchImpl = (_c = options.fetchImpl) !== null && _c !== void 0 ? _c : fetch;
|
|
21
142
|
let appTokenRaw;
|
|
22
143
|
try {
|
|
23
144
|
appTokenRaw = yield resolveAppToken(credentials);
|
|
24
145
|
}
|
|
25
|
-
catch (
|
|
146
|
+
catch (_k) {
|
|
26
147
|
appTokenRaw = undefined;
|
|
27
148
|
}
|
|
28
149
|
const hasApp = appTokenRaw !== undefined && appTokenRaw !== "";
|
|
@@ -35,7 +156,7 @@ export function runSlackDoctor() {
|
|
|
35
156
|
});
|
|
36
157
|
if (hasApp && appTokenRaw !== undefined) {
|
|
37
158
|
try {
|
|
38
|
-
const response = yield
|
|
159
|
+
const response = yield fetchImpl("https://slack.com/api/apps.connections.open", {
|
|
39
160
|
method: "POST",
|
|
40
161
|
headers: {
|
|
41
162
|
authorization: `Bearer ${appTokenRaw}`,
|
|
@@ -55,7 +176,7 @@ export function runSlackDoctor() {
|
|
|
55
176
|
checks.push({
|
|
56
177
|
id: "connections_open",
|
|
57
178
|
ok: false,
|
|
58
|
-
message: `apps.connections.open failed: ${String((
|
|
179
|
+
message: `apps.connections.open failed: ${String((_d = data.error) !== null && _d !== void 0 ? _d : "unknown")}`,
|
|
59
180
|
});
|
|
60
181
|
}
|
|
61
182
|
}
|
|
@@ -71,7 +192,7 @@ export function runSlackDoctor() {
|
|
|
71
192
|
try {
|
|
72
193
|
botToken = yield resolveBotToken(credentials);
|
|
73
194
|
}
|
|
74
|
-
catch (
|
|
195
|
+
catch (_l) {
|
|
75
196
|
botToken = undefined;
|
|
76
197
|
}
|
|
77
198
|
const hasToken = botToken !== undefined && botToken !== "";
|
|
@@ -80,25 +201,31 @@ export function runSlackDoctor() {
|
|
|
80
201
|
ok: hasToken,
|
|
81
202
|
message: hasToken ? `${envKeys.bot} present` : `${envKeys.bot} is missing`,
|
|
82
203
|
});
|
|
204
|
+
let authOk = false;
|
|
205
|
+
let botUserId = "";
|
|
83
206
|
if (hasToken) {
|
|
84
207
|
try {
|
|
85
|
-
const auth = yield
|
|
208
|
+
const auth = yield call({
|
|
86
209
|
botToken,
|
|
87
210
|
operation: "auth.test",
|
|
88
211
|
body: {},
|
|
89
212
|
});
|
|
90
213
|
if (auth.ok === true) {
|
|
214
|
+
authOk = true;
|
|
215
|
+
botUserId = typeof auth.user_id === "string" ? auth.user_id : "";
|
|
91
216
|
checks.push({
|
|
92
217
|
id: "auth_test",
|
|
93
218
|
ok: true,
|
|
94
|
-
message: `auth.test ok (team=${String((
|
|
219
|
+
message: `auth.test ok (team=${String((_e = auth.team) !== null && _e !== void 0 ? _e : "?")}` +
|
|
220
|
+
`, team_id=${String((_f = auth.team_id) !== null && _f !== void 0 ? _f : "?")}` +
|
|
221
|
+
`, user=${String((_g = auth.user) !== null && _g !== void 0 ? _g : "?")})`,
|
|
95
222
|
});
|
|
96
223
|
}
|
|
97
224
|
else {
|
|
98
225
|
checks.push({
|
|
99
226
|
id: "auth_test",
|
|
100
227
|
ok: false,
|
|
101
|
-
message: `auth.test failed: ${String((
|
|
228
|
+
message: `auth.test failed: ${String((_h = auth.error) !== null && _h !== void 0 ? _h : "unknown")}`,
|
|
102
229
|
});
|
|
103
230
|
}
|
|
104
231
|
}
|
|
@@ -110,6 +237,55 @@ export function runSlackDoctor() {
|
|
|
110
237
|
});
|
|
111
238
|
}
|
|
112
239
|
}
|
|
113
|
-
|
|
240
|
+
// The external-sender policy (`blockExternals`, on by default) classifies a
|
|
241
|
+
// sender as external whenever `users.info` fails — so a token that cannot
|
|
242
|
+
// look up users silently drops EVERY inbound event while all other checks
|
|
243
|
+
// stay green. Probe with the bot's own user id from auth.test.
|
|
244
|
+
if (authOk && botToken !== undefined && botUserId !== "") {
|
|
245
|
+
let info;
|
|
246
|
+
let threw;
|
|
247
|
+
try {
|
|
248
|
+
info = yield call({
|
|
249
|
+
botToken,
|
|
250
|
+
operation: "users.info",
|
|
251
|
+
body: { user: botUserId },
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
threw = error instanceof Error ? error.message : String(error);
|
|
256
|
+
}
|
|
257
|
+
if ((info === null || info === void 0 ? void 0 : info.ok) === true) {
|
|
258
|
+
checks.push({
|
|
259
|
+
id: "users_read",
|
|
260
|
+
ok: true,
|
|
261
|
+
message: "users.info ok (users:read granted; external-sender policy can classify senders)",
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
const error = threw !== null && threw !== void 0 ? threw : (info === undefined ? "unknown" : errorOf(info));
|
|
266
|
+
const hint = error === "missing_scope"
|
|
267
|
+
? "add the users:read scope and reinstall the app"
|
|
268
|
+
: error === "user_not_found"
|
|
269
|
+
? "the token cannot see its own workspace's users — probably a token from a different installation"
|
|
270
|
+
: "fix the token";
|
|
271
|
+
checks.push({
|
|
272
|
+
id: "users_read",
|
|
273
|
+
ok: false,
|
|
274
|
+
message: `users.info failed: ${error} — ${hint}. The external-sender policy fails open to "external" on users.info errors, so every inbound event is silently dropped.`,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
const channels = (_j = options.channels) !== null && _j !== void 0 ? _j : [];
|
|
279
|
+
if (authOk && botToken !== undefined && channels.length > 0) {
|
|
280
|
+
for (const channelId of channels) {
|
|
281
|
+
checks.push(...(yield checkChannel({ call, botToken, channelId })));
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return {
|
|
285
|
+
ok: checks.every((check) => check.ok),
|
|
286
|
+
checks,
|
|
287
|
+
envKeys,
|
|
288
|
+
notes: [eventSubscriptionNote()],
|
|
289
|
+
};
|
|
114
290
|
});
|
|
115
291
|
}
|