@cursor/july 0.1.96 → 0.1.97
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/bin/agent-serve.js +14 -3
- package/dist/channels/origin/api.d.ts +15 -4
- package/dist/channels/origin/api.d.ts.map +1 -1
- package/dist/channels/origin/api.js +19 -3
- package/dist/channels/origin/binding.d.ts +11 -0
- package/dist/channels/origin/binding.d.ts.map +1 -1
- package/dist/channels/origin/binding.js +41 -25
- package/dist/channels/origin/index.d.ts +1 -0
- package/dist/channels/origin/index.d.ts.map +1 -1
- package/dist/channels/origin/index.js +1 -0
- package/dist/channels/origin/origin-channel.d.ts.map +1 -1
- package/dist/channels/origin/origin-channel.js +3 -0
- package/dist/channels/origin/reactions.d.ts +56 -0
- package/dist/channels/origin/reactions.d.ts.map +1 -0
- package/dist/channels/origin/reactions.js +67 -0
- package/dist/channels/origin/types.d.ts +11 -0
- package/dist/channels/origin/types.d.ts.map +1 -1
- package/dist/channels/slack/approvals.d.ts +16 -0
- package/dist/channels/slack/approvals.d.ts.map +1 -1
- package/dist/channels/slack/approvals.js +86 -0
- package/dist/channels/slack/cli-project.d.ts +19 -0
- package/dist/channels/slack/cli-project.d.ts.map +1 -0
- package/dist/channels/slack/cli-project.js +234 -0
- package/dist/channels/slack/defaults.d.ts.map +1 -1
- package/dist/channels/slack/defaults.js +97 -39
- package/dist/channels/slack/init.d.ts +3 -2
- package/dist/channels/slack/init.d.ts.map +1 -1
- package/dist/channels/slack/init.js +5 -5
- package/dist/channels/slack/interactive.d.ts +2 -1
- package/dist/channels/slack/interactive.d.ts.map +1 -1
- package/dist/channels/slack/interactive.js +15 -4
- package/dist/channels/slack/manifest.d.ts +1 -1
- package/dist/channels/slack/manifest.js +1 -1
- package/dist/channels/slack/setup.d.ts +1 -1
- package/dist/channels/slack/setup.d.ts.map +1 -1
- package/dist/channels/slack/setup.js +10 -6
- package/dist/channels/slack/slack-channel.d.ts.map +1 -1
- package/dist/channels/slack/slack-channel.js +1 -0
- package/dist/channels/slack/tool-approval-events.d.ts.map +1 -1
- package/dist/channels/slack/tool-approval-events.js +91 -1
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +4 -4
- package/dist/docs/assets/{app.jXleCmOc.js → app.B3rWNYE1.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.BU9nHdnQ.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.8LhyglIT.js → VPLocalSearchBox.C1lhqJJR.js} +1 -1
- package/dist/docs/assets/chunks/{theme.9ged-pr2.js → theme.B40_SXuv.js} +2 -2
- package/dist/docs/assets/{deployment.md.D9msOFOW.js → deployment.md.MFPKqsqI.js} +2 -2
- package/dist/docs/assets/{guides_mcp-oauth.md.DPYmBCbV.js → guides_mcp-oauth.md.CN-6YmTJ.js} +4 -4
- package/dist/docs/assets/{guides_mcp-oauth.md.DPYmBCbV.lean.js → guides_mcp-oauth.md.CN-6YmTJ.lean.js} +1 -1
- package/dist/docs/assets/guides_slack.md.VDXQV3ja.js +22 -0
- package/dist/docs/assets/guides_slack.md.VDXQV3ja.lean.js +1 -0
- package/dist/docs/assets/index.md.CVeRUOeZ.js +5 -0
- package/dist/docs/assets/{reference_agent-config.md.Cp_x38Nl.js → reference_agent-config.md.BHeYMgme.js} +1 -1
- package/dist/docs/assets/{reference_cli.md.CCnG-nJx.js → reference_cli.md.RyZf5OTE.js} +9 -6
- package/dist/docs/assets/{reference_cli.md.CCnG-nJx.lean.js → reference_cli.md.RyZf5OTE.lean.js} +1 -1
- package/dist/docs/assets/{reference_connections.md.BH8Oc0D0.js → reference_connections.md.DxldvyIB.js} +4 -3
- package/dist/docs/assets/{reference_http-api.md.D89k1mdm.js → reference_http-api.md.5zOAbV86.js} +1 -1
- package/dist/docs/assets/reference_sessions.md.NxFP_CAC.js +1 -0
- package/dist/docs/assets/{reference_tools.md.BHeXn2id.js → reference_tools.md.B84gw9Ii.js} +1 -1
- package/dist/docs/assets/templates_agents-md.md.CXSMZljE.js +2 -0
- package/dist/docs/assets/templates_agents-md.md.CXSMZljE.lean.js +1 -0
- package/dist/docs/assets/troubleshooting.md.DCiPBhYs.js +1 -0
- package/dist/docs/building-with-agents.html +4 -4
- package/dist/docs/concepts.html +4 -4
- package/dist/docs/deployment.html +7 -7
- package/dist/docs/deployment.md +5 -4
- package/dist/docs/evals.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 +4 -4
- package/dist/docs/guides/github.html +4 -4
- package/dist/docs/guides/human-in-the-loop.html +4 -4
- package/dist/docs/guides/mcp-oauth.html +8 -8
- package/dist/docs/guides/mcp-oauth.md +26 -8
- package/dist/docs/guides/opentelemetry.html +4 -4
- package/dist/docs/guides/slack.html +17 -33
- package/dist/docs/guides/slack.md +92 -201
- 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/index.md +5 -3
- package/dist/docs/llms-full.txt +299 -653
- package/dist/docs/llms.txt +2 -2
- package/dist/docs/quickstart.html +4 -4
- package/dist/docs/reference/agent-config.html +6 -6
- package/dist/docs/reference/agent-config.md +2 -2
- package/dist/docs/reference/artifacts.html +4 -4
- package/dist/docs/reference/channels.html +4 -4
- package/dist/docs/reference/cli.html +13 -10
- package/dist/docs/reference/cli.md +38 -16
- package/dist/docs/reference/connections.html +9 -8
- package/dist/docs/reference/connections.md +6 -3
- package/dist/docs/reference/hooks.html +4 -4
- package/dist/docs/reference/http-api.html +6 -6
- package/dist/docs/reference/http-api.md +2 -1
- 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 +5 -5
- package/dist/docs/reference/sessions.md +4 -2
- package/dist/docs/reference/skills.html +4 -4
- package/dist/docs/reference/subagents.html +4 -4
- package/dist/docs/reference/tools.html +6 -6
- package/dist/docs/reference/tools.md +9 -2
- package/dist/docs/scaffolding-agents.html +4 -4
- package/dist/docs/storage.html +4 -4
- package/dist/docs/templates/agentic-owners.html +4 -4
- package/dist/docs/templates/agents-md.html +28 -0
- package/dist/docs/templates/agents-md.md +105 -0
- package/dist/docs/templates/code-wiki.html +5 -5
- package/dist/docs/templates/demo.html +4 -4
- package/dist/docs/templates/pr-autofixer.html +4 -4
- package/dist/docs/templates/security-help.html +4 -4
- package/dist/docs/templates/security-reviewer.html +5 -5
- package/dist/docs/templates/triage.html +4 -4
- package/dist/docs/troubleshooting.html +5 -5
- package/dist/docs/troubleshooting.md +2 -2
- package/dist/evals/run-facts.d.ts.map +1 -1
- package/dist/evals/run-facts.js +27 -0
- package/dist/internal/advertise-tools.d.ts.map +1 -1
- package/dist/internal/advertise-tools.js +15 -9
- package/dist/internal/approval-gate.d.ts +12 -13
- package/dist/internal/approval-gate.d.ts.map +1 -1
- package/dist/internal/approval-gate.js +16 -21
- package/dist/internal/chat-client.js +10 -0
- package/dist/internal/cli-deploy.d.ts +5 -0
- package/dist/internal/cli-deploy.d.ts.map +1 -1
- package/dist/internal/cli-deploy.js +50 -17
- package/dist/internal/cli-mcp-oauth.js +8 -18
- package/dist/internal/cli-slack-manual.d.ts +24 -2
- package/dist/internal/cli-slack-manual.d.ts.map +1 -1
- package/dist/internal/cli-slack-manual.js +251 -20
- package/dist/internal/cli-slack.d.ts +9 -2
- package/dist/internal/cli-slack.d.ts.map +1 -1
- package/dist/internal/cli-slack.js +29 -82
- package/dist/internal/cursor/account-mcp.d.ts +1 -0
- package/dist/internal/cursor/account-mcp.d.ts.map +1 -1
- package/dist/internal/cursor/account-mcp.js +17 -0
- package/dist/internal/cursor-agent-template.d.ts +1 -1
- package/dist/internal/cursor-agent-template.d.ts.map +1 -1
- package/dist/internal/cursor-agent-template.js +1 -0
- package/dist/internal/deploy-client.d.ts +17 -0
- package/dist/internal/deploy-client.d.ts.map +1 -1
- package/dist/internal/deploy-client.js +31 -10
- package/dist/internal/env-file.d.ts +19 -0
- package/dist/internal/env-file.d.ts.map +1 -0
- package/dist/internal/env-file.js +90 -0
- package/dist/internal/framework-file-storage.d.ts +1 -1
- package/dist/internal/framework-file-storage.d.ts.map +1 -1
- package/dist/internal/framework-file-storage.js +6 -0
- package/dist/internal/hosted-delivery-protocol.d.ts +7 -0
- package/dist/internal/hosted-delivery-protocol.d.ts.map +1 -1
- package/dist/internal/hosted-delivery-protocol.js +7 -0
- package/dist/internal/hosted-delivery.d.ts +6 -0
- package/dist/internal/hosted-delivery.d.ts.map +1 -1
- package/dist/internal/hosted-delivery.js +63 -22
- package/dist/internal/hosted-execution-diag.d.ts +48 -0
- package/dist/internal/hosted-execution-diag.d.ts.map +1 -0
- package/dist/internal/hosted-execution-diag.js +150 -0
- package/dist/internal/http-channel.d.ts.map +1 -1
- package/dist/internal/http-channel.js +81 -1
- package/dist/internal/init-project.d.ts.map +1 -1
- package/dist/internal/init-project.js +2 -0
- package/dist/internal/logs-client.d.ts +5 -0
- package/dist/internal/logs-client.d.ts.map +1 -1
- package/dist/internal/logs-client.js +5 -0
- package/dist/internal/mcp-endpoint.js +2 -3
- package/dist/internal/mcp-host.d.ts +10 -0
- package/dist/internal/mcp-host.d.ts.map +1 -1
- package/dist/internal/mcp-host.js +57 -8
- package/dist/internal/mcp-oauth-on-demand.d.ts +68 -0
- package/dist/internal/mcp-oauth-on-demand.d.ts.map +1 -0
- package/dist/internal/mcp-oauth-on-demand.js +287 -0
- package/dist/internal/mcp-oauth-persist.d.ts +42 -0
- package/dist/internal/mcp-oauth-persist.d.ts.map +1 -0
- package/dist/internal/mcp-oauth-persist.js +92 -0
- package/dist/internal/mcp-oauth.d.ts +1 -0
- package/dist/internal/mcp-oauth.d.ts.map +1 -1
- package/dist/internal/mcp-oauth.js +10 -2
- package/dist/internal/otel/collector.d.ts.map +1 -1
- package/dist/internal/otel/collector.js +22 -9
- package/dist/internal/server.d.ts +3 -0
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +49 -22
- package/dist/internal/session-engine.d.ts +88 -3
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +515 -43
- package/dist/internal/sessions-client.d.ts +9 -7
- package/dist/internal/sessions-client.d.ts.map +1 -1
- package/dist/internal/sessions-client.js +3 -1
- package/dist/internal/storage-coordinator.d.ts +7 -0
- package/dist/internal/storage-coordinator.d.ts.map +1 -1
- package/dist/internal/storage-coordinator.js +28 -5
- package/dist/internal/storage-roles.d.ts +5 -0
- package/dist/internal/storage-roles.d.ts.map +1 -1
- package/dist/internal/stream-progress.d.ts.map +1 -1
- package/dist/internal/stream-progress.js +10 -0
- package/dist/internal/tool-policy.d.ts +9 -1
- package/dist/internal/tool-policy.d.ts.map +1 -1
- package/dist/internal/tool-policy.js +13 -0
- package/dist/internal/trajectory.d.ts.map +1 -1
- package/dist/internal/trajectory.js +10 -0
- package/dist/internal/turn-outbox.d.ts +24 -0
- package/dist/internal/turn-outbox.d.ts.map +1 -0
- package/dist/internal/turn-outbox.js +80 -0
- package/dist/playground/assets/{index-Cx9CclK_.js → index-DrkI6y5O.js} +45 -45
- package/dist/playground/assets/index-DzNGwm7q.css +1 -0
- package/dist/playground/index.html +2 -2
- package/dist/storage-backends/cursor-hosted.d.ts +1 -1
- package/dist/storage-backends/cursor-hosted.d.ts.map +1 -1
- package/dist/storage-backends/cursor-hosted.js +12 -0
- package/dist/storage.d.ts +8 -0
- package/dist/storage.d.ts.map +1 -1
- package/dist/types.d.ts +63 -7
- package/dist/types.d.ts.map +1 -1
- package/docs/README.md +5 -3
- package/docs/deployment.md +5 -4
- package/docs/guides/mcp-oauth.md +26 -8
- package/docs/guides/slack.md +93 -202
- package/docs/reference/agent-config.md +2 -2
- package/docs/reference/cli.md +38 -16
- package/docs/reference/connections.md +6 -3
- package/docs/reference/http-api.md +2 -1
- package/docs/reference/sessions.md +4 -2
- package/docs/reference/tools.md +9 -2
- package/docs/templates/agents-md.md +110 -0
- package/docs/troubleshooting.md +2 -2
- package/package.json +1 -1
- package/skills/create-agent/SKILL.md +2 -1
- package/skills/mcp-auth/SKILL.md +6 -3
- package/skills/setup-slack/SKILL.md +23 -13
- package/src/bin/agent-serve.ts +13 -1
- package/src/channels/origin/api.ts +24 -5
- package/src/channels/origin/binding.ts +55 -26
- package/src/channels/origin/index.ts +1 -0
- package/src/channels/origin/origin-channel.ts +4 -0
- package/src/channels/origin/reactions.ts +82 -0
- package/src/channels/origin/types.ts +11 -0
- package/src/channels/slack/approvals.ts +105 -0
- package/src/channels/slack/cli-project.ts +267 -0
- package/src/channels/slack/defaults.ts +109 -46
- package/src/channels/slack/init.ts +10 -5
- package/src/channels/slack/interactive.ts +35 -9
- package/src/channels/slack/manifest.ts +1 -1
- package/src/channels/slack/setup.ts +10 -6
- package/src/channels/slack/slack-channel.ts +1 -0
- package/src/channels/slack/tool-approval-events.ts +93 -0
- package/src/evals/run-facts.ts +32 -0
- package/src/internal/advertise-tools.ts +31 -10
- package/src/internal/approval-gate.ts +20 -29
- package/src/internal/chat-client.ts +19 -0
- package/src/internal/cli-deploy.ts +79 -9
- package/src/internal/cli-mcp-oauth.ts +7 -21
- package/src/internal/cli-slack-manual.ts +350 -18
- package/src/internal/cli-slack.ts +40 -84
- package/src/internal/cursor/account-mcp.ts +21 -0
- package/src/internal/cursor-agent-template.ts +1 -0
- package/src/internal/deploy-client.ts +43 -0
- package/src/internal/env-file.ts +84 -0
- package/src/internal/framework-file-storage.ts +7 -1
- package/src/internal/hosted-delivery-protocol.ts +7 -0
- package/src/internal/hosted-delivery.ts +100 -27
- package/src/internal/hosted-execution-diag.ts +207 -0
- package/src/internal/http-channel.ts +99 -0
- package/src/internal/init-project.ts +2 -0
- package/src/internal/logs-client.ts +5 -0
- package/src/internal/mcp-endpoint.ts +13 -4
- package/src/internal/mcp-host.ts +77 -11
- package/src/internal/mcp-oauth-on-demand.ts +352 -0
- package/src/internal/mcp-oauth-persist.ts +127 -0
- package/src/internal/mcp-oauth.ts +17 -1
- package/src/internal/otel/collector.ts +30 -0
- package/src/internal/server.ts +59 -9
- package/src/internal/session-engine.ts +659 -56
- package/src/internal/sessions-client.ts +17 -8
- package/src/internal/storage-coordinator.ts +38 -8
- package/src/internal/storage-roles.ts +7 -0
- package/src/internal/stream-progress.ts +19 -0
- package/src/internal/tool-policy.ts +18 -0
- package/src/internal/trajectory.ts +10 -0
- package/src/internal/turn-outbox.ts +85 -0
- package/src/storage-backends/cursor-hosted.ts +13 -1
- package/src/storage.ts +12 -0
- package/src/types.ts +68 -8
- package/templates/agents-md/README.md +39 -0
- package/templates/agents-md/agent/agent.ts +8 -0
- package/templates/agents-md/agent/channels/github.ts +10 -0
- package/templates/agents-md/agent/channels/queue.ts +28 -0
- package/templates/agents-md/agent/channels/slack.ts +9 -0
- package/templates/agents-md/agent/instructions.md +39 -0
- package/templates/agents-md/agent/lib/collect.ts +439 -0
- package/templates/agents-md/agent/lib/config.ts +8 -0
- package/templates/agents-md/agent/lib/paths.ts +51 -0
- package/templates/agents-md/agent/schedules/weekly.ts +17 -0
- package/templates/agents-md/agent/tools/apply_agents.ts +226 -0
- package/templates/agents-md/evals/agents.eval.ts +135 -0
- package/templates/agents-md/evals/evals.config.ts +5 -0
- package/templates/agents-md/init.json +38 -0
- package/templates/agents-md/package.json +18 -0
- package/templates/agents-md/tsconfig.json +12 -0
- package/dist/docs/assets/chunks/@localSearchIndexroot.DFoGuETX.js +0 -1
- package/dist/docs/assets/guides_slack.md.C32HsdKk.js +0 -38
- package/dist/docs/assets/guides_slack.md.C32HsdKk.lean.js +0 -1
- package/dist/docs/assets/index.md.CI93Ltlb.js +0 -5
- package/dist/docs/assets/reference_sessions.md.D0mIh4KK.js +0 -1
- package/dist/docs/assets/troubleshooting.md.Ctv3T8C2.js +0 -1
- package/dist/docs/design/agsh.md +0 -406
- package/dist/playground/assets/index-B1DDvBeC.css +0 -1
- package/docs/design/agsh.md +0 -406
- /package/dist/docs/assets/{deployment.md.D9msOFOW.lean.js → deployment.md.MFPKqsqI.lean.js} +0 -0
- /package/dist/docs/assets/{index.md.CI93Ltlb.lean.js → index.md.CVeRUOeZ.lean.js} +0 -0
- /package/dist/docs/assets/{reference_agent-config.md.Cp_x38Nl.lean.js → reference_agent-config.md.BHeYMgme.lean.js} +0 -0
- /package/dist/docs/assets/{reference_connections.md.BH8Oc0D0.lean.js → reference_connections.md.DxldvyIB.lean.js} +0 -0
- /package/dist/docs/assets/{reference_http-api.md.D89k1mdm.lean.js → reference_http-api.md.5zOAbV86.lean.js} +0 -0
- /package/dist/docs/assets/{reference_sessions.md.D0mIh4KK.lean.js → reference_sessions.md.NxFP_CAC.lean.js} +0 -0
- /package/dist/docs/assets/{reference_tools.md.BHeXn2id.lean.js → reference_tools.md.B84gw9Ii.lean.js} +0 -0
- /package/dist/docs/assets/{troubleshooting.md.Ctv3T8C2.lean.js → troubleshooting.md.DCiPBhYs.lean.js} +0 -0
|
@@ -260,6 +260,13 @@ export interface AgentkitDeployStatus {
|
|
|
260
260
|
status: AgentkitCustomerStatus;
|
|
261
261
|
progress: AgentkitDeployProgress;
|
|
262
262
|
statusMessage: string | null;
|
|
263
|
+
/** Why the bake failed. Null/absent unless `status` is `failed`. */
|
|
264
|
+
lastError?: string | null;
|
|
265
|
+
/**
|
|
266
|
+
* `owner/name` scopes the deployment's GitHub/Origin channels watch.
|
|
267
|
+
* Absent on control planes that predate the field.
|
|
268
|
+
*/
|
|
269
|
+
repositories?: string[];
|
|
263
270
|
}
|
|
264
271
|
|
|
265
272
|
export const TERMINAL_AGENTKIT_DEPLOY_STATUSES: ReadonlySet<AgentkitCustomerStatus> =
|
|
@@ -350,6 +357,8 @@ const agentkitDeployStatusSchema = z
|
|
|
350
357
|
"stopped",
|
|
351
358
|
]),
|
|
352
359
|
statusMessage: z.string().nullable(),
|
|
360
|
+
// Optional: backends that predate the v2 bake-failure column omit it.
|
|
361
|
+
lastError: z.string().nullable().optional(),
|
|
353
362
|
})
|
|
354
363
|
.passthrough();
|
|
355
364
|
|
|
@@ -598,6 +607,8 @@ export class AgentServeDeployClient {
|
|
|
598
607
|
agentPath?: string;
|
|
599
608
|
components?: AgentkitDeployComponent[];
|
|
600
609
|
repositories?: string[];
|
|
610
|
+
/** Redeploy an existing Cursor-managed (multi-tenant) product. */
|
|
611
|
+
multiTenant?: boolean;
|
|
601
612
|
}): Promise<AgentkitDeployAccepted> {
|
|
602
613
|
const slugError = validateDeploymentSlug(args.slug);
|
|
603
614
|
if (slugError !== undefined) {
|
|
@@ -626,6 +637,7 @@ export class AgentServeDeployClient {
|
|
|
626
637
|
args.repositories !== undefined && args.repositories.length > 0
|
|
627
638
|
? args.repositories
|
|
628
639
|
: undefined,
|
|
640
|
+
...(args.multiTenant === true ? { multiTenant: true } : {}),
|
|
629
641
|
},
|
|
630
642
|
context: { verb: "deploy", slug: args.slug },
|
|
631
643
|
});
|
|
@@ -890,6 +902,37 @@ export class AgentServeDeployClient {
|
|
|
890
902
|
}
|
|
891
903
|
}
|
|
892
904
|
|
|
905
|
+
const MCP_GATEWAY_PATH =
|
|
906
|
+
/^\/internal\/agent-serve\/mcp\/(\d+)\/([^/]+)(?:\/tools)?\/?$/;
|
|
907
|
+
|
|
908
|
+
/**
|
|
909
|
+
* Inverse of {@link AgentServeDeployClient.mcpGatewayUrl}. Also accepts
|
|
910
|
+
* the hosted `--cloud-tools-url` form (`…/tools`).
|
|
911
|
+
*/
|
|
912
|
+
export function parseAgentServeMcpGatewayUrl(
|
|
913
|
+
url: string
|
|
914
|
+
): { teamId: number; slug: string } | undefined {
|
|
915
|
+
let parsed: URL;
|
|
916
|
+
try {
|
|
917
|
+
parsed = new URL(url);
|
|
918
|
+
} catch {
|
|
919
|
+
return undefined;
|
|
920
|
+
}
|
|
921
|
+
const match = MCP_GATEWAY_PATH.exec(parsed.pathname);
|
|
922
|
+
if (match === null) {
|
|
923
|
+
return undefined;
|
|
924
|
+
}
|
|
925
|
+
const teamId = Number.parseInt(match[1]!, 10);
|
|
926
|
+
if (!Number.isInteger(teamId) || teamId <= 0) {
|
|
927
|
+
return undefined;
|
|
928
|
+
}
|
|
929
|
+
const slug = decodeURIComponent(match[2]!);
|
|
930
|
+
if (validateDeploymentSlug(slug) !== undefined) {
|
|
931
|
+
return undefined;
|
|
932
|
+
}
|
|
933
|
+
return { teamId, slug };
|
|
934
|
+
}
|
|
935
|
+
|
|
893
936
|
// ============================================================================
|
|
894
937
|
// Error mapping (HTTP → actionable human messages)
|
|
895
938
|
// ============================================================================
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared `.env` / `.env.local` writers for CLI commands that mint tokens.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { chmod, readFile, writeFile } from "node:fs/promises";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
|
|
8
|
+
function isErrnoException(error: unknown): error is NodeJS.ErrnoException {
|
|
9
|
+
return error instanceof Error && "code" in error;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async function readOptionalFile(path: string): Promise<string> {
|
|
13
|
+
try {
|
|
14
|
+
return await readFile(path, "utf8");
|
|
15
|
+
} catch (error) {
|
|
16
|
+
if (isErrnoException(error) && error.code === "ENOENT") {
|
|
17
|
+
return "";
|
|
18
|
+
}
|
|
19
|
+
throw error;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Append `KEY=value` lines to an env file, replacing every existing line
|
|
25
|
+
* for the same key instead of appending duplicates. Creates the file when
|
|
26
|
+
* missing.
|
|
27
|
+
*/
|
|
28
|
+
export async function upsertEnvFile(
|
|
29
|
+
path: string,
|
|
30
|
+
entries: Array<{ key: string; value: string }>
|
|
31
|
+
): Promise<void> {
|
|
32
|
+
const existing = await readOptionalFile(path);
|
|
33
|
+
const lines =
|
|
34
|
+
existing === "" ? [] : existing.replace(/\r?\n$/, "").split(/\r?\n/);
|
|
35
|
+
// A stale `export KEY=…` line would shadow or duplicate the bare line
|
|
36
|
+
// under some loaders; treat both spellings as the same entry.
|
|
37
|
+
const matchesKey = (line: string, key: string): boolean =>
|
|
38
|
+
line.startsWith(`${key}=`) || line.startsWith(`export ${key}=`);
|
|
39
|
+
for (const { key, value } of entries) {
|
|
40
|
+
const next = `${key}=${value}`;
|
|
41
|
+
let replaced = false;
|
|
42
|
+
for (let i = 0; i < lines.length; ) {
|
|
43
|
+
if (!matchesKey(lines[i] ?? "", key)) {
|
|
44
|
+
i += 1;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (replaced) {
|
|
48
|
+
lines.splice(i, 1);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
lines[i] = next;
|
|
52
|
+
replaced = true;
|
|
53
|
+
i += 1;
|
|
54
|
+
}
|
|
55
|
+
if (!replaced) {
|
|
56
|
+
lines.push(next);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// writeFile's mode only applies on create; clamp pre-existing files too.
|
|
60
|
+
await writeFile(path, `${lines.join("\n")}\n`, { mode: 0o600 });
|
|
61
|
+
await chmod(path, 0o600);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Ensure the agent directory's .gitignore covers .env.local (create or
|
|
66
|
+
* append). A bare `.env` line does not count — that pattern matches only
|
|
67
|
+
* the file literally named .env.
|
|
68
|
+
*/
|
|
69
|
+
export async function ensureEnvLocalGitignored(dir: string): Promise<boolean> {
|
|
70
|
+
const path = join(dir, ".gitignore");
|
|
71
|
+
const existing = await readOptionalFile(path);
|
|
72
|
+
const covered = existing
|
|
73
|
+
.split(/\r?\n/)
|
|
74
|
+
.some((line) => /^\.env(\.local\*?|\.\*|\*)$/.test(line.trim()));
|
|
75
|
+
if (covered) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
const next =
|
|
79
|
+
existing === "" || existing.endsWith("\n")
|
|
80
|
+
? `${existing}.env.local\n`
|
|
81
|
+
: `${existing}\n.env.local\n`;
|
|
82
|
+
await writeFile(path, next);
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
@@ -171,7 +171,7 @@ function decodeDocument(key: string, bytes: Uint8Array): JsonValue {
|
|
|
171
171
|
export function composeFrameworkStorage(input: {
|
|
172
172
|
kv: FileSinkStorageCore;
|
|
173
173
|
files: FileSinkStorageCore;
|
|
174
|
-
}): FileSinkStorageCore {
|
|
174
|
+
}): FileSinkStorageCore & Pick<StorageConfig, "listNewest"> {
|
|
175
175
|
const route = (key: string): FileSinkStorageCore =>
|
|
176
176
|
isFrameworkFileKey(key) ? input.files : input.kv;
|
|
177
177
|
return {
|
|
@@ -179,6 +179,12 @@ export function composeFrameworkStorage(input: {
|
|
|
179
179
|
get: (key, ctx) => route(key).get(key, ctx),
|
|
180
180
|
delete: (key, ctx) => route(key).delete(key, ctx),
|
|
181
181
|
list: (prefix, ctx) => route(prefix).list(prefix, ctx),
|
|
182
|
+
// Session records — the only `listNewest` consumer — are framework file
|
|
183
|
+
// keys, and the file store has no write-time ordering. Spreading the KV
|
|
184
|
+
// backend's `listNewest` through would query rows that live elsewhere
|
|
185
|
+
// (stale KV leftovers, or nothing), so the composed core explicitly has
|
|
186
|
+
// none and the engine falls back to the routed `list`.
|
|
187
|
+
listNewest: undefined,
|
|
182
188
|
};
|
|
183
189
|
}
|
|
184
190
|
|
|
@@ -2,6 +2,13 @@ export const HOSTED_DELIVERY_PROTOCOL_VERSION = 2;
|
|
|
2
2
|
export const HOSTED_DELIVERY_HEADER = "x-agent-sdk-hosted-delivery";
|
|
3
3
|
export const HOSTED_ADMISSION_PATH = "/v1/internal/hosted-admission";
|
|
4
4
|
export const HOSTED_EXECUTION_PATH = "/v1/internal/hosted-execution";
|
|
5
|
+
/**
|
|
6
|
+
* Filename under the serve `--state-root` (not the per-slug SessionEngine
|
|
7
|
+
* dir). Hosted freeze uses `/workspace/.agent-serve`; the Temporal host
|
|
8
|
+
* reads this path after hosted-execution and logs it on the activity span.
|
|
9
|
+
* Must match `AGENT_SERVE_ENGINE_HOSTED_EXECUTION_DIAG_PATH` basename.
|
|
10
|
+
*/
|
|
11
|
+
export const HOSTED_EXECUTION_DIAG_FILENAME = "hosted-execution.json";
|
|
5
12
|
|
|
6
13
|
export type HostedAdmissionManagedAutomation = {
|
|
7
14
|
automationUuid: string;
|
|
@@ -27,6 +27,12 @@ import {
|
|
|
27
27
|
parseHostedAdmission,
|
|
28
28
|
parseHostedExecution,
|
|
29
29
|
} from "./hosted-delivery-protocol.js";
|
|
30
|
+
import {
|
|
31
|
+
abortReasonFromTurnStatus,
|
|
32
|
+
HOSTED_EXECUTION_DIAG_RUNNING_STATUS,
|
|
33
|
+
hostedExecutionDiagId,
|
|
34
|
+
writeHostedExecutionDiag,
|
|
35
|
+
} from "./hosted-execution-diag.js";
|
|
30
36
|
import { recordHostedManagedAutomation } from "./hosted-managed-automation.js";
|
|
31
37
|
import { errorResponse, readJsonObject } from "./http-channel.js";
|
|
32
38
|
import type { Router } from "./router.js";
|
|
@@ -252,6 +258,12 @@ export function registerHostedDeliveryRoute(input: {
|
|
|
252
258
|
* the conversation on the next wake.
|
|
253
259
|
*/
|
|
254
260
|
drainSessionDurability?(sessionId: string): Promise<void>;
|
|
261
|
+
/**
|
|
262
|
+
* Serve `--state-root` (not the per-slug SessionEngine dir). Hosted
|
|
263
|
+
* freeze is `/workspace/.agent-serve`; the Temporal host reads
|
|
264
|
+
* `hosted-execution.json` from that path after the POST returns.
|
|
265
|
+
*/
|
|
266
|
+
stateRoot?: string;
|
|
255
267
|
}): void {
|
|
256
268
|
const pending = new Map<
|
|
257
269
|
string,
|
|
@@ -383,39 +395,100 @@ export function registerHostedDeliveryRoute(input: {
|
|
|
383
395
|
}
|
|
384
396
|
admitted.sessionPublicId = execution.sessionPublicId;
|
|
385
397
|
admitted.execution ??= (async (): Promise<ExecutionReply> => {
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
398
|
+
const startedAt = performance.now();
|
|
399
|
+
const executionId = hostedExecutionDiagId(execution.admissionToken);
|
|
400
|
+
const writeDiag = async (fields: {
|
|
401
|
+
sessionId: string;
|
|
402
|
+
turnStatus: string;
|
|
403
|
+
message?: string;
|
|
404
|
+
abortReason?:
|
|
405
|
+
| ReturnType<typeof abortReasonFromTurnStatus>
|
|
406
|
+
| "execute_throw";
|
|
407
|
+
}): Promise<void> => {
|
|
408
|
+
if (input.stateRoot === undefined) {
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
const message =
|
|
412
|
+
fields.message === undefined
|
|
413
|
+
? undefined
|
|
414
|
+
: boundedMessage(fields.message);
|
|
415
|
+
await writeHostedExecutionDiag(input.stateRoot, {
|
|
416
|
+
v: 1,
|
|
417
|
+
executionId,
|
|
418
|
+
writtenAt: new Date().toISOString(),
|
|
419
|
+
sessionPublicId: execution.sessionPublicId,
|
|
420
|
+
sessionId: fields.sessionId,
|
|
421
|
+
turnStatus: fields.turnStatus,
|
|
422
|
+
durationMs: Math.max(0, Math.round(performance.now() - startedAt)),
|
|
423
|
+
...(message === undefined ? {} : { message }),
|
|
424
|
+
...(message !== undefined && /abort/i.test(message)
|
|
425
|
+
? { signalAborted: true }
|
|
426
|
+
: {}),
|
|
427
|
+
...(fields.abortReason === undefined
|
|
428
|
+
? {}
|
|
429
|
+
: { abortReason: fields.abortReason }),
|
|
430
|
+
});
|
|
431
|
+
};
|
|
432
|
+
// Claim the file before the turn runs. The host reads it after its
|
|
433
|
+
// own POST returns, which for a 9-minute timeout is while this turn
|
|
434
|
+
// is still going; without this marker that read would surface the
|
|
435
|
+
// PREVIOUS delivery's terminal record as if it were this one's.
|
|
436
|
+
await writeDiag({
|
|
437
|
+
sessionId: "",
|
|
438
|
+
turnStatus: HOSTED_EXECUTION_DIAG_RUNNING_STATUS,
|
|
395
439
|
});
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
440
|
+
try {
|
|
441
|
+
const args = input.buildHandlerArgs(
|
|
442
|
+
admitted.send.channelId,
|
|
443
|
+
execution.sessionPublicId
|
|
444
|
+
);
|
|
445
|
+
const session = await args.send(admitted.send.message, {
|
|
446
|
+
...admitted.send.options,
|
|
447
|
+
mailboxContinuation: {
|
|
448
|
+
key: admitted.send.mailboxContinuationKey,
|
|
449
|
+
},
|
|
450
|
+
});
|
|
451
|
+
const outcome = await session.waitForCompletion();
|
|
452
|
+
// Failed turns drain too: their record/continuation updates must
|
|
453
|
+
// survive the park just like a finished turn's.
|
|
454
|
+
await input.drainSessionDurability?.(session.id);
|
|
455
|
+
await writeDiag({
|
|
456
|
+
sessionId: session.id,
|
|
457
|
+
turnStatus: outcome.status,
|
|
458
|
+
message: outcome.errorMessage,
|
|
459
|
+
abortReason: abortReasonFromTurnStatus(
|
|
460
|
+
outcome.status,
|
|
461
|
+
outcome.errorMessage
|
|
462
|
+
),
|
|
463
|
+
});
|
|
464
|
+
if (outcome.status !== "finished") {
|
|
465
|
+
return {
|
|
466
|
+
status: 503,
|
|
467
|
+
body: {
|
|
468
|
+
ok: false,
|
|
469
|
+
error: "turn_failed",
|
|
470
|
+
turnStatus: outcome.status,
|
|
471
|
+
message: outcome.errorMessage,
|
|
472
|
+
},
|
|
473
|
+
};
|
|
474
|
+
}
|
|
401
475
|
return {
|
|
402
|
-
status:
|
|
476
|
+
status: 200,
|
|
403
477
|
body: {
|
|
404
|
-
ok:
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
message: outcome.errorMessage,
|
|
478
|
+
ok: true,
|
|
479
|
+
v: HOSTED_DELIVERY_PROTOCOL_VERSION,
|
|
480
|
+
outcome: "send",
|
|
408
481
|
},
|
|
409
482
|
};
|
|
483
|
+
} catch (error) {
|
|
484
|
+
await writeDiag({
|
|
485
|
+
sessionId: "",
|
|
486
|
+
turnStatus: "error",
|
|
487
|
+
message: error instanceof Error ? error.message : String(error),
|
|
488
|
+
abortReason: "execute_throw",
|
|
489
|
+
});
|
|
490
|
+
throw error;
|
|
410
491
|
}
|
|
411
|
-
return {
|
|
412
|
-
status: 200,
|
|
413
|
-
body: {
|
|
414
|
-
ok: true,
|
|
415
|
-
v: HOSTED_DELIVERY_PROTOCOL_VERSION,
|
|
416
|
-
outcome: "send",
|
|
417
|
-
},
|
|
418
|
-
};
|
|
419
492
|
})();
|
|
420
493
|
const reply = await admitted.execution;
|
|
421
494
|
return finish(Response.json(reply.body, { status: reply.status }));
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
+
import { mkdir, rename, unlink, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
import { HOSTED_EXECUTION_DIAG_FILENAME } from "./hosted-delivery-protocol.js";
|
|
6
|
+
|
|
7
|
+
export const HOSTED_EXECUTION_DIAG_VERSION = 1;
|
|
8
|
+
/** Cap so a runaway message cannot fill the guest disk or the host log. */
|
|
9
|
+
export const HOSTED_EXECUTION_DIAG_MAX_BYTES = 4096;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Correlate the on-disk record with the execution that produced it. One pod
|
|
13
|
+
* serves many deliveries of a continuation session, and the file lives at a
|
|
14
|
+
* single fixed path, so without this the host can read a *previous*
|
|
15
|
+
* delivery's terminal record whenever July never reaches its own write —
|
|
16
|
+
* the 9-minute host POST timeout, a 409 on an expired admission token, or a
|
|
17
|
+
* replayed token that short-circuits on the memoized reply.
|
|
18
|
+
*
|
|
19
|
+
* Derived from the admission token (which both sides already hold) rather
|
|
20
|
+
* than carried on the wire. The digest is one-way, so the token itself never
|
|
21
|
+
* reaches the guest disk or the host logs.
|
|
22
|
+
*/
|
|
23
|
+
export function hostedExecutionDiagId(admissionToken: string): string {
|
|
24
|
+
return createHash("sha256").update(admissionToken).digest("hex").slice(0, 16);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type HostedExecutionDiagAbortReason =
|
|
28
|
+
| "signal_abort"
|
|
29
|
+
| "runner_cancelled"
|
|
30
|
+
| "runner_error"
|
|
31
|
+
| "execute_throw";
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Bounded guest-side record of one hosted-execution attempt. No prompt,
|
|
35
|
+
* tool content, or API key — the host copies known keys onto the Temporal
|
|
36
|
+
* activity span for Datadog.
|
|
37
|
+
*/
|
|
38
|
+
export type HostedExecutionDiag = {
|
|
39
|
+
v: typeof HOSTED_EXECUTION_DIAG_VERSION;
|
|
40
|
+
/** {@link hostedExecutionDiagId} of the execution that wrote this record. */
|
|
41
|
+
executionId: string;
|
|
42
|
+
writtenAt: string;
|
|
43
|
+
sessionPublicId: string;
|
|
44
|
+
sessionId: string;
|
|
45
|
+
/** Terminal {@link TurnOutcome} status, or `running` before the turn ends. */
|
|
46
|
+
turnStatus: string;
|
|
47
|
+
durationMs: number;
|
|
48
|
+
message?: string;
|
|
49
|
+
signalAborted?: boolean;
|
|
50
|
+
requestId?: string;
|
|
51
|
+
abortReason?: HostedExecutionDiagAbortReason;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** Written before the turn starts so a stalled execution still has a record. */
|
|
55
|
+
export const HOSTED_EXECUTION_DIAG_RUNNING_STATUS = "running";
|
|
56
|
+
|
|
57
|
+
const DIAG_KEYS = new Set([
|
|
58
|
+
"v",
|
|
59
|
+
"executionId",
|
|
60
|
+
"writtenAt",
|
|
61
|
+
"sessionPublicId",
|
|
62
|
+
"sessionId",
|
|
63
|
+
"turnStatus",
|
|
64
|
+
"durationMs",
|
|
65
|
+
"message",
|
|
66
|
+
"signalAborted",
|
|
67
|
+
"requestId",
|
|
68
|
+
"abortReason",
|
|
69
|
+
]);
|
|
70
|
+
|
|
71
|
+
const ABORT_REASONS = new Set<HostedExecutionDiagAbortReason>([
|
|
72
|
+
"signal_abort",
|
|
73
|
+
"runner_cancelled",
|
|
74
|
+
"runner_error",
|
|
75
|
+
"execute_throw",
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
function boundedOptionalString(
|
|
79
|
+
value: unknown,
|
|
80
|
+
max: number
|
|
81
|
+
): string | undefined {
|
|
82
|
+
return typeof value === "string" && value.length > 0
|
|
83
|
+
? value.length > max
|
|
84
|
+
? `${value.slice(0, max)}…`
|
|
85
|
+
: value
|
|
86
|
+
: undefined;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function hostedExecutionDiagPath(stateRoot: string): string {
|
|
90
|
+
return join(stateRoot, HOSTED_EXECUTION_DIAG_FILENAME);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function abortReasonFromTurnStatus(
|
|
94
|
+
status: string,
|
|
95
|
+
message: string | undefined
|
|
96
|
+
): HostedExecutionDiagAbortReason | undefined {
|
|
97
|
+
if (status === "finished") {
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
if (status === "cancelled") {
|
|
101
|
+
return message !== undefined && /abort/i.test(message)
|
|
102
|
+
? "signal_abort"
|
|
103
|
+
: "runner_cancelled";
|
|
104
|
+
}
|
|
105
|
+
return "runner_error";
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function parseHostedExecutionDiag(
|
|
109
|
+
raw: string
|
|
110
|
+
): HostedExecutionDiag | undefined {
|
|
111
|
+
if (raw.length === 0 || raw.length > HOSTED_EXECUTION_DIAG_MAX_BYTES) {
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
let parsed: unknown;
|
|
115
|
+
try {
|
|
116
|
+
parsed = JSON.parse(raw);
|
|
117
|
+
} catch {
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
const body = parsed as Record<string, unknown>;
|
|
124
|
+
if (Object.keys(body).some((key) => !DIAG_KEYS.has(key))) {
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
if (
|
|
128
|
+
body.v !== HOSTED_EXECUTION_DIAG_VERSION ||
|
|
129
|
+
typeof body.executionId !== "string" ||
|
|
130
|
+
body.executionId.length === 0 ||
|
|
131
|
+
body.executionId.length > 64 ||
|
|
132
|
+
typeof body.writtenAt !== "string" ||
|
|
133
|
+
body.writtenAt.length === 0 ||
|
|
134
|
+
body.writtenAt.length > 64 ||
|
|
135
|
+
typeof body.sessionPublicId !== "string" ||
|
|
136
|
+
body.sessionPublicId.length === 0 ||
|
|
137
|
+
body.sessionPublicId.length > 128 ||
|
|
138
|
+
typeof body.sessionId !== "string" ||
|
|
139
|
+
body.sessionId.length > 128 ||
|
|
140
|
+
typeof body.turnStatus !== "string" ||
|
|
141
|
+
body.turnStatus.length === 0 ||
|
|
142
|
+
body.turnStatus.length > 64 ||
|
|
143
|
+
typeof body.durationMs !== "number" ||
|
|
144
|
+
!Number.isFinite(body.durationMs) ||
|
|
145
|
+
body.durationMs < 0
|
|
146
|
+
) {
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
const message = boundedOptionalString(body.message, 1024);
|
|
150
|
+
const requestId = boundedOptionalString(body.requestId, 128);
|
|
151
|
+
const abortReason =
|
|
152
|
+
body.abortReason === undefined
|
|
153
|
+
? undefined
|
|
154
|
+
: typeof body.abortReason === "string" &&
|
|
155
|
+
ABORT_REASONS.has(body.abortReason as HostedExecutionDiagAbortReason)
|
|
156
|
+
? (body.abortReason as HostedExecutionDiagAbortReason)
|
|
157
|
+
: undefined;
|
|
158
|
+
if (body.abortReason !== undefined && abortReason === undefined) {
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
if (
|
|
162
|
+
body.signalAborted !== undefined &&
|
|
163
|
+
typeof body.signalAborted !== "boolean"
|
|
164
|
+
) {
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
v: HOSTED_EXECUTION_DIAG_VERSION,
|
|
169
|
+
executionId: body.executionId,
|
|
170
|
+
writtenAt: body.writtenAt,
|
|
171
|
+
sessionPublicId: body.sessionPublicId,
|
|
172
|
+
sessionId: body.sessionId,
|
|
173
|
+
turnStatus: body.turnStatus,
|
|
174
|
+
durationMs: body.durationMs,
|
|
175
|
+
...(message === undefined ? {} : { message }),
|
|
176
|
+
...(body.signalAborted === true ? { signalAborted: true } : {}),
|
|
177
|
+
...(requestId === undefined ? {} : { requestId }),
|
|
178
|
+
...(abortReason === undefined ? {} : { abortReason }),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Overwrite the per-execution diag file. Soft-fail: a write error must not
|
|
184
|
+
* change the hosted-execution HTTP reply.
|
|
185
|
+
*/
|
|
186
|
+
export async function writeHostedExecutionDiag(
|
|
187
|
+
stateRoot: string,
|
|
188
|
+
diag: HostedExecutionDiag
|
|
189
|
+
): Promise<void> {
|
|
190
|
+
const path = hostedExecutionDiagPath(stateRoot);
|
|
191
|
+
const tmp = `${path}.${randomBytes(8).toString("hex")}.tmp`;
|
|
192
|
+
try {
|
|
193
|
+
await mkdir(stateRoot, { recursive: true });
|
|
194
|
+
const serialized = JSON.stringify(diag);
|
|
195
|
+
const body =
|
|
196
|
+
serialized.length > HOSTED_EXECUTION_DIAG_MAX_BYTES
|
|
197
|
+
? JSON.stringify({
|
|
198
|
+
...diag,
|
|
199
|
+
message: boundedOptionalString(diag.message, 256),
|
|
200
|
+
})
|
|
201
|
+
: serialized;
|
|
202
|
+
await writeFile(tmp, body, "utf8");
|
|
203
|
+
await rename(tmp, path);
|
|
204
|
+
} catch {
|
|
205
|
+
await unlink(tmp).catch(() => undefined);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
@@ -25,6 +25,8 @@ import type { ServerRoute } from "./router.js";
|
|
|
25
25
|
import {
|
|
26
26
|
ApprovalInterruptedError,
|
|
27
27
|
ApprovalNotFoundError,
|
|
28
|
+
AuthInterruptedError,
|
|
29
|
+
AuthNotFoundError,
|
|
28
30
|
HTTP_CHANNEL_ID,
|
|
29
31
|
SessionAuthError,
|
|
30
32
|
type SessionEngine,
|
|
@@ -454,6 +456,101 @@ export function buildHttpChannelRoutes(args: {
|
|
|
454
456
|
},
|
|
455
457
|
};
|
|
456
458
|
|
|
459
|
+
const listAuths: ServerRoute = {
|
|
460
|
+
method: "GET",
|
|
461
|
+
pattern: `${base}/v1/session/:sessionId/auths`,
|
|
462
|
+
auth,
|
|
463
|
+
handler: async (_request, ctx) => {
|
|
464
|
+
const sessionId = ctx.params.sessionId ?? "";
|
|
465
|
+
const record = await engine.getOrRestoreSession(sessionId);
|
|
466
|
+
if (record === undefined) {
|
|
467
|
+
return errorResponse(
|
|
468
|
+
404,
|
|
469
|
+
"session_not_found",
|
|
470
|
+
`Unknown session ${sessionId}.`
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
const ownership = requireSessionOwner(record, ctx.auth, {
|
|
474
|
+
relax: relaxSessionReadAuth,
|
|
475
|
+
beyondLoopback: relaxSessionReadAuthBeyondLoopback,
|
|
476
|
+
requestIp: ctx.requestIp,
|
|
477
|
+
});
|
|
478
|
+
if (ownership !== undefined) {
|
|
479
|
+
return ownership;
|
|
480
|
+
}
|
|
481
|
+
const pending = await engine.listPendingAuths(sessionId);
|
|
482
|
+
return jsonResponse({ ok: true, sessionId, pending });
|
|
483
|
+
},
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
const resolveAuth: ServerRoute = {
|
|
487
|
+
method: "POST",
|
|
488
|
+
pattern: `${base}/v1/session/:sessionId/auths/:callId`,
|
|
489
|
+
auth,
|
|
490
|
+
handler: async (request, ctx) => {
|
|
491
|
+
const sessionId = ctx.params.sessionId ?? "";
|
|
492
|
+
const callId = ctx.params.callId ?? "";
|
|
493
|
+
const record = await engine.getOrRestoreSession(sessionId);
|
|
494
|
+
if (record === undefined) {
|
|
495
|
+
return errorResponse(
|
|
496
|
+
404,
|
|
497
|
+
"session_not_found",
|
|
498
|
+
`Unknown session ${sessionId}.`
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
const bridgeSlackAuth =
|
|
502
|
+
relaxSessionApprovalAuth === true &&
|
|
503
|
+
record.auth?.authenticator === "slack" &&
|
|
504
|
+
(relaxSessionReadAuthBeyondLoopback === true ||
|
|
505
|
+
(typeof ctx.requestIp === "string" &&
|
|
506
|
+
isLoopbackAddress(ctx.requestIp)));
|
|
507
|
+
const ownership = requireSessionOwner(record, ctx.auth, {
|
|
508
|
+
relax: bridgeSlackAuth,
|
|
509
|
+
beyondLoopback: relaxSessionReadAuthBeyondLoopback,
|
|
510
|
+
requestIp: ctx.requestIp,
|
|
511
|
+
});
|
|
512
|
+
if (ownership !== undefined) {
|
|
513
|
+
return ownership;
|
|
514
|
+
}
|
|
515
|
+
const body = await readJsonObject(request);
|
|
516
|
+
const decision = body?.decision;
|
|
517
|
+
if (decision !== "approve" && decision !== "deny") {
|
|
518
|
+
return errorResponse(
|
|
519
|
+
400,
|
|
520
|
+
"invalid_request",
|
|
521
|
+
'Expected a JSON body with `decision`: "approve" | "deny".'
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
try {
|
|
525
|
+
const pending = await engine.resolveAuth(
|
|
526
|
+
sessionId,
|
|
527
|
+
callId,
|
|
528
|
+
decision,
|
|
529
|
+
ctx.auth,
|
|
530
|
+
bridgeSlackAuth ? { asSessionOwner: true } : undefined
|
|
531
|
+
);
|
|
532
|
+
return jsonResponse({
|
|
533
|
+
ok: true,
|
|
534
|
+
sessionId,
|
|
535
|
+
callId,
|
|
536
|
+
decision,
|
|
537
|
+
connection: pending.connection,
|
|
538
|
+
});
|
|
539
|
+
} catch (error) {
|
|
540
|
+
if (error instanceof AuthNotFoundError) {
|
|
541
|
+
return errorResponse(404, "auth_not_found", error.message);
|
|
542
|
+
}
|
|
543
|
+
if (error instanceof AuthInterruptedError) {
|
|
544
|
+
return errorResponse(409, "auth_interrupted", error.message);
|
|
545
|
+
}
|
|
546
|
+
if (error instanceof SessionAuthError) {
|
|
547
|
+
return errorResponse(403, "forbidden", error.message);
|
|
548
|
+
}
|
|
549
|
+
throw error;
|
|
550
|
+
}
|
|
551
|
+
},
|
|
552
|
+
};
|
|
553
|
+
|
|
457
554
|
return [
|
|
458
555
|
createSession,
|
|
459
556
|
followUp,
|
|
@@ -463,6 +560,8 @@ export function buildHttpChannelRoutes(args: {
|
|
|
463
560
|
cost,
|
|
464
561
|
listApprovals,
|
|
465
562
|
resolveApproval,
|
|
563
|
+
listAuths,
|
|
564
|
+
resolveAuth,
|
|
466
565
|
];
|
|
467
566
|
}
|
|
468
567
|
|
|
@@ -74,6 +74,7 @@ type TemplateEntry = {
|
|
|
74
74
|
|
|
75
75
|
const TEMPLATES: Readonly<Record<TemplateName, TemplateEntry>> = {
|
|
76
76
|
"agentic-owners": { dir: "agentic-owners" },
|
|
77
|
+
"agents-md": { dir: "agents-md" },
|
|
77
78
|
"code-wiki": { dir: "code-wiki" },
|
|
78
79
|
demo: { dir: "demo" },
|
|
79
80
|
"pr-autofixer": { dir: "pr-autofixer" },
|
|
@@ -711,6 +712,7 @@ Scaffold a project, then open the playground:
|
|
|
711
712
|
npx ${PACKAGE_NAME} init ./my-agent # installs deps + links agent-sdk onto PATH
|
|
712
713
|
npx ${PACKAGE_NAME} init ./my-demo --template demo # record a PR walkthrough
|
|
713
714
|
npx ${PACKAGE_NAME} init ./code-wiki --template code-wiki # keep wiki pages current after merge
|
|
715
|
+
npx ${PACKAGE_NAME} init ./agents-md --template agents-md # keep AGENTS.md current from last week's PRs and Slack
|
|
714
716
|
npx ${PACKAGE_NAME} init ./my-reviewer --template security-reviewer # review PRs for security bugs
|
|
715
717
|
npx ${PACKAGE_NAME} init ./security-help --template security-help # answer security questions in Slack
|
|
716
718
|
npx ${PACKAGE_NAME} init ./my-triage --template triage-linear # comment on Linear issues
|