@tanstack/ai-sandbox 0.2.4 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/dist/esm/agents-file.js +53 -34
  2. package/dist/esm/agents-file.js.map +1 -1
  3. package/dist/esm/align.d.ts +121 -0
  4. package/dist/esm/align.js +197 -0
  5. package/dist/esm/align.js.map +1 -0
  6. package/dist/esm/approvals.js +63 -29
  7. package/dist/esm/approvals.js.map +1 -1
  8. package/dist/esm/attach-preflight.d.ts +85 -0
  9. package/dist/esm/attach-preflight.js +189 -0
  10. package/dist/esm/attach-preflight.js.map +1 -0
  11. package/dist/esm/bootstrap.js +103 -117
  12. package/dist/esm/bootstrap.js.map +1 -1
  13. package/dist/esm/bridge-events.js +96 -71
  14. package/dist/esm/bridge-events.js.map +1 -1
  15. package/dist/esm/capabilities.d.ts +0 -5
  16. package/dist/esm/capabilities.js +32 -28
  17. package/dist/esm/capabilities.js.map +1 -1
  18. package/dist/esm/chunk-identity.d.ts +52 -0
  19. package/dist/esm/chunk-identity.js +102 -0
  20. package/dist/esm/chunk-identity.js.map +1 -0
  21. package/dist/esm/claim.d.ts +187 -0
  22. package/dist/esm/claim.js +349 -0
  23. package/dist/esm/claim.js.map +1 -0
  24. package/dist/esm/contracts.d.ts +13 -0
  25. package/dist/esm/driver.d.ts +83 -0
  26. package/dist/esm/driver.js +138 -0
  27. package/dist/esm/driver.js.map +1 -0
  28. package/dist/esm/durability.d.ts +263 -0
  29. package/dist/esm/durability.js +230 -0
  30. package/dist/esm/durability.js.map +1 -0
  31. package/dist/esm/errors.js +28 -24
  32. package/dist/esm/errors.js.map +1 -1
  33. package/dist/esm/file-diff.js +151 -135
  34. package/dist/esm/file-diff.js.map +1 -1
  35. package/dist/esm/git-exec.js +51 -62
  36. package/dist/esm/git-exec.js.map +1 -1
  37. package/dist/esm/harness-cwd.js +24 -19
  38. package/dist/esm/harness-cwd.js.map +1 -1
  39. package/dist/esm/index.d.ts +30 -8
  40. package/dist/esm/index.js +23 -91
  41. package/dist/esm/instance-store.d.ts +88 -0
  42. package/dist/esm/instance-store.js +67 -0
  43. package/dist/esm/instance-store.js.map +1 -0
  44. package/dist/esm/journal-bytes.d.ts +67 -0
  45. package/dist/esm/journal-bytes.js +110 -0
  46. package/dist/esm/journal-bytes.js.map +1 -0
  47. package/dist/esm/journal-reader.d.ts +66 -0
  48. package/dist/esm/journal-reader.js +228 -0
  49. package/dist/esm/journal-reader.js.map +1 -0
  50. package/dist/esm/journal-sweep.d.ts +113 -0
  51. package/dist/esm/journal-sweep.js +309 -0
  52. package/dist/esm/journal-sweep.js.map +1 -0
  53. package/dist/esm/journal.d.ts +542 -0
  54. package/dist/esm/journal.js +679 -0
  55. package/dist/esm/journal.js.map +1 -0
  56. package/dist/esm/key.js +36 -33
  57. package/dist/esm/key.js.map +1 -1
  58. package/dist/esm/middleware.d.ts +50 -2
  59. package/dist/esm/middleware.js +335 -208
  60. package/dist/esm/middleware.js.map +1 -1
  61. package/dist/esm/ngrok.js +75 -49
  62. package/dist/esm/ngrok.js.map +1 -1
  63. package/dist/esm/policy.js +43 -34
  64. package/dist/esm/policy.js.map +1 -1
  65. package/dist/esm/projection.js +16 -8
  66. package/dist/esm/projection.js.map +1 -1
  67. package/dist/esm/reap.d.ts +238 -0
  68. package/dist/esm/reap.js +355 -0
  69. package/dist/esm/reap.js.map +1 -0
  70. package/dist/esm/reclaim.d.ts +84 -0
  71. package/dist/esm/reclaim.js +106 -0
  72. package/dist/esm/reclaim.js.map +1 -0
  73. package/dist/esm/remote-tools.js +73 -62
  74. package/dist/esm/remote-tools.js.map +1 -1
  75. package/dist/esm/run.d.ts +93 -25
  76. package/dist/esm/run.js +274 -79
  77. package/dist/esm/run.js.map +1 -1
  78. package/dist/esm/runner.d.ts +119 -2
  79. package/dist/esm/runner.js +270 -51
  80. package/dist/esm/runner.js.map +1 -1
  81. package/dist/esm/sandbox.d.ts +3 -2
  82. package/dist/esm/sandbox.js +139 -123
  83. package/dist/esm/sandbox.js.map +1 -1
  84. package/dist/esm/secrets.js +39 -47
  85. package/dist/esm/secrets.js.map +1 -1
  86. package/dist/esm/setup-plan.js +22 -14
  87. package/dist/esm/setup-plan.js.map +1 -1
  88. package/dist/esm/shell.d.ts +8 -0
  89. package/dist/esm/shell.js +197 -158
  90. package/dist/esm/shell.js.map +1 -1
  91. package/dist/esm/testkit/conformance.d.ts +16 -0
  92. package/dist/esm/testkit/conformance.js +97 -0
  93. package/dist/esm/testkit/conformance.js.map +1 -0
  94. package/dist/esm/testkit/durable-run-fields-conformance.d.ts +4 -0
  95. package/dist/esm/testkit/durable-run-fields-conformance.js +95 -0
  96. package/dist/esm/testkit/durable-run-fields-conformance.js.map +1 -0
  97. package/dist/esm/testkit/journal-conformance.d.ts +51 -0
  98. package/dist/esm/testkit/journal-conformance.js +378 -0
  99. package/dist/esm/testkit/journal-conformance.js.map +1 -0
  100. package/dist/esm/testkit/reaper-conformance.d.ts +37 -0
  101. package/dist/esm/testkit/reaper-conformance.js +847 -0
  102. package/dist/esm/testkit/reaper-conformance.js.map +1 -0
  103. package/dist/esm/testkit/shell-spawn.d.ts +2 -0
  104. package/dist/esm/testkit/shell-spawn.js +60 -0
  105. package/dist/esm/testkit/shell-spawn.js.map +1 -0
  106. package/dist/esm/testkit/takeover-conformance.d.ts +24 -0
  107. package/dist/esm/testkit/takeover-conformance.js +685 -0
  108. package/dist/esm/testkit/takeover-conformance.js.map +1 -0
  109. package/dist/esm/tool-bridge.js +227 -180
  110. package/dist/esm/tool-bridge.js.map +1 -1
  111. package/dist/esm/tool-history.d.ts +62 -0
  112. package/dist/esm/tool-history.js +171 -0
  113. package/dist/esm/tool-history.js.map +1 -0
  114. package/dist/esm/watch.js +310 -236
  115. package/dist/esm/watch.js.map +1 -1
  116. package/dist/esm/workspace.d.ts +1 -1
  117. package/dist/esm/workspace.js +49 -28
  118. package/dist/esm/workspace.js.map +1 -1
  119. package/package.json +16 -6
  120. package/skills/ai-sandbox/SKILL.md +658 -20
  121. package/src/align.ts +297 -0
  122. package/src/attach-preflight.ts +292 -0
  123. package/src/capabilities.ts +4 -13
  124. package/src/chunk-identity.ts +154 -0
  125. package/src/claim.ts +479 -0
  126. package/src/contracts.ts +13 -0
  127. package/src/driver.ts +205 -0
  128. package/src/durability.ts +380 -0
  129. package/src/index.ts +212 -27
  130. package/src/instance-store.ts +122 -0
  131. package/src/journal-bytes.ts +136 -0
  132. package/src/journal-reader.ts +359 -0
  133. package/src/journal-sweep.ts +406 -0
  134. package/src/journal.ts +875 -0
  135. package/src/middleware.ts +470 -30
  136. package/src/reap.ts +723 -0
  137. package/src/reclaim.ts +191 -0
  138. package/src/run.ts +365 -75
  139. package/src/runner.ts +347 -3
  140. package/src/sandbox.ts +38 -8
  141. package/src/shell.ts +106 -38
  142. package/src/testkit/conformance.ts +117 -0
  143. package/src/testkit/durable-run-fields-conformance.ts +147 -0
  144. package/src/testkit/journal-conformance.ts +676 -0
  145. package/src/testkit/reaper-conformance.ts +1201 -0
  146. package/src/testkit/shell-spawn.ts +67 -0
  147. package/src/testkit/takeover-conformance.ts +1040 -0
  148. package/src/tool-history.ts +245 -0
  149. package/src/workspace.ts +1 -1
  150. package/dist/esm/index.js.map +0 -1
  151. package/dist/esm/run-log.d.ts +0 -81
  152. package/dist/esm/run-log.js +0 -107
  153. package/dist/esm/run-log.js.map +0 -1
  154. package/dist/esm/store.d.ts +0 -53
  155. package/dist/esm/store.js +0 -34
  156. package/dist/esm/store.js.map +0 -1
  157. package/src/run-log.ts +0 -224
  158. package/src/store.ts +0 -83
@@ -1 +1 @@
1
- {"version":3,"file":"file-diff.js","sources":["../../src/file-diff.ts"],"sourcesContent":["import type { SandboxFileEvent, SandboxFileHookEvent } from '@tanstack/ai'\nimport type { InternalLogger } from '@tanstack/ai/adapter-internals'\nimport type { SandboxHandle } from './contracts'\n\n/** Path relative to the repo/workspace root, POSIX form. */\nfunction relTo(root: string, path: string): string {\n const prefix = root.endsWith('/') ? root : `${root}/`\n return path.startsWith(prefix) ? path.slice(prefix.length) : path\n}\n\n/**\n * POSIX single-quote escape for embedding a value in a shell command.\n */\nfunction q(value: string): string {\n return `'${value.replace(/'/g, `'\\\\''`)}'`\n}\n\n/**\n * Unified add-patch for a brand-new file, closely following the shape `git\n * diff` produces for an added file (`diff --git` header + `new file mode` +\n * `--- /dev/null` + `+++ b/<rel>`), so synthesized `create` diffs align with\n * the real `git diff` output emitted for `change` events. `rel` must be the\n * repo-root-relative POSIX path (like git's). Reproduces git's `\\ No newline\n * at end of file` marker and the header-only form for a zero-byte file, so a\n * consumer applying the patch reconstructs the file byte-for-byte. It is not\n * byte-identical to git — it omits the `index <hash>..<hash>` line and always\n * writes the `+1,N` hunk count (git omits `,1`) — but both are valid\n * unified-diff and accepted by `git apply`/`patch`.\n */\nfunction synthesizeAddPatch(rel: string, content: string): string {\n const header = `diff --git a/${rel} b/${rel}\\nnew file mode 100644\\n`\n // A zero-byte new file has no hunk in git's output — just the header.\n if (content === '') return header\n const hasFinalNewline = content.endsWith('\\n')\n const lines = content.replace(/\\n$/, '').split('\\n')\n const body = lines.map((l) => `+${l}`).join('\\n')\n return (\n header +\n `--- /dev/null\\n` +\n `+++ b/${rel}\\n` +\n `@@ -0,0 +1,${lines.length} @@\\n` +\n body +\n (hasFinalNewline ? '\\n' : '\\n\\\\\n')\n )\n}\n\n/**\n * Wrap a raw {@link SandboxFileEvent} with lazy git-backed accessors bound to\n * the live handle. `baseSha` is the session baseline (`''` when the workspace\n * isn't a git repo). Never throws — every git/fs failure falls back to `''`\n * (or a synthesized add-patch), but is logged first via `logger` so a failure\n * is observable instead of silently becoming empty data.\n */\nexport function buildFileHookEvent(\n handle: SandboxHandle,\n root: string,\n baseSha: string,\n event: SandboxFileEvent,\n logger?: InternalLogger,\n): SandboxFileHookEvent {\n const after = async (): Promise<string> => {\n if (event.type === 'delete') return ''\n try {\n return await handle.fs.read(event.path)\n } catch (error) {\n logger?.warn('sandbox after() failed to read file', {\n path: event.path,\n error,\n })\n return ''\n }\n }\n const before = async (): Promise<string> => {\n if (baseSha === '') return ''\n const rel = relTo(root, event.path)\n try {\n const res = await handle.process.exec(\n `git show ${q(baseSha)}:${q(rel)}`,\n { cwd: root },\n )\n if (res.exitCode === 0) return res.stdout\n // Non-zero exit is EXPECTED when the file didn't exist at the baseline\n // (a newly created file) — git exits 128 with \"exists on disk, but not\n // in <sha>\". Log under `sandbox` (off by default) rather than warn so a\n // create event's before() doesn't spam a warning on every new file.\n logger?.sandbox('before() git show non-zero exit', {\n path: event.path,\n exitCode: res.exitCode,\n stderr: res.stderr,\n })\n return ''\n } catch (error) {\n logger?.warn('sandbox before() git show failed', {\n path: event.path,\n error,\n })\n return ''\n }\n }\n // Empty `git diff` fallback: `git diff <sha> -- <path>` shows nothing for a\n // file git isn't tracking, so an untracked file (the common agent action —\n // and every subsequent edit to it, which arrives as a `change`, not just the\n // first `create`) yields empty stdout even though it has content. Synthesize\n // an add-patch, but ONLY when the file is genuinely untracked at the\n // baseline — an empty diff for a *tracked* file means \"identical to\n // baseline\", a real no-op that must stay empty. Presence at `baseSha`\n // distinguishes them, probed below via the `git show` EXIT CODE (NOT\n // `before()`'s `''`, which also means \"git show threw\" — see the inline note).\n // ponytail: reached only when `git diff` came back empty — the common case\n // for an untracked file (and every edit to it), rare for a tracked one. It\n // spends up to two extra subprocesses (`git check-ignore`, then `git show`)\n // plus one `after()` read per such event; both verdicts are invariant per\n // path across a run, but memoizing them would need cross-event state the\n // per-event accessor doesn't hold. Add a per-path cache in the watcher if\n // edit-burst latency matters.\n const synthesizeIfUntracked = async (rel: string): Promise<string> => {\n const content = await after()\n if (content === '') return '' // deleted / empty / unreadable — nothing to add\n // Don't expose the CONTENTS of a git-ignored file (`.env`, credentials,\n // build artifacts, …) in the diff feed. The file event still fires so\n // consumers are notified it changed — only its diff is withheld. A\n // force-added, ignored-yet-TRACKED file produces a non-empty `git diff`\n // above and never reaches here, so its real diff is unaffected.\n try {\n const ignored = await handle.process.exec(\n `git check-ignore -q -- ${q(rel)}`,\n { cwd: root },\n )\n // check-ignore: exit 0 ⇒ path is ignored; 1 ⇒ not ignored; 128 ⇒ error.\n if (ignored.exitCode === 0) {\n logger?.sandbox('sandbox diff() withheld for git-ignored file', {\n path: event.path,\n })\n return ''\n }\n if (ignored.exitCode !== 1) {\n // Not the expected \"not ignored\" (1) — a real check-ignore error (128:\n // corrupt repo, bad invocation). Same anomaly class as the throw below,\n // so `warn`. We still fall through and diff, so a broken probe never\n // silently withholds; but log it, or an error here would look exactly\n // like \"not ignored\" and could expose a would-be-withheld file's diff.\n logger?.warn('sandbox diff() git check-ignore non-zero exit', {\n path: event.path,\n exitCode: ignored.exitCode,\n stderr: ignored.stderr,\n })\n }\n } catch (error) {\n // check-ignore threw (git/exec broken) — same anomaly class as the other\n // git execs here, so `warn`. We fall through and diff as usual rather\n // than withhold, so a broken probe can't hide a legitimate diff.\n logger?.warn('sandbox diff() git check-ignore failed', {\n path: event.path,\n error,\n })\n }\n // Distinguish \"absent at the baseline (untracked)\" from \"present at the\n // baseline\" by the git-show EXIT CODE, not by before()'s `''` — which also\n // means \"git show threw\". Conflating a transient git-show failure with\n // untracked would fabricate a full-file add-patch for an unchanged tracked\n // file the agent never touched.\n try {\n const res = await handle.process.exec(\n `git show ${q(baseSha)}:${q(rel)}`,\n { cwd: root },\n )\n // exit 0 ⇒ tracked; `git diff` was already empty ⇒ identical to baseline\n // ⇒ genuine no-op.\n if (res.exitCode === 0) return ''\n // Non-zero is the EXPECTED \"absent at baseline ⇒ untracked\" case (exit\n // 128), but a genuine git-show error (bad object, corrupt repo, invalid\n // sha) also exits non-zero and would silently fabricate a full-file\n // add-patch. Log it under `sandbox` (like `before()` does) so a\n // persistent probe error is greppable, then fall back to synthesize.\n logger?.sandbox(\n 'sandbox diff() tracked-ness probe non-zero exit (treating as untracked)',\n { path: event.path, exitCode: res.exitCode, stderr: res.stderr },\n )\n return synthesizeAddPatch(rel, content)\n } catch (error) {\n // Uncertain — don't fabricate a full-file add-patch on a probe failure.\n logger?.warn('sandbox diff() tracked-ness probe failed', {\n path: event.path,\n error,\n })\n return ''\n }\n }\n const diff = async (): Promise<string> => {\n if (baseSha === '') {\n if (event.type === 'delete') return ''\n return synthesizeAddPatch(relTo(root, event.path), await after())\n }\n // Pathspec must be relative to `root` (like `before()` above) — a bare\n // leading `/` (e.g. the virtual `/workspace/x.ts`) is resolved by git\n // against the filesystem root, not the repo root, and fails with\n // \"fatal: Invalid path\" whenever the real repo root differs from\n // `/workspace` (e.g. every local-process sandbox).\n const rel = relTo(root, event.path)\n try {\n const res = await handle.process.exec(\n `git diff ${q(baseSha)} -- ${q(rel)}`,\n {\n cwd: root,\n },\n )\n if (res.exitCode !== 0) {\n logger?.warn('sandbox diff() git diff non-zero exit', {\n path: event.path,\n exitCode: res.exitCode,\n stderr: res.stderr,\n })\n return ''\n }\n if (res.stdout !== '') return res.stdout\n return synthesizeIfUntracked(rel)\n } catch (error) {\n logger?.warn('sandbox diff() git diff failed', {\n path: event.path,\n error,\n })\n return ''\n }\n }\n return { ...event, before, after, diff }\n}\n\nexport interface ResolvedFileEvents {\n enabled: boolean\n diff: boolean\n}\n\n/** Normalize the `fileEvents` option (`boolean | { diff?: boolean }`). */\nexport function resolveFileEvents(\n opt: boolean | { diff?: boolean } | undefined,\n): ResolvedFileEvents {\n if (opt === false) return { enabled: false, diff: false }\n if (opt === undefined || opt === true) return { enabled: true, diff: false }\n return { enabled: true, diff: opt.diff === true }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"file-diff.js","names":[],"sources":["../../src/file-diff.ts"],"sourcesContent":["import type { SandboxFileEvent, SandboxFileHookEvent } from '@tanstack/ai'\nimport type { InternalLogger } from '@tanstack/ai/adapter-internals'\nimport type { SandboxHandle } from './contracts'\n\n/** Path relative to the repo/workspace root, POSIX form. */\nfunction relTo(root: string, path: string): string {\n const prefix = root.endsWith('/') ? root : `${root}/`\n return path.startsWith(prefix) ? path.slice(prefix.length) : path\n}\n\n/**\n * POSIX single-quote escape for embedding a value in a shell command.\n */\nfunction q(value: string): string {\n return `'${value.replace(/'/g, `'\\\\''`)}'`\n}\n\n/**\n * Unified add-patch for a brand-new file, closely following the shape `git\n * diff` produces for an added file (`diff --git` header + `new file mode` +\n * `--- /dev/null` + `+++ b/<rel>`), so synthesized `create` diffs align with\n * the real `git diff` output emitted for `change` events. `rel` must be the\n * repo-root-relative POSIX path (like git's). Reproduces git's `\\ No newline\n * at end of file` marker and the header-only form for a zero-byte file, so a\n * consumer applying the patch reconstructs the file byte-for-byte. It is not\n * byte-identical to git — it omits the `index <hash>..<hash>` line and always\n * writes the `+1,N` hunk count (git omits `,1`) — but both are valid\n * unified-diff and accepted by `git apply`/`patch`.\n */\nfunction synthesizeAddPatch(rel: string, content: string): string {\n const header = `diff --git a/${rel} b/${rel}\\nnew file mode 100644\\n`\n // A zero-byte new file has no hunk in git's output — just the header.\n if (content === '') return header\n const hasFinalNewline = content.endsWith('\\n')\n const lines = content.replace(/\\n$/, '').split('\\n')\n const body = lines.map((l) => `+${l}`).join('\\n')\n return (\n header +\n `--- /dev/null\\n` +\n `+++ b/${rel}\\n` +\n `@@ -0,0 +1,${lines.length} @@\\n` +\n body +\n (hasFinalNewline ? '\\n' : '\\n\\\\\n')\n )\n}\n\n/**\n * Wrap a raw {@link SandboxFileEvent} with lazy git-backed accessors bound to\n * the live handle. `baseSha` is the session baseline (`''` when the workspace\n * isn't a git repo). Never throws — every git/fs failure falls back to `''`\n * (or a synthesized add-patch), but is logged first via `logger` so a failure\n * is observable instead of silently becoming empty data.\n */\nexport function buildFileHookEvent(\n handle: SandboxHandle,\n root: string,\n baseSha: string,\n event: SandboxFileEvent,\n logger?: InternalLogger,\n): SandboxFileHookEvent {\n const after = async (): Promise<string> => {\n if (event.type === 'delete') return ''\n try {\n return await handle.fs.read(event.path)\n } catch (error) {\n logger?.warn('sandbox after() failed to read file', {\n path: event.path,\n error,\n })\n return ''\n }\n }\n const before = async (): Promise<string> => {\n if (baseSha === '') return ''\n const rel = relTo(root, event.path)\n try {\n const res = await handle.process.exec(\n `git show ${q(baseSha)}:${q(rel)}`,\n { cwd: root },\n )\n if (res.exitCode === 0) return res.stdout\n // Non-zero exit is EXPECTED when the file didn't exist at the baseline\n // (a newly created file) — git exits 128 with \"exists on disk, but not\n // in <sha>\". Log under `sandbox` (off by default) rather than warn so a\n // create event's before() doesn't spam a warning on every new file.\n logger?.sandbox('before() git show non-zero exit', {\n path: event.path,\n exitCode: res.exitCode,\n stderr: res.stderr,\n })\n return ''\n } catch (error) {\n logger?.warn('sandbox before() git show failed', {\n path: event.path,\n error,\n })\n return ''\n }\n }\n // Empty `git diff` fallback: `git diff <sha> -- <path>` shows nothing for a\n // file git isn't tracking, so an untracked file (the common agent action —\n // and every subsequent edit to it, which arrives as a `change`, not just the\n // first `create`) yields empty stdout even though it has content. Synthesize\n // an add-patch, but ONLY when the file is genuinely untracked at the\n // baseline — an empty diff for a *tracked* file means \"identical to\n // baseline\", a real no-op that must stay empty. Presence at `baseSha`\n // distinguishes them, probed below via the `git show` EXIT CODE (NOT\n // `before()`'s `''`, which also means \"git show threw\" — see the inline note).\n // ponytail: reached only when `git diff` came back empty — the common case\n // for an untracked file (and every edit to it), rare for a tracked one. It\n // spends up to two extra subprocesses (`git check-ignore`, then `git show`)\n // plus one `after()` read per such event; both verdicts are invariant per\n // path across a run, but memoizing them would need cross-event state the\n // per-event accessor doesn't hold. Add a per-path cache in the watcher if\n // edit-burst latency matters.\n const synthesizeIfUntracked = async (rel: string): Promise<string> => {\n const content = await after()\n if (content === '') return '' // deleted / empty / unreadable — nothing to add\n // Don't expose the CONTENTS of a git-ignored file (`.env`, credentials,\n // build artifacts, …) in the diff feed. The file event still fires so\n // consumers are notified it changed — only its diff is withheld. A\n // force-added, ignored-yet-TRACKED file produces a non-empty `git diff`\n // above and never reaches here, so its real diff is unaffected.\n try {\n const ignored = await handle.process.exec(\n `git check-ignore -q -- ${q(rel)}`,\n { cwd: root },\n )\n // check-ignore: exit 0 ⇒ path is ignored; 1 ⇒ not ignored; 128 ⇒ error.\n if (ignored.exitCode === 0) {\n logger?.sandbox('sandbox diff() withheld for git-ignored file', {\n path: event.path,\n })\n return ''\n }\n if (ignored.exitCode !== 1) {\n // Not the expected \"not ignored\" (1) — a real check-ignore error (128:\n // corrupt repo, bad invocation). Same anomaly class as the throw below,\n // so `warn`. We still fall through and diff, so a broken probe never\n // silently withholds; but log it, or an error here would look exactly\n // like \"not ignored\" and could expose a would-be-withheld file's diff.\n logger?.warn('sandbox diff() git check-ignore non-zero exit', {\n path: event.path,\n exitCode: ignored.exitCode,\n stderr: ignored.stderr,\n })\n }\n } catch (error) {\n // check-ignore threw (git/exec broken) — same anomaly class as the other\n // git execs here, so `warn`. We fall through and diff as usual rather\n // than withhold, so a broken probe can't hide a legitimate diff.\n logger?.warn('sandbox diff() git check-ignore failed', {\n path: event.path,\n error,\n })\n }\n // Distinguish \"absent at the baseline (untracked)\" from \"present at the\n // baseline\" by the git-show EXIT CODE, not by before()'s `''` — which also\n // means \"git show threw\". Conflating a transient git-show failure with\n // untracked would fabricate a full-file add-patch for an unchanged tracked\n // file the agent never touched.\n try {\n const res = await handle.process.exec(\n `git show ${q(baseSha)}:${q(rel)}`,\n { cwd: root },\n )\n // exit 0 ⇒ tracked; `git diff` was already empty ⇒ identical to baseline\n // ⇒ genuine no-op.\n if (res.exitCode === 0) return ''\n // Non-zero is the EXPECTED \"absent at baseline ⇒ untracked\" case (exit\n // 128), but a genuine git-show error (bad object, corrupt repo, invalid\n // sha) also exits non-zero and would silently fabricate a full-file\n // add-patch. Log it under `sandbox` (like `before()` does) so a\n // persistent probe error is greppable, then fall back to synthesize.\n logger?.sandbox(\n 'sandbox diff() tracked-ness probe non-zero exit (treating as untracked)',\n { path: event.path, exitCode: res.exitCode, stderr: res.stderr },\n )\n return synthesizeAddPatch(rel, content)\n } catch (error) {\n // Uncertain — don't fabricate a full-file add-patch on a probe failure.\n logger?.warn('sandbox diff() tracked-ness probe failed', {\n path: event.path,\n error,\n })\n return ''\n }\n }\n const diff = async (): Promise<string> => {\n if (baseSha === '') {\n if (event.type === 'delete') return ''\n return synthesizeAddPatch(relTo(root, event.path), await after())\n }\n // Pathspec must be relative to `root` (like `before()` above) — a bare\n // leading `/` (e.g. the virtual `/workspace/x.ts`) is resolved by git\n // against the filesystem root, not the repo root, and fails with\n // \"fatal: Invalid path\" whenever the real repo root differs from\n // `/workspace` (e.g. every local-process sandbox).\n const rel = relTo(root, event.path)\n try {\n const res = await handle.process.exec(\n `git diff ${q(baseSha)} -- ${q(rel)}`,\n {\n cwd: root,\n },\n )\n if (res.exitCode !== 0) {\n logger?.warn('sandbox diff() git diff non-zero exit', {\n path: event.path,\n exitCode: res.exitCode,\n stderr: res.stderr,\n })\n return ''\n }\n if (res.stdout !== '') return res.stdout\n return synthesizeIfUntracked(rel)\n } catch (error) {\n logger?.warn('sandbox diff() git diff failed', {\n path: event.path,\n error,\n })\n return ''\n }\n }\n return { ...event, before, after, diff }\n}\n\nexport interface ResolvedFileEvents {\n enabled: boolean\n diff: boolean\n}\n\n/** Normalize the `fileEvents` option (`boolean | { diff?: boolean }`). */\nexport function resolveFileEvents(\n opt: boolean | { diff?: boolean } | undefined,\n): ResolvedFileEvents {\n if (opt === false) return { enabled: false, diff: false }\n if (opt === undefined || opt === true) return { enabled: true, diff: false }\n return { enabled: true, diff: opt.diff === true }\n}\n"],"mappings":"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"}
@@ -1,68 +1,57 @@
1
+ //#region src/git-exec.ts
2
+ /** POSIX single-quote escape: wrap in '…' and escape embedded quotes. */
1
3
  function q(value) {
2
- return `'${value.replace(/'/g, `'\\''`)}'`;
4
+ return `'${value.replace(/'/g, `'\\''`)}'`;
3
5
  }
6
+ /** Reject values that could be parsed as a git flag when used as a positional. */
4
7
  function assertNoLeadingDash(value, name) {
5
- if (value.startsWith("-")) {
6
- throw new Error(
7
- `git-exec: ${name} "${value}" must not begin with "-" (argument-injection guard).`
8
- );
9
- }
8
+ if (value.startsWith("-")) throw new Error(`git-exec: ${name} "${value}" must not begin with "-" (argument-injection guard).`);
10
9
  }
11
- const CREDENTIAL_HELPER = '!f() { echo "username=${GIT_ASKPASS_USER}"; echo "password=${GIT_ASKPASS_TOKEN}"; }; f';
10
+ var CREDENTIAL_HELPER = "!f() { echo \"username=${GIT_ASKPASS_USER}\"; echo \"password=${GIT_ASKPASS_TOKEN}\"; }; f";
12
11
  function createExecBackedGit(process, defaultRoot) {
13
- const at = (dir) => {
14
- const d = dir ?? defaultRoot;
15
- assertNoLeadingDash(d, "dir");
16
- return q(d);
17
- };
18
- return {
19
- clone: async ({ url, dir, ref, auth, depth }) => {
20
- assertNoLeadingDash(url, "url");
21
- const target = dir ?? defaultRoot;
22
- assertNoLeadingDash(target, "dir");
23
- if (ref !== void 0) assertNoLeadingDash(ref, "ref");
24
- const refArg = ref ? `--branch ${q(ref)} ` : "";
25
- const resolvedDepth = depth ?? 1;
26
- if (resolvedDepth !== "full" && (!Number.isInteger(resolvedDepth) || resolvedDepth <= 0)) {
27
- throw new Error('git-exec: depth must be a positive integer or "full".');
28
- }
29
- const depthArg = resolvedDepth === "full" ? "" : `--depth ${resolvedDepth} --single-branch `;
30
- if (auth?.token) {
31
- await process.exec(
32
- `git -c credential.helper=${q(CREDENTIAL_HELPER)} clone ${refArg}${depthArg}-- ${q(url)} ${q(target)}`,
33
- {
34
- // Token lives only in the child env, never in argv.
35
- env: {
36
- GIT_ASKPASS_USER: auth.username ?? "x-access-token",
37
- GIT_ASKPASS_TOKEN: auth.token,
38
- GIT_TERMINAL_PROMPT: "0"
39
- }
40
- }
41
- );
42
- return;
43
- }
44
- await process.exec(
45
- `git clone ${refArg}${depthArg}-- ${q(url)} ${q(target)}`
46
- );
47
- },
48
- status: async (dir) => (await process.exec(`git -C ${at(dir)} status --porcelain`)).stdout,
49
- add: async (paths, dir) => {
50
- paths.forEach((p, i) => assertNoLeadingDash(p, `path[${i}]`));
51
- await process.exec(`git -C ${at(dir)} add -- ${paths.map(q).join(" ")}`);
52
- },
53
- commit: async (message, dir) => {
54
- await process.exec(`git -C ${at(dir)} commit -m ${q(message)}`);
55
- },
56
- push: async (dir) => {
57
- await process.exec(`git -C ${at(dir)} push`);
58
- },
59
- pull: async (dir) => {
60
- await process.exec(`git -C ${at(dir)} pull`);
61
- },
62
- branch: async (dir) => (await process.exec(`git -C ${at(dir)} rev-parse --abbrev-ref HEAD`)).stdout.trim()
63
- };
12
+ const at = (dir) => {
13
+ const d = dir ?? defaultRoot;
14
+ assertNoLeadingDash(d, "dir");
15
+ return q(d);
16
+ };
17
+ return {
18
+ clone: async ({ url, dir, ref, auth, depth }) => {
19
+ assertNoLeadingDash(url, "url");
20
+ const target = dir ?? defaultRoot;
21
+ assertNoLeadingDash(target, "dir");
22
+ if (ref !== void 0) assertNoLeadingDash(ref, "ref");
23
+ const refArg = ref ? `--branch ${q(ref)} ` : "";
24
+ const resolvedDepth = depth ?? 1;
25
+ if (resolvedDepth !== "full" && (!Number.isInteger(resolvedDepth) || resolvedDepth <= 0)) throw new Error("git-exec: depth must be a positive integer or \"full\".");
26
+ const depthArg = resolvedDepth === "full" ? "" : `--depth ${resolvedDepth} --single-branch `;
27
+ if (auth?.token) {
28
+ await process.exec(`git -c credential.helper=${q(CREDENTIAL_HELPER)} clone ${refArg}${depthArg}-- ${q(url)} ${q(target)}`, { env: {
29
+ GIT_ASKPASS_USER: auth.username ?? "x-access-token",
30
+ GIT_ASKPASS_TOKEN: auth.token,
31
+ GIT_TERMINAL_PROMPT: "0"
32
+ } });
33
+ return;
34
+ }
35
+ await process.exec(`git clone ${refArg}${depthArg}-- ${q(url)} ${q(target)}`);
36
+ },
37
+ status: async (dir) => (await process.exec(`git -C ${at(dir)} status --porcelain`)).stdout,
38
+ add: async (paths, dir) => {
39
+ paths.forEach((p, i) => assertNoLeadingDash(p, `path[${i}]`));
40
+ await process.exec(`git -C ${at(dir)} add -- ${paths.map(q).join(" ")}`);
41
+ },
42
+ commit: async (message, dir) => {
43
+ await process.exec(`git -C ${at(dir)} commit -m ${q(message)}`);
44
+ },
45
+ push: async (dir) => {
46
+ await process.exec(`git -C ${at(dir)} push`);
47
+ },
48
+ pull: async (dir) => {
49
+ await process.exec(`git -C ${at(dir)} pull`);
50
+ },
51
+ branch: async (dir) => (await process.exec(`git -C ${at(dir)} rev-parse --abbrev-ref HEAD`)).stdout.trim()
52
+ };
64
53
  }
65
- export {
66
- createExecBackedGit
67
- };
68
- //# sourceMappingURL=git-exec.js.map
54
+ //#endregion
55
+ export { createExecBackedGit };
56
+
57
+ //# sourceMappingURL=git-exec.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"git-exec.js","sources":["../../src/git-exec.ts"],"sourcesContent":["/**\n * An exec-backed {@link SandboxGit} implementation. Providers without a native\n * git API (local-process, Docker) get a uniform `sandbox.git` by desugaring to\n * `process.exec(\"git …\")`. Providers WITH native git (Daytona, Cloudflare) may\n * supply their own implementation instead.\n *\n * Security:\n * - Every interpolated value is single-quote escaped (no shell injection).\n * - A `--` end-of-options separator precedes untrusted positionals and values\n * are rejected if they begin with `-`, so a repo URL / ref / path can't\n * smuggle a git flag (e.g. `--upload-pack=…`).\n * - Auth tokens NEVER appear in argv (they'd leak via `ps` / process logs).\n * Instead a one-shot `credential.helper` reads the token from the child\n * process ENV. The helper string is single-quoted so the OUTER shell never\n * expands the env var — only git's own helper subshell does, at use time.\n *\n * NOTE: `SandboxProcess.exec` takes a command STRING by design (the sandbox\n * runs shell commands), so we mitigate flag smuggling with `--` + validation\n * rather than an argv array.\n */\nimport type { SandboxGit, SandboxProcess } from './contracts'\n\n/** POSIX single-quote escape: wrap in '…' and escape embedded quotes. */\nfunction q(value: string): string {\n return `'${value.replace(/'/g, `'\\\\''`)}'`\n}\n\n/** Reject values that could be parsed as a git flag when used as a positional. */\nfunction assertNoLeadingDash(value: string, name: string): void {\n if (value.startsWith('-')) {\n throw new Error(\n `git-exec: ${name} \"${value}\" must not begin with \"-\" (argument-injection guard).`,\n )\n }\n}\n\n// Credential helper that prints creds read from the child ENV. Single-quoted at\n// the call site so the outer shell passes it literally; git expands the vars in\n// its own helper subshell, keeping the token out of argv.\nconst CREDENTIAL_HELPER =\n '!f() { echo \"username=${GIT_ASKPASS_USER}\"; echo \"password=${GIT_ASKPASS_TOKEN}\"; }; f'\n\nexport function createExecBackedGit(\n process: SandboxProcess,\n defaultRoot: string,\n): SandboxGit {\n const at = (dir?: string): string => {\n const d = dir ?? defaultRoot\n assertNoLeadingDash(d, 'dir')\n return q(d)\n }\n\n return {\n clone: async ({ url, dir, ref, auth, depth }) => {\n assertNoLeadingDash(url, 'url')\n const target = dir ?? defaultRoot\n assertNoLeadingDash(target, 'dir')\n if (ref !== undefined) assertNoLeadingDash(ref, 'ref')\n const refArg = ref ? `--branch ${q(ref)} ` : ''\n const resolvedDepth = depth ?? 1\n // `depth` is interpolated unquoted into the command, so validate it the\n // same way other positionals are guarded — a non-positive-integer (e.g. an\n // untyped caller passing a string) must never reach the shell.\n if (\n resolvedDepth !== 'full' &&\n (!Number.isInteger(resolvedDepth) || resolvedDepth <= 0)\n ) {\n throw new Error('git-exec: depth must be a positive integer or \"full\".')\n }\n const depthArg =\n resolvedDepth === 'full'\n ? ''\n : `--depth ${resolvedDepth} --single-branch `\n\n if (auth?.token) {\n await process.exec(\n `git -c credential.helper=${q(CREDENTIAL_HELPER)} clone ${refArg}${depthArg}-- ${q(url)} ${q(target)}`,\n {\n // Token lives only in the child env, never in argv.\n env: {\n GIT_ASKPASS_USER: auth.username ?? 'x-access-token',\n GIT_ASKPASS_TOKEN: auth.token,\n GIT_TERMINAL_PROMPT: '0',\n },\n },\n )\n return\n }\n\n await process.exec(\n `git clone ${refArg}${depthArg}-- ${q(url)} ${q(target)}`,\n )\n },\n status: async (dir) =>\n (await process.exec(`git -C ${at(dir)} status --porcelain`)).stdout,\n add: async (paths, dir) => {\n paths.forEach((p, i) => assertNoLeadingDash(p, `path[${i}]`))\n await process.exec(`git -C ${at(dir)} add -- ${paths.map(q).join(' ')}`)\n },\n commit: async (message, dir) => {\n await process.exec(`git -C ${at(dir)} commit -m ${q(message)}`)\n },\n push: async (dir) => {\n await process.exec(`git -C ${at(dir)} push`)\n },\n pull: async (dir) => {\n await process.exec(`git -C ${at(dir)} pull`)\n },\n branch: async (dir) =>\n (\n await process.exec(`git -C ${at(dir)} rev-parse --abbrev-ref HEAD`)\n ).stdout.trim(),\n }\n}\n"],"names":[],"mappings":"AAuBA,SAAS,EAAE,OAAuB;AAChC,SAAO,IAAI,MAAM,QAAQ,MAAM,OAAO,CAAC;AACzC;AAGA,SAAS,oBAAoB,OAAe,MAAoB;AAC9D,MAAI,MAAM,WAAW,GAAG,GAAG;AACzB,UAAM,IAAI;AAAA,MACR,aAAa,IAAI,KAAK,KAAK;AAAA,IAAA;AAAA,EAE/B;AACF;AAKA,MAAM,oBACJ;AAEK,SAAS,oBACd,SACA,aACY;AACZ,QAAM,KAAK,CAAC,QAAyB;AACnC,UAAM,IAAI,OAAO;AACjB,wBAAoB,GAAG,KAAK;AAC5B,WAAO,EAAE,CAAC;AAAA,EACZ;AAEA,SAAO;AAAA,IACL,OAAO,OAAO,EAAE,KAAK,KAAK,KAAK,MAAM,YAAY;AAC/C,0BAAoB,KAAK,KAAK;AAC9B,YAAM,SAAS,OAAO;AACtB,0BAAoB,QAAQ,KAAK;AACjC,UAAI,QAAQ,OAAW,qBAAoB,KAAK,KAAK;AACrD,YAAM,SAAS,MAAM,YAAY,EAAE,GAAG,CAAC,MAAM;AAC7C,YAAM,gBAAgB,SAAS;AAI/B,UACE,kBAAkB,WACjB,CAAC,OAAO,UAAU,aAAa,KAAK,iBAAiB,IACtD;AACA,cAAM,IAAI,MAAM,uDAAuD;AAAA,MACzE;AACA,YAAM,WACJ,kBAAkB,SACd,KACA,WAAW,aAAa;AAE9B,UAAI,MAAM,OAAO;AACf,cAAM,QAAQ;AAAA,UACZ,4BAA4B,EAAE,iBAAiB,CAAC,UAAU,MAAM,GAAG,QAAQ,MAAM,EAAE,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC;AAAA,UACpG;AAAA;AAAA,YAEE,KAAK;AAAA,cACH,kBAAkB,KAAK,YAAY;AAAA,cACnC,mBAAmB,KAAK;AAAA,cACxB,qBAAqB;AAAA,YAAA;AAAA,UACvB;AAAA,QACF;AAEF;AAAA,MACF;AAEA,YAAM,QAAQ;AAAA,QACZ,aAAa,MAAM,GAAG,QAAQ,MAAM,EAAE,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC;AAAA,MAAA;AAAA,IAE3D;AAAA,IACA,QAAQ,OAAO,SACZ,MAAM,QAAQ,KAAK,UAAU,GAAG,GAAG,CAAC,qBAAqB,GAAG;AAAA,IAC/D,KAAK,OAAO,OAAO,QAAQ;AACzB,YAAM,QAAQ,CAAC,GAAG,MAAM,oBAAoB,GAAG,QAAQ,CAAC,GAAG,CAAC;AAC5D,YAAM,QAAQ,KAAK,UAAU,GAAG,GAAG,CAAC,WAAW,MAAM,IAAI,CAAC,EAAE,KAAK,GAAG,CAAC,EAAE;AAAA,IACzE;AAAA,IACA,QAAQ,OAAO,SAAS,QAAQ;AAC9B,YAAM,QAAQ,KAAK,UAAU,GAAG,GAAG,CAAC,cAAc,EAAE,OAAO,CAAC,EAAE;AAAA,IAChE;AAAA,IACA,MAAM,OAAO,QAAQ;AACnB,YAAM,QAAQ,KAAK,UAAU,GAAG,GAAG,CAAC,OAAO;AAAA,IAC7C;AAAA,IACA,MAAM,OAAO,QAAQ;AACnB,YAAM,QAAQ,KAAK,UAAU,GAAG,GAAG,CAAC,OAAO;AAAA,IAC7C;AAAA,IACA,QAAQ,OAAO,SAEX,MAAM,QAAQ,KAAK,UAAU,GAAG,GAAG,CAAC,8BAA8B,GAClE,OAAO,KAAA;AAAA,EAAK;AAEpB;"}
1
+ {"version":3,"file":"git-exec.js","names":[],"sources":["../../src/git-exec.ts"],"sourcesContent":["/**\n * An exec-backed {@link SandboxGit} implementation. Providers without a native\n * git API (local-process, Docker) get a uniform `sandbox.git` by desugaring to\n * `process.exec(\"git …\")`. Providers WITH native git (Daytona, Cloudflare) may\n * supply their own implementation instead.\n *\n * Security:\n * - Every interpolated value is single-quote escaped (no shell injection).\n * - A `--` end-of-options separator precedes untrusted positionals and values\n * are rejected if they begin with `-`, so a repo URL / ref / path can't\n * smuggle a git flag (e.g. `--upload-pack=…`).\n * - Auth tokens NEVER appear in argv (they'd leak via `ps` / process logs).\n * Instead a one-shot `credential.helper` reads the token from the child\n * process ENV. The helper string is single-quoted so the OUTER shell never\n * expands the env var — only git's own helper subshell does, at use time.\n *\n * NOTE: `SandboxProcess.exec` takes a command STRING by design (the sandbox\n * runs shell commands), so we mitigate flag smuggling with `--` + validation\n * rather than an argv array.\n */\nimport type { SandboxGit, SandboxProcess } from './contracts'\n\n/** POSIX single-quote escape: wrap in '…' and escape embedded quotes. */\nfunction q(value: string): string {\n return `'${value.replace(/'/g, `'\\\\''`)}'`\n}\n\n/** Reject values that could be parsed as a git flag when used as a positional. */\nfunction assertNoLeadingDash(value: string, name: string): void {\n if (value.startsWith('-')) {\n throw new Error(\n `git-exec: ${name} \"${value}\" must not begin with \"-\" (argument-injection guard).`,\n )\n }\n}\n\n// Credential helper that prints creds read from the child ENV. Single-quoted at\n// the call site so the outer shell passes it literally; git expands the vars in\n// its own helper subshell, keeping the token out of argv.\nconst CREDENTIAL_HELPER =\n '!f() { echo \"username=${GIT_ASKPASS_USER}\"; echo \"password=${GIT_ASKPASS_TOKEN}\"; }; f'\n\nexport function createExecBackedGit(\n process: SandboxProcess,\n defaultRoot: string,\n): SandboxGit {\n const at = (dir?: string): string => {\n const d = dir ?? defaultRoot\n assertNoLeadingDash(d, 'dir')\n return q(d)\n }\n\n return {\n clone: async ({ url, dir, ref, auth, depth }) => {\n assertNoLeadingDash(url, 'url')\n const target = dir ?? defaultRoot\n assertNoLeadingDash(target, 'dir')\n if (ref !== undefined) assertNoLeadingDash(ref, 'ref')\n const refArg = ref ? `--branch ${q(ref)} ` : ''\n const resolvedDepth = depth ?? 1\n // `depth` is interpolated unquoted into the command, so validate it the\n // same way other positionals are guarded — a non-positive-integer (e.g. an\n // untyped caller passing a string) must never reach the shell.\n if (\n resolvedDepth !== 'full' &&\n (!Number.isInteger(resolvedDepth) || resolvedDepth <= 0)\n ) {\n throw new Error('git-exec: depth must be a positive integer or \"full\".')\n }\n const depthArg =\n resolvedDepth === 'full'\n ? ''\n : `--depth ${resolvedDepth} --single-branch `\n\n if (auth?.token) {\n await process.exec(\n `git -c credential.helper=${q(CREDENTIAL_HELPER)} clone ${refArg}${depthArg}-- ${q(url)} ${q(target)}`,\n {\n // Token lives only in the child env, never in argv.\n env: {\n GIT_ASKPASS_USER: auth.username ?? 'x-access-token',\n GIT_ASKPASS_TOKEN: auth.token,\n GIT_TERMINAL_PROMPT: '0',\n },\n },\n )\n return\n }\n\n await process.exec(\n `git clone ${refArg}${depthArg}-- ${q(url)} ${q(target)}`,\n )\n },\n status: async (dir) =>\n (await process.exec(`git -C ${at(dir)} status --porcelain`)).stdout,\n add: async (paths, dir) => {\n paths.forEach((p, i) => assertNoLeadingDash(p, `path[${i}]`))\n await process.exec(`git -C ${at(dir)} add -- ${paths.map(q).join(' ')}`)\n },\n commit: async (message, dir) => {\n await process.exec(`git -C ${at(dir)} commit -m ${q(message)}`)\n },\n push: async (dir) => {\n await process.exec(`git -C ${at(dir)} push`)\n },\n pull: async (dir) => {\n await process.exec(`git -C ${at(dir)} pull`)\n },\n branch: async (dir) =>\n (\n await process.exec(`git -C ${at(dir)} rev-parse --abbrev-ref HEAD`)\n ).stdout.trim(),\n }\n}\n"],"mappings":";;AAuBA,SAAS,EAAE,OAAuB;CAChC,OAAO,IAAI,MAAM,QAAQ,MAAM,OAAO,EAAE;AAC1C;;AAGA,SAAS,oBAAoB,OAAe,MAAoB;CAC9D,IAAI,MAAM,WAAW,GAAG,GACtB,MAAM,IAAI,MACR,aAAa,KAAK,IAAI,MAAM,sDAC9B;AAEJ;AAKA,IAAM,oBACJ;AAEF,SAAgB,oBACd,SACA,aACY;CACZ,MAAM,MAAM,QAAyB;EACnC,MAAM,IAAI,OAAO;EACjB,oBAAoB,GAAG,KAAK;EAC5B,OAAO,EAAE,CAAC;CACZ;CAEA,OAAO;EACL,OAAO,OAAO,EAAE,KAAK,KAAK,KAAK,MAAM,YAAY;GAC/C,oBAAoB,KAAK,KAAK;GAC9B,MAAM,SAAS,OAAO;GACtB,oBAAoB,QAAQ,KAAK;GACjC,IAAI,QAAQ,KAAA,GAAW,oBAAoB,KAAK,KAAK;GACrD,MAAM,SAAS,MAAM,YAAY,EAAE,GAAG,EAAE,KAAK;GAC7C,MAAM,gBAAgB,SAAS;GAI/B,IACE,kBAAkB,WACjB,CAAC,OAAO,UAAU,aAAa,KAAK,iBAAiB,IAEtD,MAAM,IAAI,MAAM,yDAAuD;GAEzE,MAAM,WACJ,kBAAkB,SACd,KACA,WAAW,cAAc;GAE/B,IAAI,MAAM,OAAO;IACf,MAAM,QAAQ,KACZ,4BAA4B,EAAE,iBAAiB,EAAE,SAAS,SAAS,SAAS,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,KACnG,EAEE,KAAK;KACH,kBAAkB,KAAK,YAAY;KACnC,mBAAmB,KAAK;KACxB,qBAAqB;IACvB,EACF,CACF;IACA;GACF;GAEA,MAAM,QAAQ,KACZ,aAAa,SAAS,SAAS,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,GACxD;EACF;EACA,QAAQ,OAAO,SACZ,MAAM,QAAQ,KAAK,UAAU,GAAG,GAAG,EAAE,oBAAoB,EAAA,CAAG;EAC/D,KAAK,OAAO,OAAO,QAAQ;GACzB,MAAM,SAAS,GAAG,MAAM,oBAAoB,GAAG,QAAQ,EAAE,EAAE,CAAC;GAC5D,MAAM,QAAQ,KAAK,UAAU,GAAG,GAAG,EAAE,UAAU,MAAM,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG,GAAG;EACzE;EACA,QAAQ,OAAO,SAAS,QAAQ;GAC9B,MAAM,QAAQ,KAAK,UAAU,GAAG,GAAG,EAAE,aAAa,EAAE,OAAO,GAAG;EAChE;EACA,MAAM,OAAO,QAAQ;GACnB,MAAM,QAAQ,KAAK,UAAU,GAAG,GAAG,EAAE,MAAM;EAC7C;EACA,MAAM,OAAO,QAAQ;GACnB,MAAM,QAAQ,KAAK,UAAU,GAAG,GAAG,EAAE,MAAM;EAC7C;EACA,QAAQ,OAAO,SAEX,MAAM,QAAQ,KAAK,UAAU,GAAG,GAAG,EAAE,6BAA6B,EAAA,CAClE,OAAO,KAAK;CAClB;AACF"}
@@ -1,24 +1,29 @@
1
- import * as path from "node:path";
2
1
  import { DEFAULT_WORKSPACE_ROOT } from "./bootstrap.js";
2
+ import * as path from "node:path";
3
+ //#region src/harness-cwd.ts
4
+ /**
5
+ * Resolve a VIRTUAL sandbox cwd (e.g. `/workspace`) to the path a harness CLI
6
+ * or ACP session must use on the real filesystem.
7
+ *
8
+ * Provider handles map virtual paths for spawn/exec/fs; harness-facing APIs
9
+ * interpret cwd literally (`grok --cwd`, ACP `newSession`, opencode HTTP
10
+ * `directory`, …).
11
+ */
3
12
  function mapVirtualWorkspacePath(virtualCwd, realRoot) {
4
- if (virtualCwd === DEFAULT_WORKSPACE_ROOT) return realRoot;
5
- if (virtualCwd.startsWith(`${DEFAULT_WORKSPACE_ROOT}/`)) {
6
- const rel = virtualCwd.slice(DEFAULT_WORKSPACE_ROOT.length + 1);
7
- return realRoot === DEFAULT_WORKSPACE_ROOT ? `${DEFAULT_WORKSPACE_ROOT}/${rel}` : path.posix.join(realRoot, rel);
8
- }
9
- return virtualCwd;
13
+ if (virtualCwd === "/workspace") return realRoot;
14
+ if (virtualCwd.startsWith(`/workspace/`)) {
15
+ const rel = virtualCwd.slice(DEFAULT_WORKSPACE_ROOT.length + 1);
16
+ return realRoot === "/workspace" ? `${DEFAULT_WORKSPACE_ROOT}/${rel}` : path.posix.join(realRoot, rel);
17
+ }
18
+ return virtualCwd;
10
19
  }
11
20
  function resolveHarnessCwd(handle, virtualCwd = DEFAULT_WORKSPACE_ROOT) {
12
- if (handle.provider === "local-process") {
13
- return mapVirtualWorkspacePath(virtualCwd, handle.id);
14
- }
15
- const root = handle.workspaceRoot;
16
- if (root !== void 0 && root !== DEFAULT_WORKSPACE_ROOT) {
17
- return mapVirtualWorkspacePath(virtualCwd, root);
18
- }
19
- return virtualCwd;
21
+ if (handle.provider === "local-process") return mapVirtualWorkspacePath(virtualCwd, handle.id);
22
+ const root = handle.workspaceRoot;
23
+ if (root !== void 0 && root !== "/workspace") return mapVirtualWorkspacePath(virtualCwd, root);
24
+ return virtualCwd;
20
25
  }
21
- export {
22
- resolveHarnessCwd
23
- };
24
- //# sourceMappingURL=harness-cwd.js.map
26
+ //#endregion
27
+ export { resolveHarnessCwd };
28
+
29
+ //# sourceMappingURL=harness-cwd.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"harness-cwd.js","sources":["../../src/harness-cwd.ts"],"sourcesContent":["/**\n * Resolve a VIRTUAL sandbox cwd (e.g. `/workspace`) to the path a harness CLI\n * or ACP session must use on the real filesystem.\n *\n * Provider handles map virtual paths for spawn/exec/fs; harness-facing APIs\n * interpret cwd literally (`grok --cwd`, ACP `newSession`, opencode HTTP\n * `directory`, …).\n */\nimport * as path from 'node:path'\nimport { DEFAULT_WORKSPACE_ROOT } from './bootstrap'\nimport type { SandboxHandle } from './contracts'\n\nfunction mapVirtualWorkspacePath(virtualCwd: string, realRoot: string): string {\n if (virtualCwd === DEFAULT_WORKSPACE_ROOT) return realRoot\n if (virtualCwd.startsWith(`${DEFAULT_WORKSPACE_ROOT}/`)) {\n const rel = virtualCwd.slice(DEFAULT_WORKSPACE_ROOT.length + 1)\n return realRoot === DEFAULT_WORKSPACE_ROOT\n ? `${DEFAULT_WORKSPACE_ROOT}/${rel}`\n : path.posix.join(realRoot, rel)\n }\n return virtualCwd\n}\n\nexport function resolveHarnessCwd(\n handle: SandboxHandle,\n virtualCwd: string = DEFAULT_WORKSPACE_ROOT,\n): string {\n if (handle.provider === 'local-process') {\n return mapVirtualWorkspacePath(virtualCwd, handle.id)\n }\n\n const root = handle.workspaceRoot\n if (root !== undefined && root !== DEFAULT_WORKSPACE_ROOT) {\n return mapVirtualWorkspacePath(virtualCwd, root)\n }\n\n return virtualCwd\n}\n"],"names":[],"mappings":";;AAYA,SAAS,wBAAwB,YAAoB,UAA0B;AAC7E,MAAI,eAAe,uBAAwB,QAAO;AAClD,MAAI,WAAW,WAAW,GAAG,sBAAsB,GAAG,GAAG;AACvD,UAAM,MAAM,WAAW,MAAM,uBAAuB,SAAS,CAAC;AAC9D,WAAO,aAAa,yBAChB,GAAG,sBAAsB,IAAI,GAAG,KAChC,KAAK,MAAM,KAAK,UAAU,GAAG;AAAA,EACnC;AACA,SAAO;AACT;AAEO,SAAS,kBACd,QACA,aAAqB,wBACb;AACR,MAAI,OAAO,aAAa,iBAAiB;AACvC,WAAO,wBAAwB,YAAY,OAAO,EAAE;AAAA,EACtD;AAEA,QAAM,OAAO,OAAO;AACpB,MAAI,SAAS,UAAa,SAAS,wBAAwB;AACzD,WAAO,wBAAwB,YAAY,IAAI;AAAA,EACjD;AAEA,SAAO;AACT;"}
1
+ {"version":3,"file":"harness-cwd.js","names":[],"sources":["../../src/harness-cwd.ts"],"sourcesContent":["/**\n * Resolve a VIRTUAL sandbox cwd (e.g. `/workspace`) to the path a harness CLI\n * or ACP session must use on the real filesystem.\n *\n * Provider handles map virtual paths for spawn/exec/fs; harness-facing APIs\n * interpret cwd literally (`grok --cwd`, ACP `newSession`, opencode HTTP\n * `directory`, …).\n */\nimport * as path from 'node:path'\nimport { DEFAULT_WORKSPACE_ROOT } from './bootstrap'\nimport type { SandboxHandle } from './contracts'\n\nfunction mapVirtualWorkspacePath(virtualCwd: string, realRoot: string): string {\n if (virtualCwd === DEFAULT_WORKSPACE_ROOT) return realRoot\n if (virtualCwd.startsWith(`${DEFAULT_WORKSPACE_ROOT}/`)) {\n const rel = virtualCwd.slice(DEFAULT_WORKSPACE_ROOT.length + 1)\n return realRoot === DEFAULT_WORKSPACE_ROOT\n ? `${DEFAULT_WORKSPACE_ROOT}/${rel}`\n : path.posix.join(realRoot, rel)\n }\n return virtualCwd\n}\n\nexport function resolveHarnessCwd(\n handle: SandboxHandle,\n virtualCwd: string = DEFAULT_WORKSPACE_ROOT,\n): string {\n if (handle.provider === 'local-process') {\n return mapVirtualWorkspacePath(virtualCwd, handle.id)\n }\n\n const root = handle.workspaceRoot\n if (root !== undefined && root !== DEFAULT_WORKSPACE_ROOT) {\n return mapVirtualWorkspacePath(virtualCwd, root)\n }\n\n return virtualCwd\n}\n"],"mappings":";;;;;;;;;;;AAYA,SAAS,wBAAwB,YAAoB,UAA0B;CAC7E,IAAI,eAAA,cAAuC,OAAO;CAClD,IAAI,WAAW,WAAW,aAA4B,GAAG;EACvD,MAAM,MAAM,WAAW,MAAM,uBAAuB,SAAS,CAAC;EAC9D,OAAO,aAAA,eACH,GAAG,uBAAuB,GAAG,QAC7B,KAAK,MAAM,KAAK,UAAU,GAAG;CACnC;CACA,OAAO;AACT;AAEA,SAAgB,kBACd,QACA,aAAqB,wBACb;CACR,IAAI,OAAO,aAAa,iBACtB,OAAO,wBAAwB,YAAY,OAAO,EAAE;CAGtD,MAAM,OAAO,OAAO;CACpB,IAAI,SAAS,KAAA,KAAa,SAAA,cACxB,OAAO,wBAAwB,YAAY,IAAI;CAGjD,OAAO;AACT"}
@@ -1,7 +1,11 @@
1
- export { SandboxCapability, SandboxStoreCapability, LocksCapability, SandboxPolicyCapability, ToolBridgeProvisionerCapability, getSandbox, provideSandbox, getSandboxStore, provideSandboxStore, getLocks, provideLocks, getSandboxPolicy, provideSandboxPolicy, getToolBridgeProvisioner, provideToolBridgeProvisioner, } from './capabilities.js';
1
+ export { SandboxCapability, SandboxPolicyCapability, ToolBridgeProvisionerCapability, getSandbox, provideSandbox, getSandboxPolicy, provideSandboxPolicy, getToolBridgeProvisioner, provideToolBridgeProvisioner, } from './capabilities.js';
2
+ export { SandboxInstanceStoreCapability, getSandboxInstanceStore, provideSandboxInstanceStore, InMemorySandboxInstanceStore, defineSandboxInstanceStore, } from './instance-store.js';
3
+ export type { SandboxInstanceStore, SandboxInstanceRecord, } from './instance-store.js';
2
4
  export { ProjectionCapability, getWorkspaceProjection, provideWorkspaceProjection, } from './projection.js';
3
5
  export type { WorkspaceProjection } from './projection.js';
4
6
  export { withSandbox } from './middleware.js';
7
+ export type { SandboxMiddlewareOptions } from './middleware.js';
8
+ export { isSandboxToolCall } from './tool-history.js';
5
9
  export { defineSandbox } from './sandbox.js';
6
10
  export type { SandboxConfig, SandboxDefinition, SandboxEnsureContext, SandboxLifecycle, SandboxHooks, ReuseStrategy, SnapshotStrategy, } from './sandbox.js';
7
11
  export { defineWorkspace, gitSource, githubRepo, localSource, fileSkill, agentSkill, mcpSkill, gitSkill, } from './workspace.js';
@@ -11,25 +15,43 @@ export type { SecretRef, Secrets, BearerRef } from './secrets.js';
11
15
  export { defineSandboxPolicy, evaluateCommand, commandAliases } from './policy.js';
12
16
  export type { SandboxPolicy, PolicyDecision, CommandRules, CapabilityRules, } from './policy.js';
13
17
  export type { SandboxProvider, SandboxHandle, SandboxCapabilities, SandboxFs, SandboxGit, SandboxProcess, SandboxPorts, SandboxEnv, SandboxChannel, SpawnHandle, ExecResult, ProcessOptions, SnapshotRef, SandboxCreateInput, SandboxResumeInput, SandboxRestoreInput, SandboxDestroyInput, } from './contracts.js';
14
- export { InMemorySandboxStore, InMemoryLockStore } from './store.js';
15
- export type { SandboxStore, LockStore, SandboxRecord } from './store.js';
16
18
  export { bootstrapWorkspace, detectPackageManager, DEFAULT_WORKSPACE_ROOT, } from './bootstrap.js';
17
19
  export { resolveHarnessCwd } from './harness-cwd.js';
18
20
  export type { BootstrapResult } from './bootstrap.js';
19
21
  export { writeAgentsFile, resolveGitSkillDir, formatWorkspaceScriptsSection, mergeAgentsContent, } from './agents-file.js';
20
22
  export { createExecBackedGit } from './git-exec.js';
21
- export { spawnNdjson, toLines } from './runner.js';
22
- export type { SpawnNdjsonOptions } from './runner.js';
23
+ export { spawnNdjson, toLines, startJournaledAgent, readJournalNdjson, } from './runner.js';
24
+ export type { SpawnNdjsonOptions, JournalOptions } from './runner.js';
25
+ export { DEFAULT_JOURNAL_DIR, encodeRunId, EXIT_SENTINEL_KEY, EXIT_SENTINEL_NONCE_KEY, exitSentinelLine, parseExitSentinel, journalPaths, journaledCommand, journalFollowCommand, journalReadCommand, journalExistsCommand, journalListCommand, journalMtimeListCommand, parseJournalMtimeListing, journalExitProbeCommand, parseJournalExit, decodeJournalRunId, } from './journal.js';
26
+ export type { JournalPaths, JournalMtimeListing, JournalDirEntry, DecodedJournalRunId, } from './journal.js';
27
+ export { pruneJournals, DEFAULT_ORPHAN_TTL_MS, DEFAULT_MAX_DELETES, } from './journal-sweep.js';
28
+ export type { PruneJournalsOptions, PruneJournalsResult, KeptJournal, KeptJournalReason, PruneJournalsFailure, } from './journal-sweep.js';
29
+ export { reapDetachedRuns, probeRunExit, DEFAULT_RUN_BUDGET_MS, DEFAULT_MAX_RUNS, DEFAULT_EXIT_PROBE_BYTES, } from './reap.js';
30
+ export type { RunExitProbe, ReapRunOutcome, ReapRunEntry, ReapResult, ReapOptions, } from './reap.js';
31
+ export { reclaimSandbox, sandboxReclaimer, SandboxReclaimFailedError, } from './reclaim.js';
32
+ export type { ReclaimOutcome, ReclaimSandboxOptions } from './reclaim.js';
33
+ export { DEFAULT_JOURNAL_POLL_MS, journalReadStrategy, readJournal, } from './journal-reader.js';
34
+ export type { ReadJournalOptions } from './journal-reader.js';
35
+ export { decodeBase64Stream, toJournalLines } from './journal-bytes.js';
36
+ export type { JournalLine } from './journal-bytes.js';
37
+ export { createRunScopedIdGen, chunkFingerprint, chunkFingerprintIgnoringThreadId, chunkThreadId, } from './chunk-identity.js';
38
+ export { alignToStoredLog, isBridgeCustomChunk, JournalReplayDivergedError, JournalReplayThreadIdMismatchError, DEFAULT_MAX_OUT_OF_BAND_SKIP, } from './align.js';
39
+ export type { AlignToStoredLogOptions } from './align.js';
40
+ export { awaitAttachableJournal, JournalAttachUnavailableError, DEFAULT_ATTACH_JOURNAL_WAIT_MS, DEFAULT_ATTACH_PROBE_INTERVAL_MS, } from './attach-preflight.js';
41
+ export type { AttachUnavailableReason, AwaitAttachableJournalOptions, } from './attach-preflight.js';
42
+ export { SandboxDurabilityCapability, getSandboxDurability, provideSandboxDurability, DurableAttachNotSupportedError, DurableRunIdRequiredError, DurableThreadIdRequiredError, resolveDurableRunId, resolveDurableThreadId, journalOptionsFor, alignedIfAttaching, } from './durability.js';
43
+ export type { SandboxDurabilityOptions, SandboxDurabilityLog, SandboxRunDurability, } from './durability.js';
44
+ export { sandboxRunDriver, RunDriverPipeOutsideClaimError } from './driver.js';
45
+ export type { SandboxRunDriverOptions } from './driver.js';
46
+ export { RunClaimNotAcquiredError, RunClaimLostError, DEFAULT_FENCE_QUIET_MS, } from './claim.js';
23
47
  export { startHostToolBridge, hostForSandbox, createToolBridgeCore, handleBridgeJsonRpc, timingSafeBearerEqual, nodeHttpBridgeProvisioner, BRIDGED_MCP_SERVER_NAME, } from './tool-bridge.js';
24
48
  export type { HostToolBridge, StartBridgeOptions, ToolBridgeCore, ToolBridgeCoreOptions, ToolDescriptor, ToolCallResult, BridgePermission, PermissionToolResult, ToolBridgeProvisioner, ToolBridgeProvisionOptions, ProvisionedBridge, } from './tool-bridge.js';
25
49
  export { createBridgeEventChannel, mergeChunkStreams } from './bridge-events.js';
26
50
  export type { BridgeEventChannel } from './bridge-events.js';
27
51
  export { remoteToolStubs, toolDescriptors, httpRemoteToolExecutor, executeHostTool, isToolExecRequest, } from './remote-tools.js';
28
52
  export type { RemoteToolExecutor, RemoteToolExecuteOptions, ToolExecRequest, } from './remote-tools.js';
29
- export { InMemoryRunEventLog, isTerminalRunStatus } from './run-log.js';
30
- export type { RunEventLog, RunRecord, RunEvent, RunStatus, TerminalRunStatus, RunError, RunEventLogReadOptions, } from './run-log.js';
31
53
  export { pipeToRunLog, RunController } from './run.js';
32
- export type { PipeToRunLogOptions, RunControllerStartInput, RunHandle, } from './run.js';
54
+ export type { RunDeps, PipeToRunLogOptions, RunControllerStartInput, RunHandle, } from './run.js';
33
55
  export { resolveApproval, approvalId, buildApprovalRequestedEvent, APPROVAL_REQUESTED_EVENT, } from './approvals.js';
34
56
  export type { ResolveApprovalInput, ApprovalOutcome } from './approvals.js';
35
57
  export { watchWorkspace, diffSnapshots } from './watch.js';
package/dist/esm/index.js CHANGED
@@ -1,103 +1,35 @@
1
- import { LocksCapability, SandboxCapability, SandboxPolicyCapability, SandboxStoreCapability, ToolBridgeProvisionerCapability, getLocks, getSandbox, getSandboxPolicy, getSandboxStore, getToolBridgeProvisioner, provideLocks, provideSandbox, provideSandboxPolicy, provideSandboxStore, provideToolBridgeProvisioner } from "./capabilities.js";
1
+ import { SandboxCapability, SandboxPolicyCapability, ToolBridgeProvisionerCapability, getSandbox, getSandboxPolicy, getToolBridgeProvisioner, provideSandbox, provideSandboxPolicy, provideToolBridgeProvisioner } from "./capabilities.js";
2
+ import { InMemorySandboxInstanceStore, SandboxInstanceStoreCapability, defineSandboxInstanceStore, getSandboxInstanceStore, provideSandboxInstanceStore } from "./instance-store.js";
2
3
  import { ProjectionCapability, getWorkspaceProjection, provideWorkspaceProjection } from "./projection.js";
4
+ import { DEFAULT_JOURNAL_DIR, EXIT_SENTINEL_KEY, EXIT_SENTINEL_NONCE_KEY, decodeJournalRunId, encodeRunId, exitSentinelLine, journalExistsCommand, journalExitProbeCommand, journalFollowCommand, journalListCommand, journalMtimeListCommand, journalPaths, journalReadCommand, journaledCommand, parseExitSentinel, parseJournalExit, parseJournalMtimeListing } from "./journal.js";
5
+ import { chunkFingerprint, chunkFingerprintIgnoringThreadId, chunkThreadId, createRunScopedIdGen } from "./chunk-identity.js";
6
+ import { DEFAULT_MAX_OUT_OF_BAND_SKIP, JournalReplayDivergedError, JournalReplayThreadIdMismatchError, alignToStoredLog, isBridgeCustomChunk } from "./align.js";
7
+ import { DurableAttachNotSupportedError, DurableRunIdRequiredError, DurableThreadIdRequiredError, SandboxDurabilityCapability, alignedIfAttaching, getSandboxDurability, journalOptionsFor, provideSandboxDurability, resolveDurableRunId, resolveDurableThreadId } from "./durability.js";
8
+ import { computeSandboxKey, computeWorkspaceHash } from "./key.js";
9
+ import { bearer, createSecrets, isSecretRef, resolveAllSecrets, resolveBearer, resolveSecret } from "./secrets.js";
10
+ import { isSandboxToolCall } from "./tool-history.js";
11
+ import { formatWorkspaceScriptsSection, mergeAgentsContent, resolveGitSkillDir, writeAgentsFile } from "./agents-file.js";
12
+ import { DEFAULT_WORKSPACE_ROOT, bootstrapWorkspace, detectPackageManager } from "./bootstrap.js";
13
+ import { diffSnapshots, watchWorkspace } from "./watch.js";
3
14
  import { withSandbox } from "./middleware.js";
4
15
  import { defineSandbox } from "./sandbox.js";
5
16
  import { agentSkill, defineWorkspace, fileSkill, gitSkill, gitSource, githubRepo, localSource, mcpSkill } from "./workspace.js";
6
- import { bearer, createSecrets, isSecretRef, resolveAllSecrets, resolveBearer, resolveSecret } from "./secrets.js";
7
17
  import { commandAliases, defineSandboxPolicy, evaluateCommand } from "./policy.js";
8
- import { InMemoryLockStore, InMemorySandboxStore } from "./store.js";
9
- import { DEFAULT_WORKSPACE_ROOT, bootstrapWorkspace, detectPackageManager } from "./bootstrap.js";
10
18
  import { resolveHarnessCwd } from "./harness-cwd.js";
11
- import { formatWorkspaceScriptsSection, mergeAgentsContent, resolveGitSkillDir, writeAgentsFile } from "./agents-file.js";
12
19
  import { createExecBackedGit } from "./git-exec.js";
13
- import { spawnNdjson, toLines } from "./runner.js";
20
+ import { DEFAULT_ATTACH_JOURNAL_WAIT_MS, DEFAULT_ATTACH_PROBE_INTERVAL_MS, JournalAttachUnavailableError, awaitAttachableJournal } from "./attach-preflight.js";
21
+ import { decodeBase64Stream, toJournalLines } from "./journal-bytes.js";
22
+ import { DEFAULT_JOURNAL_POLL_MS, journalReadStrategy, readJournal } from "./journal-reader.js";
23
+ import { readJournalNdjson, spawnNdjson, startJournaledAgent, toLines } from "./runner.js";
24
+ import { DEFAULT_MAX_DELETES, DEFAULT_ORPHAN_TTL_MS, pruneJournals } from "./journal-sweep.js";
25
+ import { DEFAULT_FENCE_QUIET_MS, RunClaimLostError, RunClaimNotAcquiredError } from "./claim.js";
26
+ import { RunController, pipeToRunLog } from "./run.js";
27
+ import { DEFAULT_EXIT_PROBE_BYTES, DEFAULT_MAX_RUNS, DEFAULT_RUN_BUDGET_MS, probeRunExit, reapDetachedRuns } from "./reap.js";
28
+ import { SandboxReclaimFailedError, reclaimSandbox, sandboxReclaimer } from "./reclaim.js";
29
+ import { RunDriverPipeOutsideClaimError, sandboxRunDriver } from "./driver.js";
14
30
  import { BRIDGED_MCP_SERVER_NAME, createToolBridgeCore, handleBridgeJsonRpc, hostForSandbox, nodeHttpBridgeProvisioner, startHostToolBridge, timingSafeBearerEqual } from "./tool-bridge.js";
15
31
  import { createBridgeEventChannel, mergeChunkStreams } from "./bridge-events.js";
16
32
  import { executeHostTool, httpRemoteToolExecutor, isToolExecRequest, remoteToolStubs, toolDescriptors } from "./remote-tools.js";
17
- import { InMemoryRunEventLog, isTerminalRunStatus } from "./run-log.js";
18
- import { RunController, pipeToRunLog } from "./run.js";
19
33
  import { APPROVAL_REQUESTED_EVENT, approvalId, buildApprovalRequestedEvent, resolveApproval } from "./approvals.js";
20
- import { diffSnapshots, watchWorkspace } from "./watch.js";
21
- import { computeSandboxKey, computeWorkspaceHash } from "./key.js";
22
34
  import { MissingSandboxError, UnsupportedCapabilityError } from "./errors.js";
23
- export {
24
- APPROVAL_REQUESTED_EVENT,
25
- BRIDGED_MCP_SERVER_NAME,
26
- DEFAULT_WORKSPACE_ROOT,
27
- InMemoryLockStore,
28
- InMemoryRunEventLog,
29
- InMemorySandboxStore,
30
- LocksCapability,
31
- MissingSandboxError,
32
- ProjectionCapability,
33
- RunController,
34
- SandboxCapability,
35
- SandboxPolicyCapability,
36
- SandboxStoreCapability,
37
- ToolBridgeProvisionerCapability,
38
- UnsupportedCapabilityError,
39
- agentSkill,
40
- approvalId,
41
- bearer,
42
- bootstrapWorkspace,
43
- buildApprovalRequestedEvent,
44
- commandAliases,
45
- computeSandboxKey,
46
- computeWorkspaceHash,
47
- createBridgeEventChannel,
48
- createExecBackedGit,
49
- createSecrets,
50
- createToolBridgeCore,
51
- defineSandbox,
52
- defineSandboxPolicy,
53
- defineWorkspace,
54
- detectPackageManager,
55
- diffSnapshots,
56
- evaluateCommand,
57
- executeHostTool,
58
- fileSkill,
59
- formatWorkspaceScriptsSection,
60
- getLocks,
61
- getSandbox,
62
- getSandboxPolicy,
63
- getSandboxStore,
64
- getToolBridgeProvisioner,
65
- getWorkspaceProjection,
66
- gitSkill,
67
- gitSource,
68
- githubRepo,
69
- handleBridgeJsonRpc,
70
- hostForSandbox,
71
- httpRemoteToolExecutor,
72
- isSecretRef,
73
- isTerminalRunStatus,
74
- isToolExecRequest,
75
- localSource,
76
- mcpSkill,
77
- mergeAgentsContent,
78
- mergeChunkStreams,
79
- nodeHttpBridgeProvisioner,
80
- pipeToRunLog,
81
- provideLocks,
82
- provideSandbox,
83
- provideSandboxPolicy,
84
- provideSandboxStore,
85
- provideToolBridgeProvisioner,
86
- provideWorkspaceProjection,
87
- remoteToolStubs,
88
- resolveAllSecrets,
89
- resolveApproval,
90
- resolveBearer,
91
- resolveGitSkillDir,
92
- resolveHarnessCwd,
93
- resolveSecret,
94
- spawnNdjson,
95
- startHostToolBridge,
96
- timingSafeBearerEqual,
97
- toLines,
98
- toolDescriptors,
99
- watchWorkspace,
100
- withSandbox,
101
- writeAgentsFile
102
- };
103
- //# sourceMappingURL=index.js.map
35
+ export { APPROVAL_REQUESTED_EVENT, BRIDGED_MCP_SERVER_NAME, DEFAULT_ATTACH_JOURNAL_WAIT_MS, DEFAULT_ATTACH_PROBE_INTERVAL_MS, DEFAULT_EXIT_PROBE_BYTES, DEFAULT_FENCE_QUIET_MS, DEFAULT_JOURNAL_DIR, DEFAULT_JOURNAL_POLL_MS, DEFAULT_MAX_DELETES, DEFAULT_MAX_OUT_OF_BAND_SKIP, DEFAULT_MAX_RUNS, DEFAULT_ORPHAN_TTL_MS, DEFAULT_RUN_BUDGET_MS, DEFAULT_WORKSPACE_ROOT, DurableAttachNotSupportedError, DurableRunIdRequiredError, DurableThreadIdRequiredError, EXIT_SENTINEL_KEY, EXIT_SENTINEL_NONCE_KEY, InMemorySandboxInstanceStore, JournalAttachUnavailableError, JournalReplayDivergedError, JournalReplayThreadIdMismatchError, MissingSandboxError, ProjectionCapability, RunClaimLostError, RunClaimNotAcquiredError, RunController, RunDriverPipeOutsideClaimError, SandboxCapability, SandboxDurabilityCapability, SandboxInstanceStoreCapability, SandboxPolicyCapability, SandboxReclaimFailedError, ToolBridgeProvisionerCapability, UnsupportedCapabilityError, agentSkill, alignToStoredLog, alignedIfAttaching, approvalId, awaitAttachableJournal, bearer, bootstrapWorkspace, buildApprovalRequestedEvent, chunkFingerprint, chunkFingerprintIgnoringThreadId, chunkThreadId, commandAliases, computeSandboxKey, computeWorkspaceHash, createBridgeEventChannel, createExecBackedGit, createRunScopedIdGen, createSecrets, createToolBridgeCore, decodeBase64Stream, decodeJournalRunId, defineSandbox, defineSandboxInstanceStore, defineSandboxPolicy, defineWorkspace, detectPackageManager, diffSnapshots, encodeRunId, evaluateCommand, executeHostTool, exitSentinelLine, fileSkill, formatWorkspaceScriptsSection, getSandbox, getSandboxDurability, getSandboxInstanceStore, getSandboxPolicy, getToolBridgeProvisioner, getWorkspaceProjection, gitSkill, gitSource, githubRepo, handleBridgeJsonRpc, hostForSandbox, httpRemoteToolExecutor, isBridgeCustomChunk, isSandboxToolCall, isSecretRef, isToolExecRequest, journalExistsCommand, journalExitProbeCommand, journalFollowCommand, journalListCommand, journalMtimeListCommand, journalOptionsFor, journalPaths, journalReadCommand, journalReadStrategy, journaledCommand, localSource, mcpSkill, mergeAgentsContent, mergeChunkStreams, nodeHttpBridgeProvisioner, parseExitSentinel, parseJournalExit, parseJournalMtimeListing, pipeToRunLog, probeRunExit, provideSandbox, provideSandboxDurability, provideSandboxInstanceStore, provideSandboxPolicy, provideToolBridgeProvisioner, provideWorkspaceProjection, pruneJournals, readJournal, readJournalNdjson, reapDetachedRuns, reclaimSandbox, remoteToolStubs, resolveAllSecrets, resolveApproval, resolveBearer, resolveDurableRunId, resolveDurableThreadId, resolveGitSkillDir, resolveHarnessCwd, resolveSecret, sandboxReclaimer, sandboxRunDriver, spawnNdjson, startHostToolBridge, startJournaledAgent, timingSafeBearerEqual, toJournalLines, toLines, toolDescriptors, watchWorkspace, withSandbox, writeAgentsFile };
@@ -0,0 +1,88 @@
1
+ /** One persisted sandbox instance, keyed by the compound sandbox instance key. */
2
+ export interface SandboxInstanceRecord {
3
+ /** Compound key (see `computeSandboxKey`). */
4
+ key: string;
5
+ /** Provider name that owns `providerSandboxId`. */
6
+ provider: string;
7
+ /** Provider-assigned sandbox id used to resume. */
8
+ providerSandboxId: string;
9
+ /** Most recent snapshot id, when the provider supports snapshots. */
10
+ latestSnapshotId?: string;
11
+ threadId: string;
12
+ latestRunId?: string;
13
+ /**
14
+ * Epoch ms of last write (for keepAlive / GC by the host app).
15
+ */
16
+ updatedAt: number;
17
+ }
18
+ /**
19
+ * Maps a compound key to the provider sandbox that should be resumed.
20
+ *
21
+ * Implement against your own database (BYO). Prove the contract with
22
+ * `runSandboxInstanceStoreConformance` from `@tanstack/ai-sandbox/testkit`.
23
+ */
24
+ export interface SandboxInstanceStore {
25
+ /**
26
+ * Return the record for `key`, or `null` if none exists.
27
+ *
28
+ * INVARIANT: missing keys return `null` (never throw).
29
+ */
30
+ get: (key: string) => Promise<SandboxInstanceRecord | null>;
31
+ /**
32
+ * Insert or fully replace the record for `record.key`.
33
+ *
34
+ * INVARIANT (full replace): omitted optional fields (`latestSnapshotId`,
35
+ * `latestRunId`) MUST clear any previously stored values. Do not merge with
36
+ * the prior row — a create-without-snapshot path must not leave a stale
37
+ * snapshot id.
38
+ */
39
+ upsert: (record: SandboxInstanceRecord) => Promise<void>;
40
+ /**
41
+ * Remove the record for `key`.
42
+ *
43
+ * INVARIANT: deleting a missing key is a **no-op** (must not throw).
44
+ */
45
+ delete: (key: string) => Promise<void>;
46
+ }
47
+ /**
48
+ * Type a {@link SandboxInstanceStore} implementation inline: pass the object and
49
+ * get autocomplete + contract checking, with no separate
50
+ * `: SandboxInstanceStore` annotation. Hand the result to
51
+ * `withSandbox(sandbox, { instances })`. Matches `defineLock` /
52
+ * `defineMessageStore` style helpers elsewhere in the monorepo.
53
+ */
54
+ export declare function defineSandboxInstanceStore(store: SandboxInstanceStore): SandboxInstanceStore;
55
+ /**
56
+ * Capability for the instance map — the ambient alternative to
57
+ * `withSandbox(sandbox, { instances })`. Provide it from any middleware with
58
+ * {@link provideSandboxInstanceStore}; `withSandbox` reads it when no explicit
59
+ * option was passed.
60
+ */
61
+ export declare const SandboxInstanceStoreCapability: import('@tanstack/ai').Capability<SandboxInstanceStore, "sandbox-instance-store">;
62
+ /** Destructured accessors: `getSandboxInstanceStore` / `provideSandboxInstanceStore`. */
63
+ export declare const getSandboxInstanceStore: import('@tanstack/ai').CapabilityGetter<SandboxInstanceStore>, provideSandboxInstanceStore: import('@tanstack/ai').CapabilityProvider<SandboxInstanceStore>;
64
+ /** In-memory {@link SandboxInstanceStore}. Resume works only within one process. */
65
+ export declare class InMemorySandboxInstanceStore implements SandboxInstanceStore {
66
+ private readonly map;
67
+ get(key: string): Promise<SandboxInstanceRecord | null>;
68
+ upsert(record: SandboxInstanceRecord): Promise<void>;
69
+ delete(key: string): Promise<void>;
70
+ }
71
+ /**
72
+ * Wiring note: hand the store straight to the consumer —
73
+ * `withSandbox(sandbox, { instances: store })`. That cannot be mis-ordered,
74
+ * unlike a separate provider middleware composed after `withSandbox` (which
75
+ * silently degrades to the in-memory fallback).
76
+ *
77
+ * ```ts
78
+ * middleware: [
79
+ * withLocks(locks), // from @tanstack/ai/locks — multi-replica
80
+ * withSandbox(sandbox, { instances: instanceStore }),
81
+ * ]
82
+ * ```
83
+ *
84
+ * For ambient/platform wiring (a hosting layer injecting infra without touching
85
+ * the call site), any middleware may still
86
+ * `provideSandboxInstanceStore(ctx, store)` on the capability bus; an explicit
87
+ * option takes precedence over it.
88
+ */