@cursor/july 0.1.62 → 0.1.73
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/AGENTS.md +4 -5
- package/README.md +23 -11
- package/dist/bin/agent-serve.js +11 -2
- package/dist/channels/github/api.d.ts +2 -0
- package/dist/channels/github/api.d.ts.map +1 -1
- package/dist/channels/github/api.js +2 -1
- package/dist/channels/github/binding.d.ts +7 -0
- package/dist/channels/github/binding.d.ts.map +1 -1
- package/dist/channels/github/binding.js +4 -0
- package/dist/channels/github/checks.d.ts +99 -0
- package/dist/channels/github/checks.d.ts.map +1 -0
- package/dist/channels/github/checks.js +108 -0
- package/dist/channels/github/cursor-account.d.ts +4 -4
- package/dist/channels/github/cursor-account.d.ts.map +1 -1
- package/dist/channels/github/cursor-account.js +7 -5
- package/dist/channels/github/index.d.ts +1 -0
- package/dist/channels/github/index.d.ts.map +1 -1
- package/dist/channels/github/index.js +1 -0
- package/dist/channels/github/types.d.ts +8 -0
- package/dist/channels/github/types.d.ts.map +1 -1
- package/dist/channels/slack/channel-watch.d.ts +2 -1
- package/dist/channels/slack/channel-watch.d.ts.map +1 -1
- package/dist/channels/slack/channel-watch.js +7 -3
- package/dist/channels/slack/dispatch.d.ts +9 -0
- package/dist/channels/slack/dispatch.d.ts.map +1 -1
- package/dist/channels/slack/dispatch.js +41 -21
- package/dist/channels/slack/manifest.d.ts +2 -2
- package/dist/channels/slack/manifest.d.ts.map +1 -1
- package/dist/channels/slack/manifest.js +14 -11
- package/dist/channels/slack/nudge-store.d.ts +16 -0
- package/dist/channels/slack/nudge-store.d.ts.map +1 -1
- package/dist/channels/slack/nudge-store.js +53 -0
- package/dist/channels/slack/slack-channel.d.ts.map +1 -1
- package/dist/channels/slack/slack-channel.js +4 -1
- package/dist/channels/slack/types.d.ts +24 -7
- package/dist/channels/slack/types.d.ts.map +1 -1
- package/dist/connections.d.ts +17 -5
- package/dist/connections.d.ts.map +1 -1
- package/dist/connections.js +17 -5
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +3 -3
- package/dist/docs/assets/{app.B-biogkN.js → app.CdbcFD8T.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.DQad--_z.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.DZqKtFZS.js → VPLocalSearchBox.D_NKImM7.js} +1 -1
- package/dist/docs/assets/chunks/{theme.BwC5zP2r.js → theme.CMvz7cu9.js} +2 -2
- package/dist/docs/assets/{concepts.md.CqOsxbMU.js → concepts.md.BXAm6G-C.js} +1 -1
- package/dist/docs/assets/{guides_mcp-oauth.md.C7G7IykG.js → guides_mcp-oauth.md.CzEB6RaG.js} +1 -1
- package/dist/docs/assets/{reference_agent-config.md.DHkkvcEZ.js → reference_agent-config.md.BZnEKfEc.js} +3 -4
- package/dist/docs/assets/{reference_agent-config.md.DHkkvcEZ.lean.js → reference_agent-config.md.BZnEKfEc.lean.js} +1 -1
- package/dist/docs/assets/{reference_connections.md.CSQiNzre.js → reference_connections.md.Cu3N-S3Q.js} +26 -9
- package/dist/docs/assets/reference_connections.md.Cu3N-S3Q.lean.js +1 -0
- package/dist/docs/assets/{reference_sessions.md.DBVFi2Sx.js → reference_sessions.md.B0DdlM-K.js} +1 -1
- package/dist/docs/assets/{reference_tools.md.kJfJAP2m.js → reference_tools.md.DTg_kEsx.js} +2 -2
- package/dist/docs/assets/{reference_tools.md.kJfJAP2m.lean.js → reference_tools.md.DTg_kEsx.lean.js} +1 -1
- package/dist/docs/assets/troubleshooting.md.DQ-hYuZT.js +1 -0
- package/dist/docs/building-with-agents.html +3 -3
- package/dist/docs/concepts.html +5 -5
- package/dist/docs/deployment.html +3 -3
- package/dist/docs/evals.html +3 -3
- package/dist/docs/example-agents/approval-buddy.html +3 -3
- package/dist/docs/example-agents/benny.html +3 -3
- package/dist/docs/example-agents/bugbot.html +3 -3
- package/dist/docs/example-agents/codebase-wiki.html +3 -3
- package/dist/docs/example-agents/codeowners-review.html +3 -3
- package/dist/docs/example-agents/concierge.html +3 -3
- package/dist/docs/example-agents/fsd.html +3 -3
- package/dist/docs/example-agents/index.html +3 -3
- package/dist/docs/example-agents/knowledge-base.html +3 -3
- package/dist/docs/example-agents/oncall.html +3 -3
- package/dist/docs/example-agents/security-reviewer.html +3 -3
- package/dist/docs/example-agents/slack-agent.html +3 -3
- package/dist/docs/example-agents/weather-agent.html +3 -3
- package/dist/docs/guides/agent-to-agent.html +3 -3
- package/dist/docs/guides/cloud-runtime.html +3 -3
- package/dist/docs/guides/convert-automation.html +3 -3
- package/dist/docs/guides/github.html +3 -3
- package/dist/docs/guides/human-in-the-loop.html +3 -3
- package/dist/docs/guides/mcp-oauth.html +5 -5
- package/dist/docs/guides/opentelemetry.html +3 -3
- package/dist/docs/guides/slack.html +3 -3
- package/dist/docs/guides/webhooks.html +3 -3
- package/dist/docs/hashmap.json +1 -1
- package/dist/docs/hillclimbing.html +3 -3
- package/dist/docs/index.html +3 -3
- package/dist/docs/quickstart.html +3 -3
- package/dist/docs/reference/agent-config.html +5 -6
- package/dist/docs/reference/artifacts.html +3 -3
- package/dist/docs/reference/channels.html +3 -3
- package/dist/docs/reference/cli.html +3 -3
- package/dist/docs/reference/connections.html +28 -11
- package/dist/docs/reference/hooks.html +3 -3
- package/dist/docs/reference/http-api.html +3 -3
- package/dist/docs/reference/instructions.html +3 -3
- package/dist/docs/reference/playground.html +3 -3
- package/dist/docs/reference/project-layout.html +3 -3
- package/dist/docs/reference/prompt.html +3 -3
- package/dist/docs/reference/schedules.html +3 -3
- package/dist/docs/reference/sessions.html +5 -5
- package/dist/docs/reference/skills.html +3 -3
- package/dist/docs/reference/subagents.html +3 -3
- package/dist/docs/reference/tools.html +5 -5
- package/dist/docs/scaffolding-agents.html +3 -3
- package/dist/docs/storage.html +3 -3
- package/dist/docs/templates/agentic-owners.html +3 -3
- package/dist/docs/templates/demo.html +3 -3
- package/dist/docs/templates/pr-autofixer.html +3 -3
- package/dist/docs/templates/security-reviewer.html +3 -3
- package/dist/docs/templates/triage.html +3 -3
- package/dist/docs/troubleshooting.html +5 -5
- package/dist/files-backends/agent-store-presigned-url.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/internal/advertise-tools.d.ts +79 -0
- package/dist/internal/advertise-tools.d.ts.map +1 -0
- package/dist/internal/advertise-tools.js +185 -0
- package/dist/internal/cli-deploy.d.ts.map +1 -1
- package/dist/internal/cli-deploy.js +7 -1
- package/dist/internal/cli-manifest.d.ts +19 -0
- package/dist/internal/cli-manifest.d.ts.map +1 -0
- package/dist/internal/cli-manifest.js +49 -0
- package/dist/internal/cli-slack.d.ts.map +1 -1
- package/dist/internal/cli-slack.js +9 -1
- package/dist/internal/convert-automation/convert-workflow.d.ts.map +1 -1
- package/dist/internal/convert-automation/convert-workflow.js +2 -10
- package/dist/internal/cursor/account-mcp.d.ts +29 -3
- package/dist/internal/cursor/account-mcp.d.ts.map +1 -1
- package/dist/internal/cursor/account-mcp.js +147 -67
- package/dist/internal/cursor/backend-client.d.ts +11 -6
- package/dist/internal/cursor/backend-client.d.ts.map +1 -1
- package/dist/internal/cursor/backend-client.js +32 -10
- package/dist/internal/cursor-account-mcp-auth.d.ts +11 -0
- package/dist/internal/cursor-account-mcp-auth.d.ts.map +1 -1
- package/dist/internal/cursor-account-mcp-auth.js +69 -0
- package/dist/internal/cursor-agent-template.d.ts +9 -0
- package/dist/internal/cursor-agent-template.d.ts.map +1 -0
- package/dist/internal/cursor-agent-template.js +37 -0
- package/dist/internal/deploy-client.d.ts +1 -0
- package/dist/internal/deploy-client.d.ts.map +1 -1
- package/dist/internal/deploy-client.js +18 -2
- package/dist/internal/deploy-manifest.d.ts +76 -0
- package/dist/internal/deploy-manifest.d.ts.map +1 -0
- package/dist/internal/deploy-manifest.js +117 -0
- package/dist/internal/deployment-scm-event-stream.d.ts +116 -0
- package/dist/internal/deployment-scm-event-stream.d.ts.map +1 -0
- package/dist/internal/deployment-scm-event-stream.js +474 -0
- package/dist/internal/discovery.d.ts.map +1 -1
- package/dist/internal/discovery.js +111 -10
- package/dist/internal/init-project.d.ts.map +1 -1
- package/dist/internal/init-project.js +12 -123
- package/dist/internal/init-scaffold.d.ts +8 -0
- package/dist/internal/init-scaffold.d.ts.map +1 -0
- package/dist/internal/init-scaffold.js +168 -0
- package/dist/internal/inline-stdio-mcp.d.ts +2 -0
- package/dist/internal/inline-stdio-mcp.d.ts.map +1 -1
- package/dist/internal/inline-stdio-mcp.js +10 -8
- package/dist/internal/json-schema.d.ts +7 -0
- package/dist/internal/json-schema.d.ts.map +1 -1
- package/dist/internal/json-schema.js +18 -3
- package/dist/internal/mcp-endpoint.js +7 -7
- package/dist/internal/mcp-host.d.ts +18 -0
- package/dist/internal/mcp-host.d.ts.map +1 -1
- package/dist/internal/mcp-host.js +43 -5
- package/dist/internal/mcp-oauth.d.ts +22 -0
- package/dist/internal/mcp-oauth.d.ts.map +1 -1
- package/dist/internal/mcp-oauth.js +40 -1
- package/dist/internal/package-inline-stdio.d.ts.map +1 -1
- package/dist/internal/package-inline-stdio.js +5 -5
- package/dist/internal/review-comments.d.ts +97 -0
- package/dist/internal/review-comments.d.ts.map +1 -1
- package/dist/internal/review-comments.js +151 -0
- package/dist/internal/sanitize-mcp-tool-name.d.ts +20 -0
- package/dist/internal/sanitize-mcp-tool-name.d.ts.map +1 -0
- package/dist/internal/sanitize-mcp-tool-name.js +30 -0
- package/dist/internal/sdk-runner.d.ts +1 -1
- package/dist/internal/sdk-runner.d.ts.map +1 -1
- package/dist/internal/sdk-runner.js +36 -13
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +18 -0
- package/dist/internal/session-engine.d.ts +66 -0
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +428 -110
- package/dist/internal/shallow-clone.d.ts +170 -0
- package/dist/internal/shallow-clone.d.ts.map +1 -0
- package/dist/internal/shallow-clone.js +412 -0
- package/dist/internal/slack-provision-client.d.ts +2 -0
- package/dist/internal/slack-provision-client.d.ts.map +1 -1
- package/dist/internal/slack-provision-client.js +1 -0
- package/dist/internal/terminal-style.d.ts +18 -0
- package/dist/internal/terminal-style.d.ts.map +1 -1
- package/dist/internal/terminal-style.js +46 -1
- package/dist/internal/turn-lease-store.d.ts +44 -0
- package/dist/internal/turn-lease-store.d.ts.map +1 -0
- package/dist/internal/turn-lease-store.js +139 -0
- package/dist/internal/workspace.d.ts +14 -0
- package/dist/internal/workspace.d.ts.map +1 -1
- package/dist/internal/workspace.js +33 -0
- package/dist/playground/assets/index-77uhu5Kr.css +1 -0
- package/dist/playground/assets/index-D4buox4m.js +85 -0
- package/dist/playground/index.html +2 -2
- package/dist/storage-protocol.d.ts +122 -0
- package/dist/storage-protocol.d.ts.map +1 -0
- package/dist/storage-protocol.js +173 -0
- package/dist/storage.d.ts +2 -72
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +2 -82
- package/dist/tools.d.ts +7 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/types.d.ts +141 -6
- package/dist/types.d.ts.map +1 -1
- package/docs/concepts.md +4 -4
- package/docs/guides/mcp-oauth.md +3 -1
- package/docs/reference/agent-config.md +11 -12
- package/docs/reference/connections.md +108 -10
- package/docs/reference/sessions.md +2 -2
- package/docs/reference/tools.md +14 -0
- package/docs/troubleshooting.md +3 -2
- package/package.json +7 -2
- package/skills/create-agent/SKILL.md +12 -9
- package/skills/debug/SKILL.md +2 -1
- package/skills/framework-map/SKILL.md +6 -6
- package/skills/mcp-auth/SKILL.md +8 -6
- package/src/bin/agent-serve.ts +12 -2
- package/src/channels/github/api.ts +6 -1
- package/src/channels/github/binding.ts +21 -0
- package/src/channels/github/checks.ts +179 -0
- package/src/channels/github/cursor-account.ts +9 -6
- package/src/channels/github/index.ts +1 -0
- package/src/channels/github/types.ts +8 -0
- package/src/channels/slack/channel-watch.ts +6 -3
- package/src/channels/slack/dispatch.ts +40 -3
- package/src/channels/slack/manifest.ts +9 -10
- package/src/channels/slack/nudge-store.ts +56 -0
- package/src/channels/slack/slack-channel.ts +3 -0
- package/src/channels/slack/types.ts +24 -7
- package/src/connections.ts +17 -5
- package/src/index.ts +5 -0
- package/src/internal/advertise-tools.ts +278 -0
- package/src/internal/cli-deploy.ts +11 -1
- package/src/internal/cli-manifest.ts +63 -0
- package/src/internal/cli-slack.ts +10 -1
- package/src/internal/convert-automation/convert-workflow.ts +2 -11
- package/src/internal/cursor/account-mcp.ts +208 -73
- package/src/internal/cursor/backend-client.ts +45 -11
- package/src/internal/cursor-account-mcp-auth.ts +83 -0
- package/src/internal/cursor-agent-template.ts +46 -0
- package/src/internal/deploy-client.ts +25 -6
- package/src/internal/deploy-manifest.ts +161 -0
- package/src/internal/deployment-scm-event-stream.ts +593 -0
- package/src/internal/discovery.ts +132 -3
- package/src/internal/init-project.ts +18 -153
- package/src/internal/init-scaffold.ts +176 -0
- package/src/internal/inline-stdio-mcp.ts +13 -0
- package/src/internal/json-schema.ts +24 -3
- package/src/internal/mcp-endpoint.ts +6 -0
- package/src/internal/mcp-host.ts +70 -9
- package/src/internal/mcp-oauth.ts +55 -1
- package/src/internal/package-inline-stdio.ts +5 -1
- package/src/internal/review-comments.ts +245 -0
- package/src/internal/sanitize-mcp-tool-name.ts +33 -0
- package/src/internal/sdk-runner.ts +50 -11
- package/src/internal/server.ts +19 -0
- package/src/internal/session-engine.ts +392 -19
- package/src/internal/shallow-clone.ts +595 -0
- package/src/internal/slack-provision-client.ts +3 -0
- package/src/internal/terminal-style.ts +67 -1
- package/src/internal/turn-lease-store.ts +147 -0
- package/src/internal/workspace.ts +33 -0
- package/src/storage-protocol.ts +247 -0
- package/src/storage.ts +16 -110
- package/src/tools.ts +7 -0
- package/src/types.ts +149 -7
- package/templates/agentic-owners/agent/agent.ts +3 -0
- package/templates/demo/agent/agent.ts +3 -0
- package/templates/pr-autofixer/agent/lib/drive-schemas.ts +4 -1
- package/templates/security-reviewer/agent/agent.ts +3 -0
- package/templates/triage/agent/agent.ts +3 -0
- package/dist/docs/assets/chunks/@localSearchIndexroot.5ozYNf74.js +0 -1
- package/dist/docs/assets/reference_connections.md.CSQiNzre.lean.js +0 -1
- package/dist/docs/assets/troubleshooting.md.CuVztCKs.js +0 -1
- package/dist/playground/assets/index-B4rcOgeP.css +0 -1
- package/dist/playground/assets/index-DXcY42Jv.js +0 -85
- /package/dist/docs/assets/{concepts.md.CqOsxbMU.lean.js → concepts.md.BXAm6G-C.lean.js} +0 -0
- /package/dist/docs/assets/{guides_mcp-oauth.md.C7G7IykG.lean.js → guides_mcp-oauth.md.CzEB6RaG.lean.js} +0 -0
- /package/dist/docs/assets/{reference_sessions.md.DBVFi2Sx.lean.js → reference_sessions.md.B0DdlM-K.lean.js} +0 -0
- /package/dist/docs/assets/{troubleshooting.md.CuVztCKs.lean.js → troubleshooting.md.DQ-hYuZT.lean.js} +0 -0
|
@@ -18,6 +18,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
18
18
|
import { mkdir, rm, writeFile } from "node:fs/promises";
|
|
19
19
|
import { dirname, join } from "node:path";
|
|
20
20
|
import { mergeABInstructions, resolveABMaxPlaygroundSessions, resolveABSplit, } from "../ab.js";
|
|
21
|
+
import { SLACK_CHANNEL_ID } from "../channels/slack/constants.js";
|
|
22
|
+
import { materializeSlackNudgeCarrier } from "../channels/slack/nudge-carrier.js";
|
|
21
23
|
import { SlackAskDedupeStore, SlackNudgeStore, } from "../channels/slack/nudge-store.js";
|
|
22
24
|
import { resolveDedicatedSlackHostBinding } from "../channels/slack/slack-channel.js";
|
|
23
25
|
import { isCursorHostedFilesAvailable } from "../files-backends/cursor-hosted.js";
|
|
@@ -27,6 +29,7 @@ import { DEFAULT_MODEL, DIRECT_TOOL_CALL_CHANNEL_ID, isHostOauthConnectionTransp
|
|
|
27
29
|
import { ABCollector } from "./ab-collector.js";
|
|
28
30
|
import { foldABStream } from "./ab-fold.js";
|
|
29
31
|
import { buildABSnapshot } from "./ab-snapshot.js";
|
|
32
|
+
import { resolveAdvertisedTools } from "./advertise-tools.js";
|
|
30
33
|
import { ApprovalGate } from "./approval-gate.js";
|
|
31
34
|
import { ArtifactsStore } from "./artifacts-store.js";
|
|
32
35
|
import { SessionAuthError, samePrincipal } from "./auth.js";
|
|
@@ -54,7 +57,8 @@ import { StorageCoordinator } from "./storage-coordinator.js";
|
|
|
54
57
|
import { normalizeToolResult, toolCallErrorMessage } from "./tool-result.js";
|
|
55
58
|
import { estimateTurnCostFromApi, roundCostUsd, } from "./turn-cost.js";
|
|
56
59
|
import { TurnGovernor } from "./turn-governor.js";
|
|
57
|
-
import {
|
|
60
|
+
import { TurnLeaseStore } from "./turn-lease-store.js";
|
|
61
|
+
import { buildAgentsMdContent, buildCloudMemorySection, ensureOwnGitRoot, isNestedInGitRepo, materializeWorkspace, SERVER_TOOLS_MCP_SERVER_NAME, writeWorkspaceFiles, } from "./workspace.js";
|
|
58
62
|
export const HTTP_CHANNEL_ID = "http";
|
|
59
63
|
export { SCHEDULE_CHANNEL_PREFIX } from "./prompt-context.js";
|
|
60
64
|
export class SessionBusyError extends Error {
|
|
@@ -121,6 +125,12 @@ export class SessionEngine {
|
|
|
121
125
|
this.activeTurns = new Map();
|
|
122
126
|
/** Context used by the cloud turn's HTTP MCP callbacks. */
|
|
123
127
|
this.activeToolContexts = new Map();
|
|
128
|
+
/**
|
|
129
|
+
* Delivered-nudge count per session at the last tool boundary. The carrier
|
|
130
|
+
* drains out-of-process, so a growing count is how the engine learns a
|
|
131
|
+
* follow-up reached the agent.
|
|
132
|
+
*/
|
|
133
|
+
this.deliveredNudges = new Map();
|
|
124
134
|
/** Aborts the in-flight turn per session so a follow-up can preempt it. */
|
|
125
135
|
this.turnAborts = new Map();
|
|
126
136
|
/** Serializes turn admission per session (latest follow-up wins). */
|
|
@@ -162,6 +172,7 @@ export class SessionEngine {
|
|
|
162
172
|
this.sessions = new SessionStore(join(options.stateRoot, "sessions"));
|
|
163
173
|
this.nudgeStore = new SlackNudgeStore(join(options.stateRoot, "slack-nudges"));
|
|
164
174
|
this.askDedupe = new SlackAskDedupeStore(join(options.stateRoot, "slack-asks"));
|
|
175
|
+
this.turnLeases = new TurnLeaseStore(join(options.stateRoot, "turn-leases"));
|
|
165
176
|
this.logs = new EventLogStore((sessionId) => this.sessions.eventFilePath(sessionId), (sessionId, error) => this.logger(`[agent-sdk] failed to persist events for ${sessionId}: ${describeError(error)}`));
|
|
166
177
|
// Framework session state (records, event chunks, continuations) is
|
|
167
178
|
// mirrored to whatever this selects; see framework-storage-selection.ts
|
|
@@ -405,6 +416,102 @@ export class SessionEngine {
|
|
|
405
416
|
return record;
|
|
406
417
|
});
|
|
407
418
|
}
|
|
419
|
+
/**
|
|
420
|
+
* Install the Slack nudge carrier into a local session's harness cwd, so a
|
|
421
|
+
* follow-up that arrives mid-turn reaches the running agent at its next tool
|
|
422
|
+
* boundary instead of waiting for the boundary turn.
|
|
423
|
+
*
|
|
424
|
+
* Slack-only and local-only: the carrier is a Cursor hook in the workspace,
|
|
425
|
+
* and a cloud VM has neither the workspace nor the nudge store.
|
|
426
|
+
*
|
|
427
|
+
* The harness only reads these hooks when the workspace is its own git root;
|
|
428
|
+
* nested in an outer repo it reads that repo's instead and the carrier never
|
|
429
|
+
* runs, leaving follow-ups to drain at the turn boundary as before.
|
|
430
|
+
*/
|
|
431
|
+
materializeNudgeCarrier(record, harnessCwd) {
|
|
432
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
433
|
+
if (record.channelId !== SLACK_CHANNEL_ID ||
|
|
434
|
+
record.continuationKey === null ||
|
|
435
|
+
record.continuationKey === "" ||
|
|
436
|
+
// Borrowed: a PR worktree, or a checkout shared by every session. Hooks
|
|
437
|
+
// written there dirty a tree this session did not create, and a shared
|
|
438
|
+
// cwd holds one continuation pointer for all of them — the carrier would
|
|
439
|
+
// inject one thread's follow-ups into another thread's turn. Nothing is
|
|
440
|
+
// lost: a borrowed checkout is its own git root, so the harness reads its
|
|
441
|
+
// hooks rather than these, and follow-ups drain at the turn boundary.
|
|
442
|
+
this.hasBorrowedWorkspace(record)) {
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
try {
|
|
446
|
+
yield materializeSlackNudgeCarrier({
|
|
447
|
+
harnessCwd,
|
|
448
|
+
nudgeRootDir: join(this.stateRoot, "slack-nudges"),
|
|
449
|
+
continuationKey: record.continuationKey,
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
catch (error) {
|
|
453
|
+
// Best-effort: without the carrier, follow-ups still drain at the turn
|
|
454
|
+
// boundary — one turn later, never lost.
|
|
455
|
+
this.logger(`[session] slack nudge carrier not installed: ${describeError(error)}`);
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Report a mid-turn follow-up reaching the agent, as `nudge.delivered`.
|
|
461
|
+
*
|
|
462
|
+
* The workspace carrier drains `pending/` into `delivered/` out of process,
|
|
463
|
+
* so the engine watches the delivered count grow rather than the turn's own
|
|
464
|
+
* events. Counting rather than edge-detecting on "still queued" matters:
|
|
465
|
+
* the carrier runs at the same tool boundary this check does, so a nudge
|
|
466
|
+
* queued and drained between two readings is never seen as pending.
|
|
467
|
+
*
|
|
468
|
+
* Channels need this to move a live progress message below the follow-up —
|
|
469
|
+
* Slack cannot reorder messages.
|
|
470
|
+
*/
|
|
471
|
+
emitDeliveredNudges(record, emit) {
|
|
472
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
473
|
+
const delivered = yield this.readDeliveredNudges(record);
|
|
474
|
+
if (delivered === undefined) {
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
const previous = this.deliveredNudges.get(record.sessionId);
|
|
478
|
+
this.deliveredNudges.set(record.sessionId, delivered);
|
|
479
|
+
if (previous !== undefined && delivered > previous) {
|
|
480
|
+
emit({ type: "nudge.delivered", data: {} });
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Record the thread's delivered count before the turn runs. Earlier turns
|
|
486
|
+
* leave their own records behind, so only growth from here is this turn's.
|
|
487
|
+
*/
|
|
488
|
+
baselineDeliveredNudges(record) {
|
|
489
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
490
|
+
const delivered = yield this.readDeliveredNudges(record);
|
|
491
|
+
if (delivered !== undefined) {
|
|
492
|
+
this.deliveredNudges.set(record.sessionId, delivered);
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
readDeliveredNudges(record) {
|
|
497
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
498
|
+
const continuationKey = record.continuationKey;
|
|
499
|
+
// Same gate as the carrier: only Slack queues follow-ups mid-turn, and
|
|
500
|
+
// this runs on every tool boundary of every turn.
|
|
501
|
+
if (record.channelId !== SLACK_CHANNEL_ID ||
|
|
502
|
+
continuationKey === null ||
|
|
503
|
+
continuationKey === "") {
|
|
504
|
+
return undefined;
|
|
505
|
+
}
|
|
506
|
+
try {
|
|
507
|
+
return yield this.nudgeStore.deliveredCount(continuationKey);
|
|
508
|
+
}
|
|
509
|
+
catch (_a) {
|
|
510
|
+
// A store read must never break the turn it is observing.
|
|
511
|
+
return undefined;
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
}
|
|
408
515
|
slackNudgeHostApi() {
|
|
409
516
|
return {
|
|
410
517
|
append: (input) => this.nudgeStore.append(input),
|
|
@@ -697,6 +804,61 @@ export class SessionEngine {
|
|
|
697
804
|
clearCoalesced(sessionId) {
|
|
698
805
|
this.coalesceQueues.delete(sessionId);
|
|
699
806
|
}
|
|
807
|
+
/**
|
|
808
|
+
* Drop queued follow-ups the nudge carrier already carried into the turn
|
|
809
|
+
* that just finished — the contract {@link SendMessageOptions.coalesceSourceTs}
|
|
810
|
+
* describes. Re-asking them would answer the same message twice.
|
|
811
|
+
*
|
|
812
|
+
* Items with no `sourceTs` cannot be matched and are kept.
|
|
813
|
+
*/
|
|
814
|
+
withoutNudgedFollowups(sessionId, queued) {
|
|
815
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
816
|
+
const record = yield this.sessions.get(sessionId);
|
|
817
|
+
const continuationKey = record === null || record === void 0 ? void 0 : record.continuationKey;
|
|
818
|
+
if (continuationKey === undefined || continuationKey === null) {
|
|
819
|
+
return [...queued];
|
|
820
|
+
}
|
|
821
|
+
const kept = [];
|
|
822
|
+
for (const item of queued) {
|
|
823
|
+
if (item.sourceTs === undefined) {
|
|
824
|
+
kept.push(item);
|
|
825
|
+
continue;
|
|
826
|
+
}
|
|
827
|
+
try {
|
|
828
|
+
if (!(yield this.nudgeStore.isDelivered(continuationKey, item.sourceTs))) {
|
|
829
|
+
kept.push(item);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
catch (_a) {
|
|
833
|
+
// A store read failure must not swallow the ask.
|
|
834
|
+
kept.push(item);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
return kept;
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
/** Mark drained follow-ups delivered so the next turn's carrier skips them. */
|
|
841
|
+
settleDrainedFollowups(sessionId, items) {
|
|
842
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
843
|
+
const record = yield this.sessions.get(sessionId);
|
|
844
|
+
const continuationKey = record === null || record === void 0 ? void 0 : record.continuationKey;
|
|
845
|
+
if (continuationKey === undefined || continuationKey === null) {
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
for (const item of items) {
|
|
849
|
+
if (item.sourceTs === undefined) {
|
|
850
|
+
continue;
|
|
851
|
+
}
|
|
852
|
+
try {
|
|
853
|
+
yield this.nudgeStore.markDelivered(continuationKey, item.sourceTs);
|
|
854
|
+
}
|
|
855
|
+
catch (error) {
|
|
856
|
+
// Worst case the carrier re-injects it; the ask still gets answered.
|
|
857
|
+
this.logger(`[session] nudge markDelivered failed for ${sessionId}: ${describeError(error)}`);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
});
|
|
861
|
+
}
|
|
700
862
|
formatCoalescedFollowups(items) {
|
|
701
863
|
if (items.length === 1) {
|
|
702
864
|
return items[0].message;
|
|
@@ -722,8 +884,17 @@ export class SessionEngine {
|
|
|
722
884
|
return;
|
|
723
885
|
}
|
|
724
886
|
this.coalesceQueues.delete(sessionId);
|
|
725
|
-
const
|
|
726
|
-
|
|
887
|
+
const pending = yield this.withoutNudgedFollowups(sessionId, queued);
|
|
888
|
+
if (pending.length === 0) {
|
|
889
|
+
this.logger(`[session] coalesce drain skipped; all follow-ups already nudged ${sessionId}`);
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
// These become the next turn's prompt, so settle them in the nudge store
|
|
893
|
+
// first: a surviving pending record would be injected by that turn's own
|
|
894
|
+
// carrier, re-asking what the turn is already about.
|
|
895
|
+
yield this.settleDrainedFollowups(sessionId, pending);
|
|
896
|
+
const message = this.formatCoalescedFollowups(pending);
|
|
897
|
+
this.logger(`[session] coalesce drain starting follow-up (${pending.length}) ${sessionId}`);
|
|
727
898
|
yield this.startTurn(sessionId, message, {
|
|
728
899
|
admission: "preempt",
|
|
729
900
|
retainCoalesced: true,
|
|
@@ -741,6 +912,14 @@ export class SessionEngine {
|
|
|
741
912
|
this.logger(`[session] coalesce enqueue ${sessionId}`);
|
|
742
913
|
return true;
|
|
743
914
|
}
|
|
915
|
+
// Another server owns this session, so coalesce even a preempt. No local
|
|
916
|
+
// enqueue: `drainCoalescedFollowups` reads only this process's queue, so
|
|
917
|
+
// the shared nudge store is the carrier. Not redundant with `acquire`
|
|
918
|
+
// below — this returns before preempt's `clearCoalesced` drops the queue.
|
|
919
|
+
if (yield this.turnLeases.runningElsewhere(sessionId)) {
|
|
920
|
+
this.logger(`[session] coalesce; another server owns ${sessionId}`);
|
|
921
|
+
return true;
|
|
922
|
+
}
|
|
744
923
|
// Interrupt any turn in flight and wait for it to settle so the SDK run is
|
|
745
924
|
// terminal before this turn's send (the follow-up also force-expires as a
|
|
746
925
|
// belt-and-suspenders — see buildTurnRequest). Drain-resume skips interrupt:
|
|
@@ -754,7 +933,20 @@ export class SessionEngine {
|
|
|
754
933
|
if (admission === "preempt" && options.retainCoalesced !== true) {
|
|
755
934
|
this.clearCoalesced(sessionId);
|
|
756
935
|
}
|
|
936
|
+
// A neighbour won the race; same outcome as the branch above.
|
|
937
|
+
const lease = yield this.turnLeases.acquire(sessionId, () => {
|
|
938
|
+
var _a;
|
|
939
|
+
// Stalled past the stale window and a successor took over. Stand down
|
|
940
|
+
// rather than answer the thread alongside it.
|
|
941
|
+
this.logger(`[session] lease lost mid-turn; aborting ${sessionId}`);
|
|
942
|
+
(_a = this.turnAborts.get(sessionId)) === null || _a === void 0 ? void 0 : _a.abort();
|
|
943
|
+
});
|
|
944
|
+
if (lease === undefined) {
|
|
945
|
+
this.logger(`[session] lease lost during admit ${sessionId}`);
|
|
946
|
+
return true;
|
|
947
|
+
}
|
|
757
948
|
this.pendingTurns.add(sessionId);
|
|
949
|
+
let lifecycleInstalled = false;
|
|
758
950
|
try {
|
|
759
951
|
let record = yield this.updateRecord(sessionId, (current) => (Object.assign(Object.assign({}, current), { turnCount: current.turnCount + 1 })));
|
|
760
952
|
// Arms must be on the record before tools/instructions see SessionInfo.
|
|
@@ -807,12 +999,21 @@ export class SessionEngine {
|
|
|
807
999
|
}
|
|
808
1000
|
}
|
|
809
1001
|
}
|
|
1002
|
+
// The drain may have nested a successor turn, which took its own
|
|
1003
|
+
// reference; the lease outlives this release until that one ends too.
|
|
1004
|
+
yield lease.release();
|
|
810
1005
|
return outcome;
|
|
811
1006
|
}))();
|
|
812
1007
|
this.activeTurns.set(sessionId, lifecycle);
|
|
1008
|
+
lifecycleInstalled = true;
|
|
813
1009
|
}
|
|
814
1010
|
finally {
|
|
815
1011
|
this.pendingTurns.delete(sessionId);
|
|
1012
|
+
if (!lifecycleInstalled) {
|
|
1013
|
+
// Nothing will run the release above, and the session would sit behind
|
|
1014
|
+
// this lease until it went stale.
|
|
1015
|
+
yield lease.release();
|
|
1016
|
+
}
|
|
816
1017
|
}
|
|
817
1018
|
return false;
|
|
818
1019
|
});
|
|
@@ -955,9 +1156,15 @@ export class SessionEngine {
|
|
|
955
1156
|
}
|
|
956
1157
|
const startedAt = Date.now();
|
|
957
1158
|
try {
|
|
958
|
-
const outcome = yield this.runner.prewarm(Object.assign(Object.assign(Object.assign({ workspaceDir, runnerStateDir: join(this.stateRoot, "runner"), sandbox: ((_b = agent.local) === null || _b === void 0 ? void 0 : _b.sandbox) === true }, (agent.allowedTools === undefined
|
|
1159
|
+
const outcome = yield this.runner.prewarm(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ workspaceDir, runnerStateDir: join(this.stateRoot, "runner"), sandbox: ((_b = agent.local) === null || _b === void 0 ? void 0 : _b.sandbox) === true }, (agent.allowedTools === undefined
|
|
959
1160
|
? {}
|
|
960
|
-
: { tools: agent.allowedTools })), {
|
|
1161
|
+
: { tools: agent.allowedTools })), {
|
|
1162
|
+
// No session yet, so auth'd connections are omitted rather than
|
|
1163
|
+
// warmed without an identity; the per-send flag still has to match
|
|
1164
|
+
// the turns' requests or the warmed executor is one no turn asks for.
|
|
1165
|
+
mcpServers: this.buildMcpServers("local") }), (this.hasAttachedAuthedConnections()
|
|
1166
|
+
? { perSendMcpServers: true }
|
|
1167
|
+
: {})), { subagents: buildRunnerSubagents(agent) }), (agent.model === undefined ? {} : { model: agent.model })));
|
|
961
1168
|
// Report what happened rather than that the call returned: a runner
|
|
962
1169
|
// that cannot warm resolves cleanly, and logging success there would
|
|
963
1170
|
// tell an operator the workspace is hot when nothing touched it.
|
|
@@ -1029,6 +1236,18 @@ export class SessionEngine {
|
|
|
1029
1236
|
seed: isFirstTurn,
|
|
1030
1237
|
stateRoot: this.stateRoot,
|
|
1031
1238
|
});
|
|
1239
|
+
// Must precede the carrier: the harness resolves rules, skills,
|
|
1240
|
+
// `mcp.json` and hooks from its git root, so a workspace nested in an
|
|
1241
|
+
// outer repo would load that repo's hooks instead of the ones written
|
|
1242
|
+
// just below. Only for directories this session owns — a borrowed
|
|
1243
|
+
// checkout or worktree keeps its own root.
|
|
1244
|
+
if (!this.hasBorrowedWorkspace(record)) {
|
|
1245
|
+
yield ensureOwnGitRoot(harnessCwd);
|
|
1246
|
+
}
|
|
1247
|
+
yield this.materializeNudgeCarrier(record, harnessCwd);
|
|
1248
|
+
// Baseline before the agent can run, so a follow-up that arrives and
|
|
1249
|
+
// drains before the first tool boundary still counts as an increase.
|
|
1250
|
+
yield this.baselineDeliveredNudges(record);
|
|
1032
1251
|
const pendingFiles = record.pendingWorkspaceFiles;
|
|
1033
1252
|
const pendingImages = record.pendingImages;
|
|
1034
1253
|
if (pendingFiles !== undefined &&
|
|
@@ -1052,7 +1271,7 @@ export class SessionEngine {
|
|
|
1052
1271
|
const instructionsInvisibleToHarness = isFirstTurn &&
|
|
1053
1272
|
(this.hasBorrowedWorkspace(record) ||
|
|
1054
1273
|
(yield isNestedInGitRepo(harnessCwd)));
|
|
1055
|
-
const request = this.buildTurnRequest(record, agent, turnId, message, isFirstTurn, instructionsInvisibleToHarness, emit, (update) => __awaiter(this, void 0, void 0, function* () {
|
|
1274
|
+
const request = yield this.buildTurnRequest(record, agent, turnId, message, isFirstTurn, instructionsInvisibleToHarness, emit, (update) => __awaiter(this, void 0, void 0, function* () {
|
|
1056
1275
|
mapper.handleUpdate(update);
|
|
1057
1276
|
// Yield so the HTTP NDJSON writer can flush tool start/result
|
|
1058
1277
|
// events while sibling parallel tools are still executing.
|
|
@@ -1061,6 +1280,9 @@ export class SessionEngine {
|
|
|
1061
1280
|
// Nested subagent progress (newer SDKs); string check keeps
|
|
1062
1281
|
// typecheck green against older published `@cursor/sdk` pins.
|
|
1063
1282
|
update.type === "tool-call-delta") {
|
|
1283
|
+
// Same boundary the carrier hook runs on, so a drain is visible
|
|
1284
|
+
// by the time this fires.
|
|
1285
|
+
yield this.emitDeliveredNudges(record, emit);
|
|
1064
1286
|
yield yieldEventLoop();
|
|
1065
1287
|
}
|
|
1066
1288
|
}), signal, pendingImages);
|
|
@@ -1080,6 +1302,9 @@ export class SessionEngine {
|
|
|
1080
1302
|
permit.release();
|
|
1081
1303
|
this.approvals.cancelSession(sessionId, "deny");
|
|
1082
1304
|
yield this.clearPendingApprovals(sessionId);
|
|
1305
|
+
// Anything still queued belongs to the next turn's boundary drain, not
|
|
1306
|
+
// to this turn's edge detection.
|
|
1307
|
+
this.deliveredNudges.delete(sessionId);
|
|
1083
1308
|
}
|
|
1084
1309
|
}
|
|
1085
1310
|
mapper.finish();
|
|
@@ -1143,88 +1368,106 @@ export class SessionEngine {
|
|
|
1143
1368
|
});
|
|
1144
1369
|
}
|
|
1145
1370
|
buildTurnRequest(record, agent, turnId, prompt, isFirstTurn, instructionsInvisibleToHarness, emit, onUpdate, signal, images) {
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
// Fail closed: the SDK cannot enforce the built-in tool allowlist on
|
|
1154
|
-
// cloud agents yet, and a restriction that silently does not apply is
|
|
1155
|
-
// worse than a refused turn. Cloud-runtime agents already fail
|
|
1156
|
-
// discovery; this guards hybrid agents' per-send cloud sessions.
|
|
1157
|
-
if (runtime === "cloud" && agent.allowedTools !== undefined) {
|
|
1158
|
-
throw new Error(`Agent "${agent.name}" declares a built-in tool allowlist (tools), which cannot be enforced on cloud turns yet. Refusing the cloud session rather than running it unrestricted.`);
|
|
1159
|
-
}
|
|
1160
|
-
const mergedCloud = runtime === "cloud"
|
|
1161
|
-
? mergeCloudOptions({
|
|
1162
|
-
base: agent.cloud,
|
|
1163
|
-
override: record.cloudOverride,
|
|
1164
|
-
})
|
|
1165
|
-
: undefined;
|
|
1166
|
-
let turnPrompt = prompt;
|
|
1167
|
-
if (isFirstTurn) {
|
|
1168
|
-
const contextBlock = buildSessionContextBlock({
|
|
1169
|
-
sessionId: record.sessionId,
|
|
1170
|
-
channelId: record.channelId,
|
|
1171
|
-
channelKind: (_b = (_a = this.channelsById.get(record.channelId)) === null || _a === void 0 ? void 0 : _a.meta) === null || _b === void 0 ? void 0 : _b.kind,
|
|
1172
|
-
environment: isDevMode() ? "local dev server" : "hosted deployment",
|
|
1371
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1372
|
+
var _a, _b, _c;
|
|
1373
|
+
// Per-session cloud attach (channel/tool handoff) forces cloud runtime
|
|
1374
|
+
// even when the agent defaults to local for chat server tools.
|
|
1375
|
+
const runtime = resolveSessionRuntime({
|
|
1376
|
+
agentRuntime: agent.runtime,
|
|
1377
|
+
cloudOverride: record.cloudOverride,
|
|
1173
1378
|
});
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1379
|
+
// Fail closed: the SDK cannot enforce the built-in tool allowlist on
|
|
1380
|
+
// cloud agents yet, and a restriction that silently does not apply is
|
|
1381
|
+
// worse than a refused turn. Cloud-runtime agents already fail
|
|
1382
|
+
// discovery; this guards hybrid agents' per-send cloud sessions.
|
|
1383
|
+
if (runtime === "cloud" && agent.allowedTools !== undefined) {
|
|
1384
|
+
throw new Error(`Agent "${agent.name}" declares a built-in tool allowlist (tools), which cannot be enforced on cloud turns yet. Refusing the cloud session rather than running it unrestricted.`);
|
|
1385
|
+
}
|
|
1386
|
+
// Same fail-closed posture for per-session connection features:
|
|
1387
|
+
// advertised tools are server tools (in-process, local turns only) and
|
|
1388
|
+
// per-session auth headers cannot reach a cloud VM's MCP config. A turn
|
|
1389
|
+
// that silently runs without its per-session tools or identity is worse
|
|
1390
|
+
// than a refused one. Cloud-runtime agents already fail discovery; this
|
|
1391
|
+
// guards hybrid agents' per-send cloud sessions.
|
|
1392
|
+
const perSessionConnection = agent.connections.find((connection) => connection.advertiseTools === true || connection.auth !== undefined);
|
|
1393
|
+
if (runtime === "cloud" && perSessionConnection !== undefined) {
|
|
1394
|
+
throw new Error(`Agent "${agent.name}" connection "${perSessionConnection.name}" uses advertiseTools/auth, which only run on local turns. Refusing the cloud session rather than running it without the connection's tools or identity.`);
|
|
1395
|
+
}
|
|
1396
|
+
const mergedCloud = runtime === "cloud"
|
|
1397
|
+
? mergeCloudOptions({
|
|
1398
|
+
base: agent.cloud,
|
|
1399
|
+
override: record.cloudOverride,
|
|
1400
|
+
})
|
|
1401
|
+
: undefined;
|
|
1402
|
+
let turnPrompt = prompt;
|
|
1403
|
+
if (isFirstTurn) {
|
|
1404
|
+
const contextBlock = buildSessionContextBlock({
|
|
1405
|
+
sessionId: record.sessionId,
|
|
1406
|
+
channelId: record.channelId,
|
|
1407
|
+
channelKind: (_b = (_a = this.channelsById.get(record.channelId)) === null || _a === void 0 ? void 0 : _a.meta) === null || _b === void 0 ? void 0 : _b.kind,
|
|
1408
|
+
environment: isDevMode() ? "local dev server" : "hosted deployment",
|
|
1190
1409
|
});
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
// Local follow-ups expire any lingering active run so a preempted turn
|
|
1213
|
-
// (or one orphaned by a crashed process) never blocks this send.
|
|
1214
|
-
forceExpireActiveRun: runtime === "local" && !isFirstTurn, onSdkAgentId: (sdkAgentId) => __awaiter(this, void 0, void 0, function* () {
|
|
1215
|
-
if (record.sdkAgentId === sdkAgentId) {
|
|
1216
|
-
return;
|
|
1410
|
+
turnPrompt = `${contextBlock}\n\n${prompt}`;
|
|
1411
|
+
}
|
|
1412
|
+
if (isFirstTurn) {
|
|
1413
|
+
if (runtime === "cloud") {
|
|
1414
|
+
// Cloud VMs have no materialized workspace: ship the full context,
|
|
1415
|
+
// including agent tool script bodies. On hosted deployments the
|
|
1416
|
+
// shared memory journal is on the store mount — say where. When the
|
|
1417
|
+
// turn carries the session-bound server-tool MCP endpoint, the
|
|
1418
|
+
// preamble catalogs server tools under that MCP server name *before*
|
|
1419
|
+
// the agent-tool "write the script if missing" recipe, so a model
|
|
1420
|
+
// told to "use <server tool>" calls MCP instead of inventing a .sh.
|
|
1421
|
+
const preamble = buildAgentsMdContent(agent, {
|
|
1422
|
+
includeScripts: true,
|
|
1423
|
+
serverToolsMcpName: this.cloudServerToolsConnection === undefined
|
|
1424
|
+
? undefined
|
|
1425
|
+
: SERVER_TOOLS_MCP_SERVER_NAME,
|
|
1426
|
+
});
|
|
1427
|
+
const memorySection = isCursorHostedFilesAvailable()
|
|
1428
|
+
? `\n\n${buildCloudMemorySection(this.project.name)}`
|
|
1429
|
+
: "";
|
|
1430
|
+
turnPrompt = `${preamble}${memorySection}\n\n${turnPrompt}`;
|
|
1217
1431
|
}
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
}
|
|
1227
|
-
}
|
|
1432
|
+
else if (instructionsInvisibleToHarness) {
|
|
1433
|
+
// Local workspace whose AGENTS.md the harness will not load. With a
|
|
1434
|
+
// borrowed harness cwd, agent tool scripts live only in the durable
|
|
1435
|
+
// scaffold dir — embed bodies like cloud. Otherwise the catalog can
|
|
1436
|
+
// reference on-disk paths under the harness cwd.
|
|
1437
|
+
const includeScripts = this.hasBorrowedWorkspace(record);
|
|
1438
|
+
const preamble = buildAgentsMdContent(agent, { includeScripts });
|
|
1439
|
+
turnPrompt = `${preamble}\n\n${turnPrompt}`;
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ sessionId: record.sessionId }, (record.sdkAgentId === undefined
|
|
1443
|
+
? {}
|
|
1444
|
+
: { sdkAgentId: record.sdkAgentId })), { workspaceDir: this.harnessCwd(record), sandbox: ((_c = this.project.agent.local) === null || _c === void 0 ? void 0 : _c.sandbox) === true }), (runtime === "local" && agent.allowedTools !== undefined
|
|
1445
|
+
? { tools: agent.allowedTools }
|
|
1446
|
+
: {})), { runnerStateDir: join(this.stateRoot, "runner"), prompt: turnPrompt, isFirstTurn }), (images !== undefined && images.length > 0 ? { images } : {})), (agent.model === undefined ? {} : { model: agent.model })), { runtime }), (mergedCloud === undefined ? {} : { cloud: mergedCloud })), { customTools: runtime === "local"
|
|
1447
|
+
? yield this.buildCustomTools(record, agent, turnId, emit)
|
|
1448
|
+
: {}, mcpServers: this.buildMcpServers(runtime, record.sessionId, yield this.evaluateConnectionAuth(record)) }), (runtime === "local" && this.hasAttachedAuthedConnections()
|
|
1449
|
+
? { perSendMcpServers: true }
|
|
1450
|
+
: {})), { subagents: buildRunnerSubagents(agent), onUpdate,
|
|
1451
|
+
signal,
|
|
1452
|
+
// Local follow-ups expire any lingering active run so a preempted turn
|
|
1453
|
+
// (or one orphaned by a crashed process) never blocks this send.
|
|
1454
|
+
forceExpireActiveRun: runtime === "local" && !isFirstTurn, onRequestId: (requestId) => {
|
|
1455
|
+
emit({ type: "turn.bound", data: { requestId } });
|
|
1456
|
+
}, onSdkAgentId: (sdkAgentId) => __awaiter(this, void 0, void 0, function* () {
|
|
1457
|
+
if (record.sdkAgentId === sdkAgentId) {
|
|
1458
|
+
return;
|
|
1459
|
+
}
|
|
1460
|
+
yield this.updateRecord(record.sessionId, (current) => (Object.assign(Object.assign({}, current), { sdkAgentId })));
|
|
1461
|
+
record.sdkAgentId = sdkAgentId;
|
|
1462
|
+
yield this.appendEvent(record.sessionId, {
|
|
1463
|
+
type: "agent.bound",
|
|
1464
|
+
turnId,
|
|
1465
|
+
data: Object.assign({ sdkAgentId }, (sdkAgentId.startsWith("bc-")
|
|
1466
|
+
? { url: `https://cursor.com/agents/${sdkAgentId}` }
|
|
1467
|
+
: {})),
|
|
1468
|
+
});
|
|
1469
|
+
}) });
|
|
1470
|
+
});
|
|
1228
1471
|
}
|
|
1229
1472
|
/**
|
|
1230
1473
|
* Connection names a cloud turn can only reach through the host connection
|
|
@@ -1288,9 +1531,12 @@ export class SessionEngine {
|
|
|
1288
1531
|
* mount as host connection bridges over the tool MCP endpoint instead of
|
|
1289
1532
|
* being omitted or failing the turn closed.
|
|
1290
1533
|
*/
|
|
1291
|
-
buildMcpServers(runtime, sessionId) {
|
|
1534
|
+
buildMcpServers(runtime, sessionId, sessionAuth) {
|
|
1535
|
+
// Advertised connections surface as named server tools instead of an
|
|
1536
|
+
// attached MCP server (each tool appears exactly once), so they are
|
|
1537
|
+
// excluded here alongside hostOnly.
|
|
1292
1538
|
const hostOnlyNames = new Set(this.project.agent.connections
|
|
1293
|
-
.filter((connection) => connection.hostOnly === true)
|
|
1539
|
+
.filter((connection) => connection.hostOnly === true || connection.advertiseTools === true)
|
|
1294
1540
|
.map((connection) => connection.name));
|
|
1295
1541
|
const bridgedNames = new Set(runtime === "cloud" &&
|
|
1296
1542
|
sessionId !== undefined &&
|
|
@@ -1327,6 +1573,20 @@ export class SessionEngine {
|
|
|
1327
1573
|
}
|
|
1328
1574
|
continue;
|
|
1329
1575
|
}
|
|
1576
|
+
// Per-session auth: merge the evaluated headers over the static
|
|
1577
|
+
// ones. With no evaluation in hand (prewarm has no session) the
|
|
1578
|
+
// connection is omitted rather than attached without its identity.
|
|
1579
|
+
if (connection.auth !== undefined) {
|
|
1580
|
+
const evaluated = sessionAuth === null || sessionAuth === void 0 ? void 0 : sessionAuth.get(connection.name);
|
|
1581
|
+
if (evaluated === undefined) {
|
|
1582
|
+
continue;
|
|
1583
|
+
}
|
|
1584
|
+
const transport = connection.transport;
|
|
1585
|
+
if ("url" in transport) {
|
|
1586
|
+
servers[connection.name] = Object.assign(Object.assign({}, transport), { headers: Object.assign(Object.assign({}, transport.headers), evaluated) });
|
|
1587
|
+
}
|
|
1588
|
+
continue;
|
|
1589
|
+
}
|
|
1330
1590
|
servers[connection.name] = connection.transport;
|
|
1331
1591
|
}
|
|
1332
1592
|
for (const resolved of this.resolvedConnections) {
|
|
@@ -1365,32 +1625,88 @@ export class SessionEngine {
|
|
|
1365
1625
|
}
|
|
1366
1626
|
return servers;
|
|
1367
1627
|
}
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1628
|
+
/**
|
|
1629
|
+
* True when a connection with per-session `auth` attaches to the model
|
|
1630
|
+
* (not `hostOnly`, not advertised). These turns set
|
|
1631
|
+
* {@link RunnerTurnRequest.perSendMcpServers} so the evaluated headers
|
|
1632
|
+
* ride every send instead of being pinned on the cached agent handle.
|
|
1633
|
+
*/
|
|
1634
|
+
hasAttachedAuthedConnections() {
|
|
1635
|
+
return this.project.agent.connections.some((connection) => connection.auth !== undefined &&
|
|
1636
|
+
connection.hostOnly !== true &&
|
|
1637
|
+
connection.advertiseTools !== true);
|
|
1638
|
+
}
|
|
1639
|
+
/**
|
|
1640
|
+
* Evaluate per-session connection auth (`defineConnection({ auth })`)
|
|
1641
|
+
* for connections that attach to the model this turn. Advertised
|
|
1642
|
+
* connections evaluate their own auth inside the tool synthesis path. A
|
|
1643
|
+
* callback that throws fails the turn — a turn never silently runs
|
|
1644
|
+
* without the connection's identity.
|
|
1645
|
+
*/
|
|
1646
|
+
evaluateConnectionAuth(record) {
|
|
1647
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1648
|
+
const authed = this.project.agent.connections.filter((connection) => connection.auth !== undefined &&
|
|
1649
|
+
connection.hostOnly !== true &&
|
|
1650
|
+
connection.advertiseTools !== true);
|
|
1651
|
+
if (authed.length === 0) {
|
|
1652
|
+
return undefined;
|
|
1373
1653
|
}
|
|
1374
|
-
const
|
|
1375
|
-
|
|
1376
|
-
|
|
1654
|
+
const session = this.sessionInfo(record);
|
|
1655
|
+
const evaluated = new Map();
|
|
1656
|
+
for (const connection of authed) {
|
|
1657
|
+
const auth = connection.auth;
|
|
1658
|
+
if (auth === undefined) {
|
|
1659
|
+
continue;
|
|
1660
|
+
}
|
|
1661
|
+
try {
|
|
1662
|
+
evaluated.set(connection.name, (yield auth(session)).headers);
|
|
1663
|
+
}
|
|
1664
|
+
catch (error) {
|
|
1665
|
+
throw new Error(`Connection "${connection.name}" auth failed: ${describeError(error)}`, { cause: error });
|
|
1666
|
+
}
|
|
1377
1667
|
}
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1668
|
+
return evaluated;
|
|
1669
|
+
});
|
|
1670
|
+
}
|
|
1671
|
+
buildCustomTools(record, agent, turnId, emit) {
|
|
1672
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1673
|
+
const tools = {};
|
|
1674
|
+
const register = (tool) => {
|
|
1675
|
+
tools[tool.name] = Object.assign(Object.assign({ description: tool.description, inputSchema: tool.inputSchema }, (tool.outputSchema === undefined
|
|
1676
|
+
? {}
|
|
1677
|
+
: { outputSchema: tool.outputSchema })), { execute: (args, context) => __awaiter(this, void 0, void 0, function* () {
|
|
1678
|
+
return this.executeServerTool({
|
|
1679
|
+
tool,
|
|
1680
|
+
input: args,
|
|
1681
|
+
toolCallId: context.toolCallId,
|
|
1682
|
+
record,
|
|
1683
|
+
turnId,
|
|
1684
|
+
emit,
|
|
1685
|
+
});
|
|
1686
|
+
}) });
|
|
1391
1687
|
};
|
|
1392
|
-
|
|
1393
|
-
|
|
1688
|
+
for (const tool of agent.tools) {
|
|
1689
|
+
if (tool.execution !== "server") {
|
|
1690
|
+
continue;
|
|
1691
|
+
}
|
|
1692
|
+
if (typeof tool.definition.execute !== "function") {
|
|
1693
|
+
continue;
|
|
1694
|
+
}
|
|
1695
|
+
register(tool);
|
|
1696
|
+
}
|
|
1697
|
+
// Advertised connections (`defineConnection({ advertiseTools })`):
|
|
1698
|
+
// named passthroughs resolved fresh on every turn — reminder fires and
|
|
1699
|
+
// post-restart follow-ups included — and a listing failure throws,
|
|
1700
|
+
// failing the turn closed.
|
|
1701
|
+
for (const tool of yield resolveAdvertisedTools({
|
|
1702
|
+
agent,
|
|
1703
|
+
session: this.sessionInfo(record),
|
|
1704
|
+
hostMcp: this.mcpHost,
|
|
1705
|
+
})) {
|
|
1706
|
+
register(tool);
|
|
1707
|
+
}
|
|
1708
|
+
return tools;
|
|
1709
|
+
});
|
|
1394
1710
|
}
|
|
1395
1711
|
/**
|
|
1396
1712
|
* True while `sessionId` has a turn in flight. The host connection bridge
|
|
@@ -2577,6 +2893,8 @@ export class SessionEngine {
|
|
|
2577
2893
|
yield Promise.allSettled([...this.backgroundWork]);
|
|
2578
2894
|
yield Promise.allSettled([...this.dispatchChains.values()]);
|
|
2579
2895
|
yield Promise.allSettled([...this.updateChains.values()]);
|
|
2896
|
+
// After the drain, so a successor need not wait out the heartbeat TTL.
|
|
2897
|
+
yield this.turnLeases.releaseAll().catch(() => { });
|
|
2580
2898
|
yield this.logs.flushAll();
|
|
2581
2899
|
yield (_c = this.storage) === null || _c === void 0 ? void 0 : _c.close();
|
|
2582
2900
|
yield this.mcpHost.close();
|