@cursor/july 0.1.100 → 0.1.101
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 +3 -2
- package/dist/channels/deployments/deployments-channel.d.ts +41 -5
- package/dist/channels/deployments/deployments-channel.d.ts.map +1 -1
- package/dist/channels/deployments/deployments-channel.js +88 -8
- package/dist/channels/deployments/types.d.ts +4 -3
- package/dist/channels/deployments/types.d.ts.map +1 -1
- package/dist/channels/origin/defaults.d.ts +43 -0
- package/dist/channels/origin/defaults.d.ts.map +1 -0
- package/dist/channels/origin/defaults.js +111 -0
- package/dist/channels/origin/index.d.ts +2 -0
- package/dist/channels/origin/index.d.ts.map +1 -1
- package/dist/channels/origin/index.js +2 -0
- package/dist/channels/origin/origin-channel.d.ts +2 -1
- package/dist/channels/origin/origin-channel.d.ts.map +1 -1
- package/dist/channels/origin/origin-channel.js +24 -3
- package/dist/channels/origin/state.d.ts +32 -0
- package/dist/channels/origin/state.d.ts.map +1 -0
- package/dist/channels/origin/state.js +23 -0
- package/dist/channels/origin/types.d.ts +23 -1
- package/dist/channels/origin/types.d.ts.map +1 -1
- package/dist/channels/slack/api.d.ts.map +1 -1
- package/dist/channels/slack/api.js +2 -1
- package/dist/channels/slack/code-fences.d.ts +17 -0
- package/dist/channels/slack/code-fences.d.ts.map +1 -0
- package/dist/channels/slack/code-fences.js +168 -0
- package/dist/channels/slack/index.d.ts +1 -0
- package/dist/channels/slack/index.d.ts.map +1 -1
- package/dist/channels/slack/index.js +1 -0
- package/dist/channels/slack/live-delivery.d.ts.map +1 -1
- package/dist/channels/slack/live-delivery.js +11 -4
- package/dist/connections.d.ts +18 -2
- package/dist/connections.d.ts.map +1 -1
- package/dist/connections.js +18 -2
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +4 -4
- package/dist/docs/assets/{app.DZ1e0Ycq.js → app.BASO9Kih.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.DbFXINq2.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.l8omwc6D.js → VPLocalSearchBox.DwkqxoE0.js} +1 -1
- package/dist/docs/assets/chunks/{theme.JVcD6gel.js → theme.CCil_13e.js} +2 -2
- package/dist/docs/assets/{reference_connections.md.BiGoBAk2.js → reference_connections.md.Di6jJAXF.js} +12 -2
- package/dist/docs/assets/{reference_connections.md.BiGoBAk2.lean.js → reference_connections.md.Di6jJAXF.lean.js} +1 -1
- package/dist/docs/assets/{reference_http-api.md.BEJx9XVj.js → reference_http-api.md.oySXBO8o.js} +1 -1
- package/dist/docs/building-with-agents.html +4 -4
- package/dist/docs/concepts.html +4 -4
- package/dist/docs/deployment.html +4 -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 +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 +4 -4
- package/dist/docs/llms-full.txt +32 -1
- 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 +4 -4
- package/dist/docs/reference/connections.html +16 -6
- package/dist/docs/reference/connections.md +31 -0
- package/dist/docs/reference/hooks.html +4 -4
- package/dist/docs/reference/http-api.html +6 -6
- package/dist/docs/reference/http-api.md +1 -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 +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 +4 -4
- package/dist/docs/skills/ab.html +4 -4
- package/dist/docs/skills/create-agent.html +4 -4
- package/dist/docs/skills/debug.html +4 -4
- package/dist/docs/skills/evals.html +4 -4
- package/dist/docs/skills/framework-map.html +4 -4
- package/dist/docs/skills/github.html +4 -4
- package/dist/docs/skills/hillclimb.html +4 -4
- package/dist/docs/skills/index.html +4 -4
- package/dist/docs/skills/mcp-auth.html +4 -4
- package/dist/docs/skills/otel.html +4 -4
- package/dist/docs/skills/setup-slack.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 +4 -4
- package/dist/docs/templates/code-wiki.html +4 -4
- 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 +4 -4
- package/dist/docs/templates/triage.html +4 -4
- package/dist/docs/troubleshooting.html +4 -4
- package/dist/extensions/cursor-cloud-agents/extension.d.ts +55 -0
- package/dist/extensions/cursor-cloud-agents/extension.d.ts.map +1 -0
- package/dist/extensions/cursor-cloud-agents/extension.js +64 -0
- package/dist/extensions/cursor-cloud-agents/lib/cloud.d.ts +30 -0
- package/dist/extensions/cursor-cloud-agents/lib/cloud.d.ts.map +1 -0
- package/dist/extensions/cursor-cloud-agents/lib/cloud.js +45 -0
- package/dist/extensions/cursor-cloud-agents/lib/fleet.d.ts +25 -0
- package/dist/extensions/cursor-cloud-agents/lib/fleet.d.ts.map +1 -0
- package/dist/extensions/cursor-cloud-agents/lib/fleet.js +85 -0
- package/dist/extensions/cursor-cloud-agents/lib/repos.d.ts +15 -0
- package/dist/extensions/cursor-cloud-agents/lib/repos.d.ts.map +1 -0
- package/dist/extensions/cursor-cloud-agents/lib/repos.js +55 -0
- package/dist/extensions/cursor-cloud-agents/skills/handoff.md +27 -0
- package/dist/extensions/cursor-cloud-agents/tools/follow_up.d.ts +4 -0
- package/dist/extensions/cursor-cloud-agents/tools/follow_up.d.ts.map +1 -0
- package/dist/extensions/cursor-cloud-agents/tools/follow_up.js +37 -0
- package/dist/extensions/cursor-cloud-agents/tools/launch.d.ts +4 -0
- package/dist/extensions/cursor-cloud-agents/tools/launch.d.ts.map +1 -0
- package/dist/extensions/cursor-cloud-agents/tools/launch.js +83 -0
- package/dist/extensions/cursor-cloud-agents/tools/list.d.ts +4 -0
- package/dist/extensions/cursor-cloud-agents/tools/list.d.ts.map +1 -0
- package/dist/extensions/cursor-cloud-agents/tools/list.js +36 -0
- package/dist/extensions/cursor-cloud-agents/tools/status.d.ts +4 -0
- package/dist/extensions/cursor-cloud-agents/tools/status.d.ts.map +1 -0
- package/dist/extensions/cursor-cloud-agents/tools/status.js +41 -0
- package/dist/extensions.d.ts +49 -0
- package/dist/extensions.d.ts.map +1 -0
- package/dist/extensions.js +75 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/internal/advertise-tools.d.ts +4 -1
- package/dist/internal/advertise-tools.d.ts.map +1 -1
- package/dist/internal/advertise-tools.js +20 -1
- package/dist/internal/as-of.d.ts +18 -0
- package/dist/internal/as-of.d.ts.map +1 -1
- package/dist/internal/as-of.js +131 -0
- package/dist/internal/cli-deploy.d.ts.map +1 -1
- package/dist/internal/cli-deploy.js +64 -19
- package/dist/internal/continuation-identity.d.ts.map +1 -1
- package/dist/internal/continuation-identity.js +3 -1
- package/dist/internal/cursor/hosted-deployment-secrets.d.ts +16 -0
- package/dist/internal/cursor/hosted-deployment-secrets.d.ts.map +1 -0
- package/dist/internal/cursor/hosted-deployment-secrets.js +61 -0
- package/dist/internal/cursor-deployment-relay.d.ts +15 -1
- package/dist/internal/cursor-deployment-relay.d.ts.map +1 -1
- package/dist/internal/cursor-deployment-relay.js +46 -7
- package/dist/internal/deploy-client.d.ts +23 -0
- package/dist/internal/deploy-client.d.ts.map +1 -1
- package/dist/internal/deploy-client.js +54 -12
- package/dist/internal/discovery.d.ts.map +1 -1
- package/dist/internal/discovery.js +415 -120
- package/dist/internal/distribution.d.ts.map +1 -1
- package/dist/internal/distribution.js +5 -0
- package/dist/internal/framework-file-storage.d.ts +3 -3
- package/dist/internal/framework-file-storage.d.ts.map +1 -1
- package/dist/internal/framework-file-storage.js +34 -5
- package/dist/internal/hosted-delivery.d.ts +3 -1
- package/dist/internal/hosted-delivery.d.ts.map +1 -1
- package/dist/internal/hosted-delivery.js +19 -32
- package/dist/internal/hosted-execution-diag.d.ts +5 -0
- package/dist/internal/hosted-execution-diag.d.ts.map +1 -1
- package/dist/internal/hosted-execution-diag.js +5 -0
- package/dist/internal/hosted-execution-flush.d.ts +46 -0
- package/dist/internal/hosted-execution-flush.d.ts.map +1 -0
- package/dist/internal/hosted-execution-flush.js +81 -0
- package/dist/internal/mcp-endpoint.js +1 -0
- package/dist/internal/mcp-host.d.ts +2 -1
- package/dist/internal/mcp-host.d.ts.map +1 -1
- package/dist/internal/mcp-host.js +35 -15
- package/dist/internal/review-comments.d.ts +1 -1
- package/dist/internal/review-comments.d.ts.map +1 -1
- package/dist/internal/review-comments.js +4 -4
- package/dist/internal/server.d.ts +3 -0
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +79 -104
- package/dist/internal/session-engine.d.ts +9 -7
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +45 -34
- package/dist/internal/session-run-log.d.ts +2 -1
- package/dist/internal/session-run-log.d.ts.map +1 -1
- package/dist/internal/session-run-log.js +2 -1
- package/dist/internal/start-log-store.d.ts +60 -0
- package/dist/internal/start-log-store.d.ts.map +1 -0
- package/dist/internal/start-log-store.js +151 -0
- package/dist/internal/tool-policy.d.ts +8 -0
- package/dist/internal/tool-policy.d.ts.map +1 -1
- package/dist/internal/tool-policy.js +19 -7
- package/dist/playground/assets/{index-DVs98vPL.js → index-BnNN1-EK.js} +45 -45
- package/dist/playground/assets/index-DBQCwFp0.css +1 -0
- package/dist/playground/index.html +2 -2
- package/dist/storage-protocol.d.ts +24 -0
- package/dist/storage-protocol.d.ts.map +1 -1
- package/dist/storage-protocol.js +33 -0
- package/dist/storage.d.ts +1 -1
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +1 -1
- package/dist/types.d.ts +118 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +29 -1
- package/docs/reference/connections.md +31 -0
- package/docs/reference/http-api.md +1 -1
- package/package.json +18 -4
- package/src/bin/agent-serve.ts +11 -2
- package/src/channels/deployments/deployments-channel.ts +117 -8
- package/src/channels/deployments/types.ts +4 -3
- package/src/channels/origin/defaults.ts +143 -0
- package/src/channels/origin/index.ts +2 -0
- package/src/channels/origin/origin-channel.ts +37 -1
- package/src/channels/origin/state.ts +54 -0
- package/src/channels/origin/types.ts +24 -0
- package/src/channels/slack/api.ts +2 -1
- package/src/channels/slack/code-fences.ts +183 -0
- package/src/channels/slack/index.ts +1 -0
- package/src/channels/slack/live-delivery.ts +16 -13
- package/src/connections.ts +18 -1
- package/src/extensions/cursor-cloud-agents/extension.ts +118 -0
- package/src/extensions/cursor-cloud-agents/lib/cloud.ts +79 -0
- package/src/extensions/cursor-cloud-agents/lib/fleet.ts +109 -0
- package/src/extensions/cursor-cloud-agents/lib/repos.ts +81 -0
- package/src/extensions/cursor-cloud-agents/skills/handoff.md +27 -0
- package/src/extensions/cursor-cloud-agents/tools/follow_up.ts +29 -0
- package/src/extensions/cursor-cloud-agents/tools/launch.ts +84 -0
- package/src/extensions/cursor-cloud-agents/tools/list.ts +31 -0
- package/src/extensions/cursor-cloud-agents/tools/status.ts +39 -0
- package/src/extensions.ts +98 -0
- package/src/index.ts +2 -0
- package/src/internal/advertise-tools.ts +37 -12
- package/src/internal/as-of.ts +163 -0
- package/src/internal/cli-deploy.ts +86 -23
- package/src/internal/continuation-identity.ts +3 -1
- package/src/internal/cursor/hosted-deployment-secrets.ts +72 -0
- package/src/internal/cursor-deployment-relay.ts +65 -8
- package/src/internal/deploy-client.ts +73 -18
- package/src/internal/discovery.ts +470 -113
- package/src/internal/distribution.ts +5 -0
- package/src/internal/framework-file-storage.ts +39 -5
- package/src/internal/hosted-delivery.ts +38 -63
- package/src/internal/hosted-execution-diag.ts +6 -0
- package/src/internal/hosted-execution-flush.ts +142 -0
- package/src/internal/mcp-endpoint.ts +1 -0
- package/src/internal/mcp-host.ts +40 -14
- package/src/internal/review-comments.ts +5 -2
- package/src/internal/server.ts +81 -100
- package/src/internal/session-engine.ts +56 -28
- package/src/internal/session-run-log.ts +2 -1
- package/src/internal/start-log-store.ts +177 -0
- package/src/internal/tool-policy.ts +39 -9
- package/src/storage-protocol.ts +37 -0
- package/src/storage.ts +1 -0
- package/src/types.ts +172 -14
- package/dist/docs/assets/chunks/@localSearchIndexroot.j70vvPL4.js +0 -1
- package/dist/playground/assets/index-BszoQDc6.css +0 -1
- /package/dist/docs/assets/{reference_http-api.md.BEJx9XVj.lean.js → reference_http-api.md.oySXBO8o.lean.js} +0 -0
package/dist/types.js
CHANGED
|
@@ -92,6 +92,32 @@ export function normalizeConnectionEffects(effects) {
|
|
|
92
92
|
}
|
|
93
93
|
return unique;
|
|
94
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* Trim, drop blanks, and unique-preserve authored
|
|
97
|
+
* {@link ConnectionConfig.tools} name lists. Invalid shapes are left
|
|
98
|
+
* for discovery.
|
|
99
|
+
*/
|
|
100
|
+
export function normalizeConnectionTools(tools) {
|
|
101
|
+
const unique = [];
|
|
102
|
+
const seen = new Set();
|
|
103
|
+
for (const raw of tools) {
|
|
104
|
+
const name = raw.trim();
|
|
105
|
+
if (name === "" || seen.has(name)) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
seen.add(name);
|
|
109
|
+
unique.push(name);
|
|
110
|
+
}
|
|
111
|
+
return unique;
|
|
112
|
+
}
|
|
113
|
+
/** True when `toolName` passes the connection's `tools` / `disallowedTools` filter. */
|
|
114
|
+
export function connectionAllowsTool(filter, toolName) {
|
|
115
|
+
if (filter.tools !== undefined && !filter.tools.includes(toolName)) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
return (filter.disallowedTools === undefined ||
|
|
119
|
+
!filter.disallowedTools.includes(toolName));
|
|
120
|
+
}
|
|
95
121
|
/** True for the peer-agent transport variant (`{ agent: "<slug>" }`). */
|
|
96
122
|
export function isPeerConnectionTransport(transport) {
|
|
97
123
|
return "agent" in transport;
|
|
@@ -146,7 +172,9 @@ export function isSymbolicConnectionTransport(transport) {
|
|
|
146
172
|
}
|
|
147
173
|
/** True when the connection authors tools instead of a spawn/URL/peer. */
|
|
148
174
|
export function isInlineStdioConnection(connection) {
|
|
149
|
-
return "tools" in connection &&
|
|
175
|
+
return ("tools" in connection &&
|
|
176
|
+
connection.tools !== undefined &&
|
|
177
|
+
!Array.isArray(connection.tools));
|
|
150
178
|
}
|
|
151
179
|
// ============================================================================
|
|
152
180
|
// Direct tool calls (deterministic invocation, no model turn)
|
|
@@ -124,6 +124,37 @@ be called through the direct tool API.
|
|
|
124
124
|
In a dry-run session, MCP tools marked read-only run normally. Tools marked
|
|
125
125
|
as writes are stubbed. Tools without effect annotations are unavailable.
|
|
126
126
|
|
|
127
|
+
## Restrict which tools a connection serves
|
|
128
|
+
|
|
129
|
+
Use `tools` the same way you allowlist harness tools on the agent. When
|
|
130
|
+
set, the connection serves only those names. Use `disallowedTools` to
|
|
131
|
+
drop names instead. The two combine as deny-wins, same as the Cursor
|
|
132
|
+
SDK. The model and `host.mcp` only see what remains. On a model-visible
|
|
133
|
+
connection, set `advertiseTools: true` so the raw server is not attached.
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
export default defineConnection({
|
|
137
|
+
url: "https://mcp.example.com/inventory",
|
|
138
|
+
advertiseTools: true,
|
|
139
|
+
tools: ["search_skus", "get_stock"],
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
export default defineConnection({
|
|
143
|
+
url: "https://mcp.example.com/inventory",
|
|
144
|
+
advertiseTools: true,
|
|
145
|
+
disallowedTools: ["place_order", "delete_sku"],
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Names are the server's `tools/list` names. Unknown names are omitted. A
|
|
150
|
+
filter that matches nothing on the server fails the turn. Combine with
|
|
151
|
+
`effects: "read"` to keep only the listed tools the server classifies as
|
|
152
|
+
reads.
|
|
153
|
+
|
|
154
|
+
A list of names is an allowlist. An object of handlers authors TypeScript
|
|
155
|
+
tools. On `host-connections/`, a name list restricts `host.mcp` without
|
|
156
|
+
advertising.
|
|
157
|
+
|
|
127
158
|
## Local stdio MCP server
|
|
128
159
|
|
|
129
160
|
Run a local MCP server as a child process with `command`.
|
|
@@ -53,7 +53,7 @@ and `playgroundUrl` deep-links the session in the playground.
|
|
|
53
53
|
| `message` | Required user message |
|
|
54
54
|
| `title` | Session title |
|
|
55
55
|
| `dryRun` | Run read tools and stub write tools |
|
|
56
|
-
| `asOf` | ISO-8601 instant with a timezone, frozen at create; the prompt states it, `ctx.now()` returns it, and
|
|
56
|
+
| `asOf` | ISO-8601 instant with a timezone, frozen at create; the prompt states it, `ctx.now()` returns it, and tool calls with relative, later-than-`asOf`, or omitted schema-declared time bounds are refused. `400` when unusable |
|
|
57
57
|
| `workspaceFiles` | UTF-8 files written into the session workspace |
|
|
58
58
|
| `cloud` | Per-session cloud options merged over the agent defaults |
|
|
59
59
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cursor/july",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.101",
|
|
4
4
|
"description": "(early alpha) Filesystem-first framework for defining Cursor agents as markdown and TypeScript and serving them over channels with the Cursor SDK.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
6
|
"repository": {
|
|
@@ -95,6 +95,20 @@
|
|
|
95
95
|
"import": "./dist/skills.js",
|
|
96
96
|
"default": "./dist/skills.js"
|
|
97
97
|
},
|
|
98
|
+
"./extensions": {
|
|
99
|
+
"anysphere-source": "./src/extensions.ts",
|
|
100
|
+
"bun": "./dist/extensions.js",
|
|
101
|
+
"types": "./dist/extensions.d.ts",
|
|
102
|
+
"import": "./dist/extensions.js",
|
|
103
|
+
"default": "./dist/extensions.js"
|
|
104
|
+
},
|
|
105
|
+
"./extensions/cursor-cloud-agents": {
|
|
106
|
+
"anysphere-source": "./src/extensions/cursor-cloud-agents/extension.ts",
|
|
107
|
+
"bun": "./dist/extensions/cursor-cloud-agents/extension.js",
|
|
108
|
+
"types": "./dist/extensions/cursor-cloud-agents/extension.d.ts",
|
|
109
|
+
"import": "./dist/extensions/cursor-cloud-agents/extension.js",
|
|
110
|
+
"default": "./dist/extensions/cursor-cloud-agents/extension.js"
|
|
111
|
+
},
|
|
98
112
|
"./schedules": {
|
|
99
113
|
"anysphere-source": "./src/schedules.ts",
|
|
100
114
|
"types": "./dist/schedules.d.ts",
|
|
@@ -215,7 +229,7 @@
|
|
|
215
229
|
}
|
|
216
230
|
},
|
|
217
231
|
"scripts": {
|
|
218
|
-
"build": "tsgo -p tsconfig.build.json && node scripts/bundle-publish-shims.mjs && pnpm run build:playground && pnpm run docs:build",
|
|
232
|
+
"build": "tsgo -p tsconfig.build.json && node scripts/bundle-publish-shims.mjs && node scripts/copy-extension-assets.mjs && pnpm run build:playground && pnpm run docs:build",
|
|
219
233
|
"build:dist": "node scripts/build-hosted-dist.mjs",
|
|
220
234
|
"postinstall": "node -e \"try{const p='./scripts/install-cursor-skills.mjs';if(require('fs').existsSync(p))require('child_process').execFileSync(process.execPath,[p],{stdio:'inherit'})}catch{}\"",
|
|
221
235
|
"build:playground": "vite build --config playground/vite.config.ts",
|
|
@@ -240,7 +254,7 @@
|
|
|
240
254
|
},
|
|
241
255
|
"dependencies": {
|
|
242
256
|
"@bazel/runfiles": "6.5.0",
|
|
243
|
-
"@cursor/sdk": "1.0.
|
|
257
|
+
"@cursor/sdk": "1.0.31",
|
|
244
258
|
"@modelcontextprotocol/sdk": "1.25.1",
|
|
245
259
|
"@octokit/auth-app": "^7.2.0",
|
|
246
260
|
"@octokit/rest": "^21.1.1",
|
|
@@ -303,4 +317,4 @@
|
|
|
303
317
|
"yaml": "^2.4.2",
|
|
304
318
|
"zustand": "^5.0.9"
|
|
305
319
|
}
|
|
306
|
-
}
|
|
320
|
+
}
|
package/src/bin/agent-serve.ts
CHANGED
|
@@ -426,8 +426,8 @@ Options:
|
|
|
426
426
|
--all deploy: every child agent under a multi-agent --dir
|
|
427
427
|
(skips the interactive picker; mutually exclusive with
|
|
428
428
|
--slug).
|
|
429
|
-
--cursor-events-repo deploy: repo (owner/name)
|
|
430
|
-
|
|
429
|
+
--cursor-events-repo deploy: repo (owner/name) the deployed agent
|
|
430
|
+
watches (repeatable).
|
|
431
431
|
--allow-domain deploy: egress domain the engine pod may reach
|
|
432
432
|
(repeatable; unioned with hosting.egressDomains from the
|
|
433
433
|
agent manifest). Repo-backed deploys only.
|
|
@@ -1160,6 +1160,15 @@ async function main(argv: string[]): Promise<number> {
|
|
|
1160
1160
|
info.skills.map((s) => `${s.name} — ${s.description}`)
|
|
1161
1161
|
)
|
|
1162
1162
|
);
|
|
1163
|
+
lines.push(
|
|
1164
|
+
section(
|
|
1165
|
+
"extensions",
|
|
1166
|
+
info.extensions.map(
|
|
1167
|
+
(e) =>
|
|
1168
|
+
`${e.name} — ${[...e.tools, ...e.skills].join(", ") || "(nothing contributed)"}`
|
|
1169
|
+
)
|
|
1170
|
+
)
|
|
1171
|
+
);
|
|
1163
1172
|
lines.push(
|
|
1164
1173
|
section(
|
|
1165
1174
|
"MCP connections",
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
|
|
3
3
|
import { defineChannel, localDevStrict, POST } from "../../channels.js";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
deploymentEventMatches,
|
|
6
|
+
resolveDeploymentSubscriptions,
|
|
7
|
+
} from "../../internal/cursor-deployment-relay.js";
|
|
8
|
+
import { attachHostedAdmissionAdapter } from "../../internal/hosted-admission-adapter.js";
|
|
5
9
|
import { bindLocalIngressSend } from "../../internal/local-ingress.js";
|
|
6
10
|
import type { ChannelDefinition, ChannelHandlerArgs } from "../../types.js";
|
|
7
11
|
import {
|
|
@@ -43,14 +47,28 @@ export function getDeploymentsChannelOptions(
|
|
|
43
47
|
/**
|
|
44
48
|
* Events a {@link deploymentsChannel} dispatches on, unioned from its
|
|
45
49
|
* subscriptions. Published as `meta.deploymentEventTypes` so the deploy
|
|
46
|
-
* manifest can extract `eventTypes`.
|
|
47
|
-
*
|
|
50
|
+
* manifest can extract `eventTypes`.
|
|
51
|
+
*
|
|
52
|
+
* A channel with no resolved subscriptions declares the full event set:
|
|
53
|
+
* deploy sources usually come from env vars read at module load, and the
|
|
54
|
+
* hosted freeze bakes hermetically (no customer env/secrets), so deriving
|
|
55
|
+
* `[]` there would leave the manifest without event types and no
|
|
56
|
+
* subscription edge could ever materialize (invariant 26). Capability is
|
|
57
|
+
* the channel's code shape — `onEvent` handles any deployment event —
|
|
58
|
+
* while sources/environments/event narrowing are runtime subscription
|
|
59
|
+
* config; the hosted-admission adapter re-applies them before authored
|
|
60
|
+
* code runs. Inertness is still guaranteed by scope: a deploy that names
|
|
61
|
+
* no deploy sources binds no edges.
|
|
48
62
|
*/
|
|
49
63
|
export function deriveDeploymentEventTypes(
|
|
50
64
|
options: Pick<DeploymentsChannelOptions, "deploySourceUris" | "events">
|
|
51
65
|
): string[] {
|
|
66
|
+
const subscriptions = resolveDeploymentSubscriptions(options);
|
|
67
|
+
if (subscriptions.length === 0) {
|
|
68
|
+
return [...DEPLOYMENT_EVENT_TYPES].sort();
|
|
69
|
+
}
|
|
52
70
|
const events = new Set<string>();
|
|
53
|
-
for (const subscription of
|
|
71
|
+
for (const subscription of subscriptions) {
|
|
54
72
|
for (const event of subscription.events) {
|
|
55
73
|
events.add(event);
|
|
56
74
|
}
|
|
@@ -59,9 +77,9 @@ export function deriveDeploymentEventTypes(
|
|
|
59
77
|
}
|
|
60
78
|
|
|
61
79
|
/**
|
|
62
|
-
* Core deployment-event dispatch, used by the channel's replay route
|
|
63
|
-
*
|
|
64
|
-
* subscriptions is the caller's decision.
|
|
80
|
+
* Core deployment-event dispatch, used by the channel's replay route, the
|
|
81
|
+
* serve host's relay fan-out, and the hosted-admission adapter. Whether the
|
|
82
|
+
* event matches this channel's subscriptions is the caller's decision.
|
|
65
83
|
*/
|
|
66
84
|
export async function dispatchDeploymentEvent(input: {
|
|
67
85
|
options: DeploymentsChannelOptions;
|
|
@@ -77,7 +95,61 @@ export async function dispatchDeploymentEvent(input: {
|
|
|
77
95
|
payload: input.event,
|
|
78
96
|
})
|
|
79
97
|
);
|
|
80
|
-
|
|
98
|
+
// "task": authored `onEvent` ran to completion without a captured send.
|
|
99
|
+
// Hosted admission only consults this when no send was captured.
|
|
100
|
+
return withDispatchOutcome(Response.json({ ok: true }), "task");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export type DeploymentsDispatchOutcome = "skip" | "task";
|
|
104
|
+
|
|
105
|
+
const dispatchOutcomeByResponse = new WeakMap<
|
|
106
|
+
Response,
|
|
107
|
+
DeploymentsDispatchOutcome
|
|
108
|
+
>();
|
|
109
|
+
|
|
110
|
+
function withDispatchOutcome(
|
|
111
|
+
response: Response,
|
|
112
|
+
outcome: DeploymentsDispatchOutcome
|
|
113
|
+
): Response {
|
|
114
|
+
dispatchOutcomeByResponse.set(response, outcome);
|
|
115
|
+
return response;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function getDeploymentsDispatchOutcome(
|
|
119
|
+
response: Response
|
|
120
|
+
): DeploymentsDispatchOutcome | undefined {
|
|
121
|
+
return dispatchOutcomeByResponse.get(response);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Whether this channel admits the event on a hosted pod. The hosted
|
|
126
|
+
* control plane fans out on `(deploy_source, eventType, team)` edges only —
|
|
127
|
+
* environments are not edge identity — so this is where the environment
|
|
128
|
+
* filter (and a stale-edge guard for sources/events) lives, mirroring what
|
|
129
|
+
* the v1 relay does before `onEvent`.
|
|
130
|
+
*
|
|
131
|
+
* A channel with NO resolved subscriptions admits everything the control
|
|
132
|
+
* plane delivered: deploy sources usually come from env vars read at
|
|
133
|
+
* module load, and the hosted freeze bakes hermetically (no customer env),
|
|
134
|
+
* so on a restored pod an env-configured channel resolves empty — the
|
|
135
|
+
* deploy-time edges (bound from the watched repositories) already carry
|
|
136
|
+
* the customer's scope, and refusing here would silently drop every
|
|
137
|
+
* delivery. Same posture as {@link deriveDeploymentEventTypes}: authored
|
|
138
|
+
* subscriptions narrow; an unconfigured channel defers to the deploy scope.
|
|
139
|
+
*/
|
|
140
|
+
export function deploymentEventMatchesChannel(
|
|
141
|
+
options: DeploymentsChannelOptions,
|
|
142
|
+
event: DeploymentEvent
|
|
143
|
+
): boolean {
|
|
144
|
+
const subscriptions = resolveDeploymentSubscriptions(options);
|
|
145
|
+
if (subscriptions.length === 0) {
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
return subscriptions.some(
|
|
149
|
+
(subscription) =>
|
|
150
|
+
subscription.deploySourceUri === event.deploySourceUri.toLowerCase() &&
|
|
151
|
+
deploymentEventMatches(subscription, event)
|
|
152
|
+
);
|
|
81
153
|
}
|
|
82
154
|
|
|
83
155
|
/**
|
|
@@ -102,6 +174,13 @@ export async function dispatchDeploymentEvent(input: {
|
|
|
102
174
|
* have. Nothing is pushed to this deployment and no shared secret exists on
|
|
103
175
|
* either side.
|
|
104
176
|
*
|
|
177
|
+
* Hosted v2 deployments do not poll: the control plane fans verified factory
|
|
178
|
+
* deployment events out on `deploy_source` subscription edges — derived from
|
|
179
|
+
* the deploy's watched `repositories`, since factory records deploy sources
|
|
180
|
+
* as Origin repo URLs — and delivers through hosted admission. The attached
|
|
181
|
+
* adapter re-applies this channel's subscription filter — environments are
|
|
182
|
+
* not edge identity — and skips before authored code runs.
|
|
183
|
+
*
|
|
105
184
|
* The route on this channel exists for local replay (`agent-sdk` and tests):
|
|
106
185
|
* POST an event body and it runs the same `onEvent`.
|
|
107
186
|
*/
|
|
@@ -136,5 +215,35 @@ export function deploymentsChannel(
|
|
|
136
215
|
],
|
|
137
216
|
});
|
|
138
217
|
deploymentsChannelOptionsByDefinition.set(definition, options);
|
|
218
|
+
// Hosted v2 delivery: the control plane admits a deployments mailbox item
|
|
219
|
+
// (deploy_source edge match) and posts the claim-checked factory envelope
|
|
220
|
+
// here. Environments are not edge identity, so non-matching environments
|
|
221
|
+
// (and stale-edge sources/events) are filtered to `skip` before authored
|
|
222
|
+
// code runs — the same pre-`onEvent` filter the v1 relay applies.
|
|
223
|
+
attachHostedAdmissionAdapter(definition, {
|
|
224
|
+
kind: DEPLOYMENTS_CHANNEL_KIND,
|
|
225
|
+
dispatch: async ({ payload, args }) => {
|
|
226
|
+
const parsed = deploymentEventSchema.safeParse(payload);
|
|
227
|
+
if (!parsed.success) {
|
|
228
|
+
return Response.json(
|
|
229
|
+
{ ok: false, error: "invalid_deployment_event" },
|
|
230
|
+
{ status: 400 }
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
const event = parsed.data as DeploymentEvent;
|
|
234
|
+
if (!deploymentEventMatchesChannel(options, event)) {
|
|
235
|
+
return withDispatchOutcome(
|
|
236
|
+
Response.json({
|
|
237
|
+
ok: true,
|
|
238
|
+
skipped: true,
|
|
239
|
+
reason: "subscription_mismatch",
|
|
240
|
+
}),
|
|
241
|
+
"skip"
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
return dispatchDeploymentEvent({ options, event, args });
|
|
245
|
+
},
|
|
246
|
+
getOutcome: (response) => getDeploymentsDispatchOutcome(response),
|
|
247
|
+
});
|
|
139
248
|
return definition;
|
|
140
249
|
}
|
|
@@ -54,9 +54,10 @@ export interface DeploymentsChannelOptions {
|
|
|
54
54
|
* and then receives nothing. A bare string takes the default environment and
|
|
55
55
|
* event filters.
|
|
56
56
|
*
|
|
57
|
-
* There is no team-wide subscription: every deploy source is named.
|
|
58
|
-
*
|
|
59
|
-
*
|
|
57
|
+
* There is no team-wide subscription: every deploy source is named. An
|
|
58
|
+
* empty list is how a consumer *deregisters*, so it cannot also mean
|
|
59
|
+
* "everything". A live array that starts empty and is filled later is
|
|
60
|
+
* re-read on each registration heartbeat.
|
|
60
61
|
*/
|
|
61
62
|
deploySourceUris: readonly (string | DeploymentSubscription)[];
|
|
62
63
|
/**
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default stream-event handlers for the Origin channel: reply delivery.
|
|
3
|
+
*
|
|
4
|
+
* The GitHub channel posts completed assistant messages back to the PR via
|
|
5
|
+
* `buildDefaultGitHubEvents`; this is the Origin analogue. Outbound
|
|
6
|
+
* credentials resolve exactly like `ctx.origin` in hooks (see
|
|
7
|
+
* ./binding.ts): channel app credentials / `ORIGIN_APP_*` env when
|
|
8
|
+
* configured, otherwise — for `cursorAccount` channels — the Cursor-account
|
|
9
|
+
* session exchange, which on hosted v2 acts as the deployment
|
|
10
|
+
* service-account principal. The `agent-serve` SA scope allowlist includes
|
|
11
|
+
* `repository:pull_requests:reviews:write`, the scope
|
|
12
|
+
* `CreatePullRequestComment` checks, so no extra grant is needed.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { ChannelEventHandlers } from "../../types.js";
|
|
16
|
+
import { buildOriginBinding, type OriginHandle } from "./binding.js";
|
|
17
|
+
import type { OriginChannelState } from "./state.js";
|
|
18
|
+
import type { OriginAppOptions } from "./types.js";
|
|
19
|
+
|
|
20
|
+
/** Hidden marker on outbound Origin comments so consumers can spot them. */
|
|
21
|
+
export const ORIGIN_OUTBOUND_MARKER = "<!-- agent-serve-origin -->";
|
|
22
|
+
|
|
23
|
+
export function formatOriginOutboundComment(body: string): string {
|
|
24
|
+
if (body.includes(ORIGIN_OUTBOUND_MARKER)) {
|
|
25
|
+
return body;
|
|
26
|
+
}
|
|
27
|
+
return `${body.trimEnd()}\n\n${ORIGIN_OUTBOUND_MARKER}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface BuildDefaultOriginEventsOptions {
|
|
31
|
+
/** Force the signed-in Cursor account / deployment SA credential path. */
|
|
32
|
+
cursorAccount?: boolean | { repos: readonly string[] };
|
|
33
|
+
/** Channel-authored outbound app credentials / API base. */
|
|
34
|
+
app?: OriginAppOptions;
|
|
35
|
+
/**
|
|
36
|
+
* When false, skip posting completed assistant messages (and failure
|
|
37
|
+
* notices) back to the Origin pull request. Defaults to true.
|
|
38
|
+
*/
|
|
39
|
+
deliverReplies?: boolean;
|
|
40
|
+
/** Injectable binding builder for tests. */
|
|
41
|
+
buildBinding?: typeof buildOriginBinding;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function bindingForState(
|
|
45
|
+
options: BuildDefaultOriginEventsOptions,
|
|
46
|
+
state: OriginChannelState
|
|
47
|
+
): OriginHandle {
|
|
48
|
+
const build = options.buildBinding ?? buildOriginBinding;
|
|
49
|
+
return build({
|
|
50
|
+
repository: {
|
|
51
|
+
owner: state.owner,
|
|
52
|
+
name: state.repo,
|
|
53
|
+
fullName: `${state.owner}/${state.repo}`,
|
|
54
|
+
},
|
|
55
|
+
...(state.installationId === null
|
|
56
|
+
? {}
|
|
57
|
+
: { installationId: state.installationId }),
|
|
58
|
+
cursorAccount:
|
|
59
|
+
options.cursorAccount !== undefined && options.cursorAccount !== false,
|
|
60
|
+
app: options.app,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function postPullRequestComment(
|
|
65
|
+
options: BuildDefaultOriginEventsOptions,
|
|
66
|
+
state: OriginChannelState,
|
|
67
|
+
body: string
|
|
68
|
+
): Promise<void> {
|
|
69
|
+
if (
|
|
70
|
+
state.owner === "" ||
|
|
71
|
+
state.repo === "" ||
|
|
72
|
+
state.pullRequestNumber === null
|
|
73
|
+
) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const origin = bindingForState(options, state);
|
|
77
|
+
await origin.request({
|
|
78
|
+
method: "POST",
|
|
79
|
+
path: `/v1/origin/repos/${encodeURIComponent(state.owner)}/${encodeURIComponent(
|
|
80
|
+
state.repo
|
|
81
|
+
)}/pulls/${state.pullRequestNumber}/comments`,
|
|
82
|
+
body: { body: formatOriginOutboundComment(body) },
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Built-in delivery: post assistant text on `message.completed`, and a
|
|
88
|
+
* short error comment on turn/session failure. Every post soft-fails so a
|
|
89
|
+
* missing outbound credential (or an Origin refusal) never crashes the
|
|
90
|
+
* session — the GitHub defaults' posture.
|
|
91
|
+
*/
|
|
92
|
+
export function buildDefaultOriginEvents(
|
|
93
|
+
options: BuildDefaultOriginEventsOptions = {}
|
|
94
|
+
): ChannelEventHandlers<OriginChannelState> {
|
|
95
|
+
const deliverReplies = options.deliverReplies !== false;
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
async "message.completed"(event, channel) {
|
|
99
|
+
if (!deliverReplies) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (event.data.finishReason === "tool_call" || event.data.text === "") {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
await postPullRequestComment(options, channel.state, event.data.text);
|
|
107
|
+
} catch {
|
|
108
|
+
// Soft-fail delivery so a missing credential does not crash the session.
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
async "turn.failed"(_event, channel) {
|
|
113
|
+
await postFailure(options, channel, deliverReplies);
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
async "session.failed"(_event, channel) {
|
|
117
|
+
await postFailure(options, channel, deliverReplies);
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function postFailure(
|
|
123
|
+
options: BuildDefaultOriginEventsOptions,
|
|
124
|
+
channel: { state: OriginChannelState },
|
|
125
|
+
deliverReplies: boolean
|
|
126
|
+
): Promise<void> {
|
|
127
|
+
if (!deliverReplies) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
await postPullRequestComment(
|
|
132
|
+
options,
|
|
133
|
+
channel.state,
|
|
134
|
+
[
|
|
135
|
+
"I hit an error while handling your request.",
|
|
136
|
+
"",
|
|
137
|
+
"Please try again, or reach out if it keeps failing.",
|
|
138
|
+
].join("\n")
|
|
139
|
+
);
|
|
140
|
+
} catch {
|
|
141
|
+
// Soft-fail.
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -26,8 +26,10 @@ export * from "./api.js";
|
|
|
26
26
|
export * from "./binding.js";
|
|
27
27
|
export * from "./checks.js";
|
|
28
28
|
export * from "./cursor-account.js";
|
|
29
|
+
export * from "./defaults.js";
|
|
29
30
|
export * from "./events.js";
|
|
30
31
|
export * from "./origin-channel.js";
|
|
31
32
|
export * from "./origin-webhook.js";
|
|
32
33
|
export * from "./reactions.js";
|
|
34
|
+
export * from "./state.js";
|
|
33
35
|
export * from "./types.js";
|
|
@@ -18,13 +18,16 @@ import type {
|
|
|
18
18
|
AgentCloudOptions,
|
|
19
19
|
AuthContext,
|
|
20
20
|
ChannelDefinition,
|
|
21
|
+
ChannelEventHandlers,
|
|
21
22
|
ChannelHandlerArgs,
|
|
23
|
+
JsonValue,
|
|
22
24
|
} from "../../types.js";
|
|
23
25
|
import { buildOriginBinding } from "./binding.js";
|
|
24
26
|
import {
|
|
25
27
|
parseCursorOriginRepoScope,
|
|
26
28
|
registerOriginCursorAccountRuntime,
|
|
27
29
|
} from "./cursor-account.js";
|
|
30
|
+
import { buildDefaultOriginEvents } from "./defaults.js";
|
|
28
31
|
import { deriveOriginWebhookEvents, ORIGIN_CHANNEL_KIND } from "./events.js";
|
|
29
32
|
import {
|
|
30
33
|
DEFAULT_ORIGIN_API_BASE_URL,
|
|
@@ -32,6 +35,11 @@ import {
|
|
|
32
35
|
type OriginWebhookHeaders,
|
|
33
36
|
verifyOriginWebhook,
|
|
34
37
|
} from "./origin-webhook.js";
|
|
38
|
+
import {
|
|
39
|
+
emptyOriginChannelState,
|
|
40
|
+
type OriginChannelState,
|
|
41
|
+
originChannelStateForEvent,
|
|
42
|
+
} from "./state.js";
|
|
35
43
|
import type {
|
|
36
44
|
OriginActor,
|
|
37
45
|
OriginChannelOptions,
|
|
@@ -432,6 +440,7 @@ function enrichEventContext(input: {
|
|
|
432
440
|
...input.ctx,
|
|
433
441
|
host: input.args.host,
|
|
434
442
|
artifacts: input.args.artifacts,
|
|
443
|
+
send: input.args.send,
|
|
435
444
|
origin: buildOriginBinding({
|
|
436
445
|
repository: input.ctx.repository,
|
|
437
446
|
installationId: input.ctx.installationId,
|
|
@@ -504,6 +513,7 @@ async function startTurn(input: {
|
|
|
504
513
|
message: string;
|
|
505
514
|
dispatch: OriginTurnDispatch;
|
|
506
515
|
cloud?: AgentCloudOptions;
|
|
516
|
+
state: OriginChannelState;
|
|
507
517
|
}): Promise<Response> {
|
|
508
518
|
const source = continuationSource(input.options.continuation, "origin");
|
|
509
519
|
const session = await input.args.send(input.message, {
|
|
@@ -513,6 +523,10 @@ async function startTurn(input: {
|
|
|
513
523
|
: {}),
|
|
514
524
|
title: input.dispatch.title ?? defaultOriginTitle(input.ctx),
|
|
515
525
|
...(input.cloud === undefined ? {} : { cloud: input.cloud }),
|
|
526
|
+
state: input.state as unknown as JsonValue,
|
|
527
|
+
// Resume must refresh the triggering comment / delivery identifiers so
|
|
528
|
+
// reply delivery targets the latest wake, not the session's first.
|
|
529
|
+
refreshState: input.state as unknown as JsonValue,
|
|
516
530
|
});
|
|
517
531
|
if (isDeferredChannelSession(session)) {
|
|
518
532
|
// Hosted admission captured the send; the control plane allocates the
|
|
@@ -569,6 +583,8 @@ async function applyDispatch(input: {
|
|
|
569
583
|
extra: Record<string, unknown>;
|
|
570
584
|
message: string;
|
|
571
585
|
cloud?: AgentCloudOptions;
|
|
586
|
+
/** Comment id that woke this dispatch (comment events only). */
|
|
587
|
+
triggeringCommentId?: string;
|
|
572
588
|
}): Promise<Response> {
|
|
573
589
|
if (input.dispatch === null) {
|
|
574
590
|
return skipped("not_dispatched", input.extra);
|
|
@@ -587,6 +603,14 @@ async function applyDispatch(input: {
|
|
|
587
603
|
message,
|
|
588
604
|
dispatch: input.dispatch,
|
|
589
605
|
cloud: input.cloud,
|
|
606
|
+
state: originChannelStateForEvent({
|
|
607
|
+
repository: input.ctx.repository,
|
|
608
|
+
pullRequestNumber: input.ctx.pullRequestNumber,
|
|
609
|
+
installationId: input.ctx.installationId,
|
|
610
|
+
deliveryId: input.ctx.deliveryId,
|
|
611
|
+
eventType: input.ctx.eventType,
|
|
612
|
+
triggeringCommentId: input.triggeringCommentId,
|
|
613
|
+
}),
|
|
590
614
|
});
|
|
591
615
|
}
|
|
592
616
|
|
|
@@ -654,6 +678,7 @@ export async function dispatchOriginWebhook(input: {
|
|
|
654
678
|
dispatch,
|
|
655
679
|
extra: { event: eventType },
|
|
656
680
|
message: `Origin comment on ${ctx.repository.fullName}#${ctx.pullRequestNumber}`,
|
|
681
|
+
triggeringCommentId: commentId,
|
|
657
682
|
});
|
|
658
683
|
}
|
|
659
684
|
|
|
@@ -798,7 +823,7 @@ export function getOriginChannelOptions(
|
|
|
798
823
|
|
|
799
824
|
export function originChannel(
|
|
800
825
|
options: OriginChannelOptions = {}
|
|
801
|
-
): ChannelDefinition {
|
|
826
|
+
): ChannelDefinition<OriginChannelState> {
|
|
802
827
|
const usesCursorAccount =
|
|
803
828
|
options.cursorAccount === true || typeof options.cursorAccount === "object";
|
|
804
829
|
const cursorScope =
|
|
@@ -833,8 +858,19 @@ export function originChannel(
|
|
|
833
858
|
);
|
|
834
859
|
}
|
|
835
860
|
|
|
861
|
+
const events: ChannelEventHandlers<OriginChannelState> = {
|
|
862
|
+
...buildDefaultOriginEvents({
|
|
863
|
+
cursorAccount: channelOptions.cursorAccount,
|
|
864
|
+
app: channelOptions.app,
|
|
865
|
+
deliverReplies: channelOptions.deliverReplies,
|
|
866
|
+
}),
|
|
867
|
+
...options.events,
|
|
868
|
+
};
|
|
869
|
+
|
|
836
870
|
const definition = defineChannel({
|
|
837
871
|
auth,
|
|
872
|
+
state: emptyOriginChannelState(),
|
|
873
|
+
events,
|
|
838
874
|
onStart: options.onStart,
|
|
839
875
|
onStop: options.onStop,
|
|
840
876
|
meta: {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable per-session Origin channel state.
|
|
3
|
+
*
|
|
4
|
+
* Persisted with the turn (same mechanism as `GitHubChannelState`) so
|
|
5
|
+
* channel stream-event handlers — reply delivery in ./defaults.ts — can
|
|
6
|
+
* rebuild a repo-bound Origin handle without re-parsing the webhook.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export interface OriginChannelState {
|
|
10
|
+
owner: string;
|
|
11
|
+
repo: string;
|
|
12
|
+
pullRequestNumber: number | null;
|
|
13
|
+
/** Origin app installation id from the delivery envelope, when present. */
|
|
14
|
+
installationId: string | null;
|
|
15
|
+
/** Comment id that woke the turn (comment events only). */
|
|
16
|
+
triggeringCommentId: string | null;
|
|
17
|
+
deliveryId: string | null;
|
|
18
|
+
eventType: string | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function emptyOriginChannelState(
|
|
22
|
+
overrides: Partial<OriginChannelState> = {}
|
|
23
|
+
): OriginChannelState {
|
|
24
|
+
return {
|
|
25
|
+
owner: "",
|
|
26
|
+
repo: "",
|
|
27
|
+
pullRequestNumber: null,
|
|
28
|
+
installationId: null,
|
|
29
|
+
triggeringCommentId: null,
|
|
30
|
+
deliveryId: null,
|
|
31
|
+
eventType: null,
|
|
32
|
+
...overrides,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Build the per-turn channel state from a dispatched event's context. */
|
|
37
|
+
export function originChannelStateForEvent(input: {
|
|
38
|
+
repository: { owner: string; name: string };
|
|
39
|
+
pullRequestNumber: number;
|
|
40
|
+
installationId?: string;
|
|
41
|
+
deliveryId?: string;
|
|
42
|
+
eventType: string;
|
|
43
|
+
triggeringCommentId?: string;
|
|
44
|
+
}): OriginChannelState {
|
|
45
|
+
return emptyOriginChannelState({
|
|
46
|
+
owner: input.repository.owner,
|
|
47
|
+
repo: input.repository.name,
|
|
48
|
+
pullRequestNumber: input.pullRequestNumber,
|
|
49
|
+
installationId: input.installationId ?? null,
|
|
50
|
+
triggeringCommentId: input.triggeringCommentId ?? null,
|
|
51
|
+
deliveryId: input.deliveryId ?? null,
|
|
52
|
+
eventType: input.eventType,
|
|
53
|
+
});
|
|
54
|
+
}
|