@mastra/factory 0.24.0-alpha.8 → 0.24.0

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 (301) hide show
  1. package/README.md +4 -4
  2. package/dist/environment/build-runner.js +2 -1
  3. package/dist/environment/build-runner.js.map +1 -1
  4. package/dist/environment/build-workflow.js +1 -1
  5. package/dist/environment/build-workflow.js.map +1 -1
  6. package/dist/environment/build.js +16 -14
  7. package/dist/environment/build.js.map +1 -1
  8. package/dist/factory.d.ts.map +1 -1
  9. package/dist/factory.js +111 -19
  10. package/dist/factory.js.map +1 -1
  11. package/dist/integrations/base.d.ts +1 -1
  12. package/dist/integrations/base.d.ts.map +1 -1
  13. package/dist/integrations/github/app-identity.js.map +1 -1
  14. package/dist/integrations/github/integration.js +1 -1
  15. package/dist/integrations/github/integration.js.map +1 -1
  16. package/dist/integrations/github/reconcile-worker.js +1 -1
  17. package/dist/integrations/github/reconcile-worker.js.map +1 -1
  18. package/dist/integrations/github/routes.d.ts +1 -1
  19. package/dist/integrations/github/routes.d.ts.map +1 -1
  20. package/dist/integrations/github/routes.js +24 -13
  21. package/dist/integrations/github/routes.js.map +1 -1
  22. package/dist/integrations/github/rules.d.ts.map +1 -1
  23. package/dist/integrations/github/rules.js +53 -9
  24. package/dist/integrations/github/rules.js.map +1 -1
  25. package/dist/integrations/github/sandbox.js +2 -2
  26. package/dist/integrations/github/sandbox.js.map +1 -1
  27. package/dist/integrations/github/session-subscriptions.d.ts.map +1 -1
  28. package/dist/integrations/github/session-subscriptions.js +1 -2
  29. package/dist/integrations/github/session-subscriptions.js.map +1 -1
  30. package/dist/integrations/github/subscriptions.d.ts +7 -4
  31. package/dist/integrations/github/subscriptions.d.ts.map +1 -1
  32. package/dist/integrations/github/subscriptions.js +5 -5
  33. package/dist/integrations/github/subscriptions.js.map +1 -1
  34. package/dist/integrations/gitlab/api.js.map +1 -1
  35. package/dist/integrations/gitlab/integration.js.map +1 -1
  36. package/dist/integrations/gitlab/reconciliation-config.js +2 -1
  37. package/dist/integrations/gitlab/reconciliation-config.js.map +1 -1
  38. package/dist/integrations/gitlab/routes.d.ts.map +1 -1
  39. package/dist/integrations/gitlab/routes.js +10 -6
  40. package/dist/integrations/gitlab/routes.js.map +1 -1
  41. package/dist/integrations/gitlab/rules.js.map +1 -1
  42. package/dist/integrations/gitlab/session-subscriptions.d.ts.map +1 -1
  43. package/dist/integrations/gitlab/session-subscriptions.js +1 -2
  44. package/dist/integrations/gitlab/session-subscriptions.js.map +1 -1
  45. package/dist/integrations/gitlab/subscriptions.d.ts +7 -4
  46. package/dist/integrations/gitlab/subscriptions.d.ts.map +1 -1
  47. package/dist/integrations/gitlab/subscriptions.js +5 -5
  48. package/dist/integrations/gitlab/subscriptions.js.map +1 -1
  49. package/dist/integrations/incidentio/api.js.map +1 -1
  50. package/dist/integrations/incidentio/integration.js.map +1 -1
  51. package/dist/integrations/incidentio/rules.js.map +1 -1
  52. package/dist/integrations/issue-reconcile-worker.js +1 -1
  53. package/dist/integrations/issue-reconcile-worker.js.map +1 -1
  54. package/dist/integrations/issue-reconciler.js +1 -1
  55. package/dist/integrations/issue-reconciler.js.map +1 -1
  56. package/dist/integrations/jira/api.js.map +1 -1
  57. package/dist/integrations/jira/integration.js.map +1 -1
  58. package/dist/integrations/jira/rules.js.map +1 -1
  59. package/dist/integrations/linear/agent-tools.d.ts.map +1 -1
  60. package/dist/integrations/linear/agent-tools.js.map +1 -1
  61. package/dist/integrations/linear/integration.js.map +1 -1
  62. package/dist/integrations/linear/rules.js.map +1 -1
  63. package/dist/integrations/platform/api-client.js.map +1 -1
  64. package/dist/integrations/platform/github/event-worker.js +1 -1
  65. package/dist/integrations/platform/github/event-worker.js.map +1 -1
  66. package/dist/integrations/platform/github/integration.js +2 -2
  67. package/dist/integrations/platform/github/integration.js.map +1 -1
  68. package/dist/integrations/platform/gitlab/event-worker.js.map +1 -1
  69. package/dist/integrations/platform/gitlab/integration.js.map +1 -1
  70. package/dist/integrations/platform/incidentio/integration.js +2 -1
  71. package/dist/integrations/platform/incidentio/integration.js.map +1 -1
  72. package/dist/integrations/platform/jira/integration.js.map +1 -1
  73. package/dist/integrations/platform/linear/event-worker.js +1 -1
  74. package/dist/integrations/platform/linear/event-worker.js.map +1 -1
  75. package/dist/integrations/platform/linear/integration.js.map +1 -1
  76. package/dist/integrations/slack/connect-route.js +1 -1
  77. package/dist/integrations/slack/connect-route.js.map +1 -1
  78. package/dist/integrations/slack/feed-publisher.js +1 -1
  79. package/dist/integrations/slack/feed-publisher.js.map +1 -1
  80. package/dist/integrations/slack/integration.js +1 -1
  81. package/dist/integrations/slack/integration.js.map +1 -1
  82. package/dist/integrations/slack/slack.d.ts +16 -5
  83. package/dist/integrations/slack/slack.d.ts.map +1 -1
  84. package/dist/integrations/slack/slack.js +87 -34
  85. package/dist/integrations/slack/slack.js.map +1 -1
  86. package/dist/integrations/subscription-session.d.ts +0 -1
  87. package/dist/integrations/subscription-session.d.ts.map +1 -1
  88. package/dist/integrations/subscription-session.js +1 -4
  89. package/dist/integrations/subscription-session.js.map +1 -1
  90. package/dist/integrations/workos/integration.js.map +1 -1
  91. package/dist/mcp/routes.d.ts.map +1 -1
  92. package/dist/mcp/routes.js +1 -1
  93. package/dist/mcp/routes.js.map +1 -1
  94. package/dist/packages/_internals/workspace/dist/index.js +18 -1
  95. package/dist/packages/_internals/workspace/dist/index.js.map +1 -1
  96. package/dist/routes/attention-activity.d.ts.map +1 -1
  97. package/dist/routes/attention-activity.js +5 -2
  98. package/dist/routes/attention-activity.js.map +1 -1
  99. package/dist/routes/attention-parked.js.map +1 -1
  100. package/dist/routes/attention-providers.d.ts +0 -1
  101. package/dist/routes/attention-providers.d.ts.map +1 -1
  102. package/dist/routes/attention-providers.js +5 -6
  103. package/dist/routes/attention-providers.js.map +1 -1
  104. package/dist/routes/automation-runs.js.map +1 -1
  105. package/dist/routes/config.d.ts +6 -7
  106. package/dist/routes/config.d.ts.map +1 -1
  107. package/dist/routes/config.js +20 -19
  108. package/dist/routes/config.js.map +1 -1
  109. package/dist/routes/contracts.js +1 -1
  110. package/dist/routes/contracts.js.map +1 -1
  111. package/dist/routes/custom-provider-source.js.map +1 -1
  112. package/dist/routes/fs.d.ts +31 -23
  113. package/dist/routes/fs.d.ts.map +1 -1
  114. package/dist/routes/fs.js +218 -64
  115. package/dist/routes/fs.js.map +1 -1
  116. package/dist/routes/intake.js +2 -1
  117. package/dist/routes/intake.js.map +1 -1
  118. package/dist/routes/knowledge.js +1 -1
  119. package/dist/routes/knowledge.js.map +1 -1
  120. package/dist/routes/oauth.js +3 -2
  121. package/dist/routes/oauth.js.map +1 -1
  122. package/dist/routes/projects.d.ts +1 -1
  123. package/dist/routes/projects.d.ts.map +1 -1
  124. package/dist/routes/projects.js +33 -31
  125. package/dist/routes/projects.js.map +1 -1
  126. package/dist/routes/provider-credentials.d.ts.map +1 -1
  127. package/dist/routes/provider-credentials.js +6 -1
  128. package/dist/routes/provider-credentials.js.map +1 -1
  129. package/dist/routes/session-access.d.ts +55 -0
  130. package/dist/routes/session-access.d.ts.map +1 -0
  131. package/dist/routes/session-access.js +165 -0
  132. package/dist/routes/session-access.js.map +1 -0
  133. package/dist/routes/skills.d.ts +2 -4
  134. package/dist/routes/skills.d.ts.map +1 -1
  135. package/dist/routes/skills.js +19 -65
  136. package/dist/routes/skills.js.map +1 -1
  137. package/dist/routes/source-control-sessions.d.ts +6 -1
  138. package/dist/routes/source-control-sessions.d.ts.map +1 -1
  139. package/dist/routes/source-control-sessions.js +95 -12
  140. package/dist/routes/source-control-sessions.js.map +1 -1
  141. package/dist/routes/surface.d.ts +5 -0
  142. package/dist/routes/surface.d.ts.map +1 -1
  143. package/dist/routes/surface.js +10 -4
  144. package/dist/routes/surface.js.map +1 -1
  145. package/dist/routes/tenant-credentials.js.map +1 -1
  146. package/dist/routes/work-items.js.map +1 -1
  147. package/dist/rules/binding-context.js +11 -9
  148. package/dist/rules/binding-context.js.map +1 -1
  149. package/dist/rules/dispatcher.d.ts +1 -1
  150. package/dist/rules/dispatcher.d.ts.map +1 -1
  151. package/dist/rules/dispatcher.js +10 -10
  152. package/dist/rules/dispatcher.js.map +1 -1
  153. package/dist/rules/start-coordinator.d.ts +4 -0
  154. package/dist/rules/start-coordinator.d.ts.map +1 -1
  155. package/dist/rules/start-coordinator.js +36 -4
  156. package/dist/rules/start-coordinator.js.map +1 -1
  157. package/dist/rules/tools.js +2 -2
  158. package/dist/rules/tools.js.map +1 -1
  159. package/dist/rules/transition-service.js.map +1 -1
  160. package/dist/sandbox/callback-factory-sandbox.js.map +1 -1
  161. package/dist/sandbox/local-factory-sandbox.js.map +1 -1
  162. package/dist/sandbox/session-retirement.js.map +1 -1
  163. package/dist/secret-encryption.d.ts.map +1 -1
  164. package/dist/secret-encryption.js +65 -25
  165. package/dist/secret-encryption.js.map +1 -1
  166. package/dist/session/channel-reply-processor.d.ts +7 -6
  167. package/dist/session/channel-reply-processor.d.ts.map +1 -1
  168. package/dist/session/channel-reply-processor.js +9 -9
  169. package/dist/session/channel-reply-processor.js.map +1 -1
  170. package/dist/session/channel-session-link-processor.js +1 -0
  171. package/dist/session/channel-session-link-processor.js.map +1 -1
  172. package/dist/session/credential-policy.d.ts.map +1 -1
  173. package/dist/session/credential-policy.js +7 -6
  174. package/dist/session/credential-policy.js.map +1 -1
  175. package/dist/session/default-model-hydration.d.ts +1 -0
  176. package/dist/session/default-model-hydration.d.ts.map +1 -1
  177. package/dist/session/default-model-hydration.js +3 -2
  178. package/dist/session/default-model-hydration.js.map +1 -1
  179. package/dist/session/environment-state-processor.js +1 -1
  180. package/dist/session/environment-state-processor.js.map +1 -1
  181. package/dist/session/filesystem-capture.d.ts +3 -3
  182. package/dist/session/filesystem-capture.d.ts.map +1 -1
  183. package/dist/session/filesystem-capture.js +16 -17
  184. package/dist/session/filesystem-capture.js.map +1 -1
  185. package/dist/session/first-exec-capture.d.ts +1 -1
  186. package/dist/session/first-exec-capture.d.ts.map +1 -1
  187. package/dist/session/first-exec-capture.js +1 -1
  188. package/dist/session/first-exec-capture.js.map +1 -1
  189. package/dist/session/first-message-capture.d.ts +1 -1
  190. package/dist/session/first-message-capture.d.ts.map +1 -1
  191. package/dist/session/first-message-capture.js +1 -1
  192. package/dist/session/first-message-capture.js.map +1 -1
  193. package/dist/session/live-sessions.d.ts +1 -1
  194. package/dist/session/live-sessions.js +1 -1
  195. package/dist/session/live-sessions.js.map +1 -1
  196. package/dist/session/memory-settings-hydration.d.ts +1 -1
  197. package/dist/session/memory-settings-hydration.d.ts.map +1 -1
  198. package/dist/session/memory-settings-hydration.js +2 -1
  199. package/dist/session/memory-settings-hydration.js.map +1 -1
  200. package/dist/session/resource-migration.d.ts +105 -0
  201. package/dist/session/resource-migration.d.ts.map +1 -0
  202. package/dist/session/resource-migration.js +195 -0
  203. package/dist/session/resource-migration.js.map +1 -0
  204. package/dist/session/session-threads.d.ts +58 -0
  205. package/dist/session/session-threads.d.ts.map +1 -0
  206. package/dist/session/session-threads.js +90 -0
  207. package/dist/session/session-threads.js.map +1 -0
  208. package/dist/session/source-control-tools.js +8 -8
  209. package/dist/session/source-control-tools.js.map +1 -1
  210. package/dist/session/thinking-default.d.ts.map +1 -1
  211. package/dist/session/thinking-default.js +3 -4
  212. package/dist/session/thinking-default.js.map +1 -1
  213. package/dist/session/thread-session-stamp.d.ts +21 -0
  214. package/dist/session/thread-session-stamp.d.ts.map +1 -0
  215. package/dist/session/thread-session-stamp.js +20 -0
  216. package/dist/session/thread-session-stamp.js.map +1 -0
  217. package/dist/session/thread-title-mirror.d.ts +1 -1
  218. package/dist/session/thread-title-mirror.d.ts.map +1 -1
  219. package/dist/session/thread-title-mirror.js +1 -1
  220. package/dist/session/thread-title-mirror.js.map +1 -1
  221. package/dist/skills/service.d.ts +6 -8
  222. package/dist/skills/service.d.ts.map +1 -1
  223. package/dist/skills/service.js +3 -3
  224. package/dist/skills/service.js.map +1 -1
  225. package/dist/spa-static.js.map +1 -1
  226. package/dist/state-signing.js +1 -1
  227. package/dist/state-signing.js.map +1 -1
  228. package/dist/storage/domains/audit/agent-audit.d.ts.map +1 -1
  229. package/dist/storage/domains/audit/agent-audit.js +2 -2
  230. package/dist/storage/domains/audit/agent-audit.js.map +1 -1
  231. package/dist/storage/domains/audit/base.js.map +1 -1
  232. package/dist/storage/domains/audit/domain.js.map +1 -1
  233. package/dist/storage/domains/channel-identity/base.js.map +1 -1
  234. package/dist/storage/domains/comments/base.js.map +1 -1
  235. package/dist/storage/domains/comments/domain.js.map +1 -1
  236. package/dist/storage/domains/comments/feed-context.js.map +1 -1
  237. package/dist/storage/domains/credentials/base.js.map +1 -1
  238. package/dist/storage/domains/custom-providers/base.js.map +1 -1
  239. package/dist/storage/domains/intake/base.js.map +1 -1
  240. package/dist/storage/domains/integrations/base.d.ts +8 -0
  241. package/dist/storage/domains/integrations/base.d.ts.map +1 -1
  242. package/dist/storage/domains/integrations/base.js +7 -0
  243. package/dist/storage/domains/integrations/base.js.map +1 -1
  244. package/dist/storage/domains/memory-settings/base.js.map +1 -1
  245. package/dist/storage/domains/model-defaults/base.js.map +1 -1
  246. package/dist/storage/domains/projects/base.js.map +1 -1
  247. package/dist/storage/domains/queue-health/base.js.map +1 -1
  248. package/dist/storage/domains/source-control/base.d.ts +19 -0
  249. package/dist/storage/domains/source-control/base.d.ts.map +1 -1
  250. package/dist/storage/domains/source-control/base.js +42 -0
  251. package/dist/storage/domains/source-control/base.js.map +1 -1
  252. package/dist/storage/domains/source-control/inmemory.d.ts +5 -1
  253. package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
  254. package/dist/storage/domains/source-control/inmemory.js +7 -0
  255. package/dist/storage/domains/source-control/inmemory.js.map +1 -1
  256. package/dist/storage/domains/work-items/base.d.ts +14 -0
  257. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  258. package/dist/storage/domains/work-items/base.js +25 -1
  259. package/dist/storage/domains/work-items/base.js.map +1 -1
  260. package/dist/storage/domains/work-items/board-version.js.map +1 -1
  261. package/dist/storage/domains/work-items/metrics.js.map +1 -1
  262. package/dist/tools/approval-tools.js +1 -1
  263. package/dist/tools/approval-tools.js.map +1 -1
  264. package/dist/tools/deliver.d.ts +8 -5
  265. package/dist/tools/deliver.d.ts.map +1 -1
  266. package/dist/tools/deliver.js +22 -8
  267. package/dist/tools/deliver.js.map +1 -1
  268. package/dist/tools/messaging-tools.d.ts +1 -1
  269. package/dist/tools/messaging-tools.d.ts.map +1 -1
  270. package/dist/tools/messaging-tools.js +203 -72
  271. package/dist/tools/messaging-tools.js.map +1 -1
  272. package/dist/tools/permissions.d.ts +1 -1
  273. package/dist/tools/permissions.d.ts.map +1 -1
  274. package/dist/tools/permissions.js +1 -1
  275. package/dist/tools/permissions.js.map +1 -1
  276. package/dist/tools/reports.d.ts.map +1 -1
  277. package/dist/tools/reports.js +10 -3
  278. package/dist/tools/reports.js.map +1 -1
  279. package/dist/tools/run-watch.d.ts +2 -0
  280. package/dist/tools/run-watch.d.ts.map +1 -1
  281. package/dist/tools/run-watch.js +42 -2
  282. package/dist/tools/run-watch.js.map +1 -1
  283. package/dist/tools/scope.d.ts +2 -1
  284. package/dist/tools/scope.d.ts.map +1 -1
  285. package/dist/tools/scope.js +3 -2
  286. package/dist/tools/scope.js.map +1 -1
  287. package/dist/tools/session-tools.d.ts +1 -1
  288. package/dist/tools/session-tools.d.ts.map +1 -1
  289. package/dist/tools/session-tools.js +33 -16
  290. package/dist/tools/session-tools.js.map +1 -1
  291. package/dist/tools/sessions-state-processor.js +2 -2
  292. package/dist/tools/sessions-state-processor.js.map +1 -1
  293. package/dist/tools/side-sessions.d.ts +1 -1
  294. package/dist/tools/side-sessions.d.ts.map +1 -1
  295. package/dist/tools/side-sessions.js +1 -1
  296. package/dist/tools/side-sessions.js.map +1 -1
  297. package/dist/workspace.d.ts +5 -2
  298. package/dist/workspace.d.ts.map +1 -1
  299. package/dist/workspace.js +28 -15
  300. package/dist/workspace.js.map +1 -1
  301. package/package.json +13 -13
package/dist/routes/fs.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { requireExec } from "../sandbox/materialization.js";
2
+ import { SETUP_MARKER_DIR } from "../packages/_internals/workspace/dist/index.js";
2
3
  import { peekSessionSandbox, sessionLayout } from "../sandbox/session-sandbox.js";
3
4
  import { peekSessionEnvironment } from "../session/environment-state-processor.js";
4
- import { waitForPendingFilesystemCapture } from "../session/filesystem-capture.js";
5
5
  import { registerApiRoute } from "@mastra/core/server";
6
6
  import { isAbsolute, join, posix, resolve, sep } from "path";
7
7
  import { SandboxFilesystem } from "@mastra/code-sdk/agents/sandbox-filesystem";
@@ -9,8 +9,8 @@ import { lstat, open, readdir, realpath, stat } from "fs/promises";
9
9
  import { homedir } from "os";
10
10
  import { detectProject, getResourceIdOverride } from "@mastra/code-sdk/utils/project";
11
11
  //#region src/routes/fs.ts
12
- const MAX_TEXT_FILE_BYTES = 512 * 1024;
13
- const MAX_DIFF_BYTES = 512 * 1024;
12
+ const MAX_TEXT_FILE_BYTES = 524288;
13
+ const MAX_DIFF_BYTES = 524288;
14
14
  const WORKSPACE_NUMSTAT_SCRIPT = `
15
15
  set -e
16
16
  workdir=$1
@@ -87,11 +87,10 @@ async function realPathWithinRoot(candidate, root) {
87
87
  }
88
88
  }
89
89
  function assertRelativePath(path, label) {
90
- const trimmed = path.trim();
91
- if (!trimmed) throw new Error(`Missing required query param: ${label}`);
92
- if (isAbsolute(trimmed)) throw new Error(`${label} must be relative`);
93
- if (trimmed.split(/[\\/]+/).includes("..")) throw new Error(`${label} escapes workspace`);
94
- const normalized = resolve("/", trimmed).slice(1);
90
+ if (!path.trim()) throw new Error(`Missing required query param: ${label}`);
91
+ if (isAbsolute(path)) throw new Error(`${label} must be relative`);
92
+ if (path.split(/[\\/]+/).includes("..")) throw new Error(`${label} escapes workspace`);
93
+ const normalized = resolve("/", path).slice(1);
95
94
  if (!normalized || normalized === ".." || normalized.startsWith(`..${sep}`)) throw new Error(`${label} escapes workspace`);
96
95
  return normalized;
97
96
  }
@@ -275,24 +274,6 @@ async function resolveAuthorizedSession(c, deps, workspacePath) {
275
274
  }
276
275
  return session;
277
276
  }
278
- async function listSessionFilesystemFiles(filesystem, session, threadId) {
279
- const safeThreadId = threadId.trim();
280
- if (!safeThreadId) throw new Error("Missing required query param: threadId");
281
- await waitForPendingFilesystemCapture(session.sessionId);
282
- const layout = sessionLayout(peekSessionSandbox(session.id));
283
- return {
284
- workspacePath: session.sessionId,
285
- threadId: safeThreadId,
286
- files: await filesystem.listFiles({
287
- resourceId: session.sessionId,
288
- threadId: safeThreadId
289
- }),
290
- ...layout && layout.repos.length > 1 ? { repositories: layout.repos.map((repo) => ({
291
- slug: repo.slug,
292
- prefix: repo.prefix
293
- })) } : {}
294
- };
295
- }
296
277
  /**
297
278
  * Resolve the session's sandbox from the per-process memo and wrap the
298
279
  * session layout's root in a `SandboxFilesystem`: the checkout for a
@@ -311,7 +292,8 @@ async function sessionSandbox(session) {
311
292
  sandbox,
312
293
  filesystem: new SandboxFilesystem({
313
294
  sandbox,
314
- workdir: layout.root
295
+ workdir: layout.root,
296
+ verifySymlinks: true
315
297
  }),
316
298
  layout,
317
299
  baseBranches: repositoryBaseBranches(session, layout, entry.ownRepo)
@@ -397,26 +379,211 @@ async function listSessionRenderedPath(session, renderedRoot) {
397
379
  entries
398
380
  };
399
381
  }
400
- /** Read a file inside a session's sandbox. Paths outside rendered roots require a persisted-file allowlist check in the route. */
401
- async function readSessionWorkspaceFile(session, path, options = {}) {
382
+ /**
383
+ * Directory entries the file tree never shows or serves: git internals and the
384
+ * sandbox's own setup markers.
385
+ */
386
+ const HIDDEN_TREE_ENTRIES = /* @__PURE__ */ new Set([".git", SETUP_MARKER_DIR]);
387
+ /**
388
+ * Where a tree path lives. Inside a checkout: that checkout and the path within
389
+ * it (a bare repository directory resolves to its root). Elsewhere in the
390
+ * working directory (a multi-repository session's root, `.artifacts`): no
391
+ * checkout, so no ignore rules apply.
392
+ */
393
+ function treeLocation(layout, path) {
394
+ const safePath = path ? assertRelativePath(path, "path") : "";
395
+ if (safePath.split("/").some((segment) => HIDDEN_TREE_ENTRIES.has(segment))) throw new Error("Path is not available in this session");
396
+ if (layout.repos.length === 1) return {
397
+ repo: layout.repos[0],
398
+ relative: safePath
399
+ };
400
+ const repo = layout.repos.filter((candidate) => `${safePath}/`.startsWith(candidate.prefix)).toSorted((a, b) => b.prefix.length - a.prefix.length)[0];
401
+ return repo ? {
402
+ repo,
403
+ relative: safePath.slice(repo.prefix.length)
404
+ } : {
405
+ repo: void 0,
406
+ relative: safePath
407
+ };
408
+ }
409
+ /**
410
+ * The checkout-relative paths git ignores, in one `git check-ignore` call.
411
+ * Ignored files (`.env`, build output, `node_modules`) are neither listed nor
412
+ * served: viewers of a session shouldn't read its local secrets. Fails closed
413
+ * when git can't answer.
414
+ */
415
+ async function ignoredPaths(sandbox, repoDir, paths) {
416
+ if (paths.length === 0) return /* @__PURE__ */ new Set();
417
+ const result = await sandbox.executeCommand("sh", [
418
+ "-c",
419
+ "cd \"$1\" && shift && printf '%s\\0' \"$@\" | git check-ignore -z --stdin",
420
+ "sh",
421
+ repoDir,
422
+ ...paths
423
+ ], {
424
+ timeout: 3e4,
425
+ maxRetainedBytes: Infinity
426
+ });
427
+ if (result.exitCode === 1) return /* @__PURE__ */ new Set();
428
+ if (result.exitCode !== 0) throw new Error(`Unable to check ignored files: ${result.stderr.trim() || "git failed"}`);
429
+ return new Set(result.stdout.split("\0").filter(Boolean));
430
+ }
431
+ /**
432
+ * The real absolute location of each path (relative to `root`), following
433
+ * every symlink, or `''` when it doesn't exist; plus `root`'s own. Paths go in
434
+ * as positional arguments and come back NUL-separated.
435
+ */
436
+ async function realPaths(sandbox, root, paths) {
437
+ const result = await sandbox.executeCommand("sh", [
438
+ "-c",
439
+ "cd \"$1\" && shift && printf '%s\\0' \"$(pwd -P)\" && for p; do printf '%s\\0' \"$([ -e \"$p\" ] && readlink -f -- \"$p\")\"; done",
440
+ "sh",
441
+ root,
442
+ ...paths
443
+ ], {
444
+ timeout: 3e4,
445
+ maxRetainedBytes: Infinity
446
+ });
447
+ if (result.exitCode !== 0) throw new Error(`Unable to resolve symlinks: ${result.stderr.trim() || "failed"}`);
448
+ const [physicalRoot = "", ...resolved] = result.stdout.split("\0");
449
+ return {
450
+ root: physicalRoot,
451
+ paths: resolved.slice(0, paths.length)
452
+ };
453
+ }
454
+ /** The first `bytes` bytes of a file, base64-encoded in the sandbox so binary content survives. */
455
+ async function readFilePrefix(sandbox, path, bytes) {
456
+ const result = await sandbox.executeCommand("sh", [
457
+ "-c",
458
+ "t=$(mktemp) && trap 'rm -f \"$t\"' EXIT && head -c \"$1\" -- \"$2\" >\"$t\" && base64 <\"$t\"",
459
+ "sh",
460
+ String(bytes),
461
+ path
462
+ ], {
463
+ timeout: 3e4,
464
+ maxRetainedBytes: Infinity
465
+ });
466
+ if (result.exitCode !== 0) throw new Error(`Unable to read file: ${result.stderr.trim() || "failed"}`);
467
+ return Buffer.from(result.stdout, "base64");
468
+ }
469
+ /**
470
+ * Where a real absolute path sits in the tree, or `undefined` when the tree
471
+ * can't show it: missing, outside the working directory, or inside git
472
+ * internals or setup markers.
473
+ */
474
+ function treePathOf(layout, physicalRoot, real) {
475
+ if (!real) return void 0;
476
+ const relative = posix.relative(physicalRoot, real);
477
+ if (relative === ".." || relative.startsWith("../") || posix.isAbsolute(relative)) return void 0;
478
+ try {
479
+ treeLocation(layout, relative);
480
+ } catch {
481
+ return;
482
+ }
483
+ return relative;
484
+ }
485
+ /** Which tree paths their checkout ignores: one `git check-ignore` per checkout involved. */
486
+ async function ignoredTreePaths(sandbox, layout, paths) {
487
+ const byRepo = /* @__PURE__ */ new Map();
488
+ for (const path of paths) {
489
+ const { repo, relative } = treeLocation(layout, path);
490
+ if (!repo || !relative) continue;
491
+ const inRepo = byRepo.get(repo) ?? /* @__PURE__ */ new Map();
492
+ inRepo.set(relative, path);
493
+ byRepo.set(repo, inRepo);
494
+ }
495
+ const ignored = /* @__PURE__ */ new Set();
496
+ for (const [repo, inRepo] of byRepo) for (const relative of await ignoredPaths(sandbox, repo.dir, [...inRepo.keys()])) ignored.add(inRepo.get(relative));
497
+ return ignored;
498
+ }
499
+ /**
500
+ * List one directory of a session's working directory from its sandbox,
501
+ * without git internals or entries a checkout ignores. Passive like every
502
+ * session read: a session with no sandbox in this process comes back
503
+ * `available: false` instead of starting one.
504
+ */
505
+ async function listSessionWorkspaceTree(session, path = "") {
506
+ const requested = path.trim() ? path : "";
507
+ const handle = await sessionSandbox(session);
508
+ const listing = {
509
+ workspacePath: session.sessionId,
510
+ path: requested
511
+ };
512
+ if (!handle) return {
513
+ ...listing,
514
+ available: false,
515
+ entries: []
516
+ };
517
+ const { layout, sandbox, filesystem } = handle;
518
+ const location = treeLocation(layout, requested);
519
+ const dir = (location.repo ? location.repo.prefix + location.relative : location.relative).replace(/\/$/, "");
520
+ const children = (await filesystem.readdir(dir || ".")).filter((entry) => !HIDDEN_TREE_ENTRIES.has(entry.name));
521
+ const childPath = (name) => dir ? `${dir}/${name}` : name;
522
+ const links = children.filter((entry) => entry.isSymlink);
523
+ const resolve = [...dir ? [dir] : [], ...links.map((entry) => childPath(entry.name))];
524
+ const real = resolve.length > 0 ? await realPaths(sandbox, layout.root, resolve) : void 0;
525
+ const linkTargets = real ? real.paths.slice(dir ? 1 : 0) : [];
526
+ if (dir && real.paths[0] !== posix.join(real.root, dir)) throw new Error("Path is not available in this session");
527
+ const targets = new Map(links.map((entry, i) => [entry.name, treePathOf(layout, real.root, linkTargets[i])]));
528
+ const ignored = await ignoredTreePaths(sandbox, layout, [
529
+ ...dir ? [dir] : [],
530
+ ...children.map((entry) => childPath(entry.name)),
531
+ ...[...targets.values()].filter((target) => target !== void 0)
532
+ ]);
533
+ if (dir && ignored.has(dir)) throw new Error("Path is not available in this session");
534
+ const entries = children.filter((entry) => {
535
+ if (ignored.has(childPath(entry.name))) return false;
536
+ if (!entry.isSymlink) return true;
537
+ const target = targets.get(entry.name);
538
+ return target !== void 0 && !ignored.has(target);
539
+ }).map((entry) => ({
540
+ name: entry.name,
541
+ path: childPath(entry.name),
542
+ type: entry.type,
543
+ ...entry.type === "file" && entry.size !== void 0 && { size: entry.size },
544
+ ...entry.isSymlink && {
545
+ symlinkTarget: entry.symlinkTarget ?? targets.get(entry.name),
546
+ linkedPath: targets.get(entry.name)
547
+ }
548
+ })).toSorted((a, b) => a.type === b.type ? a.name.localeCompare(b.name) : a.type === "directory" ? -1 : 1);
549
+ return {
550
+ ...listing,
551
+ available: true,
552
+ entries
553
+ };
554
+ }
555
+ /**
556
+ * Read a file inside a session's sandbox: anything under an approved rendered
557
+ * root, or anything the tree would list (not git internals, not ignored by its
558
+ * checkout).
559
+ */
560
+ async function readSessionWorkspaceFile(session, path) {
402
561
  const safePath = assertRelativePath(path, "path");
403
- if (!options.allowUnapprovedPath) assertApprovedRenderedRoot(safePath.split("/")[0] ?? "");
404
562
  const handle = await sessionSandbox(session);
405
563
  if (!handle) throw new Error("Session workspace is not available");
564
+ const { layout, sandbox } = handle;
565
+ treeLocation(layout, safePath);
566
+ const parent = posix.dirname(safePath);
567
+ const real = await realPaths(sandbox, layout.root, parent === "." ? [safePath] : [safePath, parent]);
568
+ const target = treePathOf(layout, real.root, real.paths[0]);
569
+ const throughLink = parent !== "." && real.paths[1] !== posix.join(real.root, parent);
570
+ if (target === void 0 || throughLink) throw new Error("Path is not available in this session");
571
+ const trusted = APPROVED_RENDERED_ROOTS.has(safePath.split("/")[0] ?? "");
572
+ if ((await ignoredTreePaths(sandbox, layout, [.../* @__PURE__ */ new Set([...trusted ? [] : [safePath], ...target === safePath ? [] : [target]])])).size > 0) throw new Error("Path is not available in this session");
406
573
  const { filesystem } = handle;
407
574
  const info = await filesystem.stat(safePath);
408
575
  if (info.type === "directory") throw new Error("Path is a directory");
409
- const buffer = await filesystem.readFile(safePath);
410
- const truncated = buffer.length > MAX_TEXT_FILE_BYTES;
576
+ const truncated = info.size > MAX_TEXT_FILE_BYTES;
577
+ const buffer = truncated ? await readFilePrefix(sandbox, real.paths[0], MAX_TEXT_FILE_BYTES) : await filesystem.readFile(safePath);
411
578
  const base = {
412
579
  workspacePath: session.sessionId,
413
580
  path: safePath,
414
581
  name: posix.basename(safePath),
415
- size: buffer.length,
582
+ size: info.size,
416
583
  updatedAt: info.modifiedAt.toISOString()
417
584
  };
418
585
  try {
419
- const content = TEXT_DECODER.decode(truncated ? buffer.subarray(0, MAX_TEXT_FILE_BYTES) : buffer);
586
+ const content = TEXT_DECODER.decode(buffer);
420
587
  return {
421
588
  ...base,
422
589
  contentType: "text",
@@ -807,57 +974,55 @@ function buildFsRoutes(options = {}) {
807
974
  }
808
975
  }
809
976
  }),
810
- registerApiRoute("/web/workspace/files", {
977
+ registerApiRoute("/web/workspace/changes", {
811
978
  method: "GET",
812
979
  requiresAuth: false,
813
980
  handler: async (c) => {
814
981
  const workspacePath = c.req.query("workspacePath");
815
- const threadId = c.req.query("threadId")?.trim();
816
982
  if (!workspacePath) return c.json({ error: "Missing required query param: workspacePath" }, 400);
817
- if (!threadId) return c.json({ error: "Missing required query param: threadId" }, 400);
818
983
  try {
819
984
  const session = await resolveAuthorizedSession(loose(c), sessionFs, workspacePath);
820
- if (!session || !sessionFs) return c.json({ error: "Session workspace is not available" }, 403);
821
- return c.json(await listSessionFilesystemFiles(sessionFs.filesystem, session, threadId));
985
+ if (!session || !sessionFs) return c.json(unavailableWorkspaceChanges(workspacePath));
986
+ return c.json(await listSessionWorkspaceChanges(session));
822
987
  } catch (error) {
823
988
  const message = error instanceof Error ? error.message : String(error);
824
- const status = message.includes("not available") ? 403 : 500;
825
- return c.json({ error: message }, status);
989
+ return c.json({ error: message }, message.includes("not available") ? 403 : 500);
826
990
  }
827
991
  }
828
992
  }),
829
- registerApiRoute("/web/workspace/changes", {
993
+ registerApiRoute("/web/workspace/changes/diff", {
830
994
  method: "GET",
831
995
  requiresAuth: false,
832
996
  handler: async (c) => {
833
997
  const workspacePath = c.req.query("workspacePath");
998
+ const path = c.req.query("path");
999
+ const previousPath = c.req.query("previousPath");
834
1000
  if (!workspacePath) return c.json({ error: "Missing required query param: workspacePath" }, 400);
1001
+ if (!path) return c.json({ error: "Missing required query param: path" }, 400);
835
1002
  try {
836
1003
  const session = await resolveAuthorizedSession(loose(c), sessionFs, workspacePath);
837
- if (!session || !sessionFs) return c.json(unavailableWorkspaceChanges(workspacePath));
838
- return c.json(await listSessionWorkspaceChanges(session));
1004
+ if (!session || !sessionFs) return c.json({ error: "Session workspace is not available" }, 403);
1005
+ return c.json(await readSessionWorkspaceDiff(session, path, previousPath));
839
1006
  } catch (error) {
840
1007
  const message = error instanceof Error ? error.message : String(error);
841
- return c.json({ error: message }, message.includes("not available") ? 403 : 500);
1008
+ const status = message.includes("relative") || message.includes("escapes") || message.includes("not available") ? 403 : 500;
1009
+ return c.json({ error: message }, status);
842
1010
  }
843
1011
  }
844
1012
  }),
845
- registerApiRoute("/web/workspace/changes/diff", {
1013
+ registerApiRoute("/web/workspace/tree", {
846
1014
  method: "GET",
847
1015
  requiresAuth: false,
848
1016
  handler: async (c) => {
849
1017
  const workspacePath = c.req.query("workspacePath");
850
- const path = c.req.query("path");
851
- const previousPath = c.req.query("previousPath");
852
1018
  if (!workspacePath) return c.json({ error: "Missing required query param: workspacePath" }, 400);
853
- if (!path) return c.json({ error: "Missing required query param: path" }, 400);
854
1019
  try {
855
1020
  const session = await resolveAuthorizedSession(loose(c), sessionFs, workspacePath);
856
1021
  if (!session || !sessionFs) return c.json({ error: "Session workspace is not available" }, 403);
857
- return c.json(await readSessionWorkspaceDiff(session, path, previousPath));
1022
+ return c.json(await listSessionWorkspaceTree(session, c.req.query("path") ?? ""));
858
1023
  } catch (error) {
859
1024
  const message = error instanceof Error ? error.message : String(error);
860
- const status = message.includes("relative") || message.includes("escapes") || message.includes("not available") ? 403 : 500;
1025
+ const status = message.includes("relative") || message.includes("escapes") || message.includes("not available") ? 403 : message.includes("not found") ? 404 : 500;
861
1026
  return c.json({ error: message }, status);
862
1027
  }
863
1028
  }
@@ -868,22 +1033,11 @@ function buildFsRoutes(options = {}) {
868
1033
  handler: async (c) => {
869
1034
  const workspacePath = c.req.query("workspacePath");
870
1035
  const path = c.req.query("path");
871
- const requestedThreadId = c.req.query("threadId");
872
- const threadId = requestedThreadId?.trim();
873
1036
  if (!workspacePath) return c.json({ error: "Missing required query param: workspacePath" }, 400);
874
1037
  if (!path) return c.json({ error: "Missing required query param: path" }, 400);
875
- if (requestedThreadId !== void 0 && !threadId) return c.json({ error: "Missing required query param: threadId" }, 400);
876
1038
  try {
877
1039
  const session = await resolveAuthorizedSession(loose(c), sessionFs, workspacePath);
878
- if (session && sessionFs) {
879
- if (threadId) {
880
- const safePath = assertRelativePath(path, "path");
881
- if (!(await listSessionFilesystemFiles(sessionFs.filesystem, session, threadId)).files.some((file) => file.path === safePath)) return c.json({ error: "Path is not available for this thread" }, 404);
882
- return c.json(await readSessionWorkspaceFile(session, safePath, { allowUnapprovedPath: true }));
883
- }
884
- return c.json(await readSessionWorkspaceFile(session, path));
885
- }
886
- if (threadId) return c.json({ error: "Session workspace is not available" }, 403);
1040
+ if (session && sessionFs) return c.json(await readSessionWorkspaceFile(session, path));
887
1041
  return c.json(await readWorkspaceFile(root, workspacePath, path));
888
1042
  } catch (error) {
889
1043
  const message = error instanceof Error ? error.message : String(error);
@@ -911,6 +1065,6 @@ function buildFsRoutes(options = {}) {
911
1065
  ];
912
1066
  }
913
1067
  //#endregion
914
- export { buildFsRoutes, listArtifacts, listDirectory, listSessionFilesystemFiles, listSessionRenderedPath, listSessionWorkspaceChanges, listWorkspaceRenderedPath, parseWorkspaceChangeStats, parseWorkspaceChanges, parseWorkspaceTrackedChanges, readSessionWorkspaceDiff, readSessionWorkspaceFile, readWorkspaceFile, resolveCodebase, resolveFsRoot };
1068
+ export { buildFsRoutes, listArtifacts, listDirectory, listSessionRenderedPath, listSessionWorkspaceChanges, listSessionWorkspaceTree, listWorkspaceRenderedPath, parseWorkspaceChangeStats, parseWorkspaceChanges, parseWorkspaceTrackedChanges, readSessionWorkspaceDiff, readSessionWorkspaceFile, readWorkspaceFile, resolveCodebase, resolveFsRoot };
915
1069
 
916
1070
  //# sourceMappingURL=fs.js.map