@cursor/july 0.1.103 → 0.1.104
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/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.DbMWUyGb.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.Bqsulb9K.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.CdEIUwDg.js → VPLocalSearchBox.DcNeTh9o.js} +1 -1
- package/dist/docs/assets/chunks/{theme.YZMHgPgg.js → theme.DMjhY3gl.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/{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/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 +4 -4
- 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/human-in-the-loop.html +4 -4
- package/dist/docs/guides/mcp-oauth.html +4 -4
- package/dist/docs/guides/opentelemetry.html +4 -4
- package/dist/docs/guides/slack.html +4 -4
- package/dist/docs/guides/webhooks.html +4 -4
- package/dist/docs/hashmap.json +1 -1
- package/dist/docs/hillclimbing.html +4 -4
- package/dist/docs/index.html +4 -4
- package/dist/docs/llms-full.txt +406 -0
- package/dist/docs/llms.txt +1 -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 +4 -4
- package/dist/docs/reference/connections.html +4 -4
- package/dist/docs/reference/extensions.html +4 -4
- package/dist/docs/reference/hooks.html +4 -4
- package/dist/docs/reference/http-api.html +4 -4
- package/dist/docs/reference/instructions.html +4 -4
- package/dist/docs/reference/playground.html +4 -4
- package/dist/docs/reference/project-layout.html +4 -4
- package/dist/docs/reference/prompt.html +4 -4
- package/dist/docs/reference/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 +4 -4
- package/dist/docs/templates/demo.html +4 -4
- package/dist/docs/templates/pr-autofixer.html +4 -4
- package/dist/docs/templates/security-help.html +4 -4
- package/dist/docs/templates/security-reviewer.html +4 -4
- package/dist/docs/templates/triage.html +4 -4
- package/dist/docs/troubleshooting.html +4 -4
- package/dist/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/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/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.map +1 -1
- package/dist/internal/hosted-delivery.js +12 -2
- 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 +3 -1
- package/dist/internal/session-engine.d.ts +22 -0
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +362 -40
- 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/playground/assets/{index-59KvgREd.js → index-8YiozU89.js} +38 -38
- package/dist/playground/index.html +1 -1
- package/dist/types.d.ts +51 -3
- package/dist/types.d.ts.map +1 -1
- package/docs/design/filesystem.md +376 -0
- package/docs/guides/github.md +5 -0
- package/docs/reference/channels.md +20 -0
- package/package.json +16 -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/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/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/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/guest-network.ts +8 -2
- package/src/internal/hosted-delivery.ts +18 -1
- package/src/internal/sdk-runner.ts +4 -0
- package/src/internal/session-engine.ts +439 -29
- package/src/internal/testing/factory-resource-fixtures.ts +85 -0
- package/src/types.ts +49 -3
- package/dist/docs/assets/chunks/@localSearchIndexroot.nrl1ifBq.js +0 -1
|
@@ -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";
|
|
@@ -184,7 +207,6 @@ import {
|
|
|
184
207
|
isNestedInGitRepo,
|
|
185
208
|
materializeWorkspace,
|
|
186
209
|
SERVER_TOOLS_MCP_SERVER_NAME,
|
|
187
|
-
writeWorkspaceFiles,
|
|
188
210
|
} from "./workspace.js";
|
|
189
211
|
import { syncWorkspaceToStore } from "./workspace-sync.js";
|
|
190
212
|
|
|
@@ -510,6 +532,15 @@ export class SessionEngine {
|
|
|
510
532
|
private readonly cloudCostBackfillDone = new Set<string>();
|
|
511
533
|
/** Deduped in-flight cloud cost backfills, keyed by session id. */
|
|
512
534
|
private readonly cloudCostBackfill = new Map<string, Promise<void>>();
|
|
535
|
+
/** Per-session FileSystem handle cache (not a tree snapshot). */
|
|
536
|
+
private readonly sessionRoots = new Map<string, FileSystem>();
|
|
537
|
+
/** In-flight factory resolves, so the first turn and a racing callTool share one handle. */
|
|
538
|
+
private readonly sessionRootInflight = new Map<string, Promise<FileSystem>>();
|
|
539
|
+
/** Per-turn `unionFs({ "/": authored, "/tmp": tempFs() })`. Dropped at end of turn. */
|
|
540
|
+
private readonly turnRoots = new Map<
|
|
541
|
+
string,
|
|
542
|
+
{ root: FileSystem; cwd: string }
|
|
543
|
+
>();
|
|
513
544
|
|
|
514
545
|
constructor(options: SessionEngineOptions) {
|
|
515
546
|
this.project = options.project;
|
|
@@ -1793,6 +1824,261 @@ export class SessionEngine {
|
|
|
1793
1824
|
return this.interrupt(existing.sessionId);
|
|
1794
1825
|
}
|
|
1795
1826
|
|
|
1827
|
+
private hasRootOverride(): boolean {
|
|
1828
|
+
return this.project.agent.root !== undefined;
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
/**
|
|
1832
|
+
* FileSystem for this turn or `callTool`. Authored `root` (static or
|
|
1833
|
+
* factory) is cached per session when `cache` is set; unscoped
|
|
1834
|
+
* `callTool` passes the invocation id and does not cache, so factories
|
|
1835
|
+
* cannot share one tree across callers. A static handle is used as
|
|
1836
|
+
* authored — every session sees that same object. Host-default agents
|
|
1837
|
+
* get `localFileSystem` against the harness cwd — same path verbs as
|
|
1838
|
+
* the Cursor SDK natives, always through the synthesized tools.
|
|
1839
|
+
*/
|
|
1840
|
+
private async resolveFileSystem(options: {
|
|
1841
|
+
sessionId: string;
|
|
1842
|
+
continuationKey?: string;
|
|
1843
|
+
cache: boolean;
|
|
1844
|
+
cwd: string;
|
|
1845
|
+
}): Promise<FileSystem> {
|
|
1846
|
+
const authored = this.project.agent.root;
|
|
1847
|
+
if (authored === undefined) {
|
|
1848
|
+
return localFileSystem({ cwd: options.cwd });
|
|
1849
|
+
}
|
|
1850
|
+
const authoredRoot = await this.resolveAuthoredFileSystem(options);
|
|
1851
|
+
if (options.cache) {
|
|
1852
|
+
const existing = this.turnRoots.get(options.sessionId);
|
|
1853
|
+
if (existing !== undefined) {
|
|
1854
|
+
return existing.root;
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
const root = unionFs({
|
|
1858
|
+
"/": authoredRoot,
|
|
1859
|
+
"/tmp": tempFs(),
|
|
1860
|
+
});
|
|
1861
|
+
if (options.cache && this.isTurnRunning(options.sessionId)) {
|
|
1862
|
+
this.turnRoots.set(options.sessionId, { root, cwd: options.cwd });
|
|
1863
|
+
}
|
|
1864
|
+
return root;
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
private async resolveAuthoredFileSystem(options: {
|
|
1868
|
+
sessionId: string;
|
|
1869
|
+
continuationKey?: string;
|
|
1870
|
+
cache: boolean;
|
|
1871
|
+
}): Promise<FileSystem> {
|
|
1872
|
+
const authored = this.project.agent.root;
|
|
1873
|
+
if (authored === undefined) {
|
|
1874
|
+
throw new Error("resolveAuthoredFileSystem requires an authored root");
|
|
1875
|
+
}
|
|
1876
|
+
if (options.cache) {
|
|
1877
|
+
const cached = this.sessionRoots.get(options.sessionId);
|
|
1878
|
+
if (cached !== undefined) {
|
|
1879
|
+
return cached;
|
|
1880
|
+
}
|
|
1881
|
+
const inflight = this.sessionRootInflight.get(options.sessionId);
|
|
1882
|
+
if (inflight !== undefined) {
|
|
1883
|
+
return inflight;
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
const resolve = async (): Promise<FileSystem> => {
|
|
1887
|
+
if (typeof authored !== "function") {
|
|
1888
|
+
return authored;
|
|
1889
|
+
}
|
|
1890
|
+
const host = this.hostForSession(
|
|
1891
|
+
options.cache ? options.sessionId : undefined
|
|
1892
|
+
);
|
|
1893
|
+
const ctx: RootContext = {
|
|
1894
|
+
sessionId: options.sessionId,
|
|
1895
|
+
agentName: this.project.agent.name,
|
|
1896
|
+
...(options.continuationKey === undefined
|
|
1897
|
+
? {}
|
|
1898
|
+
: { continuationKey: options.continuationKey }),
|
|
1899
|
+
host: { files: host.files, reminders: host.reminders },
|
|
1900
|
+
};
|
|
1901
|
+
return await authored(ctx);
|
|
1902
|
+
};
|
|
1903
|
+
if (!options.cache) {
|
|
1904
|
+
return resolve();
|
|
1905
|
+
}
|
|
1906
|
+
const pending = resolve();
|
|
1907
|
+
this.sessionRootInflight.set(options.sessionId, pending);
|
|
1908
|
+
try {
|
|
1909
|
+
const resolved = await pending;
|
|
1910
|
+
this.sessionRoots.set(options.sessionId, resolved);
|
|
1911
|
+
return resolved;
|
|
1912
|
+
} finally {
|
|
1913
|
+
this.sessionRootInflight.delete(options.sessionId);
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
private dropTurnFileSystem(sessionId: string): void {
|
|
1918
|
+
this.turnRoots.delete(sessionId);
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
private async prepareScratchToolInput(options: {
|
|
1922
|
+
sessionId?: string;
|
|
1923
|
+
input: Record<string, unknown>;
|
|
1924
|
+
}): Promise<Record<string, unknown>> {
|
|
1925
|
+
if (options.sessionId === undefined) {
|
|
1926
|
+
return options.input;
|
|
1927
|
+
}
|
|
1928
|
+
const turn = this.turnRoots.get(options.sessionId);
|
|
1929
|
+
if (turn === undefined) {
|
|
1930
|
+
return options.input;
|
|
1931
|
+
}
|
|
1932
|
+
await ingestHostAgentToolsScratch({
|
|
1933
|
+
cwd: turn.cwd,
|
|
1934
|
+
root: turn.root,
|
|
1935
|
+
});
|
|
1936
|
+
return remapScratchPaths(options.input, turn.cwd);
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
private withTurnScratchTools(
|
|
1940
|
+
tools: DiscoveredTool[],
|
|
1941
|
+
sessionId: string
|
|
1942
|
+
): DiscoveredTool[] {
|
|
1943
|
+
const turn = this.turnRoots.get(sessionId);
|
|
1944
|
+
if (turn === undefined) {
|
|
1945
|
+
return tools;
|
|
1946
|
+
}
|
|
1947
|
+
return tools.map((tool) => {
|
|
1948
|
+
const execute = tool.definition.execute;
|
|
1949
|
+
if (typeof execute !== "function") {
|
|
1950
|
+
return tool;
|
|
1951
|
+
}
|
|
1952
|
+
return {
|
|
1953
|
+
...tool,
|
|
1954
|
+
definition: {
|
|
1955
|
+
...tool.definition,
|
|
1956
|
+
execute: async (input, ctx) => {
|
|
1957
|
+
const remapped = await this.prepareScratchToolInput({
|
|
1958
|
+
sessionId,
|
|
1959
|
+
input:
|
|
1960
|
+
input !== null &&
|
|
1961
|
+
typeof input === "object" &&
|
|
1962
|
+
!Array.isArray(input)
|
|
1963
|
+
? (input as Record<string, unknown>)
|
|
1964
|
+
: {},
|
|
1965
|
+
});
|
|
1966
|
+
return execute(remapped as never, ctx);
|
|
1967
|
+
},
|
|
1968
|
+
},
|
|
1969
|
+
};
|
|
1970
|
+
});
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
private async nativeFileSystemOutcome(options: {
|
|
1974
|
+
toolName: string;
|
|
1975
|
+
input: unknown;
|
|
1976
|
+
callId: string;
|
|
1977
|
+
sessionId?: string;
|
|
1978
|
+
continuationKey?: string;
|
|
1979
|
+
cwd: string;
|
|
1980
|
+
dryRun?: boolean;
|
|
1981
|
+
}): Promise<ToolCallOutcome | undefined> {
|
|
1982
|
+
const { toolName, input, callId } = options;
|
|
1983
|
+
if (!isFileSystemToolAlias(toolName)) {
|
|
1984
|
+
return undefined;
|
|
1985
|
+
}
|
|
1986
|
+
// Same policy the authored callTool path asks: a dry-run write is
|
|
1987
|
+
// answered by the host. Native edit/delete used to return before
|
|
1988
|
+
// decideToolCall and persist through the FileSystem.
|
|
1989
|
+
const decision = decideToolCall({
|
|
1990
|
+
dryRun: options.dryRun === true,
|
|
1991
|
+
effect: isMutatingFileSystemTool(toolName) ? "write" : "read",
|
|
1992
|
+
input,
|
|
1993
|
+
});
|
|
1994
|
+
if (decision.kind === "answer") {
|
|
1995
|
+
return {
|
|
1996
|
+
callId,
|
|
1997
|
+
toolName,
|
|
1998
|
+
isError: false,
|
|
1999
|
+
result: decision.result,
|
|
2000
|
+
durationMs: 0,
|
|
2001
|
+
sessionId: options.sessionId,
|
|
2002
|
+
};
|
|
2003
|
+
}
|
|
2004
|
+
const root = await this.resolveFileSystem({
|
|
2005
|
+
sessionId: options.sessionId ?? callId,
|
|
2006
|
+
continuationKey: options.continuationKey,
|
|
2007
|
+
cache: options.sessionId !== undefined,
|
|
2008
|
+
cwd: options.cwd,
|
|
2009
|
+
});
|
|
2010
|
+
const rawArgs =
|
|
2011
|
+
input !== null && typeof input === "object" && !Array.isArray(input)
|
|
2012
|
+
? (input as Record<string, unknown>)
|
|
2013
|
+
: {};
|
|
2014
|
+
const args = await this.prepareScratchToolInput({
|
|
2015
|
+
sessionId: options.sessionId,
|
|
2016
|
+
input: rawArgs,
|
|
2017
|
+
});
|
|
2018
|
+
const tool = fileSystemTool(root, toolName, args);
|
|
2019
|
+
const execute = tool?.definition.execute;
|
|
2020
|
+
if (tool === undefined || typeof execute !== "function") {
|
|
2021
|
+
// A resolved root owns this native name. Falling through would let
|
|
2022
|
+
// an authored lookalike run, or surface UnknownToolError after we
|
|
2023
|
+
// already advertised the FileSystem tool.
|
|
2024
|
+
return {
|
|
2025
|
+
callId,
|
|
2026
|
+
toolName,
|
|
2027
|
+
isError: true,
|
|
2028
|
+
result: {
|
|
2029
|
+
status: "error",
|
|
2030
|
+
error: { message: `FileSystem cannot handle ${toolName}` },
|
|
2031
|
+
},
|
|
2032
|
+
durationMs: 0,
|
|
2033
|
+
sessionId: options.sessionId,
|
|
2034
|
+
errorMessage: `FileSystem cannot handle ${toolName}`,
|
|
2035
|
+
};
|
|
2036
|
+
}
|
|
2037
|
+
const validation = validateToolInput(tool.definition.inputSchema, args);
|
|
2038
|
+
if (!validation.ok) {
|
|
2039
|
+
return {
|
|
2040
|
+
callId,
|
|
2041
|
+
toolName,
|
|
2042
|
+
isError: true,
|
|
2043
|
+
result: {
|
|
2044
|
+
status: "error",
|
|
2045
|
+
error: { message: validation.message },
|
|
2046
|
+
},
|
|
2047
|
+
durationMs: 0,
|
|
2048
|
+
sessionId: options.sessionId,
|
|
2049
|
+
errorMessage: validation.message,
|
|
2050
|
+
};
|
|
2051
|
+
}
|
|
2052
|
+
try {
|
|
2053
|
+
const value = await execute(
|
|
2054
|
+
validation.value as never,
|
|
2055
|
+
{
|
|
2056
|
+
toolCallId: callId,
|
|
2057
|
+
workspaceDir: options.cwd,
|
|
2058
|
+
} as never
|
|
2059
|
+
);
|
|
2060
|
+
return {
|
|
2061
|
+
callId,
|
|
2062
|
+
toolName,
|
|
2063
|
+
isError: false,
|
|
2064
|
+
result: normalizeToolResult(value),
|
|
2065
|
+
durationMs: 0,
|
|
2066
|
+
sessionId: options.sessionId,
|
|
2067
|
+
};
|
|
2068
|
+
} catch (error) {
|
|
2069
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2070
|
+
return {
|
|
2071
|
+
callId,
|
|
2072
|
+
toolName,
|
|
2073
|
+
isError: true,
|
|
2074
|
+
result: { status: "error", error: { message } },
|
|
2075
|
+
durationMs: 0,
|
|
2076
|
+
sessionId: options.sessionId,
|
|
2077
|
+
errorMessage: message,
|
|
2078
|
+
};
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
|
|
1796
2082
|
/**
|
|
1797
2083
|
* Local harness cwd for a session: per-send override, else the shared
|
|
1798
2084
|
* `agent.local.workspaceDir`, else `<agent.local.cwd>/<sessionId>`, else
|
|
@@ -1840,19 +2126,22 @@ export class SessionEngine {
|
|
|
1840
2126
|
if (
|
|
1841
2127
|
workspaceDir === undefined ||
|
|
1842
2128
|
agent.runtime !== "local" ||
|
|
2129
|
+
this.hasRootOverride() ||
|
|
1843
2130
|
this.runner.prewarm === undefined
|
|
1844
2131
|
) {
|
|
1845
2132
|
return;
|
|
1846
2133
|
}
|
|
1847
2134
|
const startedAt = Date.now();
|
|
1848
2135
|
try {
|
|
2136
|
+
const harness = resolveHostHarnessToolOptions(agent.allowedTools);
|
|
1849
2137
|
const outcome = await this.runner.prewarm({
|
|
1850
2138
|
workspaceDir,
|
|
1851
2139
|
runnerStateDir: join(this.stateRoot, "runner"),
|
|
1852
2140
|
sandbox: agent.local?.sandbox === true,
|
|
1853
|
-
...(
|
|
2141
|
+
...(harness.tools === undefined ? {} : { tools: harness.tools }),
|
|
2142
|
+
...(harness.disallowedTools === undefined
|
|
1854
2143
|
? {}
|
|
1855
|
-
: {
|
|
2144
|
+
: { disallowedTools: harness.disallowedTools }),
|
|
1856
2145
|
// No session yet, so auth'd connections are omitted rather than
|
|
1857
2146
|
// warmed without an identity; the per-send flag still has to match
|
|
1858
2147
|
// the turns' requests or the warmed executor is one no turn asks for.
|
|
@@ -1942,22 +2231,24 @@ export class SessionEngine {
|
|
|
1942
2231
|
// cwd is overridden (e.g. a PR worktree). A/B variant instruction
|
|
1943
2232
|
// overlays are applied via agentForSession.
|
|
1944
2233
|
const agent = this.agentForSession(record);
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
2234
|
+
if (!this.hasRootOverride()) {
|
|
2235
|
+
await materializeWorkspace({
|
|
2236
|
+
agent,
|
|
2237
|
+
workspaceDir,
|
|
2238
|
+
seed: isFirstTurn,
|
|
2239
|
+
stateRoot: this.stateRoot,
|
|
2240
|
+
});
|
|
2241
|
+
this.materializedWorkspaces.add(sessionId);
|
|
2242
|
+
// Must precede the carrier: the harness resolves rules, skills,
|
|
2243
|
+
// `mcp.json` and hooks from its git root, so a workspace nested in an
|
|
2244
|
+
// outer repo would load that repo's hooks instead of the ones written
|
|
2245
|
+
// just below. Only for directories this session owns — a borrowed
|
|
2246
|
+
// checkout or worktree keeps its own root.
|
|
2247
|
+
if (!this.hasBorrowedWorkspace(record)) {
|
|
2248
|
+
await ensureOwnGitRoot(harnessCwd);
|
|
2249
|
+
}
|
|
2250
|
+
await this.materializeNudgeCarrier(record, harnessCwd);
|
|
1959
2251
|
}
|
|
1960
|
-
await this.materializeNudgeCarrier(record, harnessCwd);
|
|
1961
2252
|
// Baseline before the agent can run, so a follow-up that arrives and
|
|
1962
2253
|
// drains before the first tool boundary still counts as an increase.
|
|
1963
2254
|
await this.baselineDeliveredNudges(record);
|
|
@@ -1967,7 +2258,13 @@ export class SessionEngine {
|
|
|
1967
2258
|
pendingFiles !== undefined &&
|
|
1968
2259
|
Object.keys(pendingFiles).length > 0
|
|
1969
2260
|
) {
|
|
1970
|
-
await
|
|
2261
|
+
const root = await this.resolveFileSystem({
|
|
2262
|
+
sessionId,
|
|
2263
|
+
continuationKey: record.continuationKey ?? undefined,
|
|
2264
|
+
cache: true,
|
|
2265
|
+
cwd: harnessCwd,
|
|
2266
|
+
});
|
|
2267
|
+
await writeWorkspaceFilesToRoot(root, pendingFiles);
|
|
1971
2268
|
}
|
|
1972
2269
|
if (
|
|
1973
2270
|
(pendingFiles !== undefined &&
|
|
@@ -2004,6 +2301,14 @@ export class SessionEngine {
|
|
|
2004
2301
|
instructionsInvisibleToHarness,
|
|
2005
2302
|
emit,
|
|
2006
2303
|
async (update) => {
|
|
2304
|
+
const turn = this.turnRoots.get(sessionId);
|
|
2305
|
+
if (turn !== undefined) {
|
|
2306
|
+
update = await applyWebScratchUpdate({
|
|
2307
|
+
cwd: turn.cwd,
|
|
2308
|
+
root: turn.root,
|
|
2309
|
+
update,
|
|
2310
|
+
});
|
|
2311
|
+
}
|
|
2007
2312
|
mapper.handleUpdate(update);
|
|
2008
2313
|
// Yield so the HTTP NDJSON writer can flush tool start/result
|
|
2009
2314
|
// events while sibling parallel tools are still executing.
|
|
@@ -2039,6 +2344,7 @@ export class SessionEngine {
|
|
|
2039
2344
|
outcome = { status: "error", errorMessage: describeError(error) };
|
|
2040
2345
|
} finally {
|
|
2041
2346
|
permit.release();
|
|
2347
|
+
this.dropTurnFileSystem(sessionId);
|
|
2042
2348
|
this.approvals.cancelSession(sessionId, "deny");
|
|
2043
2349
|
this.auths.cancelSession(sessionId, "deny");
|
|
2044
2350
|
await this.clearPendingApprovals(sessionId);
|
|
@@ -2291,6 +2597,17 @@ export class SessionEngine {
|
|
|
2291
2597
|
// allowlist, and a restriction that silently does not apply is worse
|
|
2292
2598
|
// than a refused turn. Non-local agents already fail discovery; this
|
|
2293
2599
|
// guards hybrid agents' per-send cloud sessions.
|
|
2600
|
+
//
|
|
2601
|
+
// Factory roots omit `allowedTools` at discovery (the live handle is
|
|
2602
|
+
// resolved per session). The allowlist guard below therefore never
|
|
2603
|
+
// fires for `defineAgent({ root: (ctx) => … })` with omitted `tools`,
|
|
2604
|
+
// and a per-send `cloud` attach would skip the virtual-root allowlist
|
|
2605
|
+
// and run unrestricted on a VM. Any authored root refuses non-local turns.
|
|
2606
|
+
if (runtime !== "local" && this.hasRootOverride()) {
|
|
2607
|
+
throw new Error(
|
|
2608
|
+
`Agent "${agent.name}" declares root, which cannot be served on ${runtime} turns. Refusing the session rather than running it against the host VM.`
|
|
2609
|
+
);
|
|
2610
|
+
}
|
|
2294
2611
|
if (runtime !== "local" && agent.allowedTools !== undefined) {
|
|
2295
2612
|
throw new Error(
|
|
2296
2613
|
`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 +2636,48 @@ export class SessionEngine {
|
|
|
2319
2636
|
override: record.cloudOverride,
|
|
2320
2637
|
})
|
|
2321
2638
|
: undefined;
|
|
2639
|
+
const authoredRoot = this.hasRootOverride();
|
|
2640
|
+
const root =
|
|
2641
|
+
runtime === "local"
|
|
2642
|
+
? await this.resolveFileSystem({
|
|
2643
|
+
sessionId: record.sessionId,
|
|
2644
|
+
continuationKey: record.continuationKey ?? undefined,
|
|
2645
|
+
cache: true,
|
|
2646
|
+
cwd: this.harnessCwd(record),
|
|
2647
|
+
})
|
|
2648
|
+
: undefined;
|
|
2649
|
+
const harnessTools =
|
|
2650
|
+
runtime === "local" && root !== undefined
|
|
2651
|
+
? authoredRoot
|
|
2652
|
+
? {
|
|
2653
|
+
tools: resolveFileSystemTools({
|
|
2654
|
+
root,
|
|
2655
|
+
tools: agent.allowedTools,
|
|
2656
|
+
}),
|
|
2657
|
+
}
|
|
2658
|
+
: resolveHostHarnessToolOptions(agent.allowedTools)
|
|
2659
|
+
: undefined;
|
|
2660
|
+
const fileTools =
|
|
2661
|
+
root === undefined
|
|
2662
|
+
? []
|
|
2663
|
+
: synthesizeFileSystemTools(
|
|
2664
|
+
root,
|
|
2665
|
+
authoredRoot
|
|
2666
|
+
? rootFileToolsFromAuthored(agent.allowedTools)
|
|
2667
|
+
: hostFileToolsFromAuthored(agent.allowedTools)
|
|
2668
|
+
);
|
|
2669
|
+
// Host-default keeps native SemanticSearch (the host index).
|
|
2670
|
+
// `localFileSystem.search` is empty, so synthesizing would
|
|
2671
|
+
// replace a working index with an empty custom tool.
|
|
2672
|
+
const synthesized =
|
|
2673
|
+
root === undefined
|
|
2674
|
+
? []
|
|
2675
|
+
: this.withTurnScratchTools(
|
|
2676
|
+
authoredRoot
|
|
2677
|
+
? fileTools
|
|
2678
|
+
: fileTools.filter((tool) => tool.name !== "SemanticSearch"),
|
|
2679
|
+
record.sessionId
|
|
2680
|
+
);
|
|
2322
2681
|
let turnPrompt = prompt;
|
|
2323
2682
|
if (isFirstTurn) {
|
|
2324
2683
|
const contextBlock = buildSessionContextBlock({
|
|
@@ -2374,6 +2733,18 @@ export class SessionEngine {
|
|
|
2374
2733
|
sections += `\n\n${buildCloudMemorySection(this.project.name)}`;
|
|
2375
2734
|
}
|
|
2376
2735
|
turnPrompt = `${preamble}${sections}\n\n${turnPrompt}`;
|
|
2736
|
+
} else if (runtime === "local" && authoredRoot) {
|
|
2737
|
+
// Virtual roots skip materialize, so AGENTS.md never lands on disk.
|
|
2738
|
+
// Ship the same preamble the borrowed-cwd local path uses.
|
|
2739
|
+
const preamble = buildAgentsMdContent(
|
|
2740
|
+
{ ...agent, tools: [...agent.tools, ...synthesized] },
|
|
2741
|
+
{ includeScripts: true }
|
|
2742
|
+
);
|
|
2743
|
+
const workspaceInfo = `<user_info>\nWorkspace Path: /\n</user_info>`;
|
|
2744
|
+
turnPrompt =
|
|
2745
|
+
preamble === ""
|
|
2746
|
+
? `${workspaceInfo}\n\n${turnPrompt}`
|
|
2747
|
+
: `${workspaceInfo}\n\n${preamble}\n\n${turnPrompt}`;
|
|
2377
2748
|
} else if (runtime === "local" && instructionsInvisibleToHarness) {
|
|
2378
2749
|
// Local workspace whose AGENTS.md the harness will not load. With a
|
|
2379
2750
|
// borrowed harness cwd, agent tool scripts live only in the durable
|
|
@@ -2392,9 +2763,18 @@ export class SessionEngine {
|
|
|
2392
2763
|
? {}
|
|
2393
2764
|
: { sdkAgentId: record.sdkAgentId }),
|
|
2394
2765
|
workspaceDir: this.harnessCwd(record),
|
|
2395
|
-
sandbox:
|
|
2396
|
-
|
|
2397
|
-
|
|
2766
|
+
sandbox: authoredRoot
|
|
2767
|
+
? false
|
|
2768
|
+
: this.project.agent.local?.sandbox === true,
|
|
2769
|
+
...(runtime === "local" && harnessTools !== undefined
|
|
2770
|
+
? {
|
|
2771
|
+
...(harnessTools.tools === undefined
|
|
2772
|
+
? {}
|
|
2773
|
+
: { tools: harnessTools.tools }),
|
|
2774
|
+
...(harnessTools.disallowedTools === undefined
|
|
2775
|
+
? {}
|
|
2776
|
+
: { disallowedTools: harnessTools.disallowedTools }),
|
|
2777
|
+
}
|
|
2398
2778
|
: {}),
|
|
2399
2779
|
runnerStateDir: join(this.stateRoot, "runner"),
|
|
2400
2780
|
prompt: turnPrompt,
|
|
@@ -2418,7 +2798,13 @@ export class SessionEngine {
|
|
|
2418
2798
|
: {}),
|
|
2419
2799
|
customTools:
|
|
2420
2800
|
runtime === "local"
|
|
2421
|
-
? await this.buildCustomTools(
|
|
2801
|
+
? await this.buildCustomTools(
|
|
2802
|
+
record,
|
|
2803
|
+
agent,
|
|
2804
|
+
turnId,
|
|
2805
|
+
emit,
|
|
2806
|
+
synthesized
|
|
2807
|
+
)
|
|
2422
2808
|
: {},
|
|
2423
2809
|
mcpServers: this.buildMcpServers(
|
|
2424
2810
|
runtime,
|
|
@@ -2712,7 +3098,8 @@ export class SessionEngine {
|
|
|
2712
3098
|
record: SessionRecord,
|
|
2713
3099
|
agent: ResolvedAgent,
|
|
2714
3100
|
turnId: string,
|
|
2715
|
-
emit: (payload: SessionEventPayload) => void
|
|
3101
|
+
emit: (payload: SessionEventPayload) => void,
|
|
3102
|
+
extraTools: DiscoveredTool[] = []
|
|
2716
3103
|
): Promise<Record<string, SDKCustomTool>> {
|
|
2717
3104
|
const tools: Record<string, SDKCustomTool> = {};
|
|
2718
3105
|
const register = (tool: ResolvedAgent["tools"][number]): void => {
|
|
@@ -2742,7 +3129,7 @@ export class SessionEngine {
|
|
|
2742
3129
|
},
|
|
2743
3130
|
};
|
|
2744
3131
|
};
|
|
2745
|
-
for (const tool of agent.tools) {
|
|
3132
|
+
for (const tool of [...agent.tools, ...extraTools]) {
|
|
2746
3133
|
if (tool.execution !== "server") {
|
|
2747
3134
|
continue;
|
|
2748
3135
|
}
|
|
@@ -3682,6 +4069,27 @@ export class SessionEngine {
|
|
|
3682
4069
|
const session = identity.session;
|
|
3683
4070
|
let workspaceDir = identity.workspaceDir;
|
|
3684
4071
|
let scratchDir = identity.scratchDir;
|
|
4072
|
+
if (
|
|
4073
|
+
isFileSystemToolAlias(toolName) &&
|
|
4074
|
+
identity.busy &&
|
|
4075
|
+
record !== undefined &&
|
|
4076
|
+
isMutatingFileSystemTool(toolName)
|
|
4077
|
+
) {
|
|
4078
|
+
throw new SessionBusyError(record.sessionId);
|
|
4079
|
+
}
|
|
4080
|
+
const native = await this.nativeFileSystemOutcome({
|
|
4081
|
+
toolName,
|
|
4082
|
+
input,
|
|
4083
|
+
callId,
|
|
4084
|
+
sessionId: record?.sessionId,
|
|
4085
|
+
continuationKey:
|
|
4086
|
+
record?.continuationKey ?? session.continuationKey ?? undefined,
|
|
4087
|
+
cwd: workspaceDir,
|
|
4088
|
+
dryRun: record?.dryRun === true,
|
|
4089
|
+
});
|
|
4090
|
+
if (native !== undefined) {
|
|
4091
|
+
return native;
|
|
4092
|
+
}
|
|
3685
4093
|
let tool = this.project.agent.tools.find((t) => t.name === toolName);
|
|
3686
4094
|
if (
|
|
3687
4095
|
tool === undefined &&
|
|
@@ -3783,7 +4191,10 @@ export class SessionEngine {
|
|
|
3783
4191
|
record !== undefined &&
|
|
3784
4192
|
identity.busy &&
|
|
3785
4193
|
this.materializedWorkspaces.has(record.sessionId);
|
|
3786
|
-
if (
|
|
4194
|
+
if (
|
|
4195
|
+
!this.hasRootOverride() &&
|
|
4196
|
+
(record === undefined || workspaceUnavailable || !turnMaterialized)
|
|
4197
|
+
) {
|
|
3787
4198
|
await materializeWorkspace({
|
|
3788
4199
|
agent: this.project.agent,
|
|
3789
4200
|
workspaceDir:
|
|
@@ -4745,9 +5156,8 @@ export class SessionEngine {
|
|
|
4745
5156
|
}
|
|
4746
5157
|
const record = await this.sessions.get(sessionId);
|
|
4747
5158
|
const sdkAgentId =
|
|
4748
|
-
(record?.sdkAgentId
|
|
4749
|
-
|
|
4750
|
-
: undefined) ?? cloudSdkAgentIdFromEvents(events);
|
|
5159
|
+
(record?.sdkAgentId?.startsWith("bc-") ? record.sdkAgentId : undefined) ??
|
|
5160
|
+
cloudSdkAgentIdFromEvents(events);
|
|
4751
5161
|
if (sdkAgentId === undefined) {
|
|
4752
5162
|
return;
|
|
4753
5163
|
}
|