@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.
Files changed (186) hide show
  1. package/dist/channels/change-monitors/change-monitors-channel.d.ts +5 -0
  2. package/dist/channels/change-monitors/change-monitors-channel.d.ts.map +1 -0
  3. package/dist/channels/change-monitors/change-monitors-channel.js +41 -0
  4. package/dist/channels/change-monitors/index.d.ts +4 -0
  5. package/dist/channels/change-monitors/index.d.ts.map +1 -0
  6. package/dist/channels/change-monitors/index.js +2 -0
  7. package/dist/channels/change-monitors/types.d.ts +39 -0
  8. package/dist/channels/change-monitors/types.d.ts.map +1 -0
  9. package/dist/channels/change-monitors/types.js +1 -0
  10. package/dist/channels/github/defaults.d.ts.map +1 -1
  11. package/dist/channels/github/defaults.js +4 -0
  12. package/dist/channels/github/progress.d.ts +22 -0
  13. package/dist/channels/github/progress.d.ts.map +1 -1
  14. package/dist/channels/github/progress.js +32 -5
  15. package/dist/channels/issues/index.d.ts +4 -0
  16. package/dist/channels/issues/index.d.ts.map +1 -0
  17. package/dist/channels/issues/index.js +2 -0
  18. package/dist/channels/issues/issues-channel.d.ts +5 -0
  19. package/dist/channels/issues/issues-channel.d.ts.map +1 -0
  20. package/dist/channels/issues/issues-channel.js +37 -0
  21. package/dist/channels/issues/types.d.ts +33 -0
  22. package/dist/channels/issues/types.d.ts.map +1 -0
  23. package/dist/channels/issues/types.js +1 -0
  24. package/dist/docs/404.html +2 -2
  25. package/dist/docs/ab.html +4 -4
  26. package/dist/docs/assets/{app.DW9pXkpL.js → app.DbMWUyGb.js} +1 -1
  27. package/dist/docs/assets/chunks/@localSearchIndexroot.Bqsulb9K.js +1 -0
  28. package/dist/docs/assets/chunks/{VPLocalSearchBox.CdEIUwDg.js → VPLocalSearchBox.DcNeTh9o.js} +1 -1
  29. package/dist/docs/assets/chunks/{theme.YZMHgPgg.js → theme.DMjhY3gl.js} +2 -2
  30. package/dist/docs/assets/{guides_github.md.DKYiT9do.js → guides_github.md.c0gdGJ-D.js} +1 -1
  31. package/dist/docs/assets/{guides_github.md.DKYiT9do.lean.js → guides_github.md.c0gdGJ-D.lean.js} +1 -1
  32. package/dist/docs/assets/{reference_channels.md.Dz8Cnudp.js → reference_channels.md.DBGcaB7m.js} +2 -2
  33. package/dist/docs/assets/{reference_channels.md.Dz8Cnudp.lean.js → reference_channels.md.DBGcaB7m.lean.js} +1 -1
  34. package/dist/docs/building-with-agents.html +4 -4
  35. package/dist/docs/concepts.html +4 -4
  36. package/dist/docs/deployment.html +4 -4
  37. package/dist/docs/design/filesystem.md +376 -0
  38. package/dist/docs/evals.html +4 -4
  39. package/dist/docs/guides/agent-to-agent.html +4 -4
  40. package/dist/docs/guides/cloud-agents.html +4 -4
  41. package/dist/docs/guides/convert-automation.html +4 -4
  42. package/dist/docs/guides/github.html +6 -6
  43. package/dist/docs/guides/github.md +5 -0
  44. package/dist/docs/guides/human-in-the-loop.html +4 -4
  45. package/dist/docs/guides/mcp-oauth.html +4 -4
  46. package/dist/docs/guides/opentelemetry.html +4 -4
  47. package/dist/docs/guides/slack.html +4 -4
  48. package/dist/docs/guides/webhooks.html +4 -4
  49. package/dist/docs/hashmap.json +1 -1
  50. package/dist/docs/hillclimbing.html +4 -4
  51. package/dist/docs/index.html +4 -4
  52. package/dist/docs/llms-full.txt +406 -0
  53. package/dist/docs/llms.txt +1 -0
  54. package/dist/docs/quickstart.html +4 -4
  55. package/dist/docs/reference/agent-config.html +4 -4
  56. package/dist/docs/reference/artifacts.html +4 -4
  57. package/dist/docs/reference/channels.html +6 -6
  58. package/dist/docs/reference/channels.md +20 -0
  59. package/dist/docs/reference/cli.html +4 -4
  60. package/dist/docs/reference/connections.html +4 -4
  61. package/dist/docs/reference/extensions.html +4 -4
  62. package/dist/docs/reference/hooks.html +4 -4
  63. package/dist/docs/reference/http-api.html +4 -4
  64. package/dist/docs/reference/instructions.html +4 -4
  65. package/dist/docs/reference/playground.html +4 -4
  66. package/dist/docs/reference/project-layout.html +4 -4
  67. package/dist/docs/reference/prompt.html +4 -4
  68. package/dist/docs/reference/result.html +4 -4
  69. package/dist/docs/reference/schedules.html +4 -4
  70. package/dist/docs/reference/sessions.html +4 -4
  71. package/dist/docs/reference/skills.html +4 -4
  72. package/dist/docs/reference/subagents.html +4 -4
  73. package/dist/docs/reference/tools.html +4 -4
  74. package/dist/docs/scaffolding-agents.html +4 -4
  75. package/dist/docs/skills/ab.html +4 -4
  76. package/dist/docs/skills/create-agent.html +4 -4
  77. package/dist/docs/skills/debug.html +4 -4
  78. package/dist/docs/skills/evals.html +4 -4
  79. package/dist/docs/skills/framework-map.html +4 -4
  80. package/dist/docs/skills/github.html +4 -4
  81. package/dist/docs/skills/hillclimb.html +4 -4
  82. package/dist/docs/skills/index.html +4 -4
  83. package/dist/docs/skills/mcp-auth.html +4 -4
  84. package/dist/docs/skills/otel.html +4 -4
  85. package/dist/docs/skills/setup-slack.html +4 -4
  86. package/dist/docs/storage.html +4 -4
  87. package/dist/docs/templates/agentic-owners.html +4 -4
  88. package/dist/docs/templates/agents-md.html +4 -4
  89. package/dist/docs/templates/code-wiki.html +4 -4
  90. package/dist/docs/templates/demo.html +4 -4
  91. package/dist/docs/templates/pr-autofixer.html +4 -4
  92. package/dist/docs/templates/security-help.html +4 -4
  93. package/dist/docs/templates/security-reviewer.html +4 -4
  94. package/dist/docs/templates/triage.html +4 -4
  95. package/dist/docs/troubleshooting.html +4 -4
  96. package/dist/filesystem.d.ts +440 -0
  97. package/dist/filesystem.d.ts.map +1 -0
  98. package/dist/filesystem.js +1694 -0
  99. package/dist/index.d.ts +2 -0
  100. package/dist/index.d.ts.map +1 -1
  101. package/dist/index.js +1 -0
  102. package/dist/internal/continuation-channel.d.ts +4 -2
  103. package/dist/internal/continuation-channel.d.ts.map +1 -1
  104. package/dist/internal/continuation-channel.js +21 -12
  105. package/dist/internal/continuation-identity.d.ts +13 -5
  106. package/dist/internal/continuation-identity.d.ts.map +1 -1
  107. package/dist/internal/continuation-identity.js +74 -35
  108. package/dist/internal/discovery/agent.d.ts.map +1 -1
  109. package/dist/internal/discovery/agent.js +80 -8
  110. package/dist/internal/discovery/info.d.ts.map +1 -1
  111. package/dist/internal/discovery/info.js +3 -0
  112. package/dist/internal/factory-resource-channel.d.ts +33 -0
  113. package/dist/internal/factory-resource-channel.d.ts.map +1 -0
  114. package/dist/internal/factory-resource-channel.js +77 -0
  115. package/dist/internal/filesystem/errors.d.ts +64 -0
  116. package/dist/internal/filesystem/errors.d.ts.map +1 -0
  117. package/dist/internal/filesystem/errors.js +175 -0
  118. package/dist/internal/filesystem/format.d.ts +28 -0
  119. package/dist/internal/filesystem/format.d.ts.map +1 -0
  120. package/dist/internal/filesystem/format.js +213 -0
  121. package/dist/internal/filesystem/merge.d.ts +34 -0
  122. package/dist/internal/filesystem/merge.d.ts.map +1 -0
  123. package/dist/internal/filesystem/merge.js +196 -0
  124. package/dist/internal/filesystem/scratch.d.ts +59 -0
  125. package/dist/internal/filesystem/scratch.d.ts.map +1 -0
  126. package/dist/internal/filesystem/scratch.js +212 -0
  127. package/dist/internal/filesystem/sdk-schemas.d.ts +29 -0
  128. package/dist/internal/filesystem/sdk-schemas.d.ts.map +1 -0
  129. package/dist/internal/filesystem/sdk-schemas.js +153 -0
  130. package/dist/internal/filesystem/tools.d.ts +42 -0
  131. package/dist/internal/filesystem/tools.d.ts.map +1 -0
  132. package/dist/internal/filesystem/tools.js +618 -0
  133. package/dist/internal/filesystem/walk.d.ts +74 -0
  134. package/dist/internal/filesystem/walk.d.ts.map +1 -0
  135. package/dist/internal/filesystem/walk.js +339 -0
  136. package/dist/internal/guest-network.d.ts +5 -2
  137. package/dist/internal/guest-network.d.ts.map +1 -1
  138. package/dist/internal/guest-network.js +7 -2
  139. package/dist/internal/hosted-delivery.d.ts.map +1 -1
  140. package/dist/internal/hosted-delivery.js +12 -2
  141. package/dist/internal/sdk-runner.d.ts +1 -1
  142. package/dist/internal/sdk-runner.d.ts.map +1 -1
  143. package/dist/internal/sdk-runner.js +3 -1
  144. package/dist/internal/session-engine.d.ts +22 -0
  145. package/dist/internal/session-engine.d.ts.map +1 -1
  146. package/dist/internal/session-engine.js +362 -40
  147. package/dist/internal/testing/factory-resource-fixtures.d.ts +48 -0
  148. package/dist/internal/testing/factory-resource-fixtures.d.ts.map +1 -0
  149. package/dist/internal/testing/factory-resource-fixtures.js +41 -0
  150. package/dist/playground/assets/{index-59KvgREd.js → index-8YiozU89.js} +38 -38
  151. package/dist/playground/index.html +1 -1
  152. package/dist/types.d.ts +51 -3
  153. package/dist/types.d.ts.map +1 -1
  154. package/docs/design/filesystem.md +376 -0
  155. package/docs/guides/github.md +5 -0
  156. package/docs/reference/channels.md +20 -0
  157. package/package.json +16 -2
  158. package/src/channels/change-monitors/change-monitors-channel.ts +62 -0
  159. package/src/channels/change-monitors/index.ts +8 -0
  160. package/src/channels/change-monitors/types.ts +44 -0
  161. package/src/channels/github/defaults.ts +4 -0
  162. package/src/channels/github/progress.ts +76 -10
  163. package/src/channels/issues/index.ts +9 -0
  164. package/src/channels/issues/issues-channel.ts +51 -0
  165. package/src/channels/issues/types.ts +41 -0
  166. package/src/filesystem.ts +2074 -0
  167. package/src/index.ts +28 -0
  168. package/src/internal/continuation-channel.ts +29 -12
  169. package/src/internal/continuation-identity.ts +122 -37
  170. package/src/internal/discovery/agent.ts +87 -1
  171. package/src/internal/discovery/info.ts +3 -0
  172. package/src/internal/factory-resource-channel.ts +122 -0
  173. package/src/internal/filesystem/errors.ts +220 -0
  174. package/src/internal/filesystem/format.ts +276 -0
  175. package/src/internal/filesystem/merge.ts +195 -0
  176. package/src/internal/filesystem/scratch.ts +255 -0
  177. package/src/internal/filesystem/sdk-schemas.ts +204 -0
  178. package/src/internal/filesystem/tools.ts +821 -0
  179. package/src/internal/filesystem/walk.ts +369 -0
  180. package/src/internal/guest-network.ts +8 -2
  181. package/src/internal/hosted-delivery.ts +18 -1
  182. package/src/internal/sdk-runner.ts +4 -0
  183. package/src/internal/session-engine.ts +439 -29
  184. package/src/internal/testing/factory-resource-fixtures.ts +85 -0
  185. package/src/types.ts +49 -3
  186. package/dist/docs/assets/chunks/@localSearchIndexroot.nrl1ifBq.js +0 -1
@@ -0,0 +1,369 @@
1
+ /**
2
+ * Recursive listing, existence probes, and native LS trees. Matches the
3
+ * change-monitor VFS contract on origin/main: `list` is files-only and
4
+ * recursive; `stat` tells file / directory / missing apart; composers
5
+ * merge already-sorted streams with a k-way heap.
6
+ */
7
+
8
+ import { posix } from "node:path";
9
+ import { minimatch } from "minimatch";
10
+ import type { DirEntry, FileInfo, FileSystem } from "../../filesystem.js";
11
+ import { normalizeFsPath } from "../../filesystem.js";
12
+ import { mergeSorted } from "./merge.js";
13
+
14
+ /**
15
+ * Native LS `directoryTreeRoot` (LocalLsExecutor ingest tree).
16
+ * Synthesis builds this from `list` + `stat` via `buildLsTree`.
17
+ * Not a FileSystem method result and not part of the public
18
+ * Agent SDK surface.
19
+ */
20
+ export interface LsDirectoryTreeNode {
21
+ absPath: string;
22
+ childrenDirs: LsDirectoryTreeNode[];
23
+ childrenFiles: Array<{ name: string }>;
24
+ childrenWereProcessed: boolean;
25
+ fullSubtreeExtensionCounts: Record<string, number>;
26
+ numFiles: number;
27
+ }
28
+
29
+ /** Filename-character ingest budget; matches LocalLsExecutor. */
30
+ export const LS_INGEST_CHARACTER_BUDGET = 2_500;
31
+
32
+ /**
33
+ * Yield the files under `prefix`, sorted. A file whose path equals
34
+ * `prefix` is not under itself.
35
+ */
36
+ export async function* listFilesFromPaths(
37
+ files: Iterable<DirEntry>,
38
+ prefix?: string
39
+ ): AsyncGenerator<DirEntry> {
40
+ const scope = normalizeListPrefix(prefix);
41
+ const withSlash = scope === "/" ? "/" : `${scope}/`;
42
+ const matches: DirEntry[] = [];
43
+ for (const file of files) {
44
+ const filePath = normalizeFsPath(file.path);
45
+ if (scope === "/") {
46
+ matches.push(
47
+ file.size === undefined
48
+ ? { path: filePath }
49
+ : { path: filePath, size: file.size }
50
+ );
51
+ continue;
52
+ }
53
+ if (filePath === scope || !filePath.startsWith(withSlash)) {
54
+ continue;
55
+ }
56
+ matches.push(
57
+ file.size === undefined
58
+ ? { path: filePath }
59
+ : { path: filePath, size: file.size }
60
+ );
61
+ }
62
+ matches.sort((left, right) => left.path.localeCompare(right.path));
63
+ yield* matches;
64
+ }
65
+
66
+ export function normalizeListPrefix(prefix?: string): string {
67
+ if (prefix === undefined || prefix === "" || prefix === "/") {
68
+ return "/";
69
+ }
70
+ return normalizeFsPath(prefix);
71
+ }
72
+
73
+ /**
74
+ * Existence probe over a flat file-path index. Exact key → file; any
75
+ * key with this prefix → directory; `""` / `/` → directory; else missing.
76
+ */
77
+ export function statFromFilePaths(
78
+ files: Iterable<string>,
79
+ path: string
80
+ ): FileInfo {
81
+ const normalized = normalizeListPrefix(path);
82
+ if (normalized === "/") {
83
+ return { kind: "directory" };
84
+ }
85
+ const prefix = `${normalized}/`;
86
+ for (const file of files) {
87
+ const filePath = normalizeFsPath(file);
88
+ if (filePath === normalized) {
89
+ return { kind: "file" };
90
+ }
91
+ if (filePath.startsWith(prefix)) {
92
+ return { kind: "directory" };
93
+ }
94
+ }
95
+ return { kind: "missing" };
96
+ }
97
+
98
+ export async function collectList(
99
+ root: Pick<FileSystem, "list">,
100
+ prefix?: string
101
+ ): Promise<DirEntry[]> {
102
+ const entries: DirEntry[] = [];
103
+ if (root.list === undefined) {
104
+ return entries;
105
+ }
106
+ for await (const entry of root.list(prefix)) {
107
+ entries.push(entry);
108
+ }
109
+ return entries;
110
+ }
111
+
112
+ export async function collectGlob(
113
+ root: Pick<FileSystem, "glob">,
114
+ options: { pattern: string; dir?: string }
115
+ ): Promise<DirEntry[]> {
116
+ const entries: DirEntry[] = [];
117
+ if (root.glob === undefined) {
118
+ return entries;
119
+ }
120
+ for await (const entry of root.glob(options.pattern, options.dir)) {
121
+ entries.push(entry);
122
+ }
123
+ return entries;
124
+ }
125
+
126
+ /**
127
+ * List, then `stat` only when nothing came back. A non-empty listing is
128
+ * already a directory (a file is not under itself).
129
+ */
130
+ export async function collectListOrStat(
131
+ root: Pick<FileSystem, "list" | "stat">,
132
+ path?: string
133
+ ): Promise<{ kind: FileInfo["kind"]; files: DirEntry[] }> {
134
+ const files = await collectList(root, path);
135
+ if (files.length > 0) {
136
+ return { kind: "directory", files };
137
+ }
138
+ if (root.stat === undefined) {
139
+ return { kind: "missing", files };
140
+ }
141
+ const probe = path === undefined || path === "" ? "." : path;
142
+ return { kind: (await root.stat(probe)).kind, files };
143
+ }
144
+
145
+ export async function* mergeSortedByPath(
146
+ sources: Array<Iterable<DirEntry> | AsyncIterable<DirEntry>>,
147
+ options?: { unique?: boolean }
148
+ ): AsyncGenerator<DirEntry> {
149
+ yield* mergeSorted(sources, {
150
+ compare: (left, right) => left.path.localeCompare(right.path),
151
+ unique: options?.unique === true,
152
+ });
153
+ }
154
+
155
+ function emptyLsNode(absPath: string): LsDirectoryTreeNode {
156
+ return {
157
+ absPath,
158
+ childrenDirs: [],
159
+ childrenFiles: [],
160
+ childrenWereProcessed: true,
161
+ fullSubtreeExtensionCounts: {},
162
+ numFiles: 0,
163
+ };
164
+ }
165
+
166
+ /**
167
+ * Build the native LS tree from a recursive file listing. Applies the
168
+ * LocalLsExecutor ingest budget so a huge directory truncates the same way.
169
+ */
170
+ export function buildLsTree(
171
+ dir: string,
172
+ files: readonly DirEntry[]
173
+ ): LsDirectoryTreeNode {
174
+ const rootPath = normalizeListPrefix(dir);
175
+ const root: LsDirectoryTreeNode = emptyLsNode(rootPath);
176
+ const dirNodes = new Map<string, LsDirectoryTreeNode>([[rootPath, root]]);
177
+ const state = {
178
+ charactersUsed: rootPath.length,
179
+ budgetExceeded: false,
180
+ };
181
+ for (const file of files) {
182
+ addPathToTree(
183
+ file.path,
184
+ rootPath,
185
+ dirNodes,
186
+ state,
187
+ LS_INGEST_CHARACTER_BUDGET
188
+ );
189
+ }
190
+ return root;
191
+ }
192
+
193
+ function addPathToTree(
194
+ filePath: string,
195
+ rootPath: string,
196
+ dirNodes: Map<string, LsDirectoryTreeNode>,
197
+ state: { charactersUsed: number; budgetExceeded: boolean },
198
+ characterBudget: number
199
+ ): void {
200
+ const fileName = posix.basename(filePath);
201
+ const dirPath = posixParent(filePath);
202
+ ensureDirectoryPath(dirPath, rootPath, dirNodes, state.budgetExceeded);
203
+ const parentNode = dirNodes.get(dirPath);
204
+ if (parentNode === undefined) {
205
+ return;
206
+ }
207
+ const fileCharCost = fileName.length;
208
+ if (
209
+ !state.budgetExceeded &&
210
+ state.charactersUsed + fileCharCost <= characterBudget
211
+ ) {
212
+ parentNode.childrenFiles.push({ name: fileName });
213
+ state.charactersUsed += fileCharCost;
214
+ return;
215
+ }
216
+ if (!state.budgetExceeded) {
217
+ state.budgetExceeded = true;
218
+ let currentDir = dirPath;
219
+ while (currentDir.startsWith(rootPath)) {
220
+ const node = dirNodes.get(currentDir);
221
+ if (node !== undefined) {
222
+ node.childrenWereProcessed = false;
223
+ }
224
+ const parentDir = posixParent(currentDir);
225
+ if (parentDir === currentDir) {
226
+ break;
227
+ }
228
+ currentDir = parentDir;
229
+ }
230
+ }
231
+ const extension = posix.extname(filePath);
232
+ let currentDir = dirPath;
233
+ while (currentDir.startsWith(rootPath)) {
234
+ const node = dirNodes.get(currentDir);
235
+ if (node !== undefined) {
236
+ node.fullSubtreeExtensionCounts[extension] =
237
+ (node.fullSubtreeExtensionCounts[extension] ?? 0) + 1;
238
+ node.numFiles += 1;
239
+ }
240
+ const parentDir = posixParent(currentDir);
241
+ if (parentDir === currentDir) {
242
+ break;
243
+ }
244
+ currentDir = parentDir;
245
+ }
246
+ }
247
+
248
+ function ensureDirectoryPath(
249
+ dirPath: string,
250
+ rootPath: string,
251
+ dirNodes: Map<string, LsDirectoryTreeNode>,
252
+ budgetExceeded: boolean
253
+ ): void {
254
+ if (dirNodes.has(dirPath) || dirPath === rootPath) {
255
+ return;
256
+ }
257
+ const parentPath = posixParent(dirPath);
258
+ if (parentPath !== dirPath && pathIsUnderRoot(parentPath, rootPath)) {
259
+ ensureDirectoryPath(parentPath, rootPath, dirNodes, budgetExceeded);
260
+ }
261
+ const dirNode: LsDirectoryTreeNode = {
262
+ ...emptyLsNode(dirPath),
263
+ childrenWereProcessed: !budgetExceeded,
264
+ };
265
+ dirNodes.set(dirPath, dirNode);
266
+ const parentNode = dirNodes.get(parentPath);
267
+ if (parentNode !== undefined) {
268
+ parentNode.childrenDirs.push(dirNode);
269
+ }
270
+ }
271
+
272
+ function pathIsUnderRoot(path: string, rootPath: string): boolean {
273
+ return (
274
+ path === rootPath ||
275
+ path.startsWith(rootPath === "/" ? "/" : `${rootPath}/`)
276
+ );
277
+ }
278
+
279
+ /**
280
+ * Native LS ignore_globs: a pattern that is not already recursive is
281
+ * matched at any depth (the same `** /` prefix the Cursor SDK native uses).
282
+ */
283
+ export function applyIgnores(
284
+ entries: readonly DirEntry[],
285
+ ignoreGlobs?: readonly string[]
286
+ ): DirEntry[] {
287
+ if (ignoreGlobs === undefined || ignoreGlobs.length === 0) {
288
+ return [...entries];
289
+ }
290
+ return entries.filter(
291
+ (entry) => !ignoreGlobs.some((glob) => matchesIgnoreGlob(entry.path, glob))
292
+ );
293
+ }
294
+
295
+ function matchesIgnoreGlob(filePath: string, glob: string): boolean {
296
+ const recursivePrefix = "**".concat("/");
297
+ const pattern = glob.startsWith(recursivePrefix)
298
+ ? glob
299
+ : recursivePrefix + glob;
300
+ const relative = filePath.replace(/^\//, "");
301
+ return (
302
+ minimatch(relative, pattern, { dot: true }) ||
303
+ minimatch(filePath, pattern, { dot: true })
304
+ );
305
+ }
306
+
307
+ export async function lsFromList(
308
+ root: Pick<FileSystem, "list" | "stat">,
309
+ path?: string,
310
+ ignoreGlobs?: readonly string[]
311
+ ): Promise<
312
+ | { status: "success"; value: { directoryTreeRoot: LsDirectoryTreeNode } }
313
+ | { status: "error"; error: { message: string } }
314
+ > {
315
+ const listed = await collectListOrStat(root, path);
316
+ const treePath = path ?? inferListedRoot(listed.files);
317
+ switch (listed.kind) {
318
+ case "missing":
319
+ return {
320
+ status: "error",
321
+ error: { message: `Path does not exist: ${treePath}` },
322
+ };
323
+ case "file":
324
+ return {
325
+ status: "error",
326
+ error: { message: `Path is not a directory: ${treePath}` },
327
+ };
328
+ case "directory":
329
+ return {
330
+ status: "success",
331
+ value: {
332
+ directoryTreeRoot: buildLsTree(
333
+ treePath,
334
+ applyIgnores(listed.files, ignoreGlobs)
335
+ ),
336
+ },
337
+ };
338
+ }
339
+ }
340
+
341
+ function inferListedRoot(files: readonly DirEntry[]): string {
342
+ if (files.length === 0) {
343
+ return "/";
344
+ }
345
+ let prefix = posixParent(files[0]!.path);
346
+ for (const file of files) {
347
+ while (
348
+ prefix !== "/" &&
349
+ file.path !== prefix &&
350
+ !file.path.startsWith(`${prefix}/`)
351
+ ) {
352
+ const parent = posixParent(prefix);
353
+ if (parent === prefix) {
354
+ return "/";
355
+ }
356
+ prefix = parent;
357
+ }
358
+ }
359
+ return prefix;
360
+ }
361
+
362
+ /**
363
+ * `path.posix.dirname` returns `.` for slash-free paths. Treat that as
364
+ * the virtual root so LS walks terminate and relative files attach to `/`.
365
+ */
366
+ function posixParent(path: string): string {
367
+ const dir = posix.dirname(path);
368
+ return dir === "." || dir === path ? "/" : dir;
369
+ }
@@ -14,8 +14,9 @@
14
14
  *
15
15
  * Quiet guest Connect (`TypeError: fetch failed` with no errno, Connect
16
16
  * `[unknown]`/`[canceled]`/`[unavailable]`, headers/body timeout) is a
17
- * stall, not a dead NIC. Those become `guest_connect_stalled` so TAS does
18
- * not treat them as restore-window mystery 503s.
17
+ * stall, not a dead NIC. Those become `guest_connect_stalled` so TAS
18
+ * retries the same pod including when `waitForCompletion` resolves
19
+ * with that message instead of throwing.
19
20
  */
20
21
 
21
22
  export const GUEST_NETWORK_UNAVAILABLE = "guest_network_unavailable";
@@ -112,6 +113,11 @@ export function isQuietGuestConnectStall(error: unknown): boolean {
112
113
  return isQuietSignatureAtDepth(error, 0);
113
114
  }
114
115
 
116
+ /** Message-only form for `waitForCompletion` outcomes that do not throw. */
117
+ export function isQuietGuestConnectStallMessage(message: string): boolean {
118
+ return QUIET_CONNECT_MESSAGE.test(message);
119
+ }
120
+
115
121
  function errorCode(error: Error): string | undefined {
116
122
  const code = (error as NodeJS.ErrnoException).code;
117
123
  return typeof code === "string" && code.length > 0 ? code : undefined;
@@ -15,10 +15,12 @@ import {
15
15
  } from "./deferred-channel-session.js";
16
16
  import {
17
17
  formatGuestNetworkServerTiming,
18
+ GUEST_CONNECT_STALLED,
18
19
  GUEST_NETWORK_UNAVAILABLE,
19
20
  GuestNetworkUnavailableError,
20
21
  type GuestNetworkWaitResult,
21
22
  type GuestNetworkWaitStats,
23
+ isQuietGuestConnectStallMessage,
22
24
  waitForGuestNetworkReady,
23
25
  } from "./guest-network.js";
24
26
  import {
@@ -647,11 +649,14 @@ export function registerHostedDeliveryRoute(input: {
647
649
  ),
648
650
  });
649
651
  if (outcome.status !== "finished") {
652
+ // waitForCompletion resolves (does not throw) on a quiet
653
+ // Connect abort. Map that to guest_connect_stalled so TAS
654
+ // retries the same pod. Real runner failures stay turn_failed.
650
655
  return {
651
656
  status: 503,
652
657
  body: {
653
658
  ok: false,
654
- error: "turn_failed",
659
+ error: hostedErrorForUnfinishedTurn(outcome.errorMessage),
655
660
  turnStatus: outcome.status,
656
661
  message: outcome.errorMessage,
657
662
  },
@@ -682,6 +687,18 @@ export function registerHostedDeliveryRoute(input: {
682
687
  });
683
688
  }
684
689
 
690
+ function hostedErrorForUnfinishedTurn(
691
+ errorMessage: string | undefined
692
+ ): typeof GUEST_CONNECT_STALLED | "turn_failed" {
693
+ if (
694
+ errorMessage !== undefined &&
695
+ isQuietGuestConnectStallMessage(errorMessage)
696
+ ) {
697
+ return GUEST_CONNECT_STALLED;
698
+ }
699
+ return "turn_failed";
700
+ }
701
+
685
702
  async function afterHostedWork<T>(
686
703
  flushOtel: (() => Promise<void>) | undefined,
687
704
  work: () => Promise<T>
@@ -67,6 +67,7 @@ export type RunnerPrewarmRequest = Pick<
67
67
  | "runnerStateDir"
68
68
  | "sandbox"
69
69
  | "tools"
70
+ | "disallowedTools"
70
71
  | "mcpServers"
71
72
  | "perSendMcpServers"
72
73
  | "subagents"
@@ -542,6 +543,9 @@ export class CursorSdkRunner implements AgentRunner {
542
543
  // tool restrictions on cloud agents. Passed on create *and* resume
543
544
  // (both flow through here) — the SDK does not persist it across runs.
544
545
  ...(request.tools === undefined ? {} : { tools: request.tools }),
546
+ ...(request.disallowedTools === undefined
547
+ ? {}
548
+ : { disallowedTools: request.disallowedTools }),
545
549
  local: {
546
550
  cwd: request.workspaceDir,
547
551
  settingSources: ["project"],