@cursor/july 0.1.103 → 0.1.105
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/channels/change-monitors/change-monitors-channel.d.ts +5 -0
- package/dist/channels/change-monitors/change-monitors-channel.d.ts.map +1 -0
- package/dist/channels/change-monitors/change-monitors-channel.js +41 -0
- package/dist/channels/change-monitors/index.d.ts +4 -0
- package/dist/channels/change-monitors/index.d.ts.map +1 -0
- package/dist/channels/change-monitors/index.js +2 -0
- package/dist/channels/change-monitors/types.d.ts +39 -0
- package/dist/channels/change-monitors/types.d.ts.map +1 -0
- package/dist/channels/change-monitors/types.js +1 -0
- package/dist/channels/deployments/deployments-channel.d.ts.map +1 -1
- package/dist/channels/deployments/deployments-channel.js +1 -0
- package/dist/channels/deployments/types.d.ts +2 -0
- package/dist/channels/deployments/types.d.ts.map +1 -1
- package/dist/channels/github/defaults.d.ts.map +1 -1
- package/dist/channels/github/defaults.js +4 -0
- package/dist/channels/github/progress.d.ts +22 -0
- package/dist/channels/github/progress.d.ts.map +1 -1
- package/dist/channels/github/progress.js +32 -5
- package/dist/channels/issues/index.d.ts +4 -0
- package/dist/channels/issues/index.d.ts.map +1 -0
- package/dist/channels/issues/index.js +2 -0
- package/dist/channels/issues/issues-channel.d.ts +5 -0
- package/dist/channels/issues/issues-channel.d.ts.map +1 -0
- package/dist/channels/issues/issues-channel.js +37 -0
- package/dist/channels/issues/types.d.ts +33 -0
- package/dist/channels/issues/types.d.ts.map +1 -0
- package/dist/channels/issues/types.js +1 -0
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +4 -4
- package/dist/docs/assets/{app.DW9pXkpL.js → app.BVwaffdK.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.CJxMklAy.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.CdEIUwDg.js → VPLocalSearchBox.WuWTZ1x8.js} +1 -1
- package/dist/docs/assets/chunks/{theme.YZMHgPgg.js → theme.BYdI_6sF.js} +2 -2
- package/dist/docs/assets/{guides_github.md.DKYiT9do.js → guides_github.md.c0gdGJ-D.js} +1 -1
- package/dist/docs/assets/{guides_github.md.DKYiT9do.lean.js → guides_github.md.c0gdGJ-D.lean.js} +1 -1
- package/dist/docs/assets/guides_grokbot-agents.md.DVqdhBKb.js +9 -0
- package/dist/docs/assets/guides_grokbot-agents.md.DVqdhBKb.lean.js +1 -0
- package/dist/docs/assets/index.md.BW_6tOgR.js +5 -0
- package/dist/docs/assets/index.md.BW_6tOgR.lean.js +1 -0
- package/dist/docs/assets/{reference_channels.md.Dz8Cnudp.js → reference_channels.md.DBGcaB7m.js} +2 -2
- package/dist/docs/assets/{reference_channels.md.Dz8Cnudp.lean.js → reference_channels.md.DBGcaB7m.lean.js} +1 -1
- package/dist/docs/assets/{reference_cli.md.CJNc_xYK.js → reference_cli.md.C6Lo8SEd.js} +2 -1
- package/dist/docs/assets/{reference_extensions.md.DykHW3qL.js → reference_extensions.md.CGmMLblt.js} +1 -1
- package/dist/docs/assets/templates_grokbot-agents.md.c1s6Flp9.js +3 -0
- package/dist/docs/assets/templates_grokbot-agents.md.c1s6Flp9.lean.js +1 -0
- 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/design/filesystem.md +376 -0
- package/dist/docs/evals.html +4 -4
- package/dist/docs/guides/agent-to-agent.html +4 -4
- package/dist/docs/guides/cloud-agents.html +5 -5
- package/dist/docs/guides/convert-automation.html +4 -4
- package/dist/docs/guides/github.html +6 -6
- package/dist/docs/guides/github.md +5 -0
- package/dist/docs/guides/grokbot-agents.html +35 -0
- package/dist/docs/guides/grokbot-agents.md +88 -0
- 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 +5 -5
- package/dist/docs/guides/slack.html +4 -4
- package/dist/docs/guides/webhooks.html +4 -4
- package/dist/docs/hashmap.json +1 -1
- package/dist/docs/hillclimbing.html +4 -4
- package/dist/docs/index.html +6 -6
- package/dist/docs/index.md +6 -1
- package/dist/docs/llms-full.txt +562 -2
- package/dist/docs/llms.txt +3 -0
- 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 +6 -6
- package/dist/docs/reference/channels.md +20 -0
- package/dist/docs/reference/cli.html +7 -6
- package/dist/docs/reference/cli.md +2 -1
- package/dist/docs/reference/connections.html +4 -4
- package/dist/docs/reference/extensions.html +6 -6
- package/dist/docs/reference/extensions.md +2 -0
- package/dist/docs/reference/hooks.html +4 -4
- package/dist/docs/reference/http-api.html +4 -4
- package/dist/docs/reference/instructions.html +4 -4
- package/dist/docs/reference/playground.html +4 -4
- package/dist/docs/reference/project-layout.html +4 -4
- package/dist/docs/reference/prompt.html +4 -4
- package/dist/docs/reference/result.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 +5 -5
- package/dist/docs/templates/demo.html +5 -5
- package/dist/docs/templates/grokbot-agents.html +29 -0
- package/dist/docs/templates/grokbot-agents.md +48 -0
- 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/files.d.ts +7 -2
- package/dist/files.d.ts.map +1 -1
- package/dist/files.js +7 -2
- package/dist/filesystem.d.ts +440 -0
- package/dist/filesystem.d.ts.map +1 -0
- package/dist/filesystem.js +1694 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/internal/continuation-channel.d.ts +4 -2
- package/dist/internal/continuation-channel.d.ts.map +1 -1
- package/dist/internal/continuation-channel.js +21 -12
- package/dist/internal/continuation-identity.d.ts +13 -5
- package/dist/internal/continuation-identity.d.ts.map +1 -1
- package/dist/internal/continuation-identity.js +74 -35
- package/dist/internal/cursor-agent-template.d.ts +1 -1
- package/dist/internal/cursor-agent-template.d.ts.map +1 -1
- package/dist/internal/cursor-agent-template.js +1 -0
- package/dist/internal/discovery/agent.d.ts.map +1 -1
- package/dist/internal/discovery/agent.js +80 -8
- package/dist/internal/discovery/info.d.ts.map +1 -1
- package/dist/internal/discovery/info.js +3 -0
- package/dist/internal/factory-resource-channel.d.ts +33 -0
- package/dist/internal/factory-resource-channel.d.ts.map +1 -0
- package/dist/internal/factory-resource-channel.js +77 -0
- package/dist/internal/filesystem/errors.d.ts +64 -0
- package/dist/internal/filesystem/errors.d.ts.map +1 -0
- package/dist/internal/filesystem/errors.js +175 -0
- package/dist/internal/filesystem/format.d.ts +28 -0
- package/dist/internal/filesystem/format.d.ts.map +1 -0
- package/dist/internal/filesystem/format.js +213 -0
- package/dist/internal/filesystem/merge.d.ts +34 -0
- package/dist/internal/filesystem/merge.d.ts.map +1 -0
- package/dist/internal/filesystem/merge.js +196 -0
- package/dist/internal/filesystem/scratch.d.ts +59 -0
- package/dist/internal/filesystem/scratch.d.ts.map +1 -0
- package/dist/internal/filesystem/scratch.js +212 -0
- package/dist/internal/filesystem/sdk-schemas.d.ts +29 -0
- package/dist/internal/filesystem/sdk-schemas.d.ts.map +1 -0
- package/dist/internal/filesystem/sdk-schemas.js +153 -0
- package/dist/internal/filesystem/tools.d.ts +42 -0
- package/dist/internal/filesystem/tools.d.ts.map +1 -0
- package/dist/internal/filesystem/tools.js +618 -0
- package/dist/internal/filesystem/walk.d.ts +74 -0
- package/dist/internal/filesystem/walk.d.ts.map +1 -0
- package/dist/internal/filesystem/walk.js +339 -0
- package/dist/internal/framework-file-storage.d.ts +3 -1
- package/dist/internal/framework-file-storage.d.ts.map +1 -1
- package/dist/internal/framework-file-storage.js +3 -1
- package/dist/internal/guest-network.d.ts +5 -2
- package/dist/internal/guest-network.d.ts.map +1 -1
- package/dist/internal/guest-network.js +7 -2
- package/dist/internal/hosted-delivery.d.ts +7 -0
- package/dist/internal/hosted-delivery.d.ts.map +1 -1
- package/dist/internal/hosted-delivery.js +41 -3
- package/dist/internal/hosted-managed-automation.d.ts.map +1 -1
- package/dist/internal/hosted-managed-automation.js +27 -2
- package/dist/internal/init-project.d.ts.map +1 -1
- package/dist/internal/init-project.js +4 -1
- package/dist/internal/sdk-runner.d.ts +13 -1
- package/dist/internal/sdk-runner.d.ts.map +1 -1
- package/dist/internal/sdk-runner.js +21 -1
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +11 -3
- package/dist/internal/session-engine.d.ts +30 -0
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +484 -82
- package/dist/internal/store-mirror.d.ts +2 -2
- package/dist/internal/store-mirror.js +2 -2
- package/dist/internal/tenant-slack/relay.d.ts +2 -0
- package/dist/internal/tenant-slack/relay.d.ts.map +1 -1
- package/dist/internal/tenant-slack/relay.js +18 -8
- package/dist/internal/testing/factory-resource-fixtures.d.ts +48 -0
- package/dist/internal/testing/factory-resource-fixtures.d.ts.map +1 -0
- package/dist/internal/testing/factory-resource-fixtures.js +41 -0
- package/dist/internal/workspace-sync.d.ts +31 -6
- package/dist/internal/workspace-sync.d.ts.map +1 -1
- package/dist/internal/workspace-sync.js +67 -12
- package/dist/internal/workspace.d.ts +9 -0
- package/dist/internal/workspace.d.ts.map +1 -1
- package/dist/internal/workspace.js +16 -0
- package/dist/playground/assets/{index-59KvgREd.js → index-BaQzjaNK.js} +40 -40
- package/dist/playground/assets/index-CceqnLTX.css +1 -0
- package/dist/playground/index.html +2 -2
- package/dist/types.d.ts +60 -11
- package/dist/types.d.ts.map +1 -1
- package/docs/README.md +6 -1
- package/docs/design/filesystem.md +376 -0
- package/docs/guides/github.md +5 -0
- package/docs/guides/grokbot-agents.md +93 -0
- package/docs/reference/channels.md +20 -0
- package/docs/reference/cli.md +2 -1
- package/docs/reference/extensions.md +2 -0
- package/docs/templates/grokbot-agents.md +53 -0
- package/package.json +25 -2
- package/src/channels/change-monitors/change-monitors-channel.ts +62 -0
- package/src/channels/change-monitors/index.ts +8 -0
- package/src/channels/change-monitors/types.ts +44 -0
- package/src/channels/deployments/deployments-channel.ts +1 -0
- package/src/channels/deployments/types.ts +2 -0
- package/src/channels/github/defaults.ts +4 -0
- package/src/channels/github/progress.ts +76 -10
- package/src/channels/issues/index.ts +9 -0
- package/src/channels/issues/issues-channel.ts +51 -0
- package/src/channels/issues/types.ts +41 -0
- package/src/files.ts +9 -2
- package/src/filesystem.ts +2074 -0
- package/src/index.ts +28 -0
- package/src/internal/continuation-channel.ts +29 -12
- package/src/internal/continuation-identity.ts +122 -37
- package/src/internal/cursor-agent-template.ts +1 -0
- package/src/internal/discovery/agent.ts +87 -1
- package/src/internal/discovery/info.ts +3 -0
- package/src/internal/factory-resource-channel.ts +122 -0
- package/src/internal/filesystem/errors.ts +220 -0
- package/src/internal/filesystem/format.ts +276 -0
- package/src/internal/filesystem/merge.ts +195 -0
- package/src/internal/filesystem/scratch.ts +255 -0
- package/src/internal/filesystem/sdk-schemas.ts +204 -0
- package/src/internal/filesystem/tools.ts +821 -0
- package/src/internal/filesystem/walk.ts +369 -0
- package/src/internal/framework-file-storage.ts +3 -1
- package/src/internal/guest-network.ts +8 -2
- package/src/internal/hosted-delivery.ts +71 -2
- package/src/internal/hosted-managed-automation.ts +49 -5
- package/src/internal/init-project.ts +4 -1
- package/src/internal/sdk-runner.ts +23 -0
- package/src/internal/server.ts +16 -0
- package/src/internal/session-engine.ts +595 -73
- package/src/internal/store-mirror.ts +2 -2
- package/src/internal/tenant-slack/relay.ts +28 -4
- package/src/internal/testing/factory-resource-fixtures.ts +85 -0
- package/src/internal/workspace-sync.ts +84 -12
- package/src/internal/workspace.ts +20 -0
- package/src/types.ts +58 -11
- package/templates/grokbot-agents/README.md +26 -0
- package/templates/grokbot-agents/agent/agent.ts +6 -0
- package/templates/grokbot-agents/agent/extensions/grokbot.ts +5 -0
- package/templates/grokbot-agents/agent/instructions.md +7 -0
- package/templates/grokbot-agents/evals/evals.config.ts +5 -0
- package/templates/grokbot-agents/evals/grokbot-agents.eval.ts +18 -0
- package/templates/grokbot-agents/init.json +18 -0
- package/templates/grokbot-agents/package.json +17 -0
- package/templates/grokbot-agents/tsconfig.json +12 -0
- package/dist/docs/assets/chunks/@localSearchIndexroot.nrl1ifBq.js +0 -1
- package/dist/docs/assets/index.md.D75GWhPl.js +0 -5
- package/dist/docs/assets/index.md.D75GWhPl.lean.js +0 -1
- package/dist/playground/assets/index-MbMxXg-y.css +0 -1
- /package/dist/docs/assets/{reference_cli.md.CJNc_xYK.lean.js → reference_cli.md.C6Lo8SEd.lean.js} +0 -0
- /package/dist/docs/assets/{reference_extensions.md.DykHW3qL.lean.js → reference_extensions.md.CGmMLblt.lean.js} +0 -0
|
@@ -37,6 +37,17 @@ import {
|
|
|
37
37
|
isCursorHostedFilesAvailable,
|
|
38
38
|
isCursorUserStoreFilesAvailable,
|
|
39
39
|
} from "../files-backends/cursor-hosted.js";
|
|
40
|
+
import {
|
|
41
|
+
type FileSystem,
|
|
42
|
+
isFileSystemToolAlias,
|
|
43
|
+
isMutatingFileSystemTool,
|
|
44
|
+
localFileSystem,
|
|
45
|
+
resolveFileSystemTools,
|
|
46
|
+
resolveHostHarnessToolOptions,
|
|
47
|
+
tempFs,
|
|
48
|
+
unionFs,
|
|
49
|
+
writeWorkspaceFilesToRoot,
|
|
50
|
+
} from "../filesystem.js";
|
|
40
51
|
import { storageKeysForArchitecture } from "../storage.js";
|
|
41
52
|
import {
|
|
42
53
|
type AgentProject,
|
|
@@ -69,6 +80,7 @@ import {
|
|
|
69
80
|
type PendingAuth,
|
|
70
81
|
type ReceiveInput,
|
|
71
82
|
type ResolvedAgent,
|
|
83
|
+
type RootContext,
|
|
72
84
|
type RunnerSubagent,
|
|
73
85
|
type RunnerTurnRequest,
|
|
74
86
|
type SendMessageFn,
|
|
@@ -124,6 +136,17 @@ import { describeError } from "./describe-error.js";
|
|
|
124
136
|
import { isDevMode } from "./dev-mode.js";
|
|
125
137
|
import { type EventLogMemoryStats, EventLogStore } from "./event-log.js";
|
|
126
138
|
import { TurnEventMapper } from "./event-mapper.js";
|
|
139
|
+
import {
|
|
140
|
+
applyWebScratchUpdate,
|
|
141
|
+
ingestHostAgentToolsScratch,
|
|
142
|
+
remapScratchPaths,
|
|
143
|
+
} from "./filesystem/scratch.js";
|
|
144
|
+
import {
|
|
145
|
+
fileSystemTool,
|
|
146
|
+
hostFileToolsFromAuthored,
|
|
147
|
+
rootFileToolsFromAuthored,
|
|
148
|
+
synthesizeFileSystemTools,
|
|
149
|
+
} from "./filesystem/tools.js";
|
|
127
150
|
import { selectFrameworkStorageDefinition } from "./framework-storage-selection.js";
|
|
128
151
|
import { createHostFilesApi } from "./host-files.js";
|
|
129
152
|
import { createHostKvApi } from "./host-kv.js";
|
|
@@ -179,14 +202,19 @@ import { resolveAgentV2Hosting } from "./v2-hosting.js";
|
|
|
179
202
|
import {
|
|
180
203
|
buildAgentsMdContent,
|
|
181
204
|
buildCloudMemorySection,
|
|
205
|
+
buildCloudSessionWorkspaceSection,
|
|
182
206
|
buildCloudWorkspaceSection,
|
|
183
207
|
ensureOwnGitRoot,
|
|
184
208
|
isNestedInGitRepo,
|
|
185
209
|
materializeWorkspace,
|
|
186
210
|
SERVER_TOOLS_MCP_SERVER_NAME,
|
|
187
|
-
writeWorkspaceFiles,
|
|
188
211
|
} from "./workspace.js";
|
|
189
|
-
import {
|
|
212
|
+
import {
|
|
213
|
+
syncSessionWorkspaceFiles,
|
|
214
|
+
syncWorkspaceToStore,
|
|
215
|
+
WorkspaceFileRefusal,
|
|
216
|
+
wipeSessionWorkspace,
|
|
217
|
+
} from "./workspace-sync.js";
|
|
190
218
|
|
|
191
219
|
export const HTTP_CHANNEL_ID = "http";
|
|
192
220
|
export { SCHEDULE_CHANNEL_PREFIX } from "./prompt-context.js";
|
|
@@ -200,6 +228,7 @@ interface CloudStoreTarget {
|
|
|
200
228
|
mountPath: string;
|
|
201
229
|
skillsPrefix: string;
|
|
202
230
|
workspacePrefix: string;
|
|
231
|
+
sessionWorkspacePrefix: (sessionId: string) => string;
|
|
203
232
|
label: string;
|
|
204
233
|
}
|
|
205
234
|
|
|
@@ -510,6 +539,15 @@ export class SessionEngine {
|
|
|
510
539
|
private readonly cloudCostBackfillDone = new Set<string>();
|
|
511
540
|
/** Deduped in-flight cloud cost backfills, keyed by session id. */
|
|
512
541
|
private readonly cloudCostBackfill = new Map<string, Promise<void>>();
|
|
542
|
+
/** Per-session FileSystem handle cache (not a tree snapshot). */
|
|
543
|
+
private readonly sessionRoots = new Map<string, FileSystem>();
|
|
544
|
+
/** In-flight factory resolves, so the first turn and a racing callTool share one handle. */
|
|
545
|
+
private readonly sessionRootInflight = new Map<string, Promise<FileSystem>>();
|
|
546
|
+
/** Per-turn `unionFs({ "/": authored, "/tmp": tempFs() })`. Dropped at end of turn. */
|
|
547
|
+
private readonly turnRoots = new Map<
|
|
548
|
+
string,
|
|
549
|
+
{ root: FileSystem; cwd: string }
|
|
550
|
+
>();
|
|
513
551
|
|
|
514
552
|
constructor(options: SessionEngineOptions) {
|
|
515
553
|
this.project = options.project;
|
|
@@ -1793,6 +1831,261 @@ export class SessionEngine {
|
|
|
1793
1831
|
return this.interrupt(existing.sessionId);
|
|
1794
1832
|
}
|
|
1795
1833
|
|
|
1834
|
+
private hasRootOverride(): boolean {
|
|
1835
|
+
return this.project.agent.root !== undefined;
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
/**
|
|
1839
|
+
* FileSystem for this turn or `callTool`. Authored `root` (static or
|
|
1840
|
+
* factory) is cached per session when `cache` is set; unscoped
|
|
1841
|
+
* `callTool` passes the invocation id and does not cache, so factories
|
|
1842
|
+
* cannot share one tree across callers. A static handle is used as
|
|
1843
|
+
* authored — every session sees that same object. Host-default agents
|
|
1844
|
+
* get `localFileSystem` against the harness cwd — same path verbs as
|
|
1845
|
+
* the Cursor SDK natives, always through the synthesized tools.
|
|
1846
|
+
*/
|
|
1847
|
+
private async resolveFileSystem(options: {
|
|
1848
|
+
sessionId: string;
|
|
1849
|
+
continuationKey?: string;
|
|
1850
|
+
cache: boolean;
|
|
1851
|
+
cwd: string;
|
|
1852
|
+
}): Promise<FileSystem> {
|
|
1853
|
+
const authored = this.project.agent.root;
|
|
1854
|
+
if (authored === undefined) {
|
|
1855
|
+
return localFileSystem({ cwd: options.cwd });
|
|
1856
|
+
}
|
|
1857
|
+
const authoredRoot = await this.resolveAuthoredFileSystem(options);
|
|
1858
|
+
if (options.cache) {
|
|
1859
|
+
const existing = this.turnRoots.get(options.sessionId);
|
|
1860
|
+
if (existing !== undefined) {
|
|
1861
|
+
return existing.root;
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
const root = unionFs({
|
|
1865
|
+
"/": authoredRoot,
|
|
1866
|
+
"/tmp": tempFs(),
|
|
1867
|
+
});
|
|
1868
|
+
if (options.cache && this.isTurnRunning(options.sessionId)) {
|
|
1869
|
+
this.turnRoots.set(options.sessionId, { root, cwd: options.cwd });
|
|
1870
|
+
}
|
|
1871
|
+
return root;
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
private async resolveAuthoredFileSystem(options: {
|
|
1875
|
+
sessionId: string;
|
|
1876
|
+
continuationKey?: string;
|
|
1877
|
+
cache: boolean;
|
|
1878
|
+
}): Promise<FileSystem> {
|
|
1879
|
+
const authored = this.project.agent.root;
|
|
1880
|
+
if (authored === undefined) {
|
|
1881
|
+
throw new Error("resolveAuthoredFileSystem requires an authored root");
|
|
1882
|
+
}
|
|
1883
|
+
if (options.cache) {
|
|
1884
|
+
const cached = this.sessionRoots.get(options.sessionId);
|
|
1885
|
+
if (cached !== undefined) {
|
|
1886
|
+
return cached;
|
|
1887
|
+
}
|
|
1888
|
+
const inflight = this.sessionRootInflight.get(options.sessionId);
|
|
1889
|
+
if (inflight !== undefined) {
|
|
1890
|
+
return inflight;
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
const resolve = async (): Promise<FileSystem> => {
|
|
1894
|
+
if (typeof authored !== "function") {
|
|
1895
|
+
return authored;
|
|
1896
|
+
}
|
|
1897
|
+
const host = this.hostForSession(
|
|
1898
|
+
options.cache ? options.sessionId : undefined
|
|
1899
|
+
);
|
|
1900
|
+
const ctx: RootContext = {
|
|
1901
|
+
sessionId: options.sessionId,
|
|
1902
|
+
agentName: this.project.agent.name,
|
|
1903
|
+
...(options.continuationKey === undefined
|
|
1904
|
+
? {}
|
|
1905
|
+
: { continuationKey: options.continuationKey }),
|
|
1906
|
+
host: { files: host.files, reminders: host.reminders },
|
|
1907
|
+
};
|
|
1908
|
+
return await authored(ctx);
|
|
1909
|
+
};
|
|
1910
|
+
if (!options.cache) {
|
|
1911
|
+
return resolve();
|
|
1912
|
+
}
|
|
1913
|
+
const pending = resolve();
|
|
1914
|
+
this.sessionRootInflight.set(options.sessionId, pending);
|
|
1915
|
+
try {
|
|
1916
|
+
const resolved = await pending;
|
|
1917
|
+
this.sessionRoots.set(options.sessionId, resolved);
|
|
1918
|
+
return resolved;
|
|
1919
|
+
} finally {
|
|
1920
|
+
this.sessionRootInflight.delete(options.sessionId);
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
private dropTurnFileSystem(sessionId: string): void {
|
|
1925
|
+
this.turnRoots.delete(sessionId);
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
private async prepareScratchToolInput(options: {
|
|
1929
|
+
sessionId?: string;
|
|
1930
|
+
input: Record<string, unknown>;
|
|
1931
|
+
}): Promise<Record<string, unknown>> {
|
|
1932
|
+
if (options.sessionId === undefined) {
|
|
1933
|
+
return options.input;
|
|
1934
|
+
}
|
|
1935
|
+
const turn = this.turnRoots.get(options.sessionId);
|
|
1936
|
+
if (turn === undefined) {
|
|
1937
|
+
return options.input;
|
|
1938
|
+
}
|
|
1939
|
+
await ingestHostAgentToolsScratch({
|
|
1940
|
+
cwd: turn.cwd,
|
|
1941
|
+
root: turn.root,
|
|
1942
|
+
});
|
|
1943
|
+
return remapScratchPaths(options.input, turn.cwd);
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
private withTurnScratchTools(
|
|
1947
|
+
tools: DiscoveredTool[],
|
|
1948
|
+
sessionId: string
|
|
1949
|
+
): DiscoveredTool[] {
|
|
1950
|
+
const turn = this.turnRoots.get(sessionId);
|
|
1951
|
+
if (turn === undefined) {
|
|
1952
|
+
return tools;
|
|
1953
|
+
}
|
|
1954
|
+
return tools.map((tool) => {
|
|
1955
|
+
const execute = tool.definition.execute;
|
|
1956
|
+
if (typeof execute !== "function") {
|
|
1957
|
+
return tool;
|
|
1958
|
+
}
|
|
1959
|
+
return {
|
|
1960
|
+
...tool,
|
|
1961
|
+
definition: {
|
|
1962
|
+
...tool.definition,
|
|
1963
|
+
execute: async (input, ctx) => {
|
|
1964
|
+
const remapped = await this.prepareScratchToolInput({
|
|
1965
|
+
sessionId,
|
|
1966
|
+
input:
|
|
1967
|
+
input !== null &&
|
|
1968
|
+
typeof input === "object" &&
|
|
1969
|
+
!Array.isArray(input)
|
|
1970
|
+
? (input as Record<string, unknown>)
|
|
1971
|
+
: {},
|
|
1972
|
+
});
|
|
1973
|
+
return execute(remapped as never, ctx);
|
|
1974
|
+
},
|
|
1975
|
+
},
|
|
1976
|
+
};
|
|
1977
|
+
});
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
private async nativeFileSystemOutcome(options: {
|
|
1981
|
+
toolName: string;
|
|
1982
|
+
input: unknown;
|
|
1983
|
+
callId: string;
|
|
1984
|
+
sessionId?: string;
|
|
1985
|
+
continuationKey?: string;
|
|
1986
|
+
cwd: string;
|
|
1987
|
+
dryRun?: boolean;
|
|
1988
|
+
}): Promise<ToolCallOutcome | undefined> {
|
|
1989
|
+
const { toolName, input, callId } = options;
|
|
1990
|
+
if (!isFileSystemToolAlias(toolName)) {
|
|
1991
|
+
return undefined;
|
|
1992
|
+
}
|
|
1993
|
+
// Same policy the authored callTool path asks: a dry-run write is
|
|
1994
|
+
// answered by the host. Native edit/delete used to return before
|
|
1995
|
+
// decideToolCall and persist through the FileSystem.
|
|
1996
|
+
const decision = decideToolCall({
|
|
1997
|
+
dryRun: options.dryRun === true,
|
|
1998
|
+
effect: isMutatingFileSystemTool(toolName) ? "write" : "read",
|
|
1999
|
+
input,
|
|
2000
|
+
});
|
|
2001
|
+
if (decision.kind === "answer") {
|
|
2002
|
+
return {
|
|
2003
|
+
callId,
|
|
2004
|
+
toolName,
|
|
2005
|
+
isError: false,
|
|
2006
|
+
result: decision.result,
|
|
2007
|
+
durationMs: 0,
|
|
2008
|
+
sessionId: options.sessionId,
|
|
2009
|
+
};
|
|
2010
|
+
}
|
|
2011
|
+
const root = await this.resolveFileSystem({
|
|
2012
|
+
sessionId: options.sessionId ?? callId,
|
|
2013
|
+
continuationKey: options.continuationKey,
|
|
2014
|
+
cache: options.sessionId !== undefined,
|
|
2015
|
+
cwd: options.cwd,
|
|
2016
|
+
});
|
|
2017
|
+
const rawArgs =
|
|
2018
|
+
input !== null && typeof input === "object" && !Array.isArray(input)
|
|
2019
|
+
? (input as Record<string, unknown>)
|
|
2020
|
+
: {};
|
|
2021
|
+
const args = await this.prepareScratchToolInput({
|
|
2022
|
+
sessionId: options.sessionId,
|
|
2023
|
+
input: rawArgs,
|
|
2024
|
+
});
|
|
2025
|
+
const tool = fileSystemTool(root, toolName, args);
|
|
2026
|
+
const execute = tool?.definition.execute;
|
|
2027
|
+
if (tool === undefined || typeof execute !== "function") {
|
|
2028
|
+
// A resolved root owns this native name. Falling through would let
|
|
2029
|
+
// an authored lookalike run, or surface UnknownToolError after we
|
|
2030
|
+
// already advertised the FileSystem tool.
|
|
2031
|
+
return {
|
|
2032
|
+
callId,
|
|
2033
|
+
toolName,
|
|
2034
|
+
isError: true,
|
|
2035
|
+
result: {
|
|
2036
|
+
status: "error",
|
|
2037
|
+
error: { message: `FileSystem cannot handle ${toolName}` },
|
|
2038
|
+
},
|
|
2039
|
+
durationMs: 0,
|
|
2040
|
+
sessionId: options.sessionId,
|
|
2041
|
+
errorMessage: `FileSystem cannot handle ${toolName}`,
|
|
2042
|
+
};
|
|
2043
|
+
}
|
|
2044
|
+
const validation = validateToolInput(tool.definition.inputSchema, args);
|
|
2045
|
+
if (!validation.ok) {
|
|
2046
|
+
return {
|
|
2047
|
+
callId,
|
|
2048
|
+
toolName,
|
|
2049
|
+
isError: true,
|
|
2050
|
+
result: {
|
|
2051
|
+
status: "error",
|
|
2052
|
+
error: { message: validation.message },
|
|
2053
|
+
},
|
|
2054
|
+
durationMs: 0,
|
|
2055
|
+
sessionId: options.sessionId,
|
|
2056
|
+
errorMessage: validation.message,
|
|
2057
|
+
};
|
|
2058
|
+
}
|
|
2059
|
+
try {
|
|
2060
|
+
const value = await execute(
|
|
2061
|
+
validation.value as never,
|
|
2062
|
+
{
|
|
2063
|
+
toolCallId: callId,
|
|
2064
|
+
workspaceDir: options.cwd,
|
|
2065
|
+
} as never
|
|
2066
|
+
);
|
|
2067
|
+
return {
|
|
2068
|
+
callId,
|
|
2069
|
+
toolName,
|
|
2070
|
+
isError: false,
|
|
2071
|
+
result: normalizeToolResult(value),
|
|
2072
|
+
durationMs: 0,
|
|
2073
|
+
sessionId: options.sessionId,
|
|
2074
|
+
};
|
|
2075
|
+
} catch (error) {
|
|
2076
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2077
|
+
return {
|
|
2078
|
+
callId,
|
|
2079
|
+
toolName,
|
|
2080
|
+
isError: true,
|
|
2081
|
+
result: { status: "error", error: { message } },
|
|
2082
|
+
durationMs: 0,
|
|
2083
|
+
sessionId: options.sessionId,
|
|
2084
|
+
errorMessage: message,
|
|
2085
|
+
};
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
|
|
1796
2089
|
/**
|
|
1797
2090
|
* Local harness cwd for a session: per-send override, else the shared
|
|
1798
2091
|
* `agent.local.workspaceDir`, else `<agent.local.cwd>/<sessionId>`, else
|
|
@@ -1840,19 +2133,22 @@ export class SessionEngine {
|
|
|
1840
2133
|
if (
|
|
1841
2134
|
workspaceDir === undefined ||
|
|
1842
2135
|
agent.runtime !== "local" ||
|
|
2136
|
+
this.hasRootOverride() ||
|
|
1843
2137
|
this.runner.prewarm === undefined
|
|
1844
2138
|
) {
|
|
1845
2139
|
return;
|
|
1846
2140
|
}
|
|
1847
2141
|
const startedAt = Date.now();
|
|
1848
2142
|
try {
|
|
2143
|
+
const harness = resolveHostHarnessToolOptions(agent.allowedTools);
|
|
1849
2144
|
const outcome = await this.runner.prewarm({
|
|
1850
2145
|
workspaceDir,
|
|
1851
2146
|
runnerStateDir: join(this.stateRoot, "runner"),
|
|
1852
2147
|
sandbox: agent.local?.sandbox === true,
|
|
1853
|
-
...(
|
|
2148
|
+
...(harness.tools === undefined ? {} : { tools: harness.tools }),
|
|
2149
|
+
...(harness.disallowedTools === undefined
|
|
1854
2150
|
? {}
|
|
1855
|
-
: {
|
|
2151
|
+
: { disallowedTools: harness.disallowedTools }),
|
|
1856
2152
|
// No session yet, so auth'd connections are omitted rather than
|
|
1857
2153
|
// warmed without an identity; the per-send flag still has to match
|
|
1858
2154
|
// the turns' requests or the warmed executor is one no turn asks for.
|
|
@@ -1942,41 +2238,53 @@ export class SessionEngine {
|
|
|
1942
2238
|
// cwd is overridden (e.g. a PR worktree). A/B variant instruction
|
|
1943
2239
|
// overlays are applied via agentForSession.
|
|
1944
2240
|
const agent = this.agentForSession(record);
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
2241
|
+
if (!this.hasRootOverride()) {
|
|
2242
|
+
await materializeWorkspace({
|
|
2243
|
+
agent,
|
|
2244
|
+
workspaceDir,
|
|
2245
|
+
seed: isFirstTurn,
|
|
2246
|
+
stateRoot: this.stateRoot,
|
|
2247
|
+
});
|
|
2248
|
+
this.materializedWorkspaces.add(sessionId);
|
|
2249
|
+
// Must precede the carrier: the harness resolves rules, skills,
|
|
2250
|
+
// `mcp.json` and hooks from its git root, so a workspace nested in an
|
|
2251
|
+
// outer repo would load that repo's hooks instead of the ones written
|
|
2252
|
+
// just below. Only for directories this session owns — a borrowed
|
|
2253
|
+
// checkout or worktree keeps its own root.
|
|
2254
|
+
if (!this.hasBorrowedWorkspace(record)) {
|
|
2255
|
+
await ensureOwnGitRoot(harnessCwd);
|
|
2256
|
+
}
|
|
2257
|
+
await this.materializeNudgeCarrier(record, harnessCwd);
|
|
1959
2258
|
}
|
|
1960
|
-
await this.materializeNudgeCarrier(record, harnessCwd);
|
|
1961
2259
|
// Baseline before the agent can run, so a follow-up that arrives and
|
|
1962
2260
|
// drains before the first tool boundary still counts as an increase.
|
|
1963
2261
|
await this.baselineDeliveredNudges(record);
|
|
1964
2262
|
const pendingFiles = record.pendingWorkspaceFiles;
|
|
1965
2263
|
const pendingImages = record.pendingImages;
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
)
|
|
1970
|
-
|
|
2264
|
+
const runtime = resolveSessionRuntime({
|
|
2265
|
+
agentRuntime: agent.runtime,
|
|
2266
|
+
cloudOverride: record.cloudOverride,
|
|
2267
|
+
});
|
|
2268
|
+
const hasPendingFiles =
|
|
2269
|
+
pendingFiles !== undefined && Object.keys(pendingFiles).length > 0;
|
|
2270
|
+
if (hasPendingFiles && runtime !== "cloud") {
|
|
2271
|
+
const root = await this.resolveFileSystem({
|
|
2272
|
+
sessionId,
|
|
2273
|
+
continuationKey: record.continuationKey ?? undefined,
|
|
2274
|
+
cache: true,
|
|
2275
|
+
cwd: harnessCwd,
|
|
2276
|
+
});
|
|
2277
|
+
await writeWorkspaceFilesToRoot(root, pendingFiles);
|
|
1971
2278
|
}
|
|
1972
2279
|
if (
|
|
1973
|
-
(
|
|
1974
|
-
Object.keys(pendingFiles).length > 0) ||
|
|
2280
|
+
(hasPendingFiles && runtime !== "cloud") ||
|
|
1975
2281
|
(pendingImages !== undefined && pendingImages.length > 0)
|
|
1976
2282
|
) {
|
|
1977
2283
|
await this.updateRecord(sessionId, (current) => ({
|
|
1978
2284
|
...current,
|
|
1979
2285
|
pendingWorkspaceFiles:
|
|
2286
|
+
hasPendingFiles &&
|
|
2287
|
+
runtime !== "cloud" &&
|
|
1980
2288
|
current.pendingWorkspaceFiles === pendingFiles
|
|
1981
2289
|
? undefined
|
|
1982
2290
|
: current.pendingWorkspaceFiles,
|
|
@@ -2004,6 +2312,14 @@ export class SessionEngine {
|
|
|
2004
2312
|
instructionsInvisibleToHarness,
|
|
2005
2313
|
emit,
|
|
2006
2314
|
async (update) => {
|
|
2315
|
+
const turn = this.turnRoots.get(sessionId);
|
|
2316
|
+
if (turn !== undefined) {
|
|
2317
|
+
update = await applyWebScratchUpdate({
|
|
2318
|
+
cwd: turn.cwd,
|
|
2319
|
+
root: turn.root,
|
|
2320
|
+
update,
|
|
2321
|
+
});
|
|
2322
|
+
}
|
|
2007
2323
|
mapper.handleUpdate(update);
|
|
2008
2324
|
// Yield so the HTTP NDJSON writer can flush tool start/result
|
|
2009
2325
|
// events while sibling parallel tools are still executing.
|
|
@@ -2039,6 +2355,7 @@ export class SessionEngine {
|
|
|
2039
2355
|
outcome = { status: "error", errorMessage: describeError(error) };
|
|
2040
2356
|
} finally {
|
|
2041
2357
|
permit.release();
|
|
2358
|
+
this.dropTurnFileSystem(sessionId);
|
|
2042
2359
|
this.approvals.cancelSession(sessionId, "deny");
|
|
2043
2360
|
this.auths.cancelSession(sessionId, "deny");
|
|
2044
2361
|
await this.clearPendingApprovals(sessionId);
|
|
@@ -2135,6 +2452,7 @@ export class SessionEngine {
|
|
|
2135
2452
|
data: { message: outcome.errorMessage ?? "task failed" },
|
|
2136
2453
|
}
|
|
2137
2454
|
);
|
|
2455
|
+
await this.wipeCloudSessionWorkspace(sessionId);
|
|
2138
2456
|
} else {
|
|
2139
2457
|
await this.appendEvent(sessionId, {
|
|
2140
2458
|
type: "session.waiting",
|
|
@@ -2189,6 +2507,68 @@ export class SessionEngine {
|
|
|
2189
2507
|
);
|
|
2190
2508
|
}
|
|
2191
2509
|
|
|
2510
|
+
/**
|
|
2511
|
+
* Replace this session's store prefix with the pending `workspaceFiles`.
|
|
2512
|
+
* Undefined when the write did not land; the prompt must not name the
|
|
2513
|
+
* mount, matching ensureCloudWorkspaceSynced.
|
|
2514
|
+
*/
|
|
2515
|
+
private async syncCloudSessionWorkspaceFiles(
|
|
2516
|
+
store: CloudStoreTarget,
|
|
2517
|
+
sessionId: string,
|
|
2518
|
+
files: NonNullable<SessionRecord["pendingWorkspaceFiles"]>
|
|
2519
|
+
): Promise<{ dir: string; relPaths: string[] } | undefined> {
|
|
2520
|
+
const workspacePrefix = store.sessionWorkspacePrefix(sessionId);
|
|
2521
|
+
try {
|
|
2522
|
+
const relPaths = await syncSessionWorkspaceFiles({
|
|
2523
|
+
sink: store.sink,
|
|
2524
|
+
files,
|
|
2525
|
+
workspacePrefix,
|
|
2526
|
+
});
|
|
2527
|
+
return {
|
|
2528
|
+
dir: `${store.mountPath}/${workspacePrefix}`,
|
|
2529
|
+
relPaths,
|
|
2530
|
+
};
|
|
2531
|
+
} catch (error) {
|
|
2532
|
+
this.logger(
|
|
2533
|
+
`[workspace] ${store.label} session files sync failed: ${error instanceof Error ? error.message : String(error)}`
|
|
2534
|
+
);
|
|
2535
|
+
if (error instanceof WorkspaceFileRefusal) {
|
|
2536
|
+
await this.clearPendingWorkspaceFiles(sessionId, files);
|
|
2537
|
+
}
|
|
2538
|
+
return undefined;
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
|
|
2542
|
+
private async wipeCloudSessionWorkspace(sessionId: string): Promise<void> {
|
|
2543
|
+
const store = this.cloudStore();
|
|
2544
|
+
if (store === undefined) {
|
|
2545
|
+
return;
|
|
2546
|
+
}
|
|
2547
|
+
try {
|
|
2548
|
+
await wipeSessionWorkspace({
|
|
2549
|
+
sink: store.sink,
|
|
2550
|
+
workspacePrefix: store.sessionWorkspacePrefix(sessionId),
|
|
2551
|
+
});
|
|
2552
|
+
} catch (error) {
|
|
2553
|
+
this.logger(
|
|
2554
|
+
`[workspace] ${store.label} session files wipe failed: ${error instanceof Error ? error.message : String(error)}`
|
|
2555
|
+
);
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
private async clearPendingWorkspaceFiles(
|
|
2560
|
+
sessionId: string,
|
|
2561
|
+
pending: NonNullable<SessionRecord["pendingWorkspaceFiles"]>
|
|
2562
|
+
): Promise<void> {
|
|
2563
|
+
await this.updateRecord(sessionId, (current) => ({
|
|
2564
|
+
...current,
|
|
2565
|
+
pendingWorkspaceFiles:
|
|
2566
|
+
current.pendingWorkspaceFiles === pending
|
|
2567
|
+
? undefined
|
|
2568
|
+
: current.pendingWorkspaceFiles,
|
|
2569
|
+
}));
|
|
2570
|
+
}
|
|
2571
|
+
|
|
2192
2572
|
/**
|
|
2193
2573
|
* With files authored, every first cloud turn re-copies the authored
|
|
2194
2574
|
* bytes and the memo only dedupes concurrent first turns. The mount is
|
|
@@ -2241,6 +2621,8 @@ export class SessionEngine {
|
|
|
2241
2621
|
mountPath: DEPLOYMENT_STORE_MOUNT_PATH,
|
|
2242
2622
|
skillsPrefix: agentStoreKeys.skillsPrefix(),
|
|
2243
2623
|
workspacePrefix: agentStoreKeys.workspacePrefix(),
|
|
2624
|
+
sessionWorkspacePrefix: (sessionId: string) =>
|
|
2625
|
+
agentStoreKeys.sessionWorkspacePrefix(sessionId),
|
|
2244
2626
|
label: "deployment store",
|
|
2245
2627
|
};
|
|
2246
2628
|
if (v2Hosting.kind === "cursor-managed") {
|
|
@@ -2263,6 +2645,11 @@ export class SessionEngine {
|
|
|
2263
2645
|
mountPath: USER_STORE_MOUNT_PATH,
|
|
2264
2646
|
skillsPrefix: agentStoreKeys.userSkillsPrefix(this.project.name),
|
|
2265
2647
|
workspacePrefix: agentStoreKeys.userWorkspacePrefix(this.project.name),
|
|
2648
|
+
sessionWorkspacePrefix: (sessionId: string) =>
|
|
2649
|
+
agentStoreKeys.userSessionWorkspacePrefix(
|
|
2650
|
+
this.project.name,
|
|
2651
|
+
sessionId
|
|
2652
|
+
),
|
|
2266
2653
|
label: "user store",
|
|
2267
2654
|
};
|
|
2268
2655
|
}
|
|
@@ -2291,6 +2678,17 @@ export class SessionEngine {
|
|
|
2291
2678
|
// allowlist, and a restriction that silently does not apply is worse
|
|
2292
2679
|
// than a refused turn. Non-local agents already fail discovery; this
|
|
2293
2680
|
// guards hybrid agents' per-send cloud sessions.
|
|
2681
|
+
//
|
|
2682
|
+
// Factory roots omit `allowedTools` at discovery (the live handle is
|
|
2683
|
+
// resolved per session). The allowlist guard below therefore never
|
|
2684
|
+
// fires for `defineAgent({ root: (ctx) => … })` with omitted `tools`,
|
|
2685
|
+
// and a per-send `cloud` attach would skip the virtual-root allowlist
|
|
2686
|
+
// and run unrestricted on a VM. Any authored root refuses non-local turns.
|
|
2687
|
+
if (runtime !== "local" && this.hasRootOverride()) {
|
|
2688
|
+
throw new Error(
|
|
2689
|
+
`Agent "${agent.name}" declares root, which cannot be served on ${runtime} turns. Refusing the session rather than running it against the host VM.`
|
|
2690
|
+
);
|
|
2691
|
+
}
|
|
2294
2692
|
if (runtime !== "local" && agent.allowedTools !== undefined) {
|
|
2295
2693
|
throw new Error(
|
|
2296
2694
|
`Agent "${agent.name}" declares a built-in tool allowlist (tools), which cannot be enforced on ${runtime} turns. Refusing the session rather than running it unrestricted.`
|
|
@@ -2319,6 +2717,48 @@ export class SessionEngine {
|
|
|
2319
2717
|
override: record.cloudOverride,
|
|
2320
2718
|
})
|
|
2321
2719
|
: undefined;
|
|
2720
|
+
const authoredRoot = this.hasRootOverride();
|
|
2721
|
+
const root =
|
|
2722
|
+
runtime === "local"
|
|
2723
|
+
? await this.resolveFileSystem({
|
|
2724
|
+
sessionId: record.sessionId,
|
|
2725
|
+
continuationKey: record.continuationKey ?? undefined,
|
|
2726
|
+
cache: true,
|
|
2727
|
+
cwd: this.harnessCwd(record),
|
|
2728
|
+
})
|
|
2729
|
+
: undefined;
|
|
2730
|
+
const harnessTools =
|
|
2731
|
+
runtime === "local" && root !== undefined
|
|
2732
|
+
? authoredRoot
|
|
2733
|
+
? {
|
|
2734
|
+
tools: resolveFileSystemTools({
|
|
2735
|
+
root,
|
|
2736
|
+
tools: agent.allowedTools,
|
|
2737
|
+
}),
|
|
2738
|
+
}
|
|
2739
|
+
: resolveHostHarnessToolOptions(agent.allowedTools)
|
|
2740
|
+
: undefined;
|
|
2741
|
+
const fileTools =
|
|
2742
|
+
root === undefined
|
|
2743
|
+
? []
|
|
2744
|
+
: synthesizeFileSystemTools(
|
|
2745
|
+
root,
|
|
2746
|
+
authoredRoot
|
|
2747
|
+
? rootFileToolsFromAuthored(agent.allowedTools)
|
|
2748
|
+
: hostFileToolsFromAuthored(agent.allowedTools)
|
|
2749
|
+
);
|
|
2750
|
+
// Host-default keeps native SemanticSearch (the host index).
|
|
2751
|
+
// `localFileSystem.search` is empty, so synthesizing would
|
|
2752
|
+
// replace a working index with an empty custom tool.
|
|
2753
|
+
const synthesized =
|
|
2754
|
+
root === undefined
|
|
2755
|
+
? []
|
|
2756
|
+
: this.withTurnScratchTools(
|
|
2757
|
+
authoredRoot
|
|
2758
|
+
? fileTools
|
|
2759
|
+
: fileTools.filter((tool) => tool.name !== "SemanticSearch"),
|
|
2760
|
+
record.sessionId
|
|
2761
|
+
);
|
|
2322
2762
|
let turnPrompt = prompt;
|
|
2323
2763
|
if (isFirstTurn) {
|
|
2324
2764
|
const contextBlock = buildSessionContextBlock({
|
|
@@ -2331,34 +2771,39 @@ export class SessionEngine {
|
|
|
2331
2771
|
});
|
|
2332
2772
|
turnPrompt = `${contextBlock}\n\n${prompt}`;
|
|
2333
2773
|
}
|
|
2334
|
-
if (
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2774
|
+
if (runtime === "cloud") {
|
|
2775
|
+
// Cloud VMs have no materialized workspace: ship the full context,
|
|
2776
|
+
// including agent tool script bodies. Authored skills and workspace
|
|
2777
|
+
// seeds land on the store mount (skills for native discovery, seeds
|
|
2778
|
+
// for the model's file tools), per-turn workspaceFiles land under
|
|
2779
|
+
// the session prefix, and on hosted deployments the shared memory
|
|
2780
|
+
// journal is on that mount too — say where. When the turn carries
|
|
2781
|
+
// the session-bound server-tool MCP endpoint, the preamble catalogs
|
|
2782
|
+
// server tools under that MCP server name *before* the agent-tool
|
|
2783
|
+
// "write the script if missing" recipe, so a model told to "use
|
|
2784
|
+
// <server tool>" calls MCP instead of inventing a .sh.
|
|
2785
|
+
const hosted = isCursorHostedFilesAvailable();
|
|
2786
|
+
const store =
|
|
2787
|
+
hosted || isCursorUserStoreFilesAvailable()
|
|
2788
|
+
? this.cloudStore()
|
|
2789
|
+
: undefined;
|
|
2790
|
+
let preamble = "";
|
|
2791
|
+
const sections: string[] = [];
|
|
2792
|
+
const pending = record.pendingWorkspaceFiles;
|
|
2793
|
+
if (isFirstTurn) {
|
|
2794
|
+
preamble = buildAgentsMdContent(agent, {
|
|
2346
2795
|
includeScripts: true,
|
|
2347
2796
|
serverToolsMcpName:
|
|
2348
2797
|
this.cloudServerToolsConnection === undefined
|
|
2349
2798
|
? undefined
|
|
2350
2799
|
: SERVER_TOOLS_MCP_SERVER_NAME,
|
|
2351
2800
|
});
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
? this.cloudStore()
|
|
2359
|
-
: undefined;
|
|
2360
|
-
let sections = "";
|
|
2361
|
-
if (store !== undefined) {
|
|
2801
|
+
}
|
|
2802
|
+
if (store !== undefined) {
|
|
2803
|
+
if (isFirstTurn) {
|
|
2804
|
+
// Synced whenever a store is reachable, not just when skills or
|
|
2805
|
+
// seeds exist: removing the last one has to delete its store copies
|
|
2806
|
+
// too.
|
|
2362
2807
|
const [, seedsSynced] = await Promise.all([
|
|
2363
2808
|
this.ensureCloudSkillsSynced(store),
|
|
2364
2809
|
this.ensureCloudWorkspaceSynced(store),
|
|
@@ -2366,25 +2811,63 @@ export class SessionEngine {
|
|
|
2366
2811
|
// Only name the mount when the bytes landed; a failed sync must
|
|
2367
2812
|
// not send the model looking for files that are not there.
|
|
2368
2813
|
if (seedsSynced && agent.seedFiles.length > 0) {
|
|
2369
|
-
|
|
2370
|
-
|
|
2814
|
+
sections.push(
|
|
2815
|
+
buildCloudWorkspaceSection(
|
|
2816
|
+
`${store.mountPath}/${store.workspacePrefix}`
|
|
2817
|
+
)
|
|
2818
|
+
);
|
|
2371
2819
|
}
|
|
2372
2820
|
}
|
|
2373
|
-
if (
|
|
2374
|
-
|
|
2821
|
+
if (pending !== undefined && Object.keys(pending).length > 0) {
|
|
2822
|
+
const synced = await this.syncCloudSessionWorkspaceFiles(
|
|
2823
|
+
store,
|
|
2824
|
+
record.sessionId,
|
|
2825
|
+
pending
|
|
2826
|
+
);
|
|
2827
|
+
if (synced !== undefined) {
|
|
2828
|
+
sections.push(
|
|
2829
|
+
buildCloudSessionWorkspaceSection(synced.dir, synced.relPaths)
|
|
2830
|
+
);
|
|
2831
|
+
await this.clearPendingWorkspaceFiles(record.sessionId, pending);
|
|
2832
|
+
}
|
|
2375
2833
|
}
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
// reference on-disk paths under the harness cwd. Grokbot turns are
|
|
2382
|
-
// excluded: their instructions ride the request's `instructions`
|
|
2383
|
-
// field into the Sand system prompt instead.
|
|
2384
|
-
const includeScripts = this.hasBorrowedWorkspace(record);
|
|
2385
|
-
const preamble = buildAgentsMdContent(agent, { includeScripts });
|
|
2386
|
-
turnPrompt = `${preamble}\n\n${turnPrompt}`;
|
|
2834
|
+
} else if (pending !== undefined && Object.keys(pending).length > 0) {
|
|
2835
|
+
await this.clearPendingWorkspaceFiles(record.sessionId, pending);
|
|
2836
|
+
}
|
|
2837
|
+
if (isFirstTurn && hosted) {
|
|
2838
|
+
sections.push(buildCloudMemorySection(this.project.name));
|
|
2387
2839
|
}
|
|
2840
|
+
if (preamble !== "" || sections.length > 0) {
|
|
2841
|
+
turnPrompt = [preamble, ...sections, turnPrompt]
|
|
2842
|
+
.filter((part) => part !== "")
|
|
2843
|
+
.join("\n\n");
|
|
2844
|
+
}
|
|
2845
|
+
} else if (isFirstTurn && runtime === "local" && authoredRoot) {
|
|
2846
|
+
// Virtual roots skip materialize, so AGENTS.md never lands on disk.
|
|
2847
|
+
// Ship the same preamble the borrowed-cwd local path uses.
|
|
2848
|
+
const preamble = buildAgentsMdContent(
|
|
2849
|
+
{ ...agent, tools: [...agent.tools, ...synthesized] },
|
|
2850
|
+
{ includeScripts: true }
|
|
2851
|
+
);
|
|
2852
|
+
const workspaceInfo = `<user_info>\nWorkspace Path: /\n</user_info>`;
|
|
2853
|
+
turnPrompt =
|
|
2854
|
+
preamble === ""
|
|
2855
|
+
? `${workspaceInfo}\n\n${turnPrompt}`
|
|
2856
|
+
: `${workspaceInfo}\n\n${preamble}\n\n${turnPrompt}`;
|
|
2857
|
+
} else if (
|
|
2858
|
+
isFirstTurn &&
|
|
2859
|
+
runtime === "local" &&
|
|
2860
|
+
instructionsInvisibleToHarness
|
|
2861
|
+
) {
|
|
2862
|
+
// Local workspace whose AGENTS.md the harness will not load. With a
|
|
2863
|
+
// borrowed harness cwd, agent tool scripts live only in the durable
|
|
2864
|
+
// scaffold dir — embed bodies like cloud. Otherwise the catalog can
|
|
2865
|
+
// reference on-disk paths under the harness cwd. Grokbot turns are
|
|
2866
|
+
// excluded: their instructions ride the request's `instructions`
|
|
2867
|
+
// field into the Sand system prompt instead.
|
|
2868
|
+
const includeScripts = this.hasBorrowedWorkspace(record);
|
|
2869
|
+
const preamble = buildAgentsMdContent(agent, { includeScripts });
|
|
2870
|
+
turnPrompt = `${preamble}\n\n${turnPrompt}`;
|
|
2388
2871
|
}
|
|
2389
2872
|
return {
|
|
2390
2873
|
sessionId: record.sessionId,
|
|
@@ -2392,9 +2875,18 @@ export class SessionEngine {
|
|
|
2392
2875
|
? {}
|
|
2393
2876
|
: { sdkAgentId: record.sdkAgentId }),
|
|
2394
2877
|
workspaceDir: this.harnessCwd(record),
|
|
2395
|
-
sandbox:
|
|
2396
|
-
|
|
2397
|
-
|
|
2878
|
+
sandbox: authoredRoot
|
|
2879
|
+
? false
|
|
2880
|
+
: this.project.agent.local?.sandbox === true,
|
|
2881
|
+
...(runtime === "local" && harnessTools !== undefined
|
|
2882
|
+
? {
|
|
2883
|
+
...(harnessTools.tools === undefined
|
|
2884
|
+
? {}
|
|
2885
|
+
: { tools: harnessTools.tools }),
|
|
2886
|
+
...(harnessTools.disallowedTools === undefined
|
|
2887
|
+
? {}
|
|
2888
|
+
: { disallowedTools: harnessTools.disallowedTools }),
|
|
2889
|
+
}
|
|
2398
2890
|
: {}),
|
|
2399
2891
|
runnerStateDir: join(this.stateRoot, "runner"),
|
|
2400
2892
|
prompt: turnPrompt,
|
|
@@ -2418,7 +2910,13 @@ export class SessionEngine {
|
|
|
2418
2910
|
: {}),
|
|
2419
2911
|
customTools:
|
|
2420
2912
|
runtime === "local"
|
|
2421
|
-
? await this.buildCustomTools(
|
|
2913
|
+
? await this.buildCustomTools(
|
|
2914
|
+
record,
|
|
2915
|
+
agent,
|
|
2916
|
+
turnId,
|
|
2917
|
+
emit,
|
|
2918
|
+
synthesized
|
|
2919
|
+
)
|
|
2422
2920
|
: {},
|
|
2423
2921
|
mcpServers: this.buildMcpServers(
|
|
2424
2922
|
runtime,
|
|
@@ -2712,7 +3210,8 @@ export class SessionEngine {
|
|
|
2712
3210
|
record: SessionRecord,
|
|
2713
3211
|
agent: ResolvedAgent,
|
|
2714
3212
|
turnId: string,
|
|
2715
|
-
emit: (payload: SessionEventPayload) => void
|
|
3213
|
+
emit: (payload: SessionEventPayload) => void,
|
|
3214
|
+
extraTools: DiscoveredTool[] = []
|
|
2716
3215
|
): Promise<Record<string, SDKCustomTool>> {
|
|
2717
3216
|
const tools: Record<string, SDKCustomTool> = {};
|
|
2718
3217
|
const register = (tool: ResolvedAgent["tools"][number]): void => {
|
|
@@ -2742,7 +3241,7 @@ export class SessionEngine {
|
|
|
2742
3241
|
},
|
|
2743
3242
|
};
|
|
2744
3243
|
};
|
|
2745
|
-
for (const tool of agent.tools) {
|
|
3244
|
+
for (const tool of [...agent.tools, ...extraTools]) {
|
|
2746
3245
|
if (tool.execution !== "server") {
|
|
2747
3246
|
continue;
|
|
2748
3247
|
}
|
|
@@ -3682,6 +4181,27 @@ export class SessionEngine {
|
|
|
3682
4181
|
const session = identity.session;
|
|
3683
4182
|
let workspaceDir = identity.workspaceDir;
|
|
3684
4183
|
let scratchDir = identity.scratchDir;
|
|
4184
|
+
if (
|
|
4185
|
+
isFileSystemToolAlias(toolName) &&
|
|
4186
|
+
identity.busy &&
|
|
4187
|
+
record !== undefined &&
|
|
4188
|
+
isMutatingFileSystemTool(toolName)
|
|
4189
|
+
) {
|
|
4190
|
+
throw new SessionBusyError(record.sessionId);
|
|
4191
|
+
}
|
|
4192
|
+
const native = await this.nativeFileSystemOutcome({
|
|
4193
|
+
toolName,
|
|
4194
|
+
input,
|
|
4195
|
+
callId,
|
|
4196
|
+
sessionId: record?.sessionId,
|
|
4197
|
+
continuationKey:
|
|
4198
|
+
record?.continuationKey ?? session.continuationKey ?? undefined,
|
|
4199
|
+
cwd: workspaceDir,
|
|
4200
|
+
dryRun: record?.dryRun === true,
|
|
4201
|
+
});
|
|
4202
|
+
if (native !== undefined) {
|
|
4203
|
+
return native;
|
|
4204
|
+
}
|
|
3685
4205
|
let tool = this.project.agent.tools.find((t) => t.name === toolName);
|
|
3686
4206
|
if (
|
|
3687
4207
|
tool === undefined &&
|
|
@@ -3783,7 +4303,10 @@ export class SessionEngine {
|
|
|
3783
4303
|
record !== undefined &&
|
|
3784
4304
|
identity.busy &&
|
|
3785
4305
|
this.materializedWorkspaces.has(record.sessionId);
|
|
3786
|
-
if (
|
|
4306
|
+
if (
|
|
4307
|
+
!this.hasRootOverride() &&
|
|
4308
|
+
(record === undefined || workspaceUnavailable || !turnMaterialized)
|
|
4309
|
+
) {
|
|
3787
4310
|
await materializeWorkspace({
|
|
3788
4311
|
agent: this.project.agent,
|
|
3789
4312
|
workspaceDir:
|
|
@@ -4745,9 +5268,8 @@ export class SessionEngine {
|
|
|
4745
5268
|
}
|
|
4746
5269
|
const record = await this.sessions.get(sessionId);
|
|
4747
5270
|
const sdkAgentId =
|
|
4748
|
-
(record?.sdkAgentId
|
|
4749
|
-
|
|
4750
|
-
: undefined) ?? cloudSdkAgentIdFromEvents(events);
|
|
5271
|
+
(record?.sdkAgentId?.startsWith("bc-") ? record.sdkAgentId : undefined) ??
|
|
5272
|
+
cloudSdkAgentIdFromEvents(events);
|
|
4751
5273
|
if (sdkAgentId === undefined) {
|
|
4752
5274
|
return;
|
|
4753
5275
|
}
|