@mastra/factory 0.11.1 → 0.12.0-alpha.1

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 (157) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/dist/factory.d.ts +2 -22
  3. package/dist/factory.d.ts.map +1 -1
  4. package/dist/factory.js +21 -59
  5. package/dist/factory.js.map +1 -1
  6. package/dist/feed-events.d.ts +13 -0
  7. package/dist/feed-events.d.ts.map +1 -0
  8. package/dist/feed-events.js +25 -0
  9. package/dist/feed-events.js.map +1 -0
  10. package/dist/index.d.ts +2 -1
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/integrations/base.d.ts +5 -13
  13. package/dist/integrations/base.d.ts.map +1 -1
  14. package/dist/integrations/github/config.d.ts +4 -4
  15. package/dist/integrations/github/config.d.ts.map +1 -1
  16. package/dist/integrations/github/config.js +3 -3
  17. package/dist/integrations/github/config.js.map +1 -1
  18. package/dist/integrations/github/integration.d.ts.map +1 -1
  19. package/dist/integrations/github/integration.js +2 -4
  20. package/dist/integrations/github/integration.js.map +1 -1
  21. package/dist/integrations/github/reconcile-worker.d.ts +0 -5
  22. package/dist/integrations/github/reconcile-worker.d.ts.map +1 -1
  23. package/dist/integrations/github/reconcile-worker.js +1 -8
  24. package/dist/integrations/github/reconcile-worker.js.map +1 -1
  25. package/dist/integrations/github/routes.d.ts +6 -5
  26. package/dist/integrations/github/routes.d.ts.map +1 -1
  27. package/dist/integrations/github/routes.js +36 -263
  28. package/dist/integrations/github/routes.js.map +1 -1
  29. package/dist/integrations/github/sandbox-release.d.ts +17 -47
  30. package/dist/integrations/github/sandbox-release.d.ts.map +1 -1
  31. package/dist/integrations/github/sandbox-release.js +24 -103
  32. package/dist/integrations/github/sandbox-release.js.map +1 -1
  33. package/dist/integrations/github/sandbox.d.ts +25 -139
  34. package/dist/integrations/github/sandbox.d.ts.map +1 -1
  35. package/dist/integrations/github/sandbox.js +24 -212
  36. package/dist/integrations/github/sandbox.js.map +1 -1
  37. package/dist/integrations/github/webhook.d.ts.map +1 -1
  38. package/dist/integrations/github/webhook.js +1 -2
  39. package/dist/integrations/github/webhook.js.map +1 -1
  40. package/dist/integrations/issue-reconcile-worker.js +1 -1
  41. package/dist/integrations/platform/github/event-worker.d.ts +0 -2
  42. package/dist/integrations/platform/github/event-worker.d.ts.map +1 -1
  43. package/dist/integrations/platform/github/event-worker.js +1 -8
  44. package/dist/integrations/platform/github/event-worker.js.map +1 -1
  45. package/dist/integrations/platform/github/integration.d.ts.map +1 -1
  46. package/dist/integrations/platform/github/integration.js +5 -7
  47. package/dist/integrations/platform/github/integration.js.map +1 -1
  48. package/dist/integrations/platform/linear/event-worker.js +1 -1
  49. package/dist/routes/attention-activity.d.ts +24 -0
  50. package/dist/routes/attention-activity.d.ts.map +1 -0
  51. package/dist/routes/attention-activity.js +143 -0
  52. package/dist/routes/attention-activity.js.map +1 -0
  53. package/dist/routes/attention-providers.d.ts +31 -1
  54. package/dist/routes/attention-providers.d.ts.map +1 -1
  55. package/dist/routes/attention-providers.js +4 -4
  56. package/dist/routes/attention-providers.js.map +1 -1
  57. package/dist/routes/attention.d.ts.map +1 -1
  58. package/dist/routes/attention.js +39 -11
  59. package/dist/routes/attention.js.map +1 -1
  60. package/dist/routes/config.d.ts.map +1 -1
  61. package/dist/routes/config.js +3 -1
  62. package/dist/routes/config.js.map +1 -1
  63. package/dist/routes/fs.d.ts +4 -6
  64. package/dist/routes/fs.d.ts.map +1 -1
  65. package/dist/routes/fs.js +28 -34
  66. package/dist/routes/fs.js.map +1 -1
  67. package/dist/routes/skills.d.ts.map +1 -1
  68. package/dist/routes/skills.js +6 -3
  69. package/dist/routes/skills.js.map +1 -1
  70. package/dist/routes/surface.d.ts +4 -9
  71. package/dist/routes/surface.d.ts.map +1 -1
  72. package/dist/routes/surface.js +3 -5
  73. package/dist/routes/surface.js.map +1 -1
  74. package/dist/sandbox/materialization.d.ts +36 -0
  75. package/dist/sandbox/materialization.d.ts.map +1 -0
  76. package/dist/sandbox/materialization.js +18 -0
  77. package/dist/sandbox/materialization.js.map +1 -0
  78. package/dist/sandbox/session-retirement.d.ts +8 -6
  79. package/dist/sandbox/session-retirement.d.ts.map +1 -1
  80. package/dist/sandbox/session-retirement.js +28 -123
  81. package/dist/sandbox/session-retirement.js.map +1 -1
  82. package/dist/sandbox/session-sandbox.d.ts +117 -0
  83. package/dist/sandbox/session-sandbox.d.ts.map +1 -0
  84. package/dist/sandbox/session-sandbox.js +132 -0
  85. package/dist/sandbox/session-sandbox.js.map +1 -0
  86. package/dist/sandbox/workdir.d.ts +30 -0
  87. package/dist/sandbox/workdir.d.ts.map +1 -0
  88. package/dist/sandbox/workdir.js +48 -0
  89. package/dist/sandbox/workdir.js.map +1 -0
  90. package/dist/session/filesystem-capture.d.ts +2 -1
  91. package/dist/session/filesystem-capture.d.ts.map +1 -1
  92. package/dist/session/filesystem-capture.js +45 -4
  93. package/dist/session/filesystem-capture.js.map +1 -1
  94. package/dist/session/first-exec-capture.d.ts +1 -0
  95. package/dist/session/first-exec-capture.d.ts.map +1 -1
  96. package/dist/session/first-exec-capture.js +1 -1
  97. package/dist/session/first-exec-capture.js.map +1 -1
  98. package/dist/storage/domains/comments/activity.d.ts +40 -0
  99. package/dist/storage/domains/comments/activity.d.ts.map +1 -0
  100. package/dist/storage/domains/comments/activity.js +94 -0
  101. package/dist/storage/domains/comments/activity.js.map +1 -0
  102. package/dist/storage/domains/comments/base.d.ts +13 -1
  103. package/dist/storage/domains/comments/base.d.ts.map +1 -1
  104. package/dist/storage/domains/comments/base.js +41 -3
  105. package/dist/storage/domains/comments/base.js.map +1 -1
  106. package/dist/storage/domains/comments/domain.d.ts +4 -1
  107. package/dist/storage/domains/comments/domain.d.ts.map +1 -1
  108. package/dist/storage/domains/comments/domain.js +17 -5
  109. package/dist/storage/domains/comments/domain.js.map +1 -1
  110. package/dist/storage/domains/comments/parse.d.ts +1 -0
  111. package/dist/storage/domains/comments/parse.d.ts.map +1 -1
  112. package/dist/storage/domains/comments/parse.js +2 -2
  113. package/dist/storage/domains/comments/parse.js.map +1 -1
  114. package/dist/storage/domains/comments/routes.d.ts +2 -0
  115. package/dist/storage/domains/comments/routes.d.ts.map +1 -1
  116. package/dist/storage/domains/comments/routes.js +49 -3
  117. package/dist/storage/domains/comments/routes.js.map +1 -1
  118. package/dist/storage/domains/comments/schema.d.ts +1 -0
  119. package/dist/storage/domains/comments/schema.d.ts.map +1 -1
  120. package/dist/storage/domains/comments/schema.js +45 -1
  121. package/dist/storage/domains/comments/schema.js.map +1 -1
  122. package/dist/storage/domains/integrations/base.d.ts +1 -1
  123. package/dist/storage/domains/integrations/base.js +1 -1
  124. package/dist/storage/domains/integrations/base.js.map +1 -1
  125. package/dist/storage/domains/source-control/base.d.ts +0 -144
  126. package/dist/storage/domains/source-control/base.d.ts.map +1 -1
  127. package/dist/storage/domains/source-control/base.js +3 -290
  128. package/dist/storage/domains/source-control/base.js.map +1 -1
  129. package/dist/storage/domains/source-control/inmemory.d.ts +1 -63
  130. package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
  131. package/dist/storage/domains/source-control/inmemory.js +0 -89
  132. package/dist/storage/domains/source-control/inmemory.js.map +1 -1
  133. package/dist/storage/domains/work-items/base.d.ts +14 -1
  134. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  135. package/dist/storage/domains/work-items/base.js +60 -11
  136. package/dist/storage/domains/work-items/base.js.map +1 -1
  137. package/dist/workspace.d.ts +5 -37
  138. package/dist/workspace.d.ts.map +1 -1
  139. package/dist/workspace.js +107 -245
  140. package/dist/workspace.js.map +1 -1
  141. package/package.json +6 -6
  142. package/dist/sandbox/base-checkpoint-triggers.d.ts +0 -77
  143. package/dist/sandbox/base-checkpoint-triggers.d.ts.map +0 -1
  144. package/dist/sandbox/base-checkpoint-triggers.js +0 -137
  145. package/dist/sandbox/base-checkpoint-triggers.js.map +0 -1
  146. package/dist/sandbox/base-checkpoint.d.ts +0 -59
  147. package/dist/sandbox/base-checkpoint.d.ts.map +0 -1
  148. package/dist/sandbox/base-checkpoint.js +0 -145
  149. package/dist/sandbox/base-checkpoint.js.map +0 -1
  150. package/dist/sandbox/fleet.d.ts +0 -258
  151. package/dist/sandbox/fleet.d.ts.map +0 -1
  152. package/dist/sandbox/fleet.js +0 -356
  153. package/dist/sandbox/fleet.js.map +0 -1
  154. package/dist/sandbox/reattach.d.ts +0 -3
  155. package/dist/sandbox/reattach.d.ts.map +0 -1
  156. package/dist/sandbox/reattach.js +0 -16
  157. package/dist/sandbox/reattach.js.map +0 -1
package/dist/routes/fs.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { requireExec } from "../sandbox/materialization.js";
2
+ import { peekSessionSandbox } from "../sandbox/session-sandbox.js";
1
3
  import { waitForPendingFilesystemCapture } from "../session/filesystem-capture.js";
2
4
  import { registerApiRoute } from "@mastra/core/server";
3
5
  import { isAbsolute, join, posix, resolve, sep } from "path";
@@ -285,45 +287,37 @@ async function listSessionFilesystemFiles(filesystem, session, threadId) {
285
287
  };
286
288
  }
287
289
  /**
288
- * Reattach to the session's sandbox and wrap its workdir in a
289
- * `SandboxFilesystem`. Returns `null` when the session has no provisioned
290
- * sandbox yet (nothing materialized nothing to list), or when the sandbox
291
- * can no longer be reattached (e.g. torn down by the provider's idle GC).
292
- * This is a passive read path, so it never re-provisions: the session's
293
- * filesystem is preserved in its provider checkpoint and comes back the next
294
- * time the workspace is actually opened (e.g. by sending a message).
290
+ * Resolve the session's sandbox from the per-process memo and wrap its
291
+ * workdir in a `SandboxFilesystem`. Returns `null` when the session has no
292
+ * sandbox in this process (never opened here, or evicted by retirement).
293
+ * This is a passive read path, so it never constructs or provisions: the
294
+ * session's files come back the next time the workspace is actually opened
295
+ * (e.g. by sending a message).
295
296
  */
296
- async function sessionSandbox(fleet, session) {
297
- if (!fleet.enabled || !session.sandboxId || !session.sandboxWorkdir) return null;
298
- let sandbox;
299
- try {
300
- sandbox = await fleet.reattachSandbox(session.sandboxId, {
301
- workingDirectory: session.sandboxWorkdir,
302
- actingUserId: session.userId
303
- });
304
- } catch {
305
- return null;
306
- }
297
+ async function sessionSandbox(session) {
298
+ const entry = peekSessionSandbox(session.id);
299
+ if (!entry?.workdir) return null;
300
+ const sandbox = requireExec(entry.sandbox);
307
301
  return {
308
302
  sandbox,
309
303
  filesystem: new SandboxFilesystem({
310
304
  sandbox,
311
- workdir: session.sandboxWorkdir
305
+ workdir: entry.workdir
312
306
  }),
313
- workdir: session.sandboxWorkdir
307
+ workdir: entry.workdir
314
308
  };
315
309
  }
316
310
  /** List an approved rendered root inside a Factory session's sandbox workdir. */
317
- async function listSessionRenderedPath(fleet, session, renderedRoot) {
311
+ async function listSessionRenderedPath(session, renderedRoot) {
318
312
  const safeRoot = assertApprovedRenderedRoot(renderedRoot);
319
- const rootPath = posix.join(session.sandboxWorkdir ?? "", safeRoot);
313
+ const handle = await sessionSandbox(session);
314
+ const rootPath = posix.join(handle?.workdir ?? "", safeRoot);
320
315
  const empty = {
321
316
  workspacePath: session.sessionId,
322
317
  root: safeRoot,
323
318
  rootPath,
324
319
  entries: []
325
320
  };
326
- const handle = await sessionSandbox(fleet, session);
327
321
  if (!handle) return empty;
328
322
  const quotedRoot = `'${rootPath.replace(/'/g, `'\\''`)}'`;
329
323
  const result = await handle.sandbox.executeCommand("sh", ["-c", `test -d ${quotedRoot} && find ${quotedRoot} -mindepth 1 -printf '%y\\t%s\\t%T@\\t%p\\n' 2>/dev/null || true`], { timeout: 3e4 });
@@ -352,10 +346,10 @@ async function listSessionRenderedPath(fleet, session, renderedRoot) {
352
346
  };
353
347
  }
354
348
  /** Read a file inside a session's sandbox. Paths outside rendered roots require a persisted-file allowlist check in the route. */
355
- async function readSessionWorkspaceFile(fleet, session, path, options = {}) {
349
+ async function readSessionWorkspaceFile(session, path, options = {}) {
356
350
  const safePath = assertRelativePath(path, "path");
357
351
  if (!options.allowUnapprovedPath) assertApprovedRenderedRoot(safePath.split("/")[0] ?? "");
358
- const handle = await sessionSandbox(fleet, session);
352
+ const handle = await sessionSandbox(session);
359
353
  if (!handle) throw new Error("Session workspace is not available");
360
354
  const { filesystem } = handle;
361
355
  const info = await filesystem.stat(safePath);
@@ -458,8 +452,8 @@ function unavailableWorkspaceChanges(workspacePath) {
458
452
  changes: []
459
453
  };
460
454
  }
461
- async function listSessionWorkspaceChanges(fleet, session) {
462
- const handle = await sessionSandbox(fleet, session);
455
+ async function listSessionWorkspaceChanges(session) {
456
+ const handle = await sessionSandbox(session);
463
457
  if (!handle) return unavailableWorkspaceChanges(session.sessionId);
464
458
  const [statusResult, statsResult] = await Promise.all([handle.sandbox.executeCommand("git", [
465
459
  "-C",
@@ -516,10 +510,10 @@ function truncatePatch(patchBuffer) {
516
510
  if (lastNewline >= 0) return patch.slice(0, lastNewline + 1);
517
511
  return patch.replace(/\uFFFD+$/, "");
518
512
  }
519
- async function readSessionWorkspaceDiff(fleet, session, path, previousPath) {
513
+ async function readSessionWorkspaceDiff(session, path, previousPath) {
520
514
  const safePath = assertRelativePath(path, "path");
521
515
  const safePreviousPath = previousPath ? assertRelativePath(previousPath, "previousPath") : void 0;
522
- const handle = await sessionSandbox(fleet, session);
516
+ const handle = await sessionSandbox(session);
523
517
  if (!handle) throw new Error("Session workspace is not available");
524
518
  const pathspecs = safePreviousPath ? [safePreviousPath, safePath] : [safePath];
525
519
  let result = await executeBoundedGitDiff(handle.sandbox, [
@@ -637,7 +631,7 @@ function buildFsRoutes(options = {}) {
637
631
  if (!renderedRoot) return c.json({ error: "Missing required query param: root" }, 400);
638
632
  try {
639
633
  const session = await resolveAuthorizedSession(loose(c), sessionFs, workspacePath);
640
- if (session && sessionFs) return c.json(await listSessionRenderedPath(sessionFs.fleet, session, renderedRoot));
634
+ if (session && sessionFs) return c.json(await listSessionRenderedPath(session, renderedRoot));
641
635
  return c.json(await listWorkspaceRenderedPath(root, workspacePath, renderedRoot));
642
636
  } catch (error) {
643
637
  const message = error instanceof Error ? error.message : String(error);
@@ -674,7 +668,7 @@ function buildFsRoutes(options = {}) {
674
668
  try {
675
669
  const session = await resolveAuthorizedSession(loose(c), sessionFs, workspacePath);
676
670
  if (!session || !sessionFs) return c.json(unavailableWorkspaceChanges(workspacePath));
677
- return c.json(await listSessionWorkspaceChanges(sessionFs.fleet, session));
671
+ return c.json(await listSessionWorkspaceChanges(session));
678
672
  } catch (error) {
679
673
  const message = error instanceof Error ? error.message : String(error);
680
674
  return c.json({ error: message }, message.includes("not available") ? 403 : 500);
@@ -693,7 +687,7 @@ function buildFsRoutes(options = {}) {
693
687
  try {
694
688
  const session = await resolveAuthorizedSession(loose(c), sessionFs, workspacePath);
695
689
  if (!session || !sessionFs) return c.json({ error: "Session workspace is not available" }, 403);
696
- return c.json(await readSessionWorkspaceDiff(sessionFs.fleet, session, path, previousPath));
690
+ return c.json(await readSessionWorkspaceDiff(session, path, previousPath));
697
691
  } catch (error) {
698
692
  const message = error instanceof Error ? error.message : String(error);
699
693
  const status = message.includes("relative") || message.includes("escapes") || message.includes("not available") ? 403 : 500;
@@ -718,9 +712,9 @@ function buildFsRoutes(options = {}) {
718
712
  if (threadId) {
719
713
  const safePath = assertRelativePath(path, "path");
720
714
  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);
721
- return c.json(await readSessionWorkspaceFile(sessionFs.fleet, session, safePath, { allowUnapprovedPath: true }));
715
+ return c.json(await readSessionWorkspaceFile(session, safePath, { allowUnapprovedPath: true }));
722
716
  }
723
- return c.json(await readSessionWorkspaceFile(sessionFs.fleet, session, path));
717
+ return c.json(await readSessionWorkspaceFile(session, path));
724
718
  }
725
719
  if (threadId) return c.json({ error: "Session workspace is not available" }, 403);
726
720
  return c.json(await readWorkspaceFile(root, workspacePath, path));
@@ -1 +1 @@
1
- {"version":3,"file":"fs.js","names":["posixPath"],"sources":["../../src/routes/fs.ts"],"sourcesContent":["import { lstat, open, readdir, realpath, stat } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { isAbsolute, join, posix as posixPath, resolve, sep } from 'node:path';\n\nimport { SandboxFilesystem } from '@mastra/code-sdk/agents/sandbox-filesystem';\nimport { detectProject, getResourceIdOverride } from '@mastra/code-sdk/utils/project';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { ApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\nimport type { MaterializationSandbox, SandboxFleet } from '../sandbox/fleet.js';\nimport { waitForPendingFilesystemCapture } from '../session/filesystem-capture.js';\nimport type { FilesystemStorage } from '../storage/domains/filesystem/base.js';\nimport type { SourceControlSession } from '../storage/domains/source-control/base.js';\nimport type { RouteAuth } from './route.js';\n\n/**\n * Server-side directory browser for the web project picker.\n *\n * The browser cannot read absolute filesystem paths (the File System Access API\n * only exposes a directory *name*), so the picker must ask the server — which\n * does have filesystem access — to enumerate directories. The result is real\n * absolute paths the user can select without typing.\n *\n * All access is confined to a configured `root` (default: the user's home\n * directory). Requests that try to escape the root via `..` or symlinks are\n * clamped back to the root.\n */\n\nexport interface DirectoryEntry {\n name: string;\n /** Absolute path to the entry. */\n path: string;\n}\n\nexport interface DirectoryListing {\n /** The allowed root; clients cannot browse above this. */\n root: string;\n /** The absolute path that was listed. */\n path: string;\n /** Parent directory path, or null when `path` is the root. */\n parent: string | null;\n /** Subdirectories of `path` (directories only, sorted, hidden excluded). */\n entries: DirectoryEntry[];\n}\n\nexport interface WorkspaceRenderedEntry {\n name: string;\n /** Path relative to the configured rendered root. */\n path: string;\n type: 'file' | 'directory';\n size: number;\n updatedAt: string;\n}\n\nexport interface WorkspaceRenderedListing {\n /** The confined workspace/project root. */\n workspacePath: string;\n /** Configured workspace-relative rendered root, e.g. `.artifacts`. */\n root: string;\n /** The confined absolute path for the rendered root. */\n rootPath: string;\n entries: WorkspaceRenderedEntry[];\n}\n\nexport interface WorkspaceFile {\n /** The confined workspace/project root. */\n workspacePath: string;\n /** Workspace-relative file path. */\n path: string;\n name: string;\n size: number;\n updatedAt: string;\n contentType: 'text' | 'unsupported';\n content?: string;\n truncated?: boolean;\n}\n\nexport interface WorkspaceFilesListing {\n /** The Factory session resource id. */\n workspacePath: string;\n /** The agent thread whose terminal file list was captured. */\n threadId: string;\n files: Array<{ path: string }>;\n}\n\nexport type WorkspaceChangeStatus =\n | 'modified'\n | 'added'\n | 'deleted'\n | 'renamed'\n | 'copied'\n | 'untracked'\n | 'conflicted';\n\nexport interface WorkspaceChange {\n path: string;\n previousPath?: string;\n status: WorkspaceChangeStatus;\n additions?: number;\n deletions?: number;\n binary?: boolean;\n}\n\nexport interface WorkspaceChanges {\n workspacePath: string;\n available: boolean;\n changes: WorkspaceChange[];\n additions?: number;\n deletions?: number;\n}\n\nexport interface WorkspaceDiff {\n workspacePath: string;\n path: string;\n patch: string;\n truncated: boolean;\n}\n\nexport type ArtifactEntry = WorkspaceRenderedEntry;\n\nexport interface ArtifactListing {\n /** The confined workspace/project root. */\n rootPath: string;\n /** The workspace artifact directory. */\n artifactsPath: string;\n entries: ArtifactEntry[];\n}\n\nconst MAX_TEXT_FILE_BYTES = 512 * 1024;\nconst MAX_DIFF_BYTES = 512 * 1024;\nconst WORKSPACE_NUMSTAT_SCRIPT = `\nset -e\nworkdir=$1\nindex_file=$(mktemp)\nuntracked_file=$(mktemp)\nobject_dir=$(mktemp -d)\ntrap 'rm -f \"$index_file\" \"$untracked_file\"; rm -rf \"$object_dir\"' EXIT\nrm -f \"$index_file\"\ngit -C \"$workdir\" diff --numstat -z --find-renames --no-ext-diff --no-textconv HEAD\ngit -C \"$workdir\" ls-files --others --exclude-standard -z >\"$untracked_file\"\ngit_dir=$(git -C \"$workdir\" rev-parse --absolute-git-dir)\nexport GIT_INDEX_FILE=\"$index_file\"\nexport GIT_OBJECT_DIRECTORY=\"$object_dir\"\nexport GIT_ALTERNATE_OBJECT_DIRECTORIES=\"$git_dir/objects\"\ngit -C \"$workdir\" read-tree --empty\ngit -C \"$workdir\" update-index --add -z --stdin <\"$untracked_file\"\nempty_tree=$(git -C \"$workdir\" hash-object -t tree /dev/null)\ngit -C \"$workdir\" diff --cached --numstat -z --no-ext-diff --no-textconv \"$empty_tree\"\n`;\nconst BOUNDED_GIT_DIFF_SCRIPT = `\nallow_exit_one=$1\nshift\nstatus_file=$(mktemp)\nstderr_file=$(mktemp)\ntrap 'rm -f \"$status_file\" \"$stderr_file\"' EXIT\n(\n git \"$@\" 2>\"$stderr_file\"\n printf '%s' \"$?\" >\"$status_file\"\n) | head -c ${MAX_DIFF_BYTES + 1}\nstatus=$(cat \"$status_file\")\ncase \"$status\" in\n 0|141) exit 0 ;;\n 1) [ \"$allow_exit_one\" = \"1\" ] && exit 0 ;;\nesac\ncat \"$stderr_file\" >&2\nexit \"\\${status:-1}\"\n`;\nconst TEXT_DECODER = new TextDecoder('utf-8', { fatal: true });\nconst APPROVED_RENDERED_ROOTS = new Set(['.artifacts']);\n\n/** Erase a route handler's path-parameterized context to a plain `Context`. */\nfunction loose(c: unknown): Context {\n return c as Context;\n}\n\n/** Resolve the browsable root, defaulting to the user's home directory. */\nexport function resolveFsRoot(root?: string): string {\n return resolve(root && root.trim() ? root : homedir());\n}\n\n/** True when `candidate` is `root` or nested under it. */\nfunction isWithinRoot(candidate: string, root: string): boolean {\n if (candidate === root) return true;\n const rootWithSep = root.endsWith(sep) ? root : root + sep;\n return candidate.startsWith(rootWithSep);\n}\n\nasync function realOrResolved(path: string): Promise<string> {\n try {\n return await realpath(path);\n } catch {\n return path;\n }\n}\n\n/**\n * Resolve a path's real location (following symlinks) and confirm it stays\n * within `root`. Returns the real path when confined, or `null` when it escapes\n * the root or does not exist. Used so a symlink inside the root that points\n * outside it cannot be browsed or selected.\n */\nasync function realPathWithinRoot(candidate: string, root: string): Promise<string | null> {\n try {\n const real = await realpath(candidate);\n return isWithinRoot(real, root) ? real : null;\n } catch {\n return null;\n }\n}\n\nfunction assertRelativePath(path: string, label: string): string {\n const trimmed = path.trim();\n if (!trimmed) throw new Error(`Missing required query param: ${label}`);\n if (isAbsolute(trimmed)) throw new Error(`${label} must be relative`);\n if (trimmed.split(/[\\\\/]+/).includes('..')) throw new Error(`${label} escapes workspace`);\n const normalized = resolve('/', trimmed).slice(1);\n if (!normalized || normalized === '..' || normalized.startsWith(`..${sep}`))\n throw new Error(`${label} escapes workspace`);\n return normalized;\n}\n\nfunction assertApprovedRenderedRoot(renderedRoot: string): string {\n const safeRoot = assertRelativePath(renderedRoot, 'root');\n if (!APPROVED_RENDERED_ROOTS.has(safeRoot)) throw new Error('Root is not approved for rendered workspace access');\n return safeRoot;\n}\n\nasync function confinedWorkspacePath(\n root: string,\n workspacePath: string,\n): Promise<{ resolvedRoot: string; workspace: string }> {\n const resolvedRoot = await realOrResolved(resolveFsRoot(root));\n const candidate = isAbsolute(workspacePath) ? resolve(workspacePath) : resolve(resolvedRoot, workspacePath);\n const workspace = await realPathWithinRoot(candidate, resolvedRoot);\n if (!workspace) throw new Error('Path is outside the browsable root');\n return { resolvedRoot, workspace };\n}\n\nasync function confinedWorkspaceRelativePath(\n root: string,\n workspacePath: string,\n relativePath: string,\n): Promise<{ workspace: string; path: string; relativePath: string }> {\n const safeRelativePath = assertRelativePath(relativePath, 'path');\n const { workspace } = await confinedWorkspacePath(root, workspacePath);\n const candidate = resolve(workspace, safeRelativePath);\n if (!isWithinRoot(candidate, workspace)) throw new Error('Path escapes workspace');\n const confinedPath = await realPathWithinRoot(candidate, workspace);\n if (!confinedPath) throw new Error('Path is outside the workspace');\n return { workspace, path: confinedPath, relativePath: safeRelativePath };\n}\n\n/**\n * List the directories inside `requestedPath`, confined to `root`. An absent or\n * out-of-root path is clamped to the root, so the worst a malicious client can\n * do is browse within the allowed root.\n */\nexport async function listDirectory(root: string, requestedPath?: string): Promise<DirectoryListing> {\n // Resolve the root through symlinks so all confinement checks compare real\n // paths; a symlink that escapes the root is then reliably detectable.\n const resolvedRoot = await realOrResolved(resolveFsRoot(root));\n\n let target = resolvedRoot;\n if (requestedPath && requestedPath.trim()) {\n const candidate = isAbsolute(requestedPath) ? resolve(requestedPath) : resolve(resolvedRoot, requestedPath);\n // Follow symlinks and re-confirm the real target stays within the root.\n target = (await realPathWithinRoot(candidate, resolvedRoot)) ?? resolvedRoot;\n }\n\n // Confirm the target is a real directory; fall back to root otherwise.\n try {\n const info = await stat(target);\n if (!info.isDirectory()) target = resolvedRoot;\n } catch {\n target = resolvedRoot;\n }\n\n const dirents = await readdir(target, { withFileTypes: true });\n const entries: DirectoryEntry[] = [];\n for (const dirent of dirents) {\n if (dirent.name.startsWith('.')) continue; // skip dotfiles/dirs\n const entryPath = join(target, dirent.name);\n let isDir = dirent.isDirectory();\n if (dirent.isSymbolicLink()) {\n // Only surface symlinks whose real target is a directory inside the root,\n // so a link pointing outside the root can't be browsed or selected.\n const real = await realPathWithinRoot(entryPath, resolvedRoot);\n isDir = real ? (await stat(real).catch(() => null))?.isDirectory() === true : false;\n }\n if (isDir) entries.push({ name: dirent.name, path: entryPath });\n }\n entries.sort((a, b) => a.name.localeCompare(b.name));\n\n const parent = target === resolvedRoot ? null : resolve(target, '..');\n\n return { root: resolvedRoot, path: target, parent, entries };\n}\n\nasync function listRenderedEntries(rootPath: string, currentPath = rootPath): Promise<WorkspaceRenderedEntry[]> {\n const dirents = await readdir(currentPath, { withFileTypes: true });\n const entries: WorkspaceRenderedEntry[] = [];\n\n for (const dirent of dirents) {\n const entryPath = join(currentPath, dirent.name);\n const info = await lstat(entryPath);\n const relativePath = entryPath.slice(rootPath.length + 1);\n\n if (info.isDirectory()) {\n entries.push({\n name: dirent.name,\n path: relativePath,\n type: 'directory',\n size: info.size,\n updatedAt: info.mtime.toISOString(),\n });\n entries.push(...(await listRenderedEntries(rootPath, entryPath)));\n continue;\n }\n\n if (info.isFile()) {\n entries.push({\n name: dirent.name,\n path: relativePath,\n type: 'file',\n size: info.size,\n updatedAt: info.mtime.toISOString(),\n });\n }\n }\n\n return entries.sort((a, b) => a.path.localeCompare(b.path));\n}\n\nexport async function listWorkspaceRenderedPath(\n root: string,\n workspacePath: string,\n renderedRoot: string,\n): Promise<WorkspaceRenderedListing> {\n const safeRoot = assertApprovedRenderedRoot(renderedRoot);\n const { workspace } = await confinedWorkspacePath(root, workspacePath);\n const renderedPath = resolve(workspace, safeRoot);\n if (!isWithinRoot(renderedPath, workspace)) throw new Error('Root escapes workspace');\n\n const confinedRootPath = await realPathWithinRoot(renderedPath, workspace);\n if (!confinedRootPath) return { workspacePath: workspace, root: safeRoot, rootPath: renderedPath, entries: [] };\n\n const info = await stat(confinedRootPath);\n if (!info.isDirectory()) return { workspacePath: workspace, root: safeRoot, rootPath: confinedRootPath, entries: [] };\n\n return {\n workspacePath: workspace,\n root: safeRoot,\n rootPath: confinedRootPath,\n entries: await listRenderedEntries(confinedRootPath),\n };\n}\n\nexport async function readWorkspaceFile(root: string, workspacePath: string, path: string): Promise<WorkspaceFile> {\n const safePath = assertRelativePath(path, 'path');\n const relativeRoot = safePath.split('/')[0] ?? '';\n assertApprovedRenderedRoot(relativeRoot);\n const {\n workspace,\n path: confinedPath,\n relativePath,\n } = await confinedWorkspaceRelativePath(root, workspacePath, path);\n const info = await lstat(confinedPath);\n if (info.isDirectory()) throw new Error('Path is a directory');\n if (!info.isFile()) throw new Error('Unsupported file type');\n\n const bytesToRead = Math.min(info.size, MAX_TEXT_FILE_BYTES);\n const contentBuffer = Buffer.alloc(bytesToRead);\n const handle = await open(confinedPath, 'r');\n try {\n await handle.read(contentBuffer, 0, bytesToRead, 0);\n } finally {\n await handle.close();\n }\n\n try {\n const content = TEXT_DECODER.decode(contentBuffer);\n return {\n workspacePath: workspace,\n path: relativePath,\n name: relativePath.split('/').pop() ?? relativePath,\n size: info.size,\n updatedAt: info.mtime.toISOString(),\n contentType: 'text',\n content,\n truncated: info.size > MAX_TEXT_FILE_BYTES,\n };\n } catch {\n return {\n workspacePath: workspace,\n path: relativePath,\n name: relativePath.split('/').pop() ?? relativePath,\n size: info.size,\n updatedAt: info.mtime.toISOString(),\n contentType: 'unsupported',\n };\n }\n}\n\nexport async function listArtifacts(root: string, workspacePath: string): Promise<ArtifactListing> {\n const listing = await listWorkspaceRenderedPath(root, workspacePath, '.artifacts');\n return {\n rootPath: listing.workspacePath,\n artifactsPath: listing.rootPath,\n entries: listing.entries,\n };\n}\n\n// ── Session-backed workspace access ──────────────────────────────────────────\n//\n// The web UI identifies a Factory session workspace by its session id (a UUID),\n// not by a server-local filesystem path — the session's files live inside the\n// session's sandbox (a remote VM on deployed factories). These helpers resolve\n// the session, enforce that the caller owns it, reattach to its sandbox, and\n// serve the approved rendered roots through `SandboxFilesystem`.\n\n/** Dependencies for resolving a `workspacePath` that is a Factory session id. */\nexport interface SessionFsDeps {\n auth: RouteAuth;\n fleet: SandboxFleet;\n sessions: { getBySessionId(sessionId: string): Promise<SourceControlSession | null> };\n filesystem: Pick<FilesystemStorage, 'listFiles'>;\n}\n\n/**\n * Resolve a `workspacePath` query param as a Factory session id. Returns the\n * session when one exists and the caller owns it, `null` when no session\n * matches (the caller should fall back to local-path handling), and throws\n * when a session exists but belongs to another tenant.\n */\nasync function resolveAuthorizedSession(\n c: Context,\n deps: SessionFsDeps | undefined,\n workspacePath: string,\n): Promise<SourceControlSession | null> {\n if (!deps) return null;\n const session = await deps.sessions.getBySessionId(workspacePath);\n if (!session) return null;\n if (deps.auth.enabled()) {\n await deps.auth.ensureUser(c);\n const tenant = deps.auth.tenant(c);\n if (!tenant || tenant.orgId !== session.orgId || tenant.userId !== session.userId) {\n throw new Error('Session is not available to the current user');\n }\n }\n return session;\n}\n\nexport async function listSessionFilesystemFiles(\n filesystem: Pick<FilesystemStorage, 'listFiles'>,\n session: SourceControlSession,\n threadId: string,\n): Promise<WorkspaceFilesListing> {\n const safeThreadId = threadId.trim();\n if (!safeThreadId) throw new Error('Missing required query param: threadId');\n\n // The turn-end capture no longer gates agent_end, so a reader refetching on\n // run completion could otherwise race it and serve the previous turn's\n // listing. Await the in-flight capture (bounded) before reading.\n await waitForPendingFilesystemCapture(session.sessionId);\n\n return {\n workspacePath: session.sessionId,\n threadId: safeThreadId,\n files: await filesystem.listFiles({ resourceId: session.sessionId, threadId: safeThreadId }),\n };\n}\n\ninterface SessionSandboxHandle {\n sandbox: MaterializationSandbox;\n filesystem: SandboxFilesystem;\n workdir: string;\n}\n\n/**\n * Reattach to the session's sandbox and wrap its workdir in a\n * `SandboxFilesystem`. Returns `null` when the session has no provisioned\n * sandbox yet (nothing materialized → nothing to list), or when the sandbox\n * can no longer be reattached (e.g. torn down by the provider's idle GC).\n * This is a passive read path, so it never re-provisions: the session's\n * filesystem is preserved in its provider checkpoint and comes back the next\n * time the workspace is actually opened (e.g. by sending a message).\n */\nasync function sessionSandbox(\n fleet: SandboxFleet,\n session: SourceControlSession,\n): Promise<SessionSandboxHandle | null> {\n if (!fleet.enabled || !session.sandboxId || !session.sandboxWorkdir) return null;\n let sandbox: Awaited<ReturnType<SandboxFleet['reattachSandbox']>>;\n try {\n sandbox = await fleet.reattachSandbox(session.sandboxId, {\n workingDirectory: session.sandboxWorkdir,\n actingUserId: session.userId,\n });\n } catch {\n // Sandbox is gone (idle GC) or unreachable. Degrade to an empty view\n // rather than surfacing a 500 from a file-viewer panel.\n return null;\n }\n return {\n sandbox,\n filesystem: new SandboxFilesystem({ sandbox, workdir: session.sandboxWorkdir }),\n workdir: session.sandboxWorkdir,\n };\n}\n\n/** List an approved rendered root inside a Factory session's sandbox workdir. */\nexport async function listSessionRenderedPath(\n fleet: SandboxFleet,\n session: SourceControlSession,\n renderedRoot: string,\n): Promise<WorkspaceRenderedListing> {\n const safeRoot = assertApprovedRenderedRoot(renderedRoot);\n const rootPath = posixPath.join(session.sandboxWorkdir ?? '', safeRoot);\n const empty: WorkspaceRenderedListing = { workspacePath: session.sessionId, root: safeRoot, rootPath, entries: [] };\n\n const handle = await sessionSandbox(fleet, session);\n if (!handle) return empty;\n\n // One round trip: emit \"type\\tsize\\tmtime\\tpath\" per entry. `safeRoot` comes\n // from a fixed allowlist so interpolating it (quoted) is safe.\n const quotedRoot = `'${rootPath.replace(/'/g, `'\\\\''`)}'`;\n const result = await handle.sandbox.executeCommand(\n 'sh',\n [\n '-c',\n `test -d ${quotedRoot} && find ${quotedRoot} -mindepth 1 -printf '%y\\\\t%s\\\\t%T@\\\\t%p\\\\n' 2>/dev/null || true`,\n ],\n { timeout: 30_000 },\n );\n if (result.exitCode !== 0) return empty;\n\n const entries: WorkspaceRenderedEntry[] = [];\n for (const line of result.stdout.split('\\n')) {\n if (!line) continue;\n const [type, sizeStr, mtimeStr, ...pathParts] = line.split('\\t');\n const fullPath = pathParts.join('\\t');\n if (!fullPath || !fullPath.startsWith(`${rootPath}/`)) continue;\n const relativePath = fullPath.slice(rootPath.length + 1);\n entries.push({\n name: posixPath.basename(relativePath),\n path: relativePath,\n type: type === 'd' ? 'directory' : 'file',\n size: type === 'd' ? 0 : Number(sizeStr) || 0,\n updatedAt: new Date((Number(mtimeStr) || 0) * 1000).toISOString(),\n });\n }\n entries.sort((a, b) => a.path.localeCompare(b.path));\n\n return { workspacePath: session.sessionId, root: safeRoot, rootPath, entries };\n}\n\n/** Read a file inside a session's sandbox. Paths outside rendered roots require a persisted-file allowlist check in the route. */\nexport async function readSessionWorkspaceFile(\n fleet: SandboxFleet,\n session: SourceControlSession,\n path: string,\n options: { allowUnapprovedPath?: boolean } = {},\n): Promise<WorkspaceFile> {\n const safePath = assertRelativePath(path, 'path');\n if (!options.allowUnapprovedPath) assertApprovedRenderedRoot(safePath.split('/')[0] ?? '');\n\n const handle = await sessionSandbox(fleet, session);\n if (!handle) throw new Error('Session workspace is not available');\n const { filesystem } = handle;\n const info = await filesystem.stat(safePath);\n if (info.type === 'directory') throw new Error('Path is a directory');\n\n const buffer = (await filesystem.readFile(safePath)) as Buffer;\n const truncated = buffer.length > MAX_TEXT_FILE_BYTES;\n const base = {\n workspacePath: session.sessionId,\n path: safePath,\n name: posixPath.basename(safePath),\n size: buffer.length,\n updatedAt: info.modifiedAt.toISOString(),\n };\n try {\n const content = TEXT_DECODER.decode(truncated ? buffer.subarray(0, MAX_TEXT_FILE_BYTES) : buffer);\n return { ...base, contentType: 'text', content, truncated };\n } catch {\n return { ...base, contentType: 'unsupported' };\n }\n}\n\nfunction changeStatus(code: string): WorkspaceChangeStatus {\n if (code === '??') return 'untracked';\n if (code.includes('U') || code === 'AA' || code === 'DD') return 'conflicted';\n if (code.includes('R')) return 'renamed';\n if (code.includes('C')) return 'copied';\n if (code.includes('D')) return 'deleted';\n if (code.includes('A')) return 'added';\n return 'modified';\n}\n\nexport function parseWorkspaceChanges(output: string): WorkspaceChange[] {\n const records = output.split('\\0');\n const changes: WorkspaceChange[] = [];\n\n for (let index = 0; index < records.length; index += 1) {\n const record = records[index];\n if (!record || record.length < 4) continue;\n const code = record.slice(0, 2);\n const path = record.slice(3);\n const status = changeStatus(code);\n if (status === 'renamed' || status === 'copied') {\n const previousPath = records[index + 1];\n if (previousPath) index += 1;\n changes.push({ path, previousPath: previousPath || undefined, status });\n continue;\n }\n changes.push({ path, status });\n }\n\n return changes.toSorted((a, b) => a.path.localeCompare(b.path));\n}\n\nexport function parseWorkspaceChangeStats(output: string) {\n const records = output.split('\\0');\n const stats = new Map<string, { additions?: number; deletions?: number; binary?: boolean }>();\n\n for (let index = 0; index < records.length; index += 1) {\n const record = records[index];\n if (!record) continue;\n const firstTab = record.indexOf('\\t');\n const secondTab = record.indexOf('\\t', firstTab + 1);\n if (firstTab < 0 || secondTab < 0) continue;\n\n const additionsText = record.slice(0, firstTab);\n const deletionsText = record.slice(firstTab + 1, secondTab);\n let path = record.slice(secondTab + 1);\n if (!path) {\n const renamedPath = records[index + 2];\n if (!renamedPath) continue;\n path = renamedPath;\n index += 2;\n }\n if (path.startsWith('./')) path = path.slice(2);\n\n if (additionsText === '-' || deletionsText === '-') {\n stats.set(path, { binary: true });\n continue;\n }\n\n const additions = Number(additionsText);\n const deletions = Number(deletionsText);\n if (Number.isFinite(additions) && Number.isFinite(deletions)) stats.set(path, { additions, deletions });\n }\n\n return stats;\n}\n\nfunction unavailableWorkspaceChanges(workspacePath: string): WorkspaceChanges {\n return { workspacePath, available: false, changes: [] };\n}\n\nexport async function listSessionWorkspaceChanges(\n fleet: SandboxFleet,\n session: SourceControlSession,\n): Promise<WorkspaceChanges> {\n const handle = await sessionSandbox(fleet, session);\n if (!handle) return unavailableWorkspaceChanges(session.sessionId);\n\n const [statusResult, statsResult] = await Promise.all([\n handle.sandbox.executeCommand(\n 'git',\n ['-C', handle.workdir, 'status', '--porcelain=v1', '-z', '--untracked-files=all'],\n { timeout: 30_000 },\n ),\n handle.sandbox.executeCommand('sh', ['-c', WORKSPACE_NUMSTAT_SCRIPT, 'mastracode-numstat', handle.workdir], {\n timeout: 30_000,\n }),\n ]);\n if (statusResult.exitCode !== 0) return unavailableWorkspaceChanges(session.sessionId);\n\n const changes = parseWorkspaceChanges(statusResult.stdout);\n if (statsResult.exitCode !== 0) {\n return { workspacePath: session.sessionId, available: true, changes };\n }\n\n const stats = parseWorkspaceChangeStats(statsResult.stdout);\n let additions = 0;\n let deletions = 0;\n const changesWithStats = changes.map(change => {\n const changeStats = stats.get(change.path);\n additions += changeStats?.additions ?? 0;\n deletions += changeStats?.deletions ?? 0;\n return { ...change, ...changeStats };\n });\n\n return { workspacePath: session.sessionId, available: true, changes: changesWithStats, additions, deletions };\n}\n\nasync function executeBoundedGitDiff(sandbox: MaterializationSandbox, args: string[], allowExitOne = false) {\n return sandbox.executeCommand(\n 'sh',\n ['-c', BOUNDED_GIT_DIFF_SCRIPT, 'mastracode-diff', allowExitOne ? '1' : '0', ...args],\n { timeout: 30_000 },\n );\n}\n\nfunction truncatePatch(patchBuffer: Buffer): string {\n const patch = patchBuffer.subarray(0, MAX_DIFF_BYTES).toString('utf8');\n const lastNewline = patch.lastIndexOf('\\n');\n if (lastNewline >= 0) return patch.slice(0, lastNewline + 1);\n return patch.replace(/\\uFFFD+$/, '');\n}\n\nexport async function readSessionWorkspaceDiff(\n fleet: SandboxFleet,\n session: SourceControlSession,\n path: string,\n previousPath?: string,\n): Promise<WorkspaceDiff> {\n const safePath = assertRelativePath(path, 'path');\n const safePreviousPath = previousPath ? assertRelativePath(previousPath, 'previousPath') : undefined;\n const handle = await sessionSandbox(fleet, session);\n if (!handle) throw new Error('Session workspace is not available');\n\n const pathspecs = safePreviousPath ? [safePreviousPath, safePath] : [safePath];\n let result = await executeBoundedGitDiff(handle.sandbox, [\n '--literal-pathspecs',\n '-C',\n handle.workdir,\n 'diff',\n '--find-renames',\n '--no-ext-diff',\n '--no-color',\n '--unified=3',\n 'HEAD',\n '--',\n ...pathspecs,\n ]);\n if (result.exitCode !== 0) throw new Error(result.stderr || 'Unable to read workspace diff');\n\n if (!result.stdout) {\n const untracked = await handle.sandbox.executeCommand(\n 'git',\n ['--literal-pathspecs', '-C', handle.workdir, 'ls-files', '--others', '--exclude-standard', '--', safePath],\n { timeout: 30_000 },\n );\n if (untracked.exitCode === 0 && untracked.stdout.trim()) {\n result = await executeBoundedGitDiff(\n handle.sandbox,\n [\n '-C',\n handle.workdir,\n 'diff',\n '--no-index',\n '--no-ext-diff',\n '--no-color',\n '--unified=3',\n '--',\n '/dev/null',\n safePath,\n ],\n true,\n );\n if (result.exitCode !== 0 && result.exitCode !== 1) {\n throw new Error(result.stderr || 'Unable to read workspace diff');\n }\n }\n }\n\n const patchBuffer = Buffer.from(result.stdout);\n const truncated = patchBuffer.length > MAX_DIFF_BYTES;\n return {\n workspacePath: session.sessionId,\n path: safePath,\n patch: truncated ? truncatePatch(patchBuffer) : result.stdout,\n truncated,\n };\n}\n\nexport interface ResolvedCodebase {\n /**\n * The resourceId the TUI would use for this path — derived identically so a\n * project opened in the terminal and in the web app resolve to the SAME\n * session (and therefore the same threads).\n */\n resourceId: string;\n name: string;\n rootPath: string;\n gitUrl?: string;\n gitBranch?: string;\n}\n\n/**\n * Resolve a project path to the same resourceId the TUI uses. Mirrors\n * `createMastraCode`: detect the project, then apply any resourceId override\n * (MASTRA_RESOURCE_ID env var or `.mastracode/database.json`). This is the\n * shared continuity point — start in the TUI, continue on the web, same path\n * → same resourceId → same session.\n */\nexport function resolveCodebase(projectPath: string): ResolvedCodebase {\n const info = detectProject(projectPath);\n const override = getResourceIdOverride(info.rootPath);\n return {\n resourceId: override ?? info.resourceId,\n name: info.name,\n rootPath: info.rootPath,\n gitUrl: info.gitUrl,\n gitBranch: info.gitBranch,\n };\n}\n\n/**\n * Build the web filesystem routes as Mastra `apiRoutes`:\n * - `GET /web/fs/list?path=...` — browse directories (confined to root)\n * - `GET /web/codebase/resolve?path=...` — TUI-compatible codebase resourceId\n */\nexport function buildFsRoutes(options: { root?: string; sessionFs?: SessionFsDeps } = {}): ApiRoute[] {\n const root = resolveFsRoot(options.root);\n const sessionFs = options.sessionFs;\n\n return [\n registerApiRoute('/web/fs/list', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const path = c.req.query('path');\n try {\n const listing = await listDirectory(root, path);\n return c.json(listing);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return c.json({ error: message }, 500);\n }\n },\n }),\n registerApiRoute('/web/artifacts/list', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const path = c.req.query('path');\n if (!path) return c.json({ error: 'Missing required query param: path' }, 400);\n try {\n return c.json(await listArtifacts(root, path));\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const status = message === 'Path is outside the browsable root' ? 403 : 500;\n return c.json({ error: message }, status);\n }\n },\n }),\n registerApiRoute('/web/workspace/rendered/list', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const workspacePath = c.req.query('workspacePath');\n const renderedRoot = c.req.query('root');\n if (!workspacePath) return c.json({ error: 'Missing required query param: workspacePath' }, 400);\n if (!renderedRoot) return c.json({ error: 'Missing required query param: root' }, 400);\n try {\n const session = await resolveAuthorizedSession(loose(c), sessionFs, workspacePath);\n if (session && sessionFs) {\n return c.json(await listSessionRenderedPath(sessionFs.fleet, session, renderedRoot));\n }\n return c.json(await listWorkspaceRenderedPath(root, workspacePath, renderedRoot));\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const status =\n message.includes('outside') ||\n message.includes('relative') ||\n message.includes('escapes') ||\n message.includes('not approved') ||\n message.includes('not available')\n ? 403\n : 500;\n return c.json({ error: message }, status);\n }\n },\n }),\n registerApiRoute('/web/workspace/files', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const workspacePath = c.req.query('workspacePath');\n const threadId = c.req.query('threadId')?.trim();\n if (!workspacePath) return c.json({ error: 'Missing required query param: workspacePath' }, 400);\n if (!threadId) return c.json({ error: 'Missing required query param: threadId' }, 400);\n try {\n const session = await resolveAuthorizedSession(loose(c), sessionFs, workspacePath);\n if (!session || !sessionFs) return c.json({ error: 'Session workspace is not available' }, 403);\n return c.json(await listSessionFilesystemFiles(sessionFs.filesystem, session, threadId));\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const status = message.includes('not available') ? 403 : 500;\n return c.json({ error: message }, status);\n }\n },\n }),\n registerApiRoute('/web/workspace/changes', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const workspacePath = c.req.query('workspacePath');\n if (!workspacePath) return c.json({ error: 'Missing required query param: workspacePath' }, 400);\n try {\n const session = await resolveAuthorizedSession(loose(c), sessionFs, workspacePath);\n if (!session || !sessionFs) return c.json(unavailableWorkspaceChanges(workspacePath));\n return c.json(await listSessionWorkspaceChanges(sessionFs.fleet, session));\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return c.json({ error: message }, message.includes('not available') ? 403 : 500);\n }\n },\n }),\n registerApiRoute('/web/workspace/changes/diff', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const workspacePath = c.req.query('workspacePath');\n const path = c.req.query('path');\n const previousPath = c.req.query('previousPath');\n if (!workspacePath) return c.json({ error: 'Missing required query param: workspacePath' }, 400);\n if (!path) return c.json({ error: 'Missing required query param: path' }, 400);\n try {\n const session = await resolveAuthorizedSession(loose(c), sessionFs, workspacePath);\n if (!session || !sessionFs) return c.json({ error: 'Session workspace is not available' }, 403);\n return c.json(await readSessionWorkspaceDiff(sessionFs.fleet, session, path, previousPath));\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const status =\n message.includes('relative') || message.includes('escapes') || message.includes('not available')\n ? 403\n : 500;\n return c.json({ error: message }, status);\n }\n },\n }),\n registerApiRoute('/web/workspace/file', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const workspacePath = c.req.query('workspacePath');\n const path = c.req.query('path');\n const requestedThreadId = c.req.query('threadId');\n const threadId = requestedThreadId?.trim();\n if (!workspacePath) return c.json({ error: 'Missing required query param: workspacePath' }, 400);\n if (!path) return c.json({ error: 'Missing required query param: path' }, 400);\n if (requestedThreadId !== undefined && !threadId) {\n return c.json({ error: 'Missing required query param: threadId' }, 400);\n }\n try {\n const session = await resolveAuthorizedSession(loose(c), sessionFs, workspacePath);\n if (session && sessionFs) {\n if (threadId) {\n const safePath = assertRelativePath(path, 'path');\n const listing = await listSessionFilesystemFiles(sessionFs.filesystem, session, threadId);\n if (!listing.files.some(file => file.path === safePath)) {\n return c.json({ error: 'Path is not available for this thread' }, 404);\n }\n return c.json(\n await readSessionWorkspaceFile(sessionFs.fleet, session, safePath, { allowUnapprovedPath: true }),\n );\n }\n return c.json(await readSessionWorkspaceFile(sessionFs.fleet, session, path));\n }\n if (threadId) return c.json({ error: 'Session workspace is not available' }, 403);\n return c.json(await readWorkspaceFile(root, workspacePath, path));\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const status =\n message.includes('outside') ||\n message.includes('relative') ||\n message.includes('escapes') ||\n message.includes('not approved') ||\n message.includes('not available')\n ? 403\n : message.includes('directory')\n ? 400\n : message.includes('not found')\n ? 404\n : 500;\n return c.json({ error: message }, status);\n }\n },\n }),\n registerApiRoute('/web/codebase/resolve', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const path = c.req.query('path');\n if (!path) return c.json({ error: 'Missing required query param: path' }, 400);\n // Confine resolution to the browsable root (following symlinks), so this\n // endpoint can't be used to probe arbitrary filesystem paths. The web UI\n // only ever resolves directories the user picked via the root-confined\n // browser, so legitimate requests are always within the root.\n const confined = await realPathWithinRoot(isAbsolute(path) ? resolve(path) : resolve(root, path), root);\n if (!confined) return c.json({ error: 'Path is outside the browsable root' }, 403);\n try {\n return c.json(resolveCodebase(confined));\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return c.json({ error: message }, 500);\n }\n },\n }),\n ];\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"fs.js","names":["posixPath"],"sources":["../../src/routes/fs.ts"],"sourcesContent":["import { lstat, open, readdir, realpath, stat } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { isAbsolute, join, posix as posixPath, resolve, sep } from 'node:path';\n\nimport { SandboxFilesystem } from '@mastra/code-sdk/agents/sandbox-filesystem';\nimport { detectProject, getResourceIdOverride } from '@mastra/code-sdk/utils/project';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { ApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\nimport { requireExec } from '../sandbox/materialization.js';\nimport type { ExecutableSandbox } from '../sandbox/materialization.js';\nimport { peekSessionSandbox } from '../sandbox/session-sandbox.js';\nimport { waitForPendingFilesystemCapture } from '../session/filesystem-capture.js';\nimport type { FilesystemStorage } from '../storage/domains/filesystem/base.js';\nimport type { SourceControlSession } from '../storage/domains/source-control/base.js';\nimport type { RouteAuth } from './route.js';\n\n/**\n * Server-side directory browser for the web project picker.\n *\n * The browser cannot read absolute filesystem paths (the File System Access API\n * only exposes a directory *name*), so the picker must ask the server — which\n * does have filesystem access — to enumerate directories. The result is real\n * absolute paths the user can select without typing.\n *\n * All access is confined to a configured `root` (default: the user's home\n * directory). Requests that try to escape the root via `..` or symlinks are\n * clamped back to the root.\n */\n\nexport interface DirectoryEntry {\n name: string;\n /** Absolute path to the entry. */\n path: string;\n}\n\nexport interface DirectoryListing {\n /** The allowed root; clients cannot browse above this. */\n root: string;\n /** The absolute path that was listed. */\n path: string;\n /** Parent directory path, or null when `path` is the root. */\n parent: string | null;\n /** Subdirectories of `path` (directories only, sorted, hidden excluded). */\n entries: DirectoryEntry[];\n}\n\nexport interface WorkspaceRenderedEntry {\n name: string;\n /** Path relative to the configured rendered root. */\n path: string;\n type: 'file' | 'directory';\n size: number;\n updatedAt: string;\n}\n\nexport interface WorkspaceRenderedListing {\n /** The confined workspace/project root. */\n workspacePath: string;\n /** Configured workspace-relative rendered root, e.g. `.artifacts`. */\n root: string;\n /** The confined absolute path for the rendered root. */\n rootPath: string;\n entries: WorkspaceRenderedEntry[];\n}\n\nexport interface WorkspaceFile {\n /** The confined workspace/project root. */\n workspacePath: string;\n /** Workspace-relative file path. */\n path: string;\n name: string;\n size: number;\n updatedAt: string;\n contentType: 'text' | 'unsupported';\n content?: string;\n truncated?: boolean;\n}\n\nexport interface WorkspaceFilesListing {\n /** The Factory session resource id. */\n workspacePath: string;\n /** The agent thread whose terminal file list was captured. */\n threadId: string;\n files: Array<{ path: string }>;\n}\n\nexport type WorkspaceChangeStatus =\n | 'modified'\n | 'added'\n | 'deleted'\n | 'renamed'\n | 'copied'\n | 'untracked'\n | 'conflicted';\n\nexport interface WorkspaceChange {\n path: string;\n previousPath?: string;\n status: WorkspaceChangeStatus;\n additions?: number;\n deletions?: number;\n binary?: boolean;\n}\n\nexport interface WorkspaceChanges {\n workspacePath: string;\n available: boolean;\n changes: WorkspaceChange[];\n additions?: number;\n deletions?: number;\n}\n\nexport interface WorkspaceDiff {\n workspacePath: string;\n path: string;\n patch: string;\n truncated: boolean;\n}\n\nexport type ArtifactEntry = WorkspaceRenderedEntry;\n\nexport interface ArtifactListing {\n /** The confined workspace/project root. */\n rootPath: string;\n /** The workspace artifact directory. */\n artifactsPath: string;\n entries: ArtifactEntry[];\n}\n\nconst MAX_TEXT_FILE_BYTES = 512 * 1024;\nconst MAX_DIFF_BYTES = 512 * 1024;\nconst WORKSPACE_NUMSTAT_SCRIPT = `\nset -e\nworkdir=$1\nindex_file=$(mktemp)\nuntracked_file=$(mktemp)\nobject_dir=$(mktemp -d)\ntrap 'rm -f \"$index_file\" \"$untracked_file\"; rm -rf \"$object_dir\"' EXIT\nrm -f \"$index_file\"\ngit -C \"$workdir\" diff --numstat -z --find-renames --no-ext-diff --no-textconv HEAD\ngit -C \"$workdir\" ls-files --others --exclude-standard -z >\"$untracked_file\"\ngit_dir=$(git -C \"$workdir\" rev-parse --absolute-git-dir)\nexport GIT_INDEX_FILE=\"$index_file\"\nexport GIT_OBJECT_DIRECTORY=\"$object_dir\"\nexport GIT_ALTERNATE_OBJECT_DIRECTORIES=\"$git_dir/objects\"\ngit -C \"$workdir\" read-tree --empty\ngit -C \"$workdir\" update-index --add -z --stdin <\"$untracked_file\"\nempty_tree=$(git -C \"$workdir\" hash-object -t tree /dev/null)\ngit -C \"$workdir\" diff --cached --numstat -z --no-ext-diff --no-textconv \"$empty_tree\"\n`;\nconst BOUNDED_GIT_DIFF_SCRIPT = `\nallow_exit_one=$1\nshift\nstatus_file=$(mktemp)\nstderr_file=$(mktemp)\ntrap 'rm -f \"$status_file\" \"$stderr_file\"' EXIT\n(\n git \"$@\" 2>\"$stderr_file\"\n printf '%s' \"$?\" >\"$status_file\"\n) | head -c ${MAX_DIFF_BYTES + 1}\nstatus=$(cat \"$status_file\")\ncase \"$status\" in\n 0|141) exit 0 ;;\n 1) [ \"$allow_exit_one\" = \"1\" ] && exit 0 ;;\nesac\ncat \"$stderr_file\" >&2\nexit \"\\${status:-1}\"\n`;\nconst TEXT_DECODER = new TextDecoder('utf-8', { fatal: true });\nconst APPROVED_RENDERED_ROOTS = new Set(['.artifacts']);\n\n/** Erase a route handler's path-parameterized context to a plain `Context`. */\nfunction loose(c: unknown): Context {\n return c as Context;\n}\n\n/** Resolve the browsable root, defaulting to the user's home directory. */\nexport function resolveFsRoot(root?: string): string {\n return resolve(root && root.trim() ? root : homedir());\n}\n\n/** True when `candidate` is `root` or nested under it. */\nfunction isWithinRoot(candidate: string, root: string): boolean {\n if (candidate === root) return true;\n const rootWithSep = root.endsWith(sep) ? root : root + sep;\n return candidate.startsWith(rootWithSep);\n}\n\nasync function realOrResolved(path: string): Promise<string> {\n try {\n return await realpath(path);\n } catch {\n return path;\n }\n}\n\n/**\n * Resolve a path's real location (following symlinks) and confirm it stays\n * within `root`. Returns the real path when confined, or `null` when it escapes\n * the root or does not exist. Used so a symlink inside the root that points\n * outside it cannot be browsed or selected.\n */\nasync function realPathWithinRoot(candidate: string, root: string): Promise<string | null> {\n try {\n const real = await realpath(candidate);\n return isWithinRoot(real, root) ? real : null;\n } catch {\n return null;\n }\n}\n\nfunction assertRelativePath(path: string, label: string): string {\n const trimmed = path.trim();\n if (!trimmed) throw new Error(`Missing required query param: ${label}`);\n if (isAbsolute(trimmed)) throw new Error(`${label} must be relative`);\n if (trimmed.split(/[\\\\/]+/).includes('..')) throw new Error(`${label} escapes workspace`);\n const normalized = resolve('/', trimmed).slice(1);\n if (!normalized || normalized === '..' || normalized.startsWith(`..${sep}`))\n throw new Error(`${label} escapes workspace`);\n return normalized;\n}\n\nfunction assertApprovedRenderedRoot(renderedRoot: string): string {\n const safeRoot = assertRelativePath(renderedRoot, 'root');\n if (!APPROVED_RENDERED_ROOTS.has(safeRoot)) throw new Error('Root is not approved for rendered workspace access');\n return safeRoot;\n}\n\nasync function confinedWorkspacePath(\n root: string,\n workspacePath: string,\n): Promise<{ resolvedRoot: string; workspace: string }> {\n const resolvedRoot = await realOrResolved(resolveFsRoot(root));\n const candidate = isAbsolute(workspacePath) ? resolve(workspacePath) : resolve(resolvedRoot, workspacePath);\n const workspace = await realPathWithinRoot(candidate, resolvedRoot);\n if (!workspace) throw new Error('Path is outside the browsable root');\n return { resolvedRoot, workspace };\n}\n\nasync function confinedWorkspaceRelativePath(\n root: string,\n workspacePath: string,\n relativePath: string,\n): Promise<{ workspace: string; path: string; relativePath: string }> {\n const safeRelativePath = assertRelativePath(relativePath, 'path');\n const { workspace } = await confinedWorkspacePath(root, workspacePath);\n const candidate = resolve(workspace, safeRelativePath);\n if (!isWithinRoot(candidate, workspace)) throw new Error('Path escapes workspace');\n const confinedPath = await realPathWithinRoot(candidate, workspace);\n if (!confinedPath) throw new Error('Path is outside the workspace');\n return { workspace, path: confinedPath, relativePath: safeRelativePath };\n}\n\n/**\n * List the directories inside `requestedPath`, confined to `root`. An absent or\n * out-of-root path is clamped to the root, so the worst a malicious client can\n * do is browse within the allowed root.\n */\nexport async function listDirectory(root: string, requestedPath?: string): Promise<DirectoryListing> {\n // Resolve the root through symlinks so all confinement checks compare real\n // paths; a symlink that escapes the root is then reliably detectable.\n const resolvedRoot = await realOrResolved(resolveFsRoot(root));\n\n let target = resolvedRoot;\n if (requestedPath && requestedPath.trim()) {\n const candidate = isAbsolute(requestedPath) ? resolve(requestedPath) : resolve(resolvedRoot, requestedPath);\n // Follow symlinks and re-confirm the real target stays within the root.\n target = (await realPathWithinRoot(candidate, resolvedRoot)) ?? resolvedRoot;\n }\n\n // Confirm the target is a real directory; fall back to root otherwise.\n try {\n const info = await stat(target);\n if (!info.isDirectory()) target = resolvedRoot;\n } catch {\n target = resolvedRoot;\n }\n\n const dirents = await readdir(target, { withFileTypes: true });\n const entries: DirectoryEntry[] = [];\n for (const dirent of dirents) {\n if (dirent.name.startsWith('.')) continue; // skip dotfiles/dirs\n const entryPath = join(target, dirent.name);\n let isDir = dirent.isDirectory();\n if (dirent.isSymbolicLink()) {\n // Only surface symlinks whose real target is a directory inside the root,\n // so a link pointing outside the root can't be browsed or selected.\n const real = await realPathWithinRoot(entryPath, resolvedRoot);\n isDir = real ? (await stat(real).catch(() => null))?.isDirectory() === true : false;\n }\n if (isDir) entries.push({ name: dirent.name, path: entryPath });\n }\n entries.sort((a, b) => a.name.localeCompare(b.name));\n\n const parent = target === resolvedRoot ? null : resolve(target, '..');\n\n return { root: resolvedRoot, path: target, parent, entries };\n}\n\nasync function listRenderedEntries(rootPath: string, currentPath = rootPath): Promise<WorkspaceRenderedEntry[]> {\n const dirents = await readdir(currentPath, { withFileTypes: true });\n const entries: WorkspaceRenderedEntry[] = [];\n\n for (const dirent of dirents) {\n const entryPath = join(currentPath, dirent.name);\n const info = await lstat(entryPath);\n const relativePath = entryPath.slice(rootPath.length + 1);\n\n if (info.isDirectory()) {\n entries.push({\n name: dirent.name,\n path: relativePath,\n type: 'directory',\n size: info.size,\n updatedAt: info.mtime.toISOString(),\n });\n entries.push(...(await listRenderedEntries(rootPath, entryPath)));\n continue;\n }\n\n if (info.isFile()) {\n entries.push({\n name: dirent.name,\n path: relativePath,\n type: 'file',\n size: info.size,\n updatedAt: info.mtime.toISOString(),\n });\n }\n }\n\n return entries.sort((a, b) => a.path.localeCompare(b.path));\n}\n\nexport async function listWorkspaceRenderedPath(\n root: string,\n workspacePath: string,\n renderedRoot: string,\n): Promise<WorkspaceRenderedListing> {\n const safeRoot = assertApprovedRenderedRoot(renderedRoot);\n const { workspace } = await confinedWorkspacePath(root, workspacePath);\n const renderedPath = resolve(workspace, safeRoot);\n if (!isWithinRoot(renderedPath, workspace)) throw new Error('Root escapes workspace');\n\n const confinedRootPath = await realPathWithinRoot(renderedPath, workspace);\n if (!confinedRootPath) return { workspacePath: workspace, root: safeRoot, rootPath: renderedPath, entries: [] };\n\n const info = await stat(confinedRootPath);\n if (!info.isDirectory()) return { workspacePath: workspace, root: safeRoot, rootPath: confinedRootPath, entries: [] };\n\n return {\n workspacePath: workspace,\n root: safeRoot,\n rootPath: confinedRootPath,\n entries: await listRenderedEntries(confinedRootPath),\n };\n}\n\nexport async function readWorkspaceFile(root: string, workspacePath: string, path: string): Promise<WorkspaceFile> {\n const safePath = assertRelativePath(path, 'path');\n const relativeRoot = safePath.split('/')[0] ?? '';\n assertApprovedRenderedRoot(relativeRoot);\n const {\n workspace,\n path: confinedPath,\n relativePath,\n } = await confinedWorkspaceRelativePath(root, workspacePath, path);\n const info = await lstat(confinedPath);\n if (info.isDirectory()) throw new Error('Path is a directory');\n if (!info.isFile()) throw new Error('Unsupported file type');\n\n const bytesToRead = Math.min(info.size, MAX_TEXT_FILE_BYTES);\n const contentBuffer = Buffer.alloc(bytesToRead);\n const handle = await open(confinedPath, 'r');\n try {\n await handle.read(contentBuffer, 0, bytesToRead, 0);\n } finally {\n await handle.close();\n }\n\n try {\n const content = TEXT_DECODER.decode(contentBuffer);\n return {\n workspacePath: workspace,\n path: relativePath,\n name: relativePath.split('/').pop() ?? relativePath,\n size: info.size,\n updatedAt: info.mtime.toISOString(),\n contentType: 'text',\n content,\n truncated: info.size > MAX_TEXT_FILE_BYTES,\n };\n } catch {\n return {\n workspacePath: workspace,\n path: relativePath,\n name: relativePath.split('/').pop() ?? relativePath,\n size: info.size,\n updatedAt: info.mtime.toISOString(),\n contentType: 'unsupported',\n };\n }\n}\n\nexport async function listArtifacts(root: string, workspacePath: string): Promise<ArtifactListing> {\n const listing = await listWorkspaceRenderedPath(root, workspacePath, '.artifacts');\n return {\n rootPath: listing.workspacePath,\n artifactsPath: listing.rootPath,\n entries: listing.entries,\n };\n}\n\n// ── Session-backed workspace access ──────────────────────────────────────────\n//\n// The web UI identifies a Factory session workspace by its session id (a UUID),\n// not by a server-local filesystem path — the session's files live inside the\n// session's sandbox (a remote VM on deployed factories). These helpers resolve\n// the session, enforce that the caller owns it, reattach to its sandbox, and\n// serve the approved rendered roots through `SandboxFilesystem`.\n\n/** Dependencies for resolving a `workspacePath` that is a Factory session id. */\nexport interface SessionFsDeps {\n auth: RouteAuth;\n sessions: { getBySessionId(sessionId: string): Promise<SourceControlSession | null> };\n filesystem: Pick<FilesystemStorage, 'listFiles'>;\n}\n\n/**\n * Resolve a `workspacePath` query param as a Factory session id. Returns the\n * session when one exists and the caller owns it, `null` when no session\n * matches (the caller should fall back to local-path handling), and throws\n * when a session exists but belongs to another tenant.\n */\nasync function resolveAuthorizedSession(\n c: Context,\n deps: SessionFsDeps | undefined,\n workspacePath: string,\n): Promise<SourceControlSession | null> {\n if (!deps) return null;\n const session = await deps.sessions.getBySessionId(workspacePath);\n if (!session) return null;\n if (deps.auth.enabled()) {\n await deps.auth.ensureUser(c);\n const tenant = deps.auth.tenant(c);\n if (!tenant || tenant.orgId !== session.orgId || tenant.userId !== session.userId) {\n throw new Error('Session is not available to the current user');\n }\n }\n return session;\n}\n\nexport async function listSessionFilesystemFiles(\n filesystem: Pick<FilesystemStorage, 'listFiles'>,\n session: SourceControlSession,\n threadId: string,\n): Promise<WorkspaceFilesListing> {\n const safeThreadId = threadId.trim();\n if (!safeThreadId) throw new Error('Missing required query param: threadId');\n\n // The turn-end capture no longer gates agent_end, so a reader refetching on\n // run completion could otherwise race it and serve the previous turn's\n // listing. Await the in-flight capture (bounded) before reading.\n await waitForPendingFilesystemCapture(session.sessionId);\n\n return {\n workspacePath: session.sessionId,\n threadId: safeThreadId,\n files: await filesystem.listFiles({ resourceId: session.sessionId, threadId: safeThreadId }),\n };\n}\n\ninterface SessionSandboxHandle {\n sandbox: ExecutableSandbox;\n filesystem: SandboxFilesystem;\n workdir: string;\n}\n\n/**\n * Resolve the session's sandbox from the per-process memo and wrap its\n * workdir in a `SandboxFilesystem`. Returns `null` when the session has no\n * sandbox in this process (never opened here, or evicted by retirement).\n * This is a passive read path, so it never constructs or provisions: the\n * session's files come back the next time the workspace is actually opened\n * (e.g. by sending a message).\n */\nasync function sessionSandbox(session: SourceControlSession): Promise<SessionSandboxHandle | null> {\n const entry = peekSessionSandbox(session.id);\n // An unresolved workdir means the sandbox never started in this process —\n // nothing is materialized, so there are no files to browse.\n if (!entry?.workdir) return null;\n const sandbox = requireExec(entry.sandbox);\n return {\n sandbox,\n filesystem: new SandboxFilesystem({ sandbox, workdir: entry.workdir }),\n workdir: entry.workdir,\n };\n}\n\n/** List an approved rendered root inside a Factory session's sandbox workdir. */\nexport async function listSessionRenderedPath(\n session: SourceControlSession,\n renderedRoot: string,\n): Promise<WorkspaceRenderedListing> {\n const safeRoot = assertApprovedRenderedRoot(renderedRoot);\n const handle = await sessionSandbox(session);\n const rootPath = posixPath.join(handle?.workdir ?? '', safeRoot);\n const empty: WorkspaceRenderedListing = { workspacePath: session.sessionId, root: safeRoot, rootPath, entries: [] };\n if (!handle) return empty;\n\n // One round trip: emit \"type\\tsize\\tmtime\\tpath\" per entry. `safeRoot` comes\n // from a fixed allowlist so interpolating it (quoted) is safe.\n const quotedRoot = `'${rootPath.replace(/'/g, `'\\\\''`)}'`;\n const result = await handle.sandbox.executeCommand(\n 'sh',\n [\n '-c',\n `test -d ${quotedRoot} && find ${quotedRoot} -mindepth 1 -printf '%y\\\\t%s\\\\t%T@\\\\t%p\\\\n' 2>/dev/null || true`,\n ],\n { timeout: 30_000 },\n );\n if (result.exitCode !== 0) return empty;\n\n const entries: WorkspaceRenderedEntry[] = [];\n for (const line of result.stdout.split('\\n')) {\n if (!line) continue;\n const [type, sizeStr, mtimeStr, ...pathParts] = line.split('\\t');\n const fullPath = pathParts.join('\\t');\n if (!fullPath || !fullPath.startsWith(`${rootPath}/`)) continue;\n const relativePath = fullPath.slice(rootPath.length + 1);\n entries.push({\n name: posixPath.basename(relativePath),\n path: relativePath,\n type: type === 'd' ? 'directory' : 'file',\n size: type === 'd' ? 0 : Number(sizeStr) || 0,\n updatedAt: new Date((Number(mtimeStr) || 0) * 1000).toISOString(),\n });\n }\n entries.sort((a, b) => a.path.localeCompare(b.path));\n\n return { workspacePath: session.sessionId, root: safeRoot, rootPath, entries };\n}\n\n/** Read a file inside a session's sandbox. Paths outside rendered roots require a persisted-file allowlist check in the route. */\nexport async function readSessionWorkspaceFile(\n session: SourceControlSession,\n path: string,\n options: { allowUnapprovedPath?: boolean } = {},\n): Promise<WorkspaceFile> {\n const safePath = assertRelativePath(path, 'path');\n if (!options.allowUnapprovedPath) assertApprovedRenderedRoot(safePath.split('/')[0] ?? '');\n\n const handle = await sessionSandbox(session);\n if (!handle) throw new Error('Session workspace is not available');\n const { filesystem } = handle;\n const info = await filesystem.stat(safePath);\n if (info.type === 'directory') throw new Error('Path is a directory');\n\n const buffer = (await filesystem.readFile(safePath)) as Buffer;\n const truncated = buffer.length > MAX_TEXT_FILE_BYTES;\n const base = {\n workspacePath: session.sessionId,\n path: safePath,\n name: posixPath.basename(safePath),\n size: buffer.length,\n updatedAt: info.modifiedAt.toISOString(),\n };\n try {\n const content = TEXT_DECODER.decode(truncated ? buffer.subarray(0, MAX_TEXT_FILE_BYTES) : buffer);\n return { ...base, contentType: 'text', content, truncated };\n } catch {\n return { ...base, contentType: 'unsupported' };\n }\n}\n\nfunction changeStatus(code: string): WorkspaceChangeStatus {\n if (code === '??') return 'untracked';\n if (code.includes('U') || code === 'AA' || code === 'DD') return 'conflicted';\n if (code.includes('R')) return 'renamed';\n if (code.includes('C')) return 'copied';\n if (code.includes('D')) return 'deleted';\n if (code.includes('A')) return 'added';\n return 'modified';\n}\n\nexport function parseWorkspaceChanges(output: string): WorkspaceChange[] {\n const records = output.split('\\0');\n const changes: WorkspaceChange[] = [];\n\n for (let index = 0; index < records.length; index += 1) {\n const record = records[index];\n if (!record || record.length < 4) continue;\n const code = record.slice(0, 2);\n const path = record.slice(3);\n const status = changeStatus(code);\n if (status === 'renamed' || status === 'copied') {\n const previousPath = records[index + 1];\n if (previousPath) index += 1;\n changes.push({ path, previousPath: previousPath || undefined, status });\n continue;\n }\n changes.push({ path, status });\n }\n\n return changes.toSorted((a, b) => a.path.localeCompare(b.path));\n}\n\nexport function parseWorkspaceChangeStats(output: string) {\n const records = output.split('\\0');\n const stats = new Map<string, { additions?: number; deletions?: number; binary?: boolean }>();\n\n for (let index = 0; index < records.length; index += 1) {\n const record = records[index];\n if (!record) continue;\n const firstTab = record.indexOf('\\t');\n const secondTab = record.indexOf('\\t', firstTab + 1);\n if (firstTab < 0 || secondTab < 0) continue;\n\n const additionsText = record.slice(0, firstTab);\n const deletionsText = record.slice(firstTab + 1, secondTab);\n let path = record.slice(secondTab + 1);\n if (!path) {\n const renamedPath = records[index + 2];\n if (!renamedPath) continue;\n path = renamedPath;\n index += 2;\n }\n if (path.startsWith('./')) path = path.slice(2);\n\n if (additionsText === '-' || deletionsText === '-') {\n stats.set(path, { binary: true });\n continue;\n }\n\n const additions = Number(additionsText);\n const deletions = Number(deletionsText);\n if (Number.isFinite(additions) && Number.isFinite(deletions)) stats.set(path, { additions, deletions });\n }\n\n return stats;\n}\n\nfunction unavailableWorkspaceChanges(workspacePath: string): WorkspaceChanges {\n return { workspacePath, available: false, changes: [] };\n}\n\nexport async function listSessionWorkspaceChanges(session: SourceControlSession): Promise<WorkspaceChanges> {\n const handle = await sessionSandbox(session);\n if (!handle) return unavailableWorkspaceChanges(session.sessionId);\n\n const [statusResult, statsResult] = await Promise.all([\n handle.sandbox.executeCommand(\n 'git',\n ['-C', handle.workdir, 'status', '--porcelain=v1', '-z', '--untracked-files=all'],\n { timeout: 30_000 },\n ),\n handle.sandbox.executeCommand('sh', ['-c', WORKSPACE_NUMSTAT_SCRIPT, 'mastracode-numstat', handle.workdir], {\n timeout: 30_000,\n }),\n ]);\n if (statusResult.exitCode !== 0) return unavailableWorkspaceChanges(session.sessionId);\n\n const changes = parseWorkspaceChanges(statusResult.stdout);\n if (statsResult.exitCode !== 0) {\n return { workspacePath: session.sessionId, available: true, changes };\n }\n\n const stats = parseWorkspaceChangeStats(statsResult.stdout);\n let additions = 0;\n let deletions = 0;\n const changesWithStats = changes.map(change => {\n const changeStats = stats.get(change.path);\n additions += changeStats?.additions ?? 0;\n deletions += changeStats?.deletions ?? 0;\n return { ...change, ...changeStats };\n });\n\n return { workspacePath: session.sessionId, available: true, changes: changesWithStats, additions, deletions };\n}\n\nasync function executeBoundedGitDiff(sandbox: ExecutableSandbox, args: string[], allowExitOne = false) {\n return sandbox.executeCommand(\n 'sh',\n ['-c', BOUNDED_GIT_DIFF_SCRIPT, 'mastracode-diff', allowExitOne ? '1' : '0', ...args],\n { timeout: 30_000 },\n );\n}\n\nfunction truncatePatch(patchBuffer: Buffer): string {\n const patch = patchBuffer.subarray(0, MAX_DIFF_BYTES).toString('utf8');\n const lastNewline = patch.lastIndexOf('\\n');\n if (lastNewline >= 0) return patch.slice(0, lastNewline + 1);\n return patch.replace(/\\uFFFD+$/, '');\n}\n\nexport async function readSessionWorkspaceDiff(\n session: SourceControlSession,\n path: string,\n previousPath?: string,\n): Promise<WorkspaceDiff> {\n const safePath = assertRelativePath(path, 'path');\n const safePreviousPath = previousPath ? assertRelativePath(previousPath, 'previousPath') : undefined;\n const handle = await sessionSandbox(session);\n if (!handle) throw new Error('Session workspace is not available');\n\n const pathspecs = safePreviousPath ? [safePreviousPath, safePath] : [safePath];\n let result = await executeBoundedGitDiff(handle.sandbox, [\n '--literal-pathspecs',\n '-C',\n handle.workdir,\n 'diff',\n '--find-renames',\n '--no-ext-diff',\n '--no-color',\n '--unified=3',\n 'HEAD',\n '--',\n ...pathspecs,\n ]);\n if (result.exitCode !== 0) throw new Error(result.stderr || 'Unable to read workspace diff');\n\n if (!result.stdout) {\n const untracked = await handle.sandbox.executeCommand(\n 'git',\n ['--literal-pathspecs', '-C', handle.workdir, 'ls-files', '--others', '--exclude-standard', '--', safePath],\n { timeout: 30_000 },\n );\n if (untracked.exitCode === 0 && untracked.stdout.trim()) {\n result = await executeBoundedGitDiff(\n handle.sandbox,\n [\n '-C',\n handle.workdir,\n 'diff',\n '--no-index',\n '--no-ext-diff',\n '--no-color',\n '--unified=3',\n '--',\n '/dev/null',\n safePath,\n ],\n true,\n );\n if (result.exitCode !== 0 && result.exitCode !== 1) {\n throw new Error(result.stderr || 'Unable to read workspace diff');\n }\n }\n }\n\n const patchBuffer = Buffer.from(result.stdout);\n const truncated = patchBuffer.length > MAX_DIFF_BYTES;\n return {\n workspacePath: session.sessionId,\n path: safePath,\n patch: truncated ? truncatePatch(patchBuffer) : result.stdout,\n truncated,\n };\n}\n\nexport interface ResolvedCodebase {\n /**\n * The resourceId the TUI would use for this path — derived identically so a\n * project opened in the terminal and in the web app resolve to the SAME\n * session (and therefore the same threads).\n */\n resourceId: string;\n name: string;\n rootPath: string;\n gitUrl?: string;\n gitBranch?: string;\n}\n\n/**\n * Resolve a project path to the same resourceId the TUI uses. Mirrors\n * `createMastraCode`: detect the project, then apply any resourceId override\n * (MASTRA_RESOURCE_ID env var or `.mastracode/database.json`). This is the\n * shared continuity point — start in the TUI, continue on the web, same path\n * → same resourceId → same session.\n */\nexport function resolveCodebase(projectPath: string): ResolvedCodebase {\n const info = detectProject(projectPath);\n const override = getResourceIdOverride(info.rootPath);\n return {\n resourceId: override ?? info.resourceId,\n name: info.name,\n rootPath: info.rootPath,\n gitUrl: info.gitUrl,\n gitBranch: info.gitBranch,\n };\n}\n\n/**\n * Build the web filesystem routes as Mastra `apiRoutes`:\n * - `GET /web/fs/list?path=...` — browse directories (confined to root)\n * - `GET /web/codebase/resolve?path=...` — TUI-compatible codebase resourceId\n */\nexport function buildFsRoutes(options: { root?: string; sessionFs?: SessionFsDeps } = {}): ApiRoute[] {\n const root = resolveFsRoot(options.root);\n const sessionFs = options.sessionFs;\n\n return [\n registerApiRoute('/web/fs/list', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const path = c.req.query('path');\n try {\n const listing = await listDirectory(root, path);\n return c.json(listing);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return c.json({ error: message }, 500);\n }\n },\n }),\n registerApiRoute('/web/artifacts/list', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const path = c.req.query('path');\n if (!path) return c.json({ error: 'Missing required query param: path' }, 400);\n try {\n return c.json(await listArtifacts(root, path));\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const status = message === 'Path is outside the browsable root' ? 403 : 500;\n return c.json({ error: message }, status);\n }\n },\n }),\n registerApiRoute('/web/workspace/rendered/list', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const workspacePath = c.req.query('workspacePath');\n const renderedRoot = c.req.query('root');\n if (!workspacePath) return c.json({ error: 'Missing required query param: workspacePath' }, 400);\n if (!renderedRoot) return c.json({ error: 'Missing required query param: root' }, 400);\n try {\n const session = await resolveAuthorizedSession(loose(c), sessionFs, workspacePath);\n if (session && sessionFs) {\n return c.json(await listSessionRenderedPath(session, renderedRoot));\n }\n return c.json(await listWorkspaceRenderedPath(root, workspacePath, renderedRoot));\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const status =\n message.includes('outside') ||\n message.includes('relative') ||\n message.includes('escapes') ||\n message.includes('not approved') ||\n message.includes('not available')\n ? 403\n : 500;\n return c.json({ error: message }, status);\n }\n },\n }),\n registerApiRoute('/web/workspace/files', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const workspacePath = c.req.query('workspacePath');\n const threadId = c.req.query('threadId')?.trim();\n if (!workspacePath) return c.json({ error: 'Missing required query param: workspacePath' }, 400);\n if (!threadId) return c.json({ error: 'Missing required query param: threadId' }, 400);\n try {\n const session = await resolveAuthorizedSession(loose(c), sessionFs, workspacePath);\n if (!session || !sessionFs) return c.json({ error: 'Session workspace is not available' }, 403);\n return c.json(await listSessionFilesystemFiles(sessionFs.filesystem, session, threadId));\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const status = message.includes('not available') ? 403 : 500;\n return c.json({ error: message }, status);\n }\n },\n }),\n registerApiRoute('/web/workspace/changes', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const workspacePath = c.req.query('workspacePath');\n if (!workspacePath) return c.json({ error: 'Missing required query param: workspacePath' }, 400);\n try {\n const session = await resolveAuthorizedSession(loose(c), sessionFs, workspacePath);\n if (!session || !sessionFs) return c.json(unavailableWorkspaceChanges(workspacePath));\n return c.json(await listSessionWorkspaceChanges(session));\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return c.json({ error: message }, message.includes('not available') ? 403 : 500);\n }\n },\n }),\n registerApiRoute('/web/workspace/changes/diff', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const workspacePath = c.req.query('workspacePath');\n const path = c.req.query('path');\n const previousPath = c.req.query('previousPath');\n if (!workspacePath) return c.json({ error: 'Missing required query param: workspacePath' }, 400);\n if (!path) return c.json({ error: 'Missing required query param: path' }, 400);\n try {\n const session = await resolveAuthorizedSession(loose(c), sessionFs, workspacePath);\n if (!session || !sessionFs) return c.json({ error: 'Session workspace is not available' }, 403);\n return c.json(await readSessionWorkspaceDiff(session, path, previousPath));\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const status =\n message.includes('relative') || message.includes('escapes') || message.includes('not available')\n ? 403\n : 500;\n return c.json({ error: message }, status);\n }\n },\n }),\n registerApiRoute('/web/workspace/file', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const workspacePath = c.req.query('workspacePath');\n const path = c.req.query('path');\n const requestedThreadId = c.req.query('threadId');\n const threadId = requestedThreadId?.trim();\n if (!workspacePath) return c.json({ error: 'Missing required query param: workspacePath' }, 400);\n if (!path) return c.json({ error: 'Missing required query param: path' }, 400);\n if (requestedThreadId !== undefined && !threadId) {\n return c.json({ error: 'Missing required query param: threadId' }, 400);\n }\n try {\n const session = await resolveAuthorizedSession(loose(c), sessionFs, workspacePath);\n if (session && sessionFs) {\n if (threadId) {\n const safePath = assertRelativePath(path, 'path');\n const listing = await listSessionFilesystemFiles(sessionFs.filesystem, session, threadId);\n if (!listing.files.some(file => file.path === safePath)) {\n return c.json({ error: 'Path is not available for this thread' }, 404);\n }\n return c.json(await readSessionWorkspaceFile(session, safePath, { allowUnapprovedPath: true }));\n }\n return c.json(await readSessionWorkspaceFile(session, path));\n }\n if (threadId) return c.json({ error: 'Session workspace is not available' }, 403);\n return c.json(await readWorkspaceFile(root, workspacePath, path));\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const status =\n message.includes('outside') ||\n message.includes('relative') ||\n message.includes('escapes') ||\n message.includes('not approved') ||\n message.includes('not available')\n ? 403\n : message.includes('directory')\n ? 400\n : message.includes('not found')\n ? 404\n : 500;\n return c.json({ error: message }, status);\n }\n },\n }),\n registerApiRoute('/web/codebase/resolve', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const path = c.req.query('path');\n if (!path) return c.json({ error: 'Missing required query param: path' }, 400);\n // Confine resolution to the browsable root (following symlinks), so this\n // endpoint can't be used to probe arbitrary filesystem paths. The web UI\n // only ever resolves directories the user picked via the root-confined\n // browser, so legitimate requests are always within the root.\n const confined = await realPathWithinRoot(isAbsolute(path) ? resolve(path) : resolve(root, path), root);\n if (!confined) return c.json({ error: 'Path is outside the browsable root' }, 403);\n try {\n return c.json(resolveCodebase(confined));\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return c.json({ error: message }, 500);\n }\n },\n }),\n ];\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../src/routes/skills.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAIpC,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,2CAA2C,CAAC;AAC5F,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAgBnC,UAAU,0BAA0B;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;IACzB,IAAI,CAAC,EAAE,iBAAiB,GAAG,cAAc,GAAG,mBAAmB,CAAC;IAChE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAgB,SAAQ,iBAAiB;IACxD,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,IAAI,CAAC,eAAe,CAAC,eAAe,CAAC,EAAE,sBAAsB,CAAC,CAAC;IAC3E,oBAAoB,CAAC,EAAE,0BAA0B,CAAC;IAClD,wBAAwB,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,uBAAuB,CAAC,EAAE,CACxB,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,KAC1E,OAAO,CAAC,0BAA0B,CAAC,CAAC;CAC1C;AAiCD,qBAAa,WAAY,SAAQ,KAAK,CAAC,eAAe,CAAC;;IA6DrD,MAAM,IAAI,QAAQ,EAAE;CA8ErB"}
1
+ {"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../src/routes/skills.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAKpC,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,2CAA2C,CAAC;AAC5F,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAgBnC,UAAU,0BAA0B;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;IACzB,IAAI,CAAC,EAAE,iBAAiB,GAAG,cAAc,GAAG,mBAAmB,CAAC;IAChE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAgB,SAAQ,iBAAiB;IACxD,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,IAAI,CAAC,eAAe,CAAC,eAAe,CAAC,EAAE,sBAAsB,CAAC,CAAC;IAC3E,oBAAoB,CAAC,EAAE,0BAA0B,CAAC;IAClD,wBAAwB,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,uBAAuB,CAAC,EAAE,CACxB,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,KAC1E,OAAO,CAAC,0BAA0B,CAAC,CAAC;CAC1C;AAiCD,qBAAa,WAAY,SAAQ,KAAK,CAAC,eAAe,CAAC;;IAoErD,MAAM,IAAI,QAAQ,EAAE;CA8ErB"}
@@ -1,3 +1,4 @@
1
+ import { peekSessionSandbox } from "../sandbox/session-sandbox.js";
1
2
  import { Route } from "./route.js";
2
3
  import { listFactorySkills } from "../skills/catalog.js";
3
4
  import { SkillInvocationError, resolveSkillInvocation } from "../skills/service.js";
@@ -91,10 +92,12 @@ var SkillRoutes = class extends Route {
91
92
  code: "session_forbidden",
92
93
  message: "Session access denied."
93
94
  };
94
- return await storage.worktrees.findByPath({
95
+ return (await storage.sessions.list({
95
96
  projectRepositoryId: address.projectRepositoryId,
96
- userId: tenant.userId,
97
- worktreePath: address.scope
97
+ viewerUserId: tenant.userId
98
+ })).some((row) => {
99
+ const liveWorkdir = peekSessionSandbox(row.id)?.workdir;
100
+ return liveWorkdir !== void 0 && liveWorkdir === address.scope;
98
101
  }) ? { allowed: true } : {
99
102
  allowed: false,
100
103
  status: 403,
@@ -1 +1 @@
1
- {"version":3,"file":"skills.js","names":["#authorizeSessionAddress"],"sources":["../../src/routes/skills.ts"],"sourcesContent":["import type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentController } from '@mastra/core/agent-controller';\nimport type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\nimport { listFactorySkills } from '../skills/catalog.js';\nimport { resolveSkillInvocation, SkillInvocationError } from '../skills/service.js';\nimport type { SourceControlStorageHandle } from '../storage/domains/source-control/base.js';\nimport type { RouteDependencies } from './route.js';\nimport { Route } from './route.js';\n\nconst MAX_RESOURCE_ID_LENGTH = 512;\nconst MAX_SCOPE_LENGTH = 2048;\nconst MAX_ARGUMENTS_LENGTH = 16_384;\nconst SKILL_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n\ninterface SkillInvocationBody {\n resourceId: string;\n projectRepositoryId?: string;\n scope?: string;\n name: string;\n arguments?: string;\n}\n\ninterface SessionAuthorizationResult {\n allowed: boolean;\n status?: 400 | 401 | 403;\n code?: 'invalid_request' | 'unauthorized' | 'session_forbidden';\n message?: string;\n}\n\nexport interface SkillRoutesDeps extends RouteDependencies {\n controllerId: string;\n controller: Pick<AgentController<MastraCodeState>, 'getSessionByResource'>;\n sourceControlStorage?: SourceControlStorageHandle;\n ensureSourceControlReady?: () => Promise<void>;\n authorizeSessionAddress?: (\n context: Context,\n address: { resourceId: string; projectRepositoryId?: string; scope?: string },\n ) => Promise<SessionAuthorizationResult>;\n}\n\nfunction loose(context: unknown): Context {\n return context as Context;\n}\n\nfunction parseBody(value: unknown): SkillInvocationBody | undefined {\n if (!value || typeof value !== 'object') return undefined;\n const input = value as Record<string, unknown>;\n if (typeof input.resourceId !== 'string' || input.resourceId.length === 0) return undefined;\n if (input.resourceId.length > MAX_RESOURCE_ID_LENGTH) return undefined;\n if (\n input.projectRepositoryId !== undefined &&\n (typeof input.projectRepositoryId !== 'string' || !UUID_RE.test(input.projectRepositoryId))\n ) {\n return undefined;\n }\n if (input.scope !== undefined && (typeof input.scope !== 'string' || input.scope.length > MAX_SCOPE_LENGTH)) {\n return undefined;\n }\n if (typeof input.name !== 'string' || input.name.length > 64 || !SKILL_NAME_RE.test(input.name)) return undefined;\n if (input.arguments !== undefined) {\n if (typeof input.arguments !== 'string' || input.arguments.length > MAX_ARGUMENTS_LENGTH) return undefined;\n }\n return {\n resourceId: input.resourceId,\n ...(input.projectRepositoryId ? { projectRepositoryId: input.projectRepositoryId } : {}),\n ...(input.scope ? { scope: input.scope } : {}),\n name: input.name,\n ...(input.arguments !== undefined ? { arguments: input.arguments } : {}),\n };\n}\n\nexport class SkillRoutes extends Route<SkillRoutesDeps> {\n async #authorizeSessionAddress(\n context: Context,\n address: { resourceId: string; projectRepositoryId?: string; scope?: string },\n ): Promise<SessionAuthorizationResult> {\n const { auth, sourceControlStorage: storage, ensureSourceControlReady } = this.deps;\n if (!auth.enabled()) return { allowed: true };\n\n await auth.ensureUser(context);\n const tenant = auth.tenant(context);\n if (!tenant) {\n return { allowed: false, status: 401, code: 'unauthorized', message: 'Authentication required.' };\n }\n\n // Personal sessions are keyed by the authenticated WorkOS user id. Their\n // scope is a client-managed local/user-session worktree and needs no app DB.\n if (address.resourceId === tenant.userId) return { allowed: true };\n\n // Factory sessions are keyed by factoryProjectId and explicitly identify the\n // linked repository whose user-owned worktree supplies the session scope.\n if (\n !UUID_RE.test(address.resourceId) ||\n !address.projectRepositoryId ||\n !UUID_RE.test(address.projectRepositoryId)\n ) {\n return { allowed: false, status: 400, code: 'invalid_request', message: 'Invalid skill invocation request.' };\n }\n if (!tenant.orgId || !address.scope) {\n return { allowed: false, status: 403, code: 'session_forbidden', message: 'Session access denied.' };\n }\n if (!storage) {\n return { allowed: false, status: 403, code: 'session_forbidden', message: 'Session access denied.' };\n }\n if (ensureSourceControlReady) {\n try {\n await ensureSourceControlReady();\n } catch {\n return { allowed: false, status: 403, code: 'session_forbidden', message: 'Session access denied.' };\n }\n }\n const projectRepository = await storage.projectRepositories.get({\n orgId: tenant.orgId,\n id: address.projectRepositoryId,\n });\n if (!projectRepository) {\n return { allowed: false, status: 403, code: 'session_forbidden', message: 'Session access denied.' };\n }\n const connection = await storage.connections.get({ orgId: tenant.orgId, id: projectRepository.connectionId });\n if (!connection || connection.factoryProjectId !== address.resourceId) {\n return { allowed: false, status: 403, code: 'session_forbidden', message: 'Session access denied.' };\n }\n const worktree = await storage.worktrees.findByPath({\n projectRepositoryId: address.projectRepositoryId,\n userId: tenant.userId,\n worktreePath: address.scope,\n });\n return worktree\n ? { allowed: true }\n : { allowed: false, status: 403, code: 'session_forbidden', message: 'Session access denied.' };\n }\n\n routes(): ApiRoute[] {\n const { controllerId, controller, authorizeSessionAddress: customAuthorize } = this.deps;\n const authorize =\n customAuthorize ??\n ((context: Context, address: { resourceId: string; projectRepositoryId?: string; scope?: string }) =>\n this.#authorizeSessionAddress(context, address));\n const handleSkillRequest = async (context: unknown, dispatch: boolean) => {\n const c = loose(context);\n if (c.req.param('controllerId') !== controllerId) {\n return c.json({ error: 'controller_not_found', message: 'Agent controller not found.' }, 404);\n }\n\n let rawBody: unknown;\n try {\n rawBody = await c.req.json();\n } catch {\n return c.json({ error: 'invalid_request', message: 'Invalid JSON body.' }, 400);\n }\n const body = parseBody(rawBody);\n if (!body) {\n return c.json({ error: 'invalid_request', message: 'Invalid skill invocation request.' }, 400);\n }\n\n const authorization = await authorize(c, {\n resourceId: body.resourceId,\n projectRepositoryId: body.projectRepositoryId,\n scope: body.scope,\n });\n if (!authorization.allowed) {\n return c.json({ error: authorization.code, message: authorization.message }, authorization.status ?? 403);\n }\n\n try {\n const resolved = await resolveSkillInvocation(controller, body);\n if (dispatch) {\n void resolved.session.sendMessage({ content: resolved.message }).catch((error: unknown) => {\n console.error('Workspace skill dispatch failed after acceptance', error);\n });\n }\n return c.json({ ok: true, skill: resolved.skillName, message: resolved.message });\n } catch (error) {\n if (error instanceof SkillInvocationError) {\n return c.json({ error: error.code, message: error.message }, 404);\n }\n throw error;\n }\n };\n\n const handleFactorySkillsList = async (context: unknown) => {\n const c = loose(context);\n const { auth } = this.deps;\n if (auth.enabled()) {\n await auth.ensureUser(c);\n if (!auth.tenant(c)) {\n return c.json({ error: 'unauthorized', message: 'Authentication required.' }, 401);\n }\n }\n return c.json({ skills: await listFactorySkills() });\n };\n\n return [\n registerApiRoute('/web/factory/skills', {\n method: 'GET',\n requiresAuth: false,\n handler: context => handleFactorySkillsList(context),\n }),\n registerApiRoute('/web/agent-controller/:controllerId/skills/prepare', {\n method: 'POST',\n requiresAuth: false,\n handler: context => handleSkillRequest(context, false),\n }),\n registerApiRoute('/web/agent-controller/:controllerId/skills/invoke', {\n method: 'POST',\n requiresAuth: false,\n handler: context => handleSkillRequest(context, true),\n }),\n ];\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"skills.js","names":["#authorizeSessionAddress"],"sources":["../../src/routes/skills.ts"],"sourcesContent":["import type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentController } from '@mastra/core/agent-controller';\nimport type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\nimport { peekSessionSandbox } from '../sandbox/session-sandbox.js';\nimport { listFactorySkills } from '../skills/catalog.js';\nimport { resolveSkillInvocation, SkillInvocationError } from '../skills/service.js';\nimport type { SourceControlStorageHandle } from '../storage/domains/source-control/base.js';\nimport type { RouteDependencies } from './route.js';\nimport { Route } from './route.js';\n\nconst MAX_RESOURCE_ID_LENGTH = 512;\nconst MAX_SCOPE_LENGTH = 2048;\nconst MAX_ARGUMENTS_LENGTH = 16_384;\nconst SKILL_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n\ninterface SkillInvocationBody {\n resourceId: string;\n projectRepositoryId?: string;\n scope?: string;\n name: string;\n arguments?: string;\n}\n\ninterface SessionAuthorizationResult {\n allowed: boolean;\n status?: 400 | 401 | 403;\n code?: 'invalid_request' | 'unauthorized' | 'session_forbidden';\n message?: string;\n}\n\nexport interface SkillRoutesDeps extends RouteDependencies {\n controllerId: string;\n controller: Pick<AgentController<MastraCodeState>, 'getSessionByResource'>;\n sourceControlStorage?: SourceControlStorageHandle;\n ensureSourceControlReady?: () => Promise<void>;\n authorizeSessionAddress?: (\n context: Context,\n address: { resourceId: string; projectRepositoryId?: string; scope?: string },\n ) => Promise<SessionAuthorizationResult>;\n}\n\nfunction loose(context: unknown): Context {\n return context as Context;\n}\n\nfunction parseBody(value: unknown): SkillInvocationBody | undefined {\n if (!value || typeof value !== 'object') return undefined;\n const input = value as Record<string, unknown>;\n if (typeof input.resourceId !== 'string' || input.resourceId.length === 0) return undefined;\n if (input.resourceId.length > MAX_RESOURCE_ID_LENGTH) return undefined;\n if (\n input.projectRepositoryId !== undefined &&\n (typeof input.projectRepositoryId !== 'string' || !UUID_RE.test(input.projectRepositoryId))\n ) {\n return undefined;\n }\n if (input.scope !== undefined && (typeof input.scope !== 'string' || input.scope.length > MAX_SCOPE_LENGTH)) {\n return undefined;\n }\n if (typeof input.name !== 'string' || input.name.length > 64 || !SKILL_NAME_RE.test(input.name)) return undefined;\n if (input.arguments !== undefined) {\n if (typeof input.arguments !== 'string' || input.arguments.length > MAX_ARGUMENTS_LENGTH) return undefined;\n }\n return {\n resourceId: input.resourceId,\n ...(input.projectRepositoryId ? { projectRepositoryId: input.projectRepositoryId } : {}),\n ...(input.scope ? { scope: input.scope } : {}),\n name: input.name,\n ...(input.arguments !== undefined ? { arguments: input.arguments } : {}),\n };\n}\n\nexport class SkillRoutes extends Route<SkillRoutesDeps> {\n async #authorizeSessionAddress(\n context: Context,\n address: { resourceId: string; projectRepositoryId?: string; scope?: string },\n ): Promise<SessionAuthorizationResult> {\n const { auth, sourceControlStorage: storage, ensureSourceControlReady } = this.deps;\n if (!auth.enabled()) return { allowed: true };\n\n await auth.ensureUser(context);\n const tenant = auth.tenant(context);\n if (!tenant) {\n return { allowed: false, status: 401, code: 'unauthorized', message: 'Authentication required.' };\n }\n\n // Personal sessions are keyed by the authenticated WorkOS user id. Their\n // scope is a client-managed local/user-session worktree and needs no app DB.\n if (address.resourceId === tenant.userId) return { allowed: true };\n\n // Factory sessions are keyed by factoryProjectId and explicitly identify the\n // linked repository whose user-owned worktree supplies the session scope.\n if (\n !UUID_RE.test(address.resourceId) ||\n !address.projectRepositoryId ||\n !UUID_RE.test(address.projectRepositoryId)\n ) {\n return { allowed: false, status: 400, code: 'invalid_request', message: 'Invalid skill invocation request.' };\n }\n if (!tenant.orgId || !address.scope) {\n return { allowed: false, status: 403, code: 'session_forbidden', message: 'Session access denied.' };\n }\n if (!storage) {\n return { allowed: false, status: 403, code: 'session_forbidden', message: 'Session access denied.' };\n }\n if (ensureSourceControlReady) {\n try {\n await ensureSourceControlReady();\n } catch {\n return { allowed: false, status: 403, code: 'session_forbidden', message: 'Session access denied.' };\n }\n }\n const projectRepository = await storage.projectRepositories.get({\n orgId: tenant.orgId,\n id: address.projectRepositoryId,\n });\n if (!projectRepository) {\n return { allowed: false, status: 403, code: 'session_forbidden', message: 'Session access denied.' };\n }\n const connection = await storage.connections.get({ orgId: tenant.orgId, id: projectRepository.connectionId });\n if (!connection || connection.factoryProjectId !== address.resourceId) {\n return { allowed: false, status: 403, code: 'session_forbidden', message: 'Session access denied.' };\n }\n // The scope must be the live workdir of a session the viewer can see under\n // this repository. The live memoized sandbox entry is the only truth for\n // workdirs (persisted columns are observability); no running/memoized\n // sandbox at that path means no grant (fail closed).\n const sessions = await storage.sessions.list({\n projectRepositoryId: address.projectRepositoryId,\n viewerUserId: tenant.userId,\n });\n const scopeIsLiveSessionWorkdir = sessions.some(row => {\n const liveWorkdir = peekSessionSandbox(row.id)?.workdir;\n return liveWorkdir !== undefined && liveWorkdir === address.scope;\n });\n return scopeIsLiveSessionWorkdir\n ? { allowed: true }\n : { allowed: false, status: 403, code: 'session_forbidden', message: 'Session access denied.' };\n }\n\n routes(): ApiRoute[] {\n const { controllerId, controller, authorizeSessionAddress: customAuthorize } = this.deps;\n const authorize =\n customAuthorize ??\n ((context: Context, address: { resourceId: string; projectRepositoryId?: string; scope?: string }) =>\n this.#authorizeSessionAddress(context, address));\n const handleSkillRequest = async (context: unknown, dispatch: boolean) => {\n const c = loose(context);\n if (c.req.param('controllerId') !== controllerId) {\n return c.json({ error: 'controller_not_found', message: 'Agent controller not found.' }, 404);\n }\n\n let rawBody: unknown;\n try {\n rawBody = await c.req.json();\n } catch {\n return c.json({ error: 'invalid_request', message: 'Invalid JSON body.' }, 400);\n }\n const body = parseBody(rawBody);\n if (!body) {\n return c.json({ error: 'invalid_request', message: 'Invalid skill invocation request.' }, 400);\n }\n\n const authorization = await authorize(c, {\n resourceId: body.resourceId,\n projectRepositoryId: body.projectRepositoryId,\n scope: body.scope,\n });\n if (!authorization.allowed) {\n return c.json({ error: authorization.code, message: authorization.message }, authorization.status ?? 403);\n }\n\n try {\n const resolved = await resolveSkillInvocation(controller, body);\n if (dispatch) {\n void resolved.session.sendMessage({ content: resolved.message }).catch((error: unknown) => {\n console.error('Workspace skill dispatch failed after acceptance', error);\n });\n }\n return c.json({ ok: true, skill: resolved.skillName, message: resolved.message });\n } catch (error) {\n if (error instanceof SkillInvocationError) {\n return c.json({ error: error.code, message: error.message }, 404);\n }\n throw error;\n }\n };\n\n const handleFactorySkillsList = async (context: unknown) => {\n const c = loose(context);\n const { auth } = this.deps;\n if (auth.enabled()) {\n await auth.ensureUser(c);\n if (!auth.tenant(c)) {\n return c.json({ error: 'unauthorized', message: 'Authentication required.' }, 401);\n }\n }\n return c.json({ skills: await listFactorySkills() });\n };\n\n return [\n registerApiRoute('/web/factory/skills', {\n method: 'GET',\n requiresAuth: false,\n handler: context => handleFactorySkillsList(context),\n }),\n registerApiRoute('/web/agent-controller/:controllerId/skills/prepare', {\n method: 'POST',\n requiresAuth: false,\n handler: context => handleSkillRequest(context, false),\n }),\n registerApiRoute('/web/agent-controller/:controllerId/skills/invoke', {\n method: 'POST',\n requiresAuth: false,\n handler: context => handleSkillRequest(context, true),\n }),\n ];\n }\n}\n"],"mappings":"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"}
@@ -9,8 +9,7 @@ import type { FactoryBindingPreparationInput } from '../rules/dispatcher.js';
9
9
  import { FactoryStartCoordinator } from '../rules/start-coordinator.js';
10
10
  import { FactoryTransitionService } from '../rules/transition-service.js';
11
11
  import type { FactoryRules } from '../rules/types.js';
12
- import type { BaseCheckpointTriggers } from '../sandbox/base-checkpoint-triggers.js';
13
- import type { SandboxFleet } from '../sandbox/fleet.js';
12
+ import type { MastraFactorySandboxConfig } from '../sandbox/session-sandbox.js';
14
13
  import type { StateSigner } from '../state-signing.js';
15
14
  import type { AuditEmitter } from '../storage/domains/audit/domain.js';
16
15
  import type { ChannelIdentityStorage } from '../storage/domains/channel-identity/base.js';
@@ -44,10 +43,8 @@ export interface FactoryApiRoutesDeps {
44
43
  fsRoot?: string;
45
44
  publicOrigin: string;
46
45
  stateSigner?: StateSigner;
47
- /** Sandbox fleet constructed by the factory (disabled when no machine). */
48
- fleet: SandboxFleet;
49
- /** Base-checkpoint trigger surface, when the factory constructed one. */
50
- baseCheckpoints?: BaseCheckpointTriggers;
46
+ /** Sandbox surface (enablement, provider label, create callback). */
47
+ sandbox?: MastraFactorySandboxConfig;
51
48
  /** Root factory storage backend (distributed locks, app-db diagnostics). */
52
49
  factoryStorage?: FactoryStorage;
53
50
  integrationStorage: IntegrationStorage;
@@ -93,7 +90,7 @@ export declare function prepareFactoryRuleBinding(github: GithubIntegration, coo
93
90
  * `assembleFactoryApiRoutes` uses it per registration, and `MastraFactory` uses it
94
91
  * when collecting integration workers at finalize.
95
92
  */
96
- export declare function buildIntegrationContext(deps: Pick<FactoryApiRoutesDeps, 'controller' | 'publicOrigin' | 'auth' | 'users' | 'fleet' | 'factoryStorage' | 'integrationStorage' | 'sourceControlStorage'> & {
93
+ export declare function buildIntegrationContext(deps: Pick<FactoryApiRoutesDeps, 'controller' | 'publicOrigin' | 'auth' | 'sandbox' | 'users' | 'factoryStorage' | 'integrationStorage' | 'sourceControlStorage'> & {
97
94
  stateSigner: StateSigner;
98
95
  emitAudit?: AuditEmitter['emit'];
99
96
  rules: FactoryRules;
@@ -105,8 +102,6 @@ export declare function buildIntegrationContext(deps: Pick<FactoryApiRoutesDeps,
105
102
  * `routes()`, `channels()`, and `workers()` all see the same context shape.
106
103
  */
107
104
  sourceControlOwnerId?: string;
108
- /** Base-checkpoint trigger surface, when the factory constructed one. */
109
- baseCheckpoints?: BaseCheckpointTriggers;
110
105
  }, integrationId: string): IntegrationContext;
111
106
  /**
112
107
  * Assemble the custom `/web/*` API routes as Mastra `server.apiRoutes`:
@@ -1 +1 @@
1
- {"version":3,"file":"surface.d.ts","sourceRoot":"","sources":["../../src/routes/surface.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"surface.d.ts","sourceRoot":"","sources":["../../src/routes/surface.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
- import { MaterializeError } from "../integrations/github/sandbox.js";
2
1
  import { factoryRuleStage } from "../rules/types.js";
3
2
  import { getGithubFeatureDiagnostics } from "../integrations/github/config.js";
3
+ import { MaterializeError } from "../integrations/github/sandbox.js";
4
4
  import { invalidateCustomProvidersSnapshots } from "./custom-provider-source.js";
5
5
  import { FactoryDispatchError } from "../rules/dispatch-errors.js";
6
6
  import { FactorySourceSessionResolutionError, ensureFactorySourceSession, resolveFactoryDefaultModelId } from "../session/factory-session.js";
@@ -134,9 +134,8 @@ async function prepareFactoryRuleBinding(github, coordinator, projects, input) {
134
134
  function buildIntegrationContext(deps, integrationId) {
135
135
  return {
136
136
  auth: deps.auth,
137
+ sandbox: deps.sandbox,
137
138
  ...deps.users ? { users: deps.users } : {},
138
- fleet: deps.fleet,
139
- ...deps.baseCheckpoints ? { baseCheckpoints: deps.baseCheckpoints } : {},
140
139
  factoryStorage: deps.factoryStorage,
141
140
  baseUrl: deps.publicOrigin,
142
141
  controller: deps.controller,
@@ -178,7 +177,7 @@ function disabledIntegrationStatusRoutes(deps, id, configured = false) {
178
177
  auth: deps.auth,
179
178
  appDbConfigured: deps.factoryStorage !== void 0,
180
179
  stateSigner: deps.stateSigner,
181
- fleet: deps.fleet
180
+ sandbox: deps.sandbox
182
181
  })
183
182
  })
184
183
  })];
@@ -265,7 +264,6 @@ function assembleFactoryApiRoutes(deps) {
265
264
  root: deps.fsRoot,
266
265
  sessionFs: {
267
266
  auth: deps.auth,
268
- fleet: deps.fleet,
269
267
  sessions: deps.sourceControlStorage.forIntegration("github").sessions,
270
268
  filesystem: deps.domains.filesystem
271
269
  }