open-multi-agent-kit 0.95.2 → 0.96.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 (259) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +37 -4
  3. package/dist/approvals/approval-receipt-store.d.ts.map +1 -1
  4. package/dist/approvals/approval-receipt-store.js +25 -0
  5. package/dist/approvals/approval-receipt-store.js.map +1 -1
  6. package/dist/commands/doctor-provider.d.ts.map +1 -1
  7. package/dist/commands/doctor-provider.js +15 -20
  8. package/dist/commands/doctor-provider.js.map +1 -1
  9. package/dist/commands/sdk-session-cli.d.ts +24 -0
  10. package/dist/commands/sdk-session-cli.d.ts.map +1 -0
  11. package/dist/commands/sdk-session-cli.js +225 -0
  12. package/dist/commands/sdk-session-cli.js.map +1 -0
  13. package/dist/core/advisory-judge-model.d.ts +18 -0
  14. package/dist/core/advisory-judge-model.d.ts.map +1 -0
  15. package/dist/core/advisory-judge-model.js +168 -0
  16. package/dist/core/advisory-judge-model.js.map +1 -0
  17. package/dist/core/advisory-judge-response.d.ts +8 -0
  18. package/dist/core/advisory-judge-response.d.ts.map +1 -0
  19. package/dist/core/advisory-judge-response.js +61 -0
  20. package/dist/core/advisory-judge-response.js.map +1 -0
  21. package/dist/core/advisory-judge-types.d.ts +53 -0
  22. package/dist/core/advisory-judge-types.d.ts.map +1 -0
  23. package/dist/core/advisory-judge-types.js +2 -0
  24. package/dist/core/advisory-judge-types.js.map +1 -0
  25. package/dist/core/advisory-judge.d.ts +8 -0
  26. package/dist/core/advisory-judge.d.ts.map +1 -0
  27. package/dist/core/advisory-judge.js +178 -0
  28. package/dist/core/advisory-judge.js.map +1 -0
  29. package/dist/core/agent-session-services.d.ts +1 -0
  30. package/dist/core/agent-session-services.d.ts.map +1 -1
  31. package/dist/core/agent-session-services.js +1 -0
  32. package/dist/core/agent-session-services.js.map +1 -1
  33. package/dist/core/agent-session.d.ts +6 -3
  34. package/dist/core/agent-session.d.ts.map +1 -1
  35. package/dist/core/agent-session.js +141 -184
  36. package/dist/core/agent-session.js.map +1 -1
  37. package/dist/core/auth-storage.d.ts.map +1 -1
  38. package/dist/core/auth-storage.js +28 -4
  39. package/dist/core/auth-storage.js.map +1 -1
  40. package/dist/core/compaction-gate.d.ts +28 -0
  41. package/dist/core/compaction-gate.d.ts.map +1 -0
  42. package/dist/core/compaction-gate.js +34 -0
  43. package/dist/core/compaction-gate.js.map +1 -0
  44. package/dist/core/context-budget-system-prompt-items.d.ts.map +1 -1
  45. package/dist/core/context-budget-system-prompt-items.js +5 -3
  46. package/dist/core/context-budget-system-prompt-items.js.map +1 -1
  47. package/dist/core/domain-loadouts.d.ts.map +1 -1
  48. package/dist/core/domain-loadouts.js +2 -2
  49. package/dist/core/domain-loadouts.js.map +1 -1
  50. package/dist/core/durable-goal-checkpoint.d.ts +6 -0
  51. package/dist/core/durable-goal-checkpoint.d.ts.map +1 -0
  52. package/dist/core/durable-goal-checkpoint.js +195 -0
  53. package/dist/core/durable-goal-checkpoint.js.map +1 -0
  54. package/dist/core/durable-goal-parse.d.ts +3 -0
  55. package/dist/core/durable-goal-parse.d.ts.map +1 -0
  56. package/dist/core/durable-goal-parse.js +86 -0
  57. package/dist/core/durable-goal-parse.js.map +1 -0
  58. package/dist/core/durable-goal-reducer.d.ts +3 -0
  59. package/dist/core/durable-goal-reducer.d.ts.map +1 -0
  60. package/dist/core/durable-goal-reducer.js +175 -0
  61. package/dist/core/durable-goal-reducer.js.map +1 -0
  62. package/dist/core/durable-goal-store.d.ts +19 -0
  63. package/dist/core/durable-goal-store.d.ts.map +1 -0
  64. package/dist/core/durable-goal-store.js +126 -0
  65. package/dist/core/durable-goal-store.js.map +1 -0
  66. package/dist/core/durable-goal.d.ts +92 -0
  67. package/dist/core/durable-goal.d.ts.map +1 -0
  68. package/dist/core/durable-goal.js +62 -0
  69. package/dist/core/durable-goal.js.map +1 -0
  70. package/dist/core/extensions/builtin/goal-controller.d.ts +3 -0
  71. package/dist/core/extensions/builtin/goal-controller.d.ts.map +1 -0
  72. package/dist/core/extensions/builtin/goal-controller.js +125 -0
  73. package/dist/core/extensions/builtin/goal-controller.js.map +1 -0
  74. package/dist/core/extensions/builtin/identical-loop.d.ts +3 -0
  75. package/dist/core/extensions/builtin/identical-loop.d.ts.map +1 -0
  76. package/dist/core/extensions/builtin/identical-loop.js +31 -0
  77. package/dist/core/extensions/builtin/identical-loop.js.map +1 -0
  78. package/dist/core/extensions/builtin/prompt-preset.d.ts +3 -0
  79. package/dist/core/extensions/builtin/prompt-preset.d.ts.map +1 -0
  80. package/dist/core/extensions/builtin/prompt-preset.js +13 -0
  81. package/dist/core/extensions/builtin/prompt-preset.js.map +1 -0
  82. package/dist/core/extensions/builtin/tool-pair-repair.d.ts +3 -0
  83. package/dist/core/extensions/builtin/tool-pair-repair.d.ts.map +1 -0
  84. package/dist/core/extensions/builtin/tool-pair-repair.js +12 -0
  85. package/dist/core/extensions/builtin/tool-pair-repair.js.map +1 -0
  86. package/dist/core/extensions/types.d.ts +6 -2
  87. package/dist/core/extensions/types.d.ts.map +1 -1
  88. package/dist/core/extensions/types.js.map +1 -1
  89. package/dist/core/goal-continuation.d.ts +13 -0
  90. package/dist/core/goal-continuation.d.ts.map +1 -0
  91. package/dist/core/goal-continuation.js +10 -0
  92. package/dist/core/goal-continuation.js.map +1 -0
  93. package/dist/core/grok-harness.d.ts +1 -1
  94. package/dist/core/grok-harness.d.ts.map +1 -1
  95. package/dist/core/grok-harness.js +1 -1
  96. package/dist/core/grok-harness.js.map +1 -1
  97. package/dist/core/grok-playbook.d.ts +1 -1
  98. package/dist/core/grok-playbook.d.ts.map +1 -1
  99. package/dist/core/grok-playbook.js +1 -1
  100. package/dist/core/grok-playbook.js.map +1 -1
  101. package/dist/core/identical-loop.d.ts +15 -0
  102. package/dist/core/identical-loop.d.ts.map +1 -0
  103. package/dist/core/identical-loop.js +34 -0
  104. package/dist/core/identical-loop.js.map +1 -0
  105. package/dist/core/index.d.ts +1 -1
  106. package/dist/core/index.d.ts.map +1 -1
  107. package/dist/core/index.js +1 -1
  108. package/dist/core/index.js.map +1 -1
  109. package/dist/core/model-prompt-policy.d.ts +11 -0
  110. package/dist/core/model-prompt-policy.d.ts.map +1 -0
  111. package/dist/core/model-prompt-policy.js +17 -0
  112. package/dist/core/model-prompt-policy.js.map +1 -0
  113. package/dist/core/model-registry.d.ts.map +1 -1
  114. package/dist/core/model-registry.js +16 -4
  115. package/dist/core/model-registry.js.map +1 -1
  116. package/dist/core/model-resolver.d.ts.map +1 -1
  117. package/dist/core/model-resolver.js +2 -2
  118. package/dist/core/model-resolver.js.map +1 -1
  119. package/dist/core/prompt-budget.d.ts +21 -0
  120. package/dist/core/prompt-budget.d.ts.map +1 -0
  121. package/dist/core/prompt-budget.js +51 -0
  122. package/dist/core/prompt-budget.js.map +1 -0
  123. package/dist/core/prompt-cache.d.ts +14 -0
  124. package/dist/core/prompt-cache.d.ts.map +1 -0
  125. package/dist/core/prompt-cache.js +16 -0
  126. package/dist/core/prompt-cache.js.map +1 -0
  127. package/dist/core/prompt-preset.d.ts +8 -0
  128. package/dist/core/prompt-preset.d.ts.map +1 -0
  129. package/dist/core/prompt-preset.js +56 -0
  130. package/dist/core/prompt-preset.js.map +1 -0
  131. package/dist/core/provider-resilience.d.ts +23 -0
  132. package/dist/core/provider-resilience.d.ts.map +1 -1
  133. package/dist/core/provider-resilience.js +34 -1
  134. package/dist/core/provider-resilience.js.map +1 -1
  135. package/dist/core/provider-retry.d.ts +29 -0
  136. package/dist/core/provider-retry.d.ts.map +1 -0
  137. package/dist/core/provider-retry.js +49 -0
  138. package/dist/core/provider-retry.js.map +1 -0
  139. package/dist/core/provider-usage.d.ts +2 -1
  140. package/dist/core/provider-usage.d.ts.map +1 -1
  141. package/dist/core/provider-usage.js +40 -6
  142. package/dist/core/provider-usage.js.map +1 -1
  143. package/dist/core/resource-loader.d.ts.map +1 -1
  144. package/dist/core/resource-loader.js +32 -0
  145. package/dist/core/resource-loader.js.map +1 -1
  146. package/dist/core/sandbox/backend.d.ts +12 -0
  147. package/dist/core/sandbox/backend.d.ts.map +1 -1
  148. package/dist/core/sandbox/backend.js +52 -42
  149. package/dist/core/sandbox/backend.js.map +1 -1
  150. package/dist/core/sandbox/policy.d.ts +2 -0
  151. package/dist/core/sandbox/policy.d.ts.map +1 -1
  152. package/dist/core/sandbox/policy.js +2 -1
  153. package/dist/core/sandbox/policy.js.map +1 -1
  154. package/dist/core/sdk.d.ts +2 -0
  155. package/dist/core/sdk.d.ts.map +1 -1
  156. package/dist/core/sdk.js +1 -0
  157. package/dist/core/sdk.js.map +1 -1
  158. package/dist/core/session-system-prompt.d.ts +28 -0
  159. package/dist/core/session-system-prompt.d.ts.map +1 -0
  160. package/dist/core/session-system-prompt.js +37 -0
  161. package/dist/core/session-system-prompt.js.map +1 -0
  162. package/dist/core/strict-record.d.ts +2 -0
  163. package/dist/core/strict-record.d.ts.map +1 -0
  164. package/dist/core/strict-record.js +9 -0
  165. package/dist/core/strict-record.js.map +1 -0
  166. package/dist/core/subagent-orchestration.d.ts.map +1 -1
  167. package/dist/core/subagent-orchestration.js +4 -2
  168. package/dist/core/subagent-orchestration.js.map +1 -1
  169. package/dist/core/tool-pair-repair.d.ts +11 -0
  170. package/dist/core/tool-pair-repair.d.ts.map +1 -0
  171. package/dist/core/tool-pair-repair.js +38 -0
  172. package/dist/core/tool-pair-repair.js.map +1 -0
  173. package/dist/core/tools/artifact-spill.d.ts +13 -0
  174. package/dist/core/tools/artifact-spill.d.ts.map +1 -0
  175. package/dist/core/tools/artifact-spill.js +26 -0
  176. package/dist/core/tools/artifact-spill.js.map +1 -0
  177. package/dist/core/tools/bash.d.ts +7 -4
  178. package/dist/core/tools/bash.d.ts.map +1 -1
  179. package/dist/core/tools/bash.js +8 -7
  180. package/dist/core/tools/bash.js.map +1 -1
  181. package/dist/core/tools/diagnostics.d.ts.map +1 -1
  182. package/dist/core/tools/diagnostics.js +8 -2
  183. package/dist/core/tools/diagnostics.js.map +1 -1
  184. package/dist/core/tools/index.d.ts +1 -1
  185. package/dist/core/tools/index.d.ts.map +1 -1
  186. package/dist/core/tools/index.js.map +1 -1
  187. package/dist/core/tools/read.d.ts +1 -0
  188. package/dist/core/tools/read.d.ts.map +1 -1
  189. package/dist/core/tools/read.js +10 -1
  190. package/dist/core/tools/read.js.map +1 -1
  191. package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
  192. package/dist/core/tools/tool-definition-wrapper.js +2 -0
  193. package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
  194. package/dist/guardrails/evidence-protocol.d.ts +10 -0
  195. package/dist/guardrails/evidence-protocol.d.ts.map +1 -0
  196. package/dist/guardrails/evidence-protocol.js +34 -0
  197. package/dist/guardrails/evidence-protocol.js.map +1 -0
  198. package/dist/guardrails/evidence-system.d.ts +3 -1
  199. package/dist/guardrails/evidence-system.d.ts.map +1 -1
  200. package/dist/guardrails/evidence-system.js +22 -14
  201. package/dist/guardrails/evidence-system.js.map +1 -1
  202. package/dist/guardrails/replay-ledger-lock-owner.d.ts.map +1 -1
  203. package/dist/guardrails/replay-ledger-lock-owner.js +3 -1
  204. package/dist/guardrails/replay-ledger-lock-owner.js.map +1 -1
  205. package/dist/guardrails/replay-payload-hash.d.ts +6 -0
  206. package/dist/guardrails/replay-payload-hash.d.ts.map +1 -0
  207. package/dist/guardrails/replay-payload-hash.js +24 -0
  208. package/dist/guardrails/replay-payload-hash.js.map +1 -0
  209. package/dist/index.d.ts +20 -2
  210. package/dist/index.d.ts.map +1 -1
  211. package/dist/index.js +19 -0
  212. package/dist/index.js.map +1 -1
  213. package/dist/main.d.ts.map +1 -1
  214. package/dist/main.js +8 -0
  215. package/dist/main.js.map +1 -1
  216. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  217. package/dist/modes/interactive/theme/theme.js +14 -3
  218. package/dist/modes/interactive/theme/theme.js.map +1 -1
  219. package/dist/types/evidence.d.ts +11 -3
  220. package/dist/types/evidence.d.ts.map +1 -1
  221. package/dist/types/evidence.js.map +1 -1
  222. package/docs/adaptorch-preview-spec.md +1 -1
  223. package/docs/adaptorch-preview.md +2 -2
  224. package/docs/book-to-skill.md +79 -0
  225. package/docs/compaction.md +10 -0
  226. package/docs/containerization.md +9 -0
  227. package/docs/docs.json +18 -2
  228. package/docs/environment-variables.md +11 -4
  229. package/docs/extensions.md +14 -1
  230. package/docs/grok-harness.md +35 -12
  231. package/docs/index.md +9 -5
  232. package/docs/loadout-domains/grok-harness.md +2 -2
  233. package/docs/models.md +21 -8
  234. package/docs/provider-resilience.md +42 -55
  235. package/docs/providers.md +13 -4
  236. package/docs/run-protocol.md +104 -0
  237. package/docs/sdk.md +111 -5
  238. package/docs/security.md +22 -32
  239. package/docs/sessions.md +20 -0
  240. package/docs/settings.md +2 -0
  241. package/docs/skills.md +9 -1
  242. package/docs/usage.md +38 -16
  243. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  244. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  245. package/examples/extensions/custom-provider-gitlab-duo/index.ts +6 -1
  246. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  247. package/examples/extensions/gondolin/package-lock.json +2 -2
  248. package/examples/extensions/gondolin/package.json +1 -1
  249. package/examples/extensions/sandbox/package-lock.json +2 -2
  250. package/examples/extensions/sandbox/package.json +1 -1
  251. package/examples/extensions/with-deps/package-lock.json +2 -2
  252. package/examples/extensions/with-deps/package.json +1 -1
  253. package/npm-shrinkwrap.json +37 -15
  254. package/package.json +8 -5
  255. package/dist/core/context-budget-compressors.d.ts +0 -50
  256. package/dist/core/context-budget-compressors.d.ts.map +0 -1
  257. package/dist/core/context-budget-compressors.js +0 -166
  258. package/dist/core/context-budget-compressors.js.map +0 -1
  259. package/dist/core/extensions/loader.js.bak-sharedjiti-20260812-111246 +0 -553
@@ -1 +1 @@
1
- {"version":3,"file":"backend.js","sourceRoot":"","sources":["../../../src/core/sandbox/backend.ts"],"names":[],"mappings":"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","sourcesContent":["import { execSync } from \"node:child_process\";\nimport type { NetworkMode, SandboxBackendStatus, SandboxDecision, SandboxPolicy } from \"./policy.ts\";\n\nexport type SandboxBackendType = \"unsupported\" | \"seatbelt\" | \"bubblewrap\";\n\nexport interface SandboxInvocation {\n\treadonly argv: readonly string[];\n\treadonly cwd: string;\n\treadonly env: Record<string, string>;\n\treadonly filesystem: {\n\t\treadonly root: string;\n\t\treadonly readAllow: readonly string[];\n\t\treadonly writeAllow: readonly string[];\n\t\treadonly tempWrite: readonly string[];\n\t\treadonly denyWrite: readonly string[];\n\t};\n\treadonly network: {\n\t\treadonly mode: NetworkMode;\n\t\treadonly allowedDomains?: readonly string[];\n\t\treadonly deniedDomains?: readonly string[];\n\t};\n}\n\nfunction commandExists(command: string): boolean {\n\ttry {\n\t\texecSync(`command -v ${command}`, { stdio: \"ignore\" });\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction userNamespacesEnabled(): boolean {\n\ttry {\n\t\tconst value = execSync(\"sysctl -n kernel.unprivileged_userns_clone\", {\n\t\t\tstdio: [\"ignore\", \"pipe\", \"ignore\"],\n\t\t\tencoding: \"utf8\",\n\t\t}).trim();\n\t\treturn value === \"1\";\n\t} catch {\n\t\treturn true;\n\t}\n}\n\nexport function detectSandboxBackend(): SandboxBackendStatus {\n\tif (process.platform === \"darwin\") {\n\t\tconst available = commandExists(\"sandbox-exec\");\n\t\treturn {\n\t\t\tplatform: \"macos\",\n\t\t\tbackendAvailable: available,\n\t\t\tdomainAllowlistAvailable: available,\n\t\t};\n\t}\n\tif (process.platform === \"linux\") {\n\t\tconst bwrap = commandExists(\"bwrap\");\n\t\tconst userns = userNamespacesEnabled();\n\t\treturn {\n\t\t\tplatform: \"linux\",\n\t\t\tbackendAvailable: bwrap && userns,\n\t\t\tdomainAllowlistAvailable: false,\n\t\t};\n\t}\n\treturn {\n\t\tplatform: \"unsupported\",\n\t\tbackendAvailable: false,\n\t};\n}\n\nfunction seatbeltLiteral(value: string): string {\n\treturn JSON.stringify(value);\n}\n\nfunction seatbeltProfile(invocation: SandboxInvocation): string {\n\tconst lines: string[] = [\"(version 1)\", \"(debug deny)\", \"(allow default)\"];\n\tconst writablePaths = [...new Set([...invocation.filesystem.writeAllow, ...invocation.filesystem.tempWrite])];\n\n\tif (writablePaths.length === 0) {\n\t\tlines.push(\"(deny file-write*)\");\n\t} else {\n\t\tlines.push(\"(deny file-write*\");\n\t\tfor (const path of writablePaths) {\n\t\t\tlines.push(` (require-not (subpath ${seatbeltLiteral(path)}))`);\n\t\t}\n\t\tlines.push(' (require-not (literal \"/dev/null\"))');\n\t\tlines.push(' (require-not (literal \"/dev/tty\"))');\n\t\tlines.push(\")\");\n\t}\n\tfor (const path of invocation.filesystem.denyWrite) {\n\t\tlines.push(`(deny file-write* (subpath ${seatbeltLiteral(path)}))`);\n\t}\n\n\tif (invocation.network.mode === \"none\") {\n\t\tlines.push(\"(deny network*)\");\n\t} else if (invocation.network.mode === \"domain-allowlist\" && invocation.network.allowedDomains) {\n\t\tlines.push(\"(deny network*)\");\n\t\tfor (const domain of invocation.network.allowedDomains) {\n\t\t\tlines.push(`(allow network-outbound (remote ${seatbeltLiteral(domain)}))`);\n\t\t}\n\t} else if (invocation.network.mode === \"loopback\") {\n\t\tlines.push(\"(deny network*)\");\n\t\tlines.push('(allow network-outbound (remote \"localhost\"))');\n\t\tlines.push('(allow network-outbound (remote \"127.0.0.1\"))');\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n\nexport function buildSeatbeltArgv(invocation: SandboxInvocation): readonly string[] {\n\treturn [\"sandbox-exec\", \"-p\", seatbeltProfile(invocation), ...invocation.argv];\n}\n\nexport function buildBubblewrapArgv(invocation: SandboxInvocation): readonly string[] {\n\tconst argv: string[] = [\n\t\t\"bwrap\",\n\t\t\"--die-with-parent\",\n\t\t\"--new-session\",\n\t\t\"--unshare-all\",\n\t\t\"--ro-bind\",\n\t\t\"/\",\n\t\t\"/\",\n\t\t\"--proc\",\n\t\t\"/proc\",\n\t\t\"--dev\",\n\t\t\"/dev\",\n\t];\n\n\tfor (const path of invocation.filesystem.readAllow) {\n\t\tif (path !== \"/\" && path !== invocation.filesystem.root) {\n\t\t\targv.push(\"--ro-bind\", path, path);\n\t\t}\n\t}\n\tfor (const path of invocation.filesystem.tempWrite) {\n\t\targv.push(\"--bind\", path, path);\n\t}\n\targv.push(\"--bind\", invocation.filesystem.root, invocation.filesystem.root);\n\tfor (const path of invocation.filesystem.writeAllow) {\n\t\tif (path !== invocation.filesystem.root) {\n\t\t\targv.push(\"--bind\", path, path);\n\t\t}\n\t}\n\tfor (const path of invocation.filesystem.denyWrite) {\n\t\targv.push(\"--remount-ro\", path);\n\t}\n\n\tif (invocation.network.mode === \"none\") {\n\t\targv.push(\"--unshare-net\");\n\t} else {\n\t\targv.push(\"--share-net\");\n\t}\n\n\targv.push(\"--chdir\", invocation.cwd);\n\targv.push(\"--clearenv\");\n\tfor (const [key, value] of Object.entries(invocation.env)) {\n\t\targv.push(\"--setenv\", key, value);\n\t}\n\n\targv.push(...invocation.argv);\n\treturn argv;\n}\n\nexport function buildBackendArgv(backend: SandboxBackendStatus, invocation: SandboxInvocation): readonly string[] {\n\tif (backend.platform === \"macos\" && backend.backendAvailable) {\n\t\treturn buildSeatbeltArgv(invocation);\n\t}\n\tif (backend.platform === \"linux\" && backend.backendAvailable) {\n\t\treturn buildBubblewrapArgv(invocation);\n\t}\n\tthrow new Error(`Unsupported or unavailable sandbox backend: ${backend.platform}`);\n}\n\nexport function strictBackendMissingVerdict(policy: SandboxPolicy, backend: SandboxBackendStatus): SandboxDecision {\n\tif (policy.mode === \"off\") {\n\t\treturn {\n\t\t\tallowed: true,\n\t\t\trule: \"sandbox.off\",\n\t\t\treason: \"Sandbox policy is disabled.\",\n\t\t};\n\t}\n\tif (backend.backendAvailable) {\n\t\treturn {\n\t\t\tallowed: true,\n\t\t\trule: \"sandbox.backend_available\",\n\t\t\treason: \"Sandbox backend is available.\",\n\t\t};\n\t}\n\tif (policy.mode === \"audit\") {\n\t\treturn {\n\t\t\tallowed: true,\n\t\t\trule: \"sandbox.audit_fallback\",\n\t\t\treason: \"Audit mode permits execution without backend.\",\n\t\t};\n\t}\n\treturn {\n\t\tallowed: false,\n\t\trule: \"sandbox.backend_missing\",\n\t\treason: \"Enforce mode blocks shell and exec when no backend is available.\",\n\t};\n}\n"]}
1
+ {"version":3,"file":"backend.js","sourceRoot":"","sources":["../../../src/core/sandbox/backend.ts"],"names":[],"mappings":"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","sourcesContent":["import { execSync } from \"node:child_process\";\nimport type { NetworkMode, SandboxBackendStatus, SandboxDecision, SandboxPolicy } from \"./policy.ts\";\nimport { decideSandboxFallback } from \"./policy.ts\";\n\nexport type SandboxBackendType = \"unsupported\" | \"seatbelt\" | \"bubblewrap\";\n\nexport type SandboxBackendProbe =\n\t| { readonly platform: \"macos\"; readonly seatbeltAvailable: boolean }\n\t| { readonly platform: \"linux\"; readonly bubblewrapAvailable: boolean; readonly userNamespacesEnabled: boolean }\n\t| { readonly platform: \"unsupported\"; readonly hostPlatform: string };\n\nexport interface SandboxInvocation {\n\treadonly argv: readonly string[];\n\treadonly cwd: string;\n\treadonly env: Record<string, string>;\n\treadonly filesystem: {\n\t\treadonly root: string;\n\t\treadonly readAllow: readonly string[];\n\t\treadonly writeAllow: readonly string[];\n\t\treadonly tempWrite: readonly string[];\n\t\treadonly denyWrite: readonly string[];\n\t};\n\treadonly network: {\n\t\treadonly mode: NetworkMode;\n\t\treadonly allowedDomains?: readonly string[];\n\t\treadonly deniedDomains?: readonly string[];\n\t};\n}\n\nfunction commandExists(command: string): boolean {\n\ttry {\n\t\texecSync(`command -v ${command}`, { stdio: \"ignore\" });\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction userNamespacesEnabled(): boolean {\n\ttry {\n\t\tconst value = execSync(\"sysctl -n kernel.unprivileged_userns_clone\", {\n\t\t\tstdio: [\"ignore\", \"pipe\", \"ignore\"],\n\t\t\tencoding: \"utf8\",\n\t\t}).trim();\n\t\treturn value === \"1\";\n\t} catch {\n\t\treturn true;\n\t}\n}\n\nfunction assertNever(value: never): never {\n\tthrow new Error(`Unexpected sandbox backend probe: ${JSON.stringify(value)}`);\n}\n\nexport function classifySandboxBackendProbe(probe: SandboxBackendProbe): SandboxBackendStatus {\n\tswitch (probe.platform) {\n\t\tcase \"macos\":\n\t\t\treturn {\n\t\t\t\tplatform: \"macos\",\n\t\t\t\tbackendAvailable: probe.seatbeltAvailable,\n\t\t\t\tdomainAllowlistAvailable: probe.seatbeltAvailable,\n\t\t\t\t...(probe.seatbeltAvailable ? {} : { unavailableReason: \"sandbox-exec is not installed or unavailable.\" }),\n\t\t\t};\n\t\tcase \"linux\": {\n\t\t\tconst backendAvailable = probe.bubblewrapAvailable && probe.userNamespacesEnabled;\n\t\t\tlet unavailableReason: string | undefined;\n\t\t\tif (!probe.bubblewrapAvailable) {\n\t\t\t\tunavailableReason = probe.userNamespacesEnabled\n\t\t\t\t\t? \"bwrap is not installed or unavailable.\"\n\t\t\t\t\t: \"bwrap is unavailable and unprivileged user namespaces are disabled.\";\n\t\t\t} else if (!probe.userNamespacesEnabled) {\n\t\t\t\tunavailableReason = \"Unprivileged user namespaces are disabled.\";\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tplatform: \"linux\",\n\t\t\t\tbackendAvailable,\n\t\t\t\tdomainAllowlistAvailable: false,\n\t\t\t\t...(unavailableReason ? { unavailableReason } : {}),\n\t\t\t};\n\t\t}\n\t\tcase \"unsupported\":\n\t\t\treturn {\n\t\t\t\tplatform: \"unsupported\",\n\t\t\t\tbackendAvailable: false,\n\t\t\t\tunavailableReason: `No supported sandbox backend exists for ${probe.hostPlatform}.`,\n\t\t\t};\n\t\tdefault:\n\t\t\treturn assertNever(probe);\n\t}\n}\n\nexport function detectSandboxBackend(): SandboxBackendStatus {\n\tif (process.platform === \"darwin\") {\n\t\treturn classifySandboxBackendProbe({\n\t\t\tplatform: \"macos\",\n\t\t\tseatbeltAvailable: commandExists(\"sandbox-exec\"),\n\t\t});\n\t}\n\tif (process.platform === \"linux\") {\n\t\treturn classifySandboxBackendProbe({\n\t\t\tplatform: \"linux\",\n\t\t\tbubblewrapAvailable: commandExists(\"bwrap\"),\n\t\t\tuserNamespacesEnabled: userNamespacesEnabled(),\n\t\t});\n\t}\n\treturn classifySandboxBackendProbe({ platform: \"unsupported\", hostPlatform: process.platform });\n}\n\nfunction seatbeltLiteral(value: string): string {\n\treturn JSON.stringify(value);\n}\n\nfunction seatbeltProfile(invocation: SandboxInvocation): string {\n\tconst lines: string[] = [\"(version 1)\", \"(debug deny)\", \"(allow default)\"];\n\tconst writablePaths = [...new Set([...invocation.filesystem.writeAllow, ...invocation.filesystem.tempWrite])];\n\n\tif (writablePaths.length === 0) {\n\t\tlines.push(\"(deny file-write*)\");\n\t} else {\n\t\tlines.push(\"(deny file-write*\");\n\t\tfor (const path of writablePaths) {\n\t\t\tlines.push(` (require-not (subpath ${seatbeltLiteral(path)}))`);\n\t\t}\n\t\tlines.push(' (require-not (literal \"/dev/null\"))');\n\t\tlines.push(' (require-not (literal \"/dev/tty\"))');\n\t\tlines.push(\")\");\n\t}\n\tfor (const path of invocation.filesystem.denyWrite) {\n\t\tlines.push(`(deny file-write* (subpath ${seatbeltLiteral(path)}))`);\n\t}\n\n\tif (invocation.network.mode === \"none\") {\n\t\tlines.push(\"(deny network*)\");\n\t} else if (invocation.network.mode === \"domain-allowlist\" && invocation.network.allowedDomains) {\n\t\tlines.push(\"(deny network*)\");\n\t\tfor (const domain of invocation.network.allowedDomains) {\n\t\t\tlines.push(`(allow network-outbound (remote ${seatbeltLiteral(domain)}))`);\n\t\t}\n\t} else if (invocation.network.mode === \"loopback\") {\n\t\tlines.push(\"(deny network*)\");\n\t\tlines.push('(allow network-outbound (remote \"localhost\"))');\n\t\tlines.push('(allow network-outbound (remote \"127.0.0.1\"))');\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n\nexport function buildSeatbeltArgv(invocation: SandboxInvocation): readonly string[] {\n\treturn [\"sandbox-exec\", \"-p\", seatbeltProfile(invocation), ...invocation.argv];\n}\n\nexport function buildBubblewrapArgv(invocation: SandboxInvocation): readonly string[] {\n\tconst argv: string[] = [\n\t\t\"bwrap\",\n\t\t\"--die-with-parent\",\n\t\t\"--new-session\",\n\t\t\"--unshare-all\",\n\t\t\"--ro-bind\",\n\t\t\"/\",\n\t\t\"/\",\n\t\t\"--proc\",\n\t\t\"/proc\",\n\t\t\"--dev\",\n\t\t\"/dev\",\n\t];\n\n\tfor (const path of invocation.filesystem.readAllow) {\n\t\tif (path !== \"/\" && path !== invocation.filesystem.root) {\n\t\t\targv.push(\"--ro-bind\", path, path);\n\t\t}\n\t}\n\tfor (const path of invocation.filesystem.tempWrite) {\n\t\targv.push(\"--bind\", path, path);\n\t}\n\targv.push(\"--bind\", invocation.filesystem.root, invocation.filesystem.root);\n\tfor (const path of invocation.filesystem.writeAllow) {\n\t\tif (path !== invocation.filesystem.root) {\n\t\t\targv.push(\"--bind\", path, path);\n\t\t}\n\t}\n\tfor (const path of invocation.filesystem.denyWrite) {\n\t\targv.push(\"--remount-ro\", path);\n\t}\n\n\tif (invocation.network.mode === \"none\") {\n\t\targv.push(\"--unshare-net\");\n\t} else {\n\t\targv.push(\"--share-net\");\n\t}\n\n\targv.push(\"--chdir\", invocation.cwd);\n\targv.push(\"--clearenv\");\n\tfor (const [key, value] of Object.entries(invocation.env)) {\n\t\targv.push(\"--setenv\", key, value);\n\t}\n\n\targv.push(...invocation.argv);\n\treturn argv;\n}\n\nexport function buildBackendArgv(backend: SandboxBackendStatus, invocation: SandboxInvocation): readonly string[] {\n\tif (backend.platform === \"macos\" && backend.backendAvailable) {\n\t\treturn buildSeatbeltArgv(invocation);\n\t}\n\tif (backend.platform === \"linux\" && backend.backendAvailable) {\n\t\treturn buildBubblewrapArgv(invocation);\n\t}\n\tthrow new Error(`Unsupported or unavailable sandbox backend: ${backend.platform}`);\n}\n\nexport function strictBackendMissingVerdict(policy: SandboxPolicy, backend: SandboxBackendStatus): SandboxDecision {\n\tconst { allowed, rule, reason } = decideSandboxFallback(policy, backend);\n\treturn { allowed, rule, reason };\n}\n"]}
@@ -57,6 +57,8 @@ export interface SandboxBackendStatus {
57
57
  platform: SandboxPlatform;
58
58
  backendAvailable: boolean;
59
59
  domainAllowlistAvailable?: boolean;
60
+ /** Human-readable probe detail when the backend cannot enforce the policy. */
61
+ unavailableReason?: string;
60
62
  }
61
63
  export interface SandboxFallbackDecision extends SandboxDecision {
62
64
  allowShell: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../../../src/core/sandbox/policy.ts"],"names":[],"mappings":"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","sourcesContent":["import path from \"node:path\";\n\nexport type SandboxMode = \"off\" | \"audit\" | \"enforce\";\nexport type SandboxProfile = \"readonly\" | \"workspace-write\" | \"dev-server\" | \"networked\";\nexport type NetworkMode = \"none\" | \"loopback\" | \"domain-allowlist\" | \"all-explicit\";\nexport type PathAccessKind = \"read\" | \"write\";\nexport type SandboxPlatform = \"linux\" | \"macos\" | \"unsupported\";\n\nexport interface SandboxPolicy {\n\tmode: SandboxMode;\n\tprofile: SandboxProfile;\n\tfilesystem: {\n\t\troot: string;\n\t\treadAllow: readonly string[];\n\t\treadDeny: readonly string[];\n\t\twriteAllow: readonly string[];\n\t\tdenyWrite: readonly string[];\n\t\ttempWrite: readonly string[];\n\t\tfollowSymlinks: false;\n\t};\n\tnetwork: {\n\t\tmode: NetworkMode;\n\t\tallowedDomains: readonly string[];\n\t\tdeniedDomains: readonly string[];\n\t\tallowUnixSockets: readonly string[];\n\t\tallowBrowser: false;\n\t};\n\tprocess: {\n\t\tallowExec: boolean;\n\t\tallowShell: boolean;\n\t\tallowPrivilege: false;\n\t};\n}\n\nexport interface ResolvedSandboxPath {\n\trequestedPath: string;\n\texists: boolean;\n\trealPath?: string;\n\tnearestExistingParentRealPath?: string;\n\tisSymlink?: boolean;\n\terror?: string;\n}\n\nexport type SandboxPathResolver = (requestPath: string) => ResolvedSandboxPath;\n\nexport interface PathAccessRequest {\n\tkind: PathAccessKind;\n\tpath: string;\n}\n\nexport interface SandboxDecision {\n\tallowed: boolean;\n\trule: string;\n\treason: string;\n}\n\nexport interface NetworkAccessRequest {\n\thost?: string;\n\turl?: string;\n\tunixSocketPath?: string;\n\tbrowser?: boolean;\n\tloopback?: boolean;\n}\n\nexport interface SandboxBackendStatus {\n\tplatform: SandboxPlatform;\n\tbackendAvailable: boolean;\n\tdomainAllowlistAvailable?: boolean;\n}\n\nexport interface SandboxFallbackDecision extends SandboxDecision {\n\tallowShell: boolean;\n\tallowExec: boolean;\n\tallowReadOnlyTools: boolean;\n}\n\nconst SENSITIVE_PATH_PATTERN =\n\t/(?:^|[/\\\\])(?:\\.env(?:\\..*)?|auth\\.json|oauth\\.json|\\.netrc|\\.npmrc|\\.pgpass|credentials|id_rsa|id_dsa|id_ecdsa|id_ed25519|.*(?:secret|token|private[_-]?key|credential).*)$/i;\n\nfunction decision(allowed: boolean, rule: string, reason: string): SandboxDecision {\n\treturn { allowed, rule, reason };\n}\n\nfunction normalize(value: string): string {\n\treturn path\n\t\t.resolve(value)\n\t\t.replace(/\\\\/g, \"/\")\n\t\t.replace(/\\/+$/g, (suffix) => (value === suffix ? \"/\" : \"\"));\n}\n\nfunction isInside(parent: string, child: string): boolean {\n\tconst normalizedParent = normalize(parent);\n\tconst normalizedChild = normalize(child);\n\treturn normalizedChild === normalizedParent || normalizedChild.startsWith(`${normalizedParent}/`);\n}\n\nfunction isSensitivePath(value: string): boolean {\n\treturn SENSITIVE_PATH_PATTERN.test(value.replace(/\\\\/g, \"/\"));\n}\n\nfunction stripGlobSuffix(pattern: string): string {\n\tif (pattern.endsWith(\"/**\")) return pattern.slice(0, -3);\n\tif (pattern.endsWith(\"/*\")) return pattern.slice(0, -2);\n\treturn pattern;\n}\n\nfunction matchesPattern(pattern: string, candidate: string): boolean {\n\tconst normalizedPattern = normalize(stripGlobSuffix(pattern));\n\tconst normalizedCandidate = normalize(candidate);\n\treturn normalizedCandidate === normalizedPattern || normalizedCandidate.startsWith(`${normalizedPattern}/`);\n}\n\nfunction matchesAny(patterns: readonly string[], candidate: string): boolean {\n\treturn patterns.some((pattern) => matchesPattern(pattern, candidate));\n}\n\nfunction resolvedDecisionPath(resolved: ResolvedSandboxPath): string | undefined {\n\treturn resolved.realPath ?? resolved.nearestExistingParentRealPath;\n}\n\nfunction rootEscaped(policy: SandboxPolicy, resolved: ResolvedSandboxPath): boolean {\n\tconst candidate = resolvedDecisionPath(resolved);\n\treturn candidate === undefined || !isInside(policy.filesystem.root, candidate);\n}\n\nexport function decidePathAccess(\n\tpolicy: SandboxPolicy,\n\trequest: PathAccessRequest,\n\tresolver: SandboxPathResolver,\n): SandboxDecision {\n\tif (policy.mode === \"off\") {\n\t\treturn decision(true, \"sandbox.off\", \"Sandbox policy is disabled.\");\n\t}\n\n\tif (request.path.includes(\"\\0\")) {\n\t\treturn decision(false, \"path.invalid\", \"Path contains a NUL byte.\");\n\t}\n\n\tif (isSensitivePath(request.path)) {\n\t\treturn decision(false, \"path.secret\", \"Sensitive credential-like paths are denied.\");\n\t}\n\n\tconst resolved = resolver(request.path);\n\tif (resolved.error) {\n\t\treturn decision(false, \"path.resolve_error\", resolved.error);\n\t}\n\tif (rootEscaped(policy, resolved)) {\n\t\treturn decision(false, \"path.root_escape\", \"Resolved path is outside the sandbox root.\");\n\t}\n\tif (resolved.isSymlink && !policy.filesystem.followSymlinks) {\n\t\treturn decision(false, \"path.symlink\", \"Symlink traversal is denied by policy.\");\n\t}\n\n\tconst candidate = resolvedDecisionPath(resolved) ?? request.path;\n\tif (request.kind === \"read\") {\n\t\tif (matchesAny(policy.filesystem.readDeny, candidate) || matchesAny(policy.filesystem.readDeny, request.path)) {\n\t\t\treturn decision(false, \"path.read_deny\", \"Read path matches a deny rule.\");\n\t\t}\n\t\tif (!matchesAny(policy.filesystem.readAllow, candidate)) {\n\t\t\treturn decision(false, \"path.read_not_allowed\", \"Read path is not in an allow rule.\");\n\t\t}\n\t\treturn decision(true, \"path.read_allow\", \"Read path is allowed.\");\n\t}\n\n\tif (matchesAny(policy.filesystem.denyWrite, candidate) || matchesAny(policy.filesystem.denyWrite, request.path)) {\n\t\treturn decision(false, \"path.deny_write\", \"Write path matches an unoverrideable denyWrite rule.\");\n\t}\n\tif (matchesAny(policy.filesystem.writeAllow, candidate) || matchesAny(policy.filesystem.tempWrite, candidate)) {\n\t\treturn decision(true, \"path.write_allow\", \"Write path is allowed.\");\n\t}\n\treturn decision(false, \"path.write_not_allowed\", \"Write path is not in an allow rule.\");\n}\n\nfunction hostFromRequest(request: NetworkAccessRequest): string | undefined {\n\tif (request.host) return request.host.toLowerCase();\n\tif (!request.url) return undefined;\n\ttry {\n\t\treturn new URL(request.url).hostname.toLowerCase();\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction hostMatches(patterns: readonly string[], host: string): boolean {\n\treturn patterns.some((pattern) => {\n\t\tconst normalized = pattern.toLowerCase();\n\t\treturn host === normalized || host.endsWith(`.${normalized}`);\n\t});\n}\n\nexport function decideNetworkAccess(policy: SandboxPolicy, request: NetworkAccessRequest): SandboxDecision {\n\tif (policy.mode === \"off\") return decision(true, \"sandbox.off\", \"Sandbox policy is disabled.\");\n\tif (request.browser || request.unixSocketPath) {\n\t\treturn decision(false, \"network.socket_or_browser\", \"Browser and Unix socket access is denied by default.\");\n\t}\n\tif (policy.network.mode === \"none\") {\n\t\treturn decision(false, \"network.none\", \"Network access is disabled by policy.\");\n\t}\n\tif (request.loopback) {\n\t\treturn policy.network.mode === \"loopback\" ||\n\t\t\tpolicy.network.mode === \"domain-allowlist\" ||\n\t\t\tpolicy.network.mode === \"all-explicit\"\n\t\t\t? decision(true, \"network.loopback\", \"Loopback access is allowed.\")\n\t\t\t: decision(false, \"network.loopback_denied\", \"Loopback access is denied.\");\n\t}\n\tconst host = hostFromRequest(request);\n\tif (!host) return decision(false, \"network.unknown_host\", \"Network host could not be resolved from request.\");\n\tif (hostMatches(policy.network.deniedDomains, host)) {\n\t\treturn decision(false, \"network.domain_deny\", \"Host matches a denied domain.\");\n\t}\n\tif (policy.network.mode === \"all-explicit\") {\n\t\treturn decision(true, \"network.explicit_all\", \"Explicit network profile allows outbound host access.\");\n\t}\n\tif (policy.network.mode === \"domain-allowlist\" && hostMatches(policy.network.allowedDomains, host)) {\n\t\treturn decision(true, \"network.domain_allow\", \"Host matches an allowed domain.\");\n\t}\n\treturn decision(false, \"network.domain_not_allowed\", \"Host is not allowed by network policy.\");\n}\n\nexport function decideSandboxFallback(policy: SandboxPolicy, backend: SandboxBackendStatus): SandboxFallbackDecision {\n\tif (policy.mode === \"off\") {\n\t\treturn {\n\t\t\t...decision(true, \"sandbox.off\", \"Sandbox policy is disabled.\"),\n\t\t\tallowShell: true,\n\t\t\tallowExec: true,\n\t\t\tallowReadOnlyTools: true,\n\t\t};\n\t}\n\tif (backend.backendAvailable) {\n\t\treturn {\n\t\t\t...decision(true, \"sandbox.backend_available\", \"Sandbox backend is available.\"),\n\t\t\tallowShell: policy.process.allowShell,\n\t\t\tallowExec: policy.process.allowExec,\n\t\t\tallowReadOnlyTools: true,\n\t\t};\n\t}\n\tif (policy.mode === \"audit\") {\n\t\treturn {\n\t\t\t...decision(true, \"sandbox.audit_fallback\", \"Audit mode permits execution without backend.\"),\n\t\t\tallowShell: true,\n\t\t\tallowExec: true,\n\t\t\tallowReadOnlyTools: true,\n\t\t};\n\t}\n\treturn {\n\t\t...decision(false, \"sandbox.backend_missing\", \"Enforce mode blocks shell and exec when no backend is available.\"),\n\t\tallowShell: false,\n\t\tallowExec: false,\n\t\tallowReadOnlyTools: true,\n\t};\n}\n\nexport function mergeSandboxPolicy(\n\tbase: SandboxPolicy,\n\toverride: Partial<SandboxPolicy>,\n\toptions: { allowBroaden?: boolean } = {},\n): SandboxPolicy {\n\tconst allowBroaden = options.allowBroaden === true;\n\treturn {\n\t\tmode: override.mode ?? base.mode,\n\t\tprofile: override.profile ?? base.profile,\n\t\tfilesystem: {\n\t\t\troot: override.filesystem?.root ?? base.filesystem.root,\n\t\t\treadAllow: allowBroaden\n\t\t\t\t? (override.filesystem?.readAllow ?? base.filesystem.readAllow)\n\t\t\t\t: intersectOrBase(base.filesystem.readAllow, override.filesystem?.readAllow),\n\t\t\treadDeny: union(base.filesystem.readDeny, override.filesystem?.readDeny),\n\t\t\twriteAllow: allowBroaden\n\t\t\t\t? (override.filesystem?.writeAllow ?? base.filesystem.writeAllow)\n\t\t\t\t: intersectOrBase(base.filesystem.writeAllow, override.filesystem?.writeAllow),\n\t\t\tdenyWrite: union(base.filesystem.denyWrite, override.filesystem?.denyWrite),\n\t\t\ttempWrite: allowBroaden\n\t\t\t\t? (override.filesystem?.tempWrite ?? base.filesystem.tempWrite)\n\t\t\t\t: intersectOrBase(base.filesystem.tempWrite, override.filesystem?.tempWrite),\n\t\t\tfollowSymlinks: false,\n\t\t},\n\t\tnetwork: {\n\t\t\tmode: allowBroaden\n\t\t\t\t? (override.network?.mode ?? base.network.mode)\n\t\t\t\t: narrowNetworkMode(base.network.mode, override.network?.mode),\n\t\t\tallowedDomains: allowBroaden\n\t\t\t\t? (override.network?.allowedDomains ?? base.network.allowedDomains)\n\t\t\t\t: intersectOrBase(base.network.allowedDomains, override.network?.allowedDomains),\n\t\t\tdeniedDomains: union(base.network.deniedDomains, override.network?.deniedDomains),\n\t\t\tallowUnixSockets: allowBroaden\n\t\t\t\t? (override.network?.allowUnixSockets ?? base.network.allowUnixSockets)\n\t\t\t\t: intersectOrBase(base.network.allowUnixSockets, override.network?.allowUnixSockets),\n\t\t\tallowBrowser: false,\n\t\t},\n\t\tprocess: {\n\t\t\tallowExec: allowBroaden\n\t\t\t\t? (override.process?.allowExec ?? base.process.allowExec)\n\t\t\t\t: base.process.allowExec && (override.process?.allowExec ?? true),\n\t\t\tallowShell: allowBroaden\n\t\t\t\t? (override.process?.allowShell ?? base.process.allowShell)\n\t\t\t\t: base.process.allowShell && (override.process?.allowShell ?? true),\n\t\t\tallowPrivilege: false,\n\t\t},\n\t};\n}\n\nfunction union(base: readonly string[], override: readonly string[] | undefined): string[] {\n\treturn [...new Set([...base, ...(override ?? [])])];\n}\n\nfunction intersectOrBase(base: readonly string[], override: readonly string[] | undefined): string[] {\n\tif (override === undefined) return [...base];\n\tconst result: string[] = [];\n\tfor (const candidate of override) {\n\t\tfor (const entry of base) {\n\t\t\tif (normalize(candidate) === normalize(entry)) {\n\t\t\t\tresult.push(candidate);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (matchesPattern(entry, candidate)) {\n\t\t\t\tresult.push(candidate);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (matchesPattern(candidate, entry)) {\n\t\t\t\tresult.push(entry);\n\t\t\t}\n\t\t}\n\t}\n\treturn [...new Set(result)];\n}\n\nfunction narrowNetworkMode(base: NetworkMode, override: NetworkMode | undefined): NetworkMode {\n\tif (override === undefined) return base;\n\tconst rank: Record<NetworkMode, number> = { none: 0, loopback: 1, \"domain-allowlist\": 2, \"all-explicit\": 3 };\n\treturn rank[override] <= rank[base] ? override : base;\n}\n\nexport interface BashSpawnPreflightContext {\n\tcommand: string;\n\tcwd: string;\n}\n\nexport interface BashSpawnPreflightDecision extends SandboxDecision {\n\tallowShell: boolean;\n}\n\n/**\n * Final pure preflight for a local bash spawn. Combines the backend-availability\n * fallback (deny shell when an OS sandbox backend is missing under enforce mode)\n * with a working-directory containment check (deny when cwd resolves outside the\n * sandbox root). This helper does not itself wrap the process in an OS sandbox; it\n * only decides whether the spawn may proceed.\n */\nexport function preflightBashSpawn(\n\tpolicy: SandboxPolicy,\n\tbackend: SandboxBackendStatus,\n\tcontext: BashSpawnPreflightContext,\n\tresolver?: SandboxPathResolver,\n): BashSpawnPreflightDecision {\n\tif (policy.mode === \"off\") {\n\t\treturn { ...decision(true, \"sandbox.off\", \"Sandbox policy is disabled.\"), allowShell: true };\n\t}\n\tif (!policy.process.allowShell) {\n\t\treturn {\n\t\t\t...decision(false, \"process.shell_denied\", \"Shell execution is disabled by policy.\"),\n\t\t\tallowShell: false,\n\t\t};\n\t}\n\tconst fallback = decideSandboxFallback(policy, backend);\n\tif (!fallback.allowShell) {\n\t\treturn { ...decision(false, fallback.rule, fallback.reason), allowShell: false };\n\t}\n\tif (context.cwd.includes(\"\\0\")) {\n\t\treturn { ...decision(false, \"cwd.invalid\", \"Working directory contains a NUL byte.\"), allowShell: false };\n\t}\n\tconst resolved = resolver?.(context.cwd);\n\tif (resolved?.error) {\n\t\treturn { ...decision(false, \"cwd.resolve_error\", resolved.error), allowShell: false };\n\t}\n\tif (resolved?.isSymlink) {\n\t\treturn {\n\t\t\t...decision(false, \"cwd.symlink\", \"Working directory is a symlink and traversal is denied.\"),\n\t\t\tallowShell: false,\n\t\t};\n\t}\n\tconst candidate = resolved ? (resolvedDecisionPath(resolved) ?? context.cwd) : context.cwd;\n\tif (!isInside(policy.filesystem.root, candidate)) {\n\t\treturn {\n\t\t\t...decision(false, \"cwd.root_escape\", \"Working directory is outside the sandbox root.\"),\n\t\t\tallowShell: false,\n\t\t};\n\t}\n\treturn {\n\t\t...decision(true, \"sandbox.shell_preflight_ok\", \"Bash spawn is permitted by sandbox policy.\"),\n\t\tallowShell: true,\n\t};\n}\n"]}
1
+ {"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../../../src/core/sandbox/policy.ts"],"names":[],"mappings":"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","sourcesContent":["import path from \"node:path\";\n\nexport type SandboxMode = \"off\" | \"audit\" | \"enforce\";\nexport type SandboxProfile = \"readonly\" | \"workspace-write\" | \"dev-server\" | \"networked\";\nexport type NetworkMode = \"none\" | \"loopback\" | \"domain-allowlist\" | \"all-explicit\";\nexport type PathAccessKind = \"read\" | \"write\";\nexport type SandboxPlatform = \"linux\" | \"macos\" | \"unsupported\";\n\nexport interface SandboxPolicy {\n\tmode: SandboxMode;\n\tprofile: SandboxProfile;\n\tfilesystem: {\n\t\troot: string;\n\t\treadAllow: readonly string[];\n\t\treadDeny: readonly string[];\n\t\twriteAllow: readonly string[];\n\t\tdenyWrite: readonly string[];\n\t\ttempWrite: readonly string[];\n\t\tfollowSymlinks: false;\n\t};\n\tnetwork: {\n\t\tmode: NetworkMode;\n\t\tallowedDomains: readonly string[];\n\t\tdeniedDomains: readonly string[];\n\t\tallowUnixSockets: readonly string[];\n\t\tallowBrowser: false;\n\t};\n\tprocess: {\n\t\tallowExec: boolean;\n\t\tallowShell: boolean;\n\t\tallowPrivilege: false;\n\t};\n}\n\nexport interface ResolvedSandboxPath {\n\trequestedPath: string;\n\texists: boolean;\n\trealPath?: string;\n\tnearestExistingParentRealPath?: string;\n\tisSymlink?: boolean;\n\terror?: string;\n}\n\nexport type SandboxPathResolver = (requestPath: string) => ResolvedSandboxPath;\n\nexport interface PathAccessRequest {\n\tkind: PathAccessKind;\n\tpath: string;\n}\n\nexport interface SandboxDecision {\n\tallowed: boolean;\n\trule: string;\n\treason: string;\n}\n\nexport interface NetworkAccessRequest {\n\thost?: string;\n\turl?: string;\n\tunixSocketPath?: string;\n\tbrowser?: boolean;\n\tloopback?: boolean;\n}\n\nexport interface SandboxBackendStatus {\n\tplatform: SandboxPlatform;\n\tbackendAvailable: boolean;\n\tdomainAllowlistAvailable?: boolean;\n\t/** Human-readable probe detail when the backend cannot enforce the policy. */\n\tunavailableReason?: string;\n}\n\nexport interface SandboxFallbackDecision extends SandboxDecision {\n\tallowShell: boolean;\n\tallowExec: boolean;\n\tallowReadOnlyTools: boolean;\n}\n\nconst SENSITIVE_PATH_PATTERN =\n\t/(?:^|[/\\\\])(?:\\.env(?:\\..*)?|auth\\.json|oauth\\.json|\\.netrc|\\.npmrc|\\.pgpass|credentials|id_rsa|id_dsa|id_ecdsa|id_ed25519|.*(?:secret|token|private[_-]?key|credential).*)$/i;\n\nfunction decision(allowed: boolean, rule: string, reason: string): SandboxDecision {\n\treturn { allowed, rule, reason };\n}\n\nfunction normalize(value: string): string {\n\treturn path\n\t\t.resolve(value)\n\t\t.replace(/\\\\/g, \"/\")\n\t\t.replace(/\\/+$/g, (suffix) => (value === suffix ? \"/\" : \"\"));\n}\n\nfunction isInside(parent: string, child: string): boolean {\n\tconst normalizedParent = normalize(parent);\n\tconst normalizedChild = normalize(child);\n\treturn normalizedChild === normalizedParent || normalizedChild.startsWith(`${normalizedParent}/`);\n}\n\nfunction isSensitivePath(value: string): boolean {\n\treturn SENSITIVE_PATH_PATTERN.test(value.replace(/\\\\/g, \"/\"));\n}\n\nfunction stripGlobSuffix(pattern: string): string {\n\tif (pattern.endsWith(\"/**\")) return pattern.slice(0, -3);\n\tif (pattern.endsWith(\"/*\")) return pattern.slice(0, -2);\n\treturn pattern;\n}\n\nfunction matchesPattern(pattern: string, candidate: string): boolean {\n\tconst normalizedPattern = normalize(stripGlobSuffix(pattern));\n\tconst normalizedCandidate = normalize(candidate);\n\treturn normalizedCandidate === normalizedPattern || normalizedCandidate.startsWith(`${normalizedPattern}/`);\n}\n\nfunction matchesAny(patterns: readonly string[], candidate: string): boolean {\n\treturn patterns.some((pattern) => matchesPattern(pattern, candidate));\n}\n\nfunction resolvedDecisionPath(resolved: ResolvedSandboxPath): string | undefined {\n\treturn resolved.realPath ?? resolved.nearestExistingParentRealPath;\n}\n\nfunction rootEscaped(policy: SandboxPolicy, resolved: ResolvedSandboxPath): boolean {\n\tconst candidate = resolvedDecisionPath(resolved);\n\treturn candidate === undefined || !isInside(policy.filesystem.root, candidate);\n}\n\nexport function decidePathAccess(\n\tpolicy: SandboxPolicy,\n\trequest: PathAccessRequest,\n\tresolver: SandboxPathResolver,\n): SandboxDecision {\n\tif (policy.mode === \"off\") {\n\t\treturn decision(true, \"sandbox.off\", \"Sandbox policy is disabled.\");\n\t}\n\n\tif (request.path.includes(\"\\0\")) {\n\t\treturn decision(false, \"path.invalid\", \"Path contains a NUL byte.\");\n\t}\n\n\tif (isSensitivePath(request.path)) {\n\t\treturn decision(false, \"path.secret\", \"Sensitive credential-like paths are denied.\");\n\t}\n\n\tconst resolved = resolver(request.path);\n\tif (resolved.error) {\n\t\treturn decision(false, \"path.resolve_error\", resolved.error);\n\t}\n\tif (rootEscaped(policy, resolved)) {\n\t\treturn decision(false, \"path.root_escape\", \"Resolved path is outside the sandbox root.\");\n\t}\n\tif (resolved.isSymlink && !policy.filesystem.followSymlinks) {\n\t\treturn decision(false, \"path.symlink\", \"Symlink traversal is denied by policy.\");\n\t}\n\n\tconst candidate = resolvedDecisionPath(resolved) ?? request.path;\n\tif (request.kind === \"read\") {\n\t\tif (matchesAny(policy.filesystem.readDeny, candidate) || matchesAny(policy.filesystem.readDeny, request.path)) {\n\t\t\treturn decision(false, \"path.read_deny\", \"Read path matches a deny rule.\");\n\t\t}\n\t\tif (!matchesAny(policy.filesystem.readAllow, candidate)) {\n\t\t\treturn decision(false, \"path.read_not_allowed\", \"Read path is not in an allow rule.\");\n\t\t}\n\t\treturn decision(true, \"path.read_allow\", \"Read path is allowed.\");\n\t}\n\n\tif (matchesAny(policy.filesystem.denyWrite, candidate) || matchesAny(policy.filesystem.denyWrite, request.path)) {\n\t\treturn decision(false, \"path.deny_write\", \"Write path matches an unoverrideable denyWrite rule.\");\n\t}\n\tif (matchesAny(policy.filesystem.writeAllow, candidate) || matchesAny(policy.filesystem.tempWrite, candidate)) {\n\t\treturn decision(true, \"path.write_allow\", \"Write path is allowed.\");\n\t}\n\treturn decision(false, \"path.write_not_allowed\", \"Write path is not in an allow rule.\");\n}\n\nfunction hostFromRequest(request: NetworkAccessRequest): string | undefined {\n\tif (request.host) return request.host.toLowerCase();\n\tif (!request.url) return undefined;\n\ttry {\n\t\treturn new URL(request.url).hostname.toLowerCase();\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction hostMatches(patterns: readonly string[], host: string): boolean {\n\treturn patterns.some((pattern) => {\n\t\tconst normalized = pattern.toLowerCase();\n\t\treturn host === normalized || host.endsWith(`.${normalized}`);\n\t});\n}\n\nexport function decideNetworkAccess(policy: SandboxPolicy, request: NetworkAccessRequest): SandboxDecision {\n\tif (policy.mode === \"off\") return decision(true, \"sandbox.off\", \"Sandbox policy is disabled.\");\n\tif (request.browser || request.unixSocketPath) {\n\t\treturn decision(false, \"network.socket_or_browser\", \"Browser and Unix socket access is denied by default.\");\n\t}\n\tif (policy.network.mode === \"none\") {\n\t\treturn decision(false, \"network.none\", \"Network access is disabled by policy.\");\n\t}\n\tif (request.loopback) {\n\t\treturn policy.network.mode === \"loopback\" ||\n\t\t\tpolicy.network.mode === \"domain-allowlist\" ||\n\t\t\tpolicy.network.mode === \"all-explicit\"\n\t\t\t? decision(true, \"network.loopback\", \"Loopback access is allowed.\")\n\t\t\t: decision(false, \"network.loopback_denied\", \"Loopback access is denied.\");\n\t}\n\tconst host = hostFromRequest(request);\n\tif (!host) return decision(false, \"network.unknown_host\", \"Network host could not be resolved from request.\");\n\tif (hostMatches(policy.network.deniedDomains, host)) {\n\t\treturn decision(false, \"network.domain_deny\", \"Host matches a denied domain.\");\n\t}\n\tif (policy.network.mode === \"all-explicit\") {\n\t\treturn decision(true, \"network.explicit_all\", \"Explicit network profile allows outbound host access.\");\n\t}\n\tif (policy.network.mode === \"domain-allowlist\" && hostMatches(policy.network.allowedDomains, host)) {\n\t\treturn decision(true, \"network.domain_allow\", \"Host matches an allowed domain.\");\n\t}\n\treturn decision(false, \"network.domain_not_allowed\", \"Host is not allowed by network policy.\");\n}\n\nexport function decideSandboxFallback(policy: SandboxPolicy, backend: SandboxBackendStatus): SandboxFallbackDecision {\n\tif (policy.mode === \"off\") {\n\t\treturn {\n\t\t\t...decision(true, \"sandbox.off\", \"Sandbox policy is disabled.\"),\n\t\t\tallowShell: true,\n\t\t\tallowExec: true,\n\t\t\tallowReadOnlyTools: true,\n\t\t};\n\t}\n\tif (backend.backendAvailable) {\n\t\treturn {\n\t\t\t...decision(true, \"sandbox.backend_available\", \"Sandbox backend is available.\"),\n\t\t\tallowShell: policy.process.allowShell,\n\t\t\tallowExec: policy.process.allowExec,\n\t\t\tallowReadOnlyTools: true,\n\t\t};\n\t}\n\tif (policy.mode === \"audit\") {\n\t\treturn {\n\t\t\t...decision(true, \"sandbox.audit_fallback\", \"Audit mode permits execution without backend.\"),\n\t\t\tallowShell: true,\n\t\t\tallowExec: true,\n\t\t\tallowReadOnlyTools: true,\n\t\t};\n\t}\n\tconst backendDetail = backend.unavailableReason ? ` ${backend.unavailableReason}` : \"\";\n\treturn {\n\t\t...decision(\n\t\t\tfalse,\n\t\t\t\"sandbox.backend_missing\",\n\t\t\t`Enforce mode blocks shell and exec when no backend is available.${backendDetail}`,\n\t\t),\n\t\tallowShell: false,\n\t\tallowExec: false,\n\t\tallowReadOnlyTools: true,\n\t};\n}\n\nexport function mergeSandboxPolicy(\n\tbase: SandboxPolicy,\n\toverride: Partial<SandboxPolicy>,\n\toptions: { allowBroaden?: boolean } = {},\n): SandboxPolicy {\n\tconst allowBroaden = options.allowBroaden === true;\n\treturn {\n\t\tmode: override.mode ?? base.mode,\n\t\tprofile: override.profile ?? base.profile,\n\t\tfilesystem: {\n\t\t\troot: override.filesystem?.root ?? base.filesystem.root,\n\t\t\treadAllow: allowBroaden\n\t\t\t\t? (override.filesystem?.readAllow ?? base.filesystem.readAllow)\n\t\t\t\t: intersectOrBase(base.filesystem.readAllow, override.filesystem?.readAllow),\n\t\t\treadDeny: union(base.filesystem.readDeny, override.filesystem?.readDeny),\n\t\t\twriteAllow: allowBroaden\n\t\t\t\t? (override.filesystem?.writeAllow ?? base.filesystem.writeAllow)\n\t\t\t\t: intersectOrBase(base.filesystem.writeAllow, override.filesystem?.writeAllow),\n\t\t\tdenyWrite: union(base.filesystem.denyWrite, override.filesystem?.denyWrite),\n\t\t\ttempWrite: allowBroaden\n\t\t\t\t? (override.filesystem?.tempWrite ?? base.filesystem.tempWrite)\n\t\t\t\t: intersectOrBase(base.filesystem.tempWrite, override.filesystem?.tempWrite),\n\t\t\tfollowSymlinks: false,\n\t\t},\n\t\tnetwork: {\n\t\t\tmode: allowBroaden\n\t\t\t\t? (override.network?.mode ?? base.network.mode)\n\t\t\t\t: narrowNetworkMode(base.network.mode, override.network?.mode),\n\t\t\tallowedDomains: allowBroaden\n\t\t\t\t? (override.network?.allowedDomains ?? base.network.allowedDomains)\n\t\t\t\t: intersectOrBase(base.network.allowedDomains, override.network?.allowedDomains),\n\t\t\tdeniedDomains: union(base.network.deniedDomains, override.network?.deniedDomains),\n\t\t\tallowUnixSockets: allowBroaden\n\t\t\t\t? (override.network?.allowUnixSockets ?? base.network.allowUnixSockets)\n\t\t\t\t: intersectOrBase(base.network.allowUnixSockets, override.network?.allowUnixSockets),\n\t\t\tallowBrowser: false,\n\t\t},\n\t\tprocess: {\n\t\t\tallowExec: allowBroaden\n\t\t\t\t? (override.process?.allowExec ?? base.process.allowExec)\n\t\t\t\t: base.process.allowExec && (override.process?.allowExec ?? true),\n\t\t\tallowShell: allowBroaden\n\t\t\t\t? (override.process?.allowShell ?? base.process.allowShell)\n\t\t\t\t: base.process.allowShell && (override.process?.allowShell ?? true),\n\t\t\tallowPrivilege: false,\n\t\t},\n\t};\n}\n\nfunction union(base: readonly string[], override: readonly string[] | undefined): string[] {\n\treturn [...new Set([...base, ...(override ?? [])])];\n}\n\nfunction intersectOrBase(base: readonly string[], override: readonly string[] | undefined): string[] {\n\tif (override === undefined) return [...base];\n\tconst result: string[] = [];\n\tfor (const candidate of override) {\n\t\tfor (const entry of base) {\n\t\t\tif (normalize(candidate) === normalize(entry)) {\n\t\t\t\tresult.push(candidate);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (matchesPattern(entry, candidate)) {\n\t\t\t\tresult.push(candidate);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (matchesPattern(candidate, entry)) {\n\t\t\t\tresult.push(entry);\n\t\t\t}\n\t\t}\n\t}\n\treturn [...new Set(result)];\n}\n\nfunction narrowNetworkMode(base: NetworkMode, override: NetworkMode | undefined): NetworkMode {\n\tif (override === undefined) return base;\n\tconst rank: Record<NetworkMode, number> = { none: 0, loopback: 1, \"domain-allowlist\": 2, \"all-explicit\": 3 };\n\treturn rank[override] <= rank[base] ? override : base;\n}\n\nexport interface BashSpawnPreflightContext {\n\tcommand: string;\n\tcwd: string;\n}\n\nexport interface BashSpawnPreflightDecision extends SandboxDecision {\n\tallowShell: boolean;\n}\n\n/**\n * Final pure preflight for a local bash spawn. Combines the backend-availability\n * fallback (deny shell when an OS sandbox backend is missing under enforce mode)\n * with a working-directory containment check (deny when cwd resolves outside the\n * sandbox root). This helper does not itself wrap the process in an OS sandbox; it\n * only decides whether the spawn may proceed.\n */\nexport function preflightBashSpawn(\n\tpolicy: SandboxPolicy,\n\tbackend: SandboxBackendStatus,\n\tcontext: BashSpawnPreflightContext,\n\tresolver?: SandboxPathResolver,\n): BashSpawnPreflightDecision {\n\tif (policy.mode === \"off\") {\n\t\treturn { ...decision(true, \"sandbox.off\", \"Sandbox policy is disabled.\"), allowShell: true };\n\t}\n\tif (!policy.process.allowShell) {\n\t\treturn {\n\t\t\t...decision(false, \"process.shell_denied\", \"Shell execution is disabled by policy.\"),\n\t\t\tallowShell: false,\n\t\t};\n\t}\n\tconst fallback = decideSandboxFallback(policy, backend);\n\tif (!fallback.allowShell) {\n\t\treturn { ...decision(false, fallback.rule, fallback.reason), allowShell: false };\n\t}\n\tif (context.cwd.includes(\"\\0\")) {\n\t\treturn { ...decision(false, \"cwd.invalid\", \"Working directory contains a NUL byte.\"), allowShell: false };\n\t}\n\tconst resolved = resolver?.(context.cwd);\n\tif (resolved?.error) {\n\t\treturn { ...decision(false, \"cwd.resolve_error\", resolved.error), allowShell: false };\n\t}\n\tif (resolved?.isSymlink) {\n\t\treturn {\n\t\t\t...decision(false, \"cwd.symlink\", \"Working directory is a symlink and traversal is denied.\"),\n\t\t\tallowShell: false,\n\t\t};\n\t}\n\tconst candidate = resolved ? (resolvedDecisionPath(resolved) ?? context.cwd) : context.cwd;\n\tif (!isInside(policy.filesystem.root, candidate)) {\n\t\treturn {\n\t\t\t...decision(false, \"cwd.root_escape\", \"Working directory is outside the sandbox root.\"),\n\t\t\tallowShell: false,\n\t\t};\n\t}\n\treturn {\n\t\t...decision(true, \"sandbox.shell_preflight_ok\", \"Bash spawn is permitted by sandbox policy.\"),\n\t\tallowShell: true,\n\t};\n}\n"]}
@@ -150,8 +150,9 @@ export function decideSandboxFallback(policy, backend) {
150
150
  allowReadOnlyTools: true,
151
151
  };
152
152
  }
153
+ const backendDetail = backend.unavailableReason ? ` ${backend.unavailableReason}` : "";
153
154
  return {
154
- ...decision(false, "sandbox.backend_missing", "Enforce mode blocks shell and exec when no backend is available."),
155
+ ...decision(false, "sandbox.backend_missing", `Enforce mode blocks shell and exec when no backend is available.${backendDetail}`),
155
156
  allowShell: false,
156
157
  allowExec: false,
157
158
  allowReadOnlyTools: true,
@@ -1 +1 @@
1
- {"version":3,"file":"policy.js","sourceRoot":"","sources":["../../../src/core/sandbox/policy.ts"],"names":[],"mappings":"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","sourcesContent":["import path from \"node:path\";\n\nexport type SandboxMode = \"off\" | \"audit\" | \"enforce\";\nexport type SandboxProfile = \"readonly\" | \"workspace-write\" | \"dev-server\" | \"networked\";\nexport type NetworkMode = \"none\" | \"loopback\" | \"domain-allowlist\" | \"all-explicit\";\nexport type PathAccessKind = \"read\" | \"write\";\nexport type SandboxPlatform = \"linux\" | \"macos\" | \"unsupported\";\n\nexport interface SandboxPolicy {\n\tmode: SandboxMode;\n\tprofile: SandboxProfile;\n\tfilesystem: {\n\t\troot: string;\n\t\treadAllow: readonly string[];\n\t\treadDeny: readonly string[];\n\t\twriteAllow: readonly string[];\n\t\tdenyWrite: readonly string[];\n\t\ttempWrite: readonly string[];\n\t\tfollowSymlinks: false;\n\t};\n\tnetwork: {\n\t\tmode: NetworkMode;\n\t\tallowedDomains: readonly string[];\n\t\tdeniedDomains: readonly string[];\n\t\tallowUnixSockets: readonly string[];\n\t\tallowBrowser: false;\n\t};\n\tprocess: {\n\t\tallowExec: boolean;\n\t\tallowShell: boolean;\n\t\tallowPrivilege: false;\n\t};\n}\n\nexport interface ResolvedSandboxPath {\n\trequestedPath: string;\n\texists: boolean;\n\trealPath?: string;\n\tnearestExistingParentRealPath?: string;\n\tisSymlink?: boolean;\n\terror?: string;\n}\n\nexport type SandboxPathResolver = (requestPath: string) => ResolvedSandboxPath;\n\nexport interface PathAccessRequest {\n\tkind: PathAccessKind;\n\tpath: string;\n}\n\nexport interface SandboxDecision {\n\tallowed: boolean;\n\trule: string;\n\treason: string;\n}\n\nexport interface NetworkAccessRequest {\n\thost?: string;\n\turl?: string;\n\tunixSocketPath?: string;\n\tbrowser?: boolean;\n\tloopback?: boolean;\n}\n\nexport interface SandboxBackendStatus {\n\tplatform: SandboxPlatform;\n\tbackendAvailable: boolean;\n\tdomainAllowlistAvailable?: boolean;\n}\n\nexport interface SandboxFallbackDecision extends SandboxDecision {\n\tallowShell: boolean;\n\tallowExec: boolean;\n\tallowReadOnlyTools: boolean;\n}\n\nconst SENSITIVE_PATH_PATTERN =\n\t/(?:^|[/\\\\])(?:\\.env(?:\\..*)?|auth\\.json|oauth\\.json|\\.netrc|\\.npmrc|\\.pgpass|credentials|id_rsa|id_dsa|id_ecdsa|id_ed25519|.*(?:secret|token|private[_-]?key|credential).*)$/i;\n\nfunction decision(allowed: boolean, rule: string, reason: string): SandboxDecision {\n\treturn { allowed, rule, reason };\n}\n\nfunction normalize(value: string): string {\n\treturn path\n\t\t.resolve(value)\n\t\t.replace(/\\\\/g, \"/\")\n\t\t.replace(/\\/+$/g, (suffix) => (value === suffix ? \"/\" : \"\"));\n}\n\nfunction isInside(parent: string, child: string): boolean {\n\tconst normalizedParent = normalize(parent);\n\tconst normalizedChild = normalize(child);\n\treturn normalizedChild === normalizedParent || normalizedChild.startsWith(`${normalizedParent}/`);\n}\n\nfunction isSensitivePath(value: string): boolean {\n\treturn SENSITIVE_PATH_PATTERN.test(value.replace(/\\\\/g, \"/\"));\n}\n\nfunction stripGlobSuffix(pattern: string): string {\n\tif (pattern.endsWith(\"/**\")) return pattern.slice(0, -3);\n\tif (pattern.endsWith(\"/*\")) return pattern.slice(0, -2);\n\treturn pattern;\n}\n\nfunction matchesPattern(pattern: string, candidate: string): boolean {\n\tconst normalizedPattern = normalize(stripGlobSuffix(pattern));\n\tconst normalizedCandidate = normalize(candidate);\n\treturn normalizedCandidate === normalizedPattern || normalizedCandidate.startsWith(`${normalizedPattern}/`);\n}\n\nfunction matchesAny(patterns: readonly string[], candidate: string): boolean {\n\treturn patterns.some((pattern) => matchesPattern(pattern, candidate));\n}\n\nfunction resolvedDecisionPath(resolved: ResolvedSandboxPath): string | undefined {\n\treturn resolved.realPath ?? resolved.nearestExistingParentRealPath;\n}\n\nfunction rootEscaped(policy: SandboxPolicy, resolved: ResolvedSandboxPath): boolean {\n\tconst candidate = resolvedDecisionPath(resolved);\n\treturn candidate === undefined || !isInside(policy.filesystem.root, candidate);\n}\n\nexport function decidePathAccess(\n\tpolicy: SandboxPolicy,\n\trequest: PathAccessRequest,\n\tresolver: SandboxPathResolver,\n): SandboxDecision {\n\tif (policy.mode === \"off\") {\n\t\treturn decision(true, \"sandbox.off\", \"Sandbox policy is disabled.\");\n\t}\n\n\tif (request.path.includes(\"\\0\")) {\n\t\treturn decision(false, \"path.invalid\", \"Path contains a NUL byte.\");\n\t}\n\n\tif (isSensitivePath(request.path)) {\n\t\treturn decision(false, \"path.secret\", \"Sensitive credential-like paths are denied.\");\n\t}\n\n\tconst resolved = resolver(request.path);\n\tif (resolved.error) {\n\t\treturn decision(false, \"path.resolve_error\", resolved.error);\n\t}\n\tif (rootEscaped(policy, resolved)) {\n\t\treturn decision(false, \"path.root_escape\", \"Resolved path is outside the sandbox root.\");\n\t}\n\tif (resolved.isSymlink && !policy.filesystem.followSymlinks) {\n\t\treturn decision(false, \"path.symlink\", \"Symlink traversal is denied by policy.\");\n\t}\n\n\tconst candidate = resolvedDecisionPath(resolved) ?? request.path;\n\tif (request.kind === \"read\") {\n\t\tif (matchesAny(policy.filesystem.readDeny, candidate) || matchesAny(policy.filesystem.readDeny, request.path)) {\n\t\t\treturn decision(false, \"path.read_deny\", \"Read path matches a deny rule.\");\n\t\t}\n\t\tif (!matchesAny(policy.filesystem.readAllow, candidate)) {\n\t\t\treturn decision(false, \"path.read_not_allowed\", \"Read path is not in an allow rule.\");\n\t\t}\n\t\treturn decision(true, \"path.read_allow\", \"Read path is allowed.\");\n\t}\n\n\tif (matchesAny(policy.filesystem.denyWrite, candidate) || matchesAny(policy.filesystem.denyWrite, request.path)) {\n\t\treturn decision(false, \"path.deny_write\", \"Write path matches an unoverrideable denyWrite rule.\");\n\t}\n\tif (matchesAny(policy.filesystem.writeAllow, candidate) || matchesAny(policy.filesystem.tempWrite, candidate)) {\n\t\treturn decision(true, \"path.write_allow\", \"Write path is allowed.\");\n\t}\n\treturn decision(false, \"path.write_not_allowed\", \"Write path is not in an allow rule.\");\n}\n\nfunction hostFromRequest(request: NetworkAccessRequest): string | undefined {\n\tif (request.host) return request.host.toLowerCase();\n\tif (!request.url) return undefined;\n\ttry {\n\t\treturn new URL(request.url).hostname.toLowerCase();\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction hostMatches(patterns: readonly string[], host: string): boolean {\n\treturn patterns.some((pattern) => {\n\t\tconst normalized = pattern.toLowerCase();\n\t\treturn host === normalized || host.endsWith(`.${normalized}`);\n\t});\n}\n\nexport function decideNetworkAccess(policy: SandboxPolicy, request: NetworkAccessRequest): SandboxDecision {\n\tif (policy.mode === \"off\") return decision(true, \"sandbox.off\", \"Sandbox policy is disabled.\");\n\tif (request.browser || request.unixSocketPath) {\n\t\treturn decision(false, \"network.socket_or_browser\", \"Browser and Unix socket access is denied by default.\");\n\t}\n\tif (policy.network.mode === \"none\") {\n\t\treturn decision(false, \"network.none\", \"Network access is disabled by policy.\");\n\t}\n\tif (request.loopback) {\n\t\treturn policy.network.mode === \"loopback\" ||\n\t\t\tpolicy.network.mode === \"domain-allowlist\" ||\n\t\t\tpolicy.network.mode === \"all-explicit\"\n\t\t\t? decision(true, \"network.loopback\", \"Loopback access is allowed.\")\n\t\t\t: decision(false, \"network.loopback_denied\", \"Loopback access is denied.\");\n\t}\n\tconst host = hostFromRequest(request);\n\tif (!host) return decision(false, \"network.unknown_host\", \"Network host could not be resolved from request.\");\n\tif (hostMatches(policy.network.deniedDomains, host)) {\n\t\treturn decision(false, \"network.domain_deny\", \"Host matches a denied domain.\");\n\t}\n\tif (policy.network.mode === \"all-explicit\") {\n\t\treturn decision(true, \"network.explicit_all\", \"Explicit network profile allows outbound host access.\");\n\t}\n\tif (policy.network.mode === \"domain-allowlist\" && hostMatches(policy.network.allowedDomains, host)) {\n\t\treturn decision(true, \"network.domain_allow\", \"Host matches an allowed domain.\");\n\t}\n\treturn decision(false, \"network.domain_not_allowed\", \"Host is not allowed by network policy.\");\n}\n\nexport function decideSandboxFallback(policy: SandboxPolicy, backend: SandboxBackendStatus): SandboxFallbackDecision {\n\tif (policy.mode === \"off\") {\n\t\treturn {\n\t\t\t...decision(true, \"sandbox.off\", \"Sandbox policy is disabled.\"),\n\t\t\tallowShell: true,\n\t\t\tallowExec: true,\n\t\t\tallowReadOnlyTools: true,\n\t\t};\n\t}\n\tif (backend.backendAvailable) {\n\t\treturn {\n\t\t\t...decision(true, \"sandbox.backend_available\", \"Sandbox backend is available.\"),\n\t\t\tallowShell: policy.process.allowShell,\n\t\t\tallowExec: policy.process.allowExec,\n\t\t\tallowReadOnlyTools: true,\n\t\t};\n\t}\n\tif (policy.mode === \"audit\") {\n\t\treturn {\n\t\t\t...decision(true, \"sandbox.audit_fallback\", \"Audit mode permits execution without backend.\"),\n\t\t\tallowShell: true,\n\t\t\tallowExec: true,\n\t\t\tallowReadOnlyTools: true,\n\t\t};\n\t}\n\treturn {\n\t\t...decision(false, \"sandbox.backend_missing\", \"Enforce mode blocks shell and exec when no backend is available.\"),\n\t\tallowShell: false,\n\t\tallowExec: false,\n\t\tallowReadOnlyTools: true,\n\t};\n}\n\nexport function mergeSandboxPolicy(\n\tbase: SandboxPolicy,\n\toverride: Partial<SandboxPolicy>,\n\toptions: { allowBroaden?: boolean } = {},\n): SandboxPolicy {\n\tconst allowBroaden = options.allowBroaden === true;\n\treturn {\n\t\tmode: override.mode ?? base.mode,\n\t\tprofile: override.profile ?? base.profile,\n\t\tfilesystem: {\n\t\t\troot: override.filesystem?.root ?? base.filesystem.root,\n\t\t\treadAllow: allowBroaden\n\t\t\t\t? (override.filesystem?.readAllow ?? base.filesystem.readAllow)\n\t\t\t\t: intersectOrBase(base.filesystem.readAllow, override.filesystem?.readAllow),\n\t\t\treadDeny: union(base.filesystem.readDeny, override.filesystem?.readDeny),\n\t\t\twriteAllow: allowBroaden\n\t\t\t\t? (override.filesystem?.writeAllow ?? base.filesystem.writeAllow)\n\t\t\t\t: intersectOrBase(base.filesystem.writeAllow, override.filesystem?.writeAllow),\n\t\t\tdenyWrite: union(base.filesystem.denyWrite, override.filesystem?.denyWrite),\n\t\t\ttempWrite: allowBroaden\n\t\t\t\t? (override.filesystem?.tempWrite ?? base.filesystem.tempWrite)\n\t\t\t\t: intersectOrBase(base.filesystem.tempWrite, override.filesystem?.tempWrite),\n\t\t\tfollowSymlinks: false,\n\t\t},\n\t\tnetwork: {\n\t\t\tmode: allowBroaden\n\t\t\t\t? (override.network?.mode ?? base.network.mode)\n\t\t\t\t: narrowNetworkMode(base.network.mode, override.network?.mode),\n\t\t\tallowedDomains: allowBroaden\n\t\t\t\t? (override.network?.allowedDomains ?? base.network.allowedDomains)\n\t\t\t\t: intersectOrBase(base.network.allowedDomains, override.network?.allowedDomains),\n\t\t\tdeniedDomains: union(base.network.deniedDomains, override.network?.deniedDomains),\n\t\t\tallowUnixSockets: allowBroaden\n\t\t\t\t? (override.network?.allowUnixSockets ?? base.network.allowUnixSockets)\n\t\t\t\t: intersectOrBase(base.network.allowUnixSockets, override.network?.allowUnixSockets),\n\t\t\tallowBrowser: false,\n\t\t},\n\t\tprocess: {\n\t\t\tallowExec: allowBroaden\n\t\t\t\t? (override.process?.allowExec ?? base.process.allowExec)\n\t\t\t\t: base.process.allowExec && (override.process?.allowExec ?? true),\n\t\t\tallowShell: allowBroaden\n\t\t\t\t? (override.process?.allowShell ?? base.process.allowShell)\n\t\t\t\t: base.process.allowShell && (override.process?.allowShell ?? true),\n\t\t\tallowPrivilege: false,\n\t\t},\n\t};\n}\n\nfunction union(base: readonly string[], override: readonly string[] | undefined): string[] {\n\treturn [...new Set([...base, ...(override ?? [])])];\n}\n\nfunction intersectOrBase(base: readonly string[], override: readonly string[] | undefined): string[] {\n\tif (override === undefined) return [...base];\n\tconst result: string[] = [];\n\tfor (const candidate of override) {\n\t\tfor (const entry of base) {\n\t\t\tif (normalize(candidate) === normalize(entry)) {\n\t\t\t\tresult.push(candidate);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (matchesPattern(entry, candidate)) {\n\t\t\t\tresult.push(candidate);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (matchesPattern(candidate, entry)) {\n\t\t\t\tresult.push(entry);\n\t\t\t}\n\t\t}\n\t}\n\treturn [...new Set(result)];\n}\n\nfunction narrowNetworkMode(base: NetworkMode, override: NetworkMode | undefined): NetworkMode {\n\tif (override === undefined) return base;\n\tconst rank: Record<NetworkMode, number> = { none: 0, loopback: 1, \"domain-allowlist\": 2, \"all-explicit\": 3 };\n\treturn rank[override] <= rank[base] ? override : base;\n}\n\nexport interface BashSpawnPreflightContext {\n\tcommand: string;\n\tcwd: string;\n}\n\nexport interface BashSpawnPreflightDecision extends SandboxDecision {\n\tallowShell: boolean;\n}\n\n/**\n * Final pure preflight for a local bash spawn. Combines the backend-availability\n * fallback (deny shell when an OS sandbox backend is missing under enforce mode)\n * with a working-directory containment check (deny when cwd resolves outside the\n * sandbox root). This helper does not itself wrap the process in an OS sandbox; it\n * only decides whether the spawn may proceed.\n */\nexport function preflightBashSpawn(\n\tpolicy: SandboxPolicy,\n\tbackend: SandboxBackendStatus,\n\tcontext: BashSpawnPreflightContext,\n\tresolver?: SandboxPathResolver,\n): BashSpawnPreflightDecision {\n\tif (policy.mode === \"off\") {\n\t\treturn { ...decision(true, \"sandbox.off\", \"Sandbox policy is disabled.\"), allowShell: true };\n\t}\n\tif (!policy.process.allowShell) {\n\t\treturn {\n\t\t\t...decision(false, \"process.shell_denied\", \"Shell execution is disabled by policy.\"),\n\t\t\tallowShell: false,\n\t\t};\n\t}\n\tconst fallback = decideSandboxFallback(policy, backend);\n\tif (!fallback.allowShell) {\n\t\treturn { ...decision(false, fallback.rule, fallback.reason), allowShell: false };\n\t}\n\tif (context.cwd.includes(\"\\0\")) {\n\t\treturn { ...decision(false, \"cwd.invalid\", \"Working directory contains a NUL byte.\"), allowShell: false };\n\t}\n\tconst resolved = resolver?.(context.cwd);\n\tif (resolved?.error) {\n\t\treturn { ...decision(false, \"cwd.resolve_error\", resolved.error), allowShell: false };\n\t}\n\tif (resolved?.isSymlink) {\n\t\treturn {\n\t\t\t...decision(false, \"cwd.symlink\", \"Working directory is a symlink and traversal is denied.\"),\n\t\t\tallowShell: false,\n\t\t};\n\t}\n\tconst candidate = resolved ? (resolvedDecisionPath(resolved) ?? context.cwd) : context.cwd;\n\tif (!isInside(policy.filesystem.root, candidate)) {\n\t\treturn {\n\t\t\t...decision(false, \"cwd.root_escape\", \"Working directory is outside the sandbox root.\"),\n\t\t\tallowShell: false,\n\t\t};\n\t}\n\treturn {\n\t\t...decision(true, \"sandbox.shell_preflight_ok\", \"Bash spawn is permitted by sandbox policy.\"),\n\t\tallowShell: true,\n\t};\n}\n"]}
1
+ {"version":3,"file":"policy.js","sourceRoot":"","sources":["../../../src/core/sandbox/policy.ts"],"names":[],"mappings":"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","sourcesContent":["import path from \"node:path\";\n\nexport type SandboxMode = \"off\" | \"audit\" | \"enforce\";\nexport type SandboxProfile = \"readonly\" | \"workspace-write\" | \"dev-server\" | \"networked\";\nexport type NetworkMode = \"none\" | \"loopback\" | \"domain-allowlist\" | \"all-explicit\";\nexport type PathAccessKind = \"read\" | \"write\";\nexport type SandboxPlatform = \"linux\" | \"macos\" | \"unsupported\";\n\nexport interface SandboxPolicy {\n\tmode: SandboxMode;\n\tprofile: SandboxProfile;\n\tfilesystem: {\n\t\troot: string;\n\t\treadAllow: readonly string[];\n\t\treadDeny: readonly string[];\n\t\twriteAllow: readonly string[];\n\t\tdenyWrite: readonly string[];\n\t\ttempWrite: readonly string[];\n\t\tfollowSymlinks: false;\n\t};\n\tnetwork: {\n\t\tmode: NetworkMode;\n\t\tallowedDomains: readonly string[];\n\t\tdeniedDomains: readonly string[];\n\t\tallowUnixSockets: readonly string[];\n\t\tallowBrowser: false;\n\t};\n\tprocess: {\n\t\tallowExec: boolean;\n\t\tallowShell: boolean;\n\t\tallowPrivilege: false;\n\t};\n}\n\nexport interface ResolvedSandboxPath {\n\trequestedPath: string;\n\texists: boolean;\n\trealPath?: string;\n\tnearestExistingParentRealPath?: string;\n\tisSymlink?: boolean;\n\terror?: string;\n}\n\nexport type SandboxPathResolver = (requestPath: string) => ResolvedSandboxPath;\n\nexport interface PathAccessRequest {\n\tkind: PathAccessKind;\n\tpath: string;\n}\n\nexport interface SandboxDecision {\n\tallowed: boolean;\n\trule: string;\n\treason: string;\n}\n\nexport interface NetworkAccessRequest {\n\thost?: string;\n\turl?: string;\n\tunixSocketPath?: string;\n\tbrowser?: boolean;\n\tloopback?: boolean;\n}\n\nexport interface SandboxBackendStatus {\n\tplatform: SandboxPlatform;\n\tbackendAvailable: boolean;\n\tdomainAllowlistAvailable?: boolean;\n\t/** Human-readable probe detail when the backend cannot enforce the policy. */\n\tunavailableReason?: string;\n}\n\nexport interface SandboxFallbackDecision extends SandboxDecision {\n\tallowShell: boolean;\n\tallowExec: boolean;\n\tallowReadOnlyTools: boolean;\n}\n\nconst SENSITIVE_PATH_PATTERN =\n\t/(?:^|[/\\\\])(?:\\.env(?:\\..*)?|auth\\.json|oauth\\.json|\\.netrc|\\.npmrc|\\.pgpass|credentials|id_rsa|id_dsa|id_ecdsa|id_ed25519|.*(?:secret|token|private[_-]?key|credential).*)$/i;\n\nfunction decision(allowed: boolean, rule: string, reason: string): SandboxDecision {\n\treturn { allowed, rule, reason };\n}\n\nfunction normalize(value: string): string {\n\treturn path\n\t\t.resolve(value)\n\t\t.replace(/\\\\/g, \"/\")\n\t\t.replace(/\\/+$/g, (suffix) => (value === suffix ? \"/\" : \"\"));\n}\n\nfunction isInside(parent: string, child: string): boolean {\n\tconst normalizedParent = normalize(parent);\n\tconst normalizedChild = normalize(child);\n\treturn normalizedChild === normalizedParent || normalizedChild.startsWith(`${normalizedParent}/`);\n}\n\nfunction isSensitivePath(value: string): boolean {\n\treturn SENSITIVE_PATH_PATTERN.test(value.replace(/\\\\/g, \"/\"));\n}\n\nfunction stripGlobSuffix(pattern: string): string {\n\tif (pattern.endsWith(\"/**\")) return pattern.slice(0, -3);\n\tif (pattern.endsWith(\"/*\")) return pattern.slice(0, -2);\n\treturn pattern;\n}\n\nfunction matchesPattern(pattern: string, candidate: string): boolean {\n\tconst normalizedPattern = normalize(stripGlobSuffix(pattern));\n\tconst normalizedCandidate = normalize(candidate);\n\treturn normalizedCandidate === normalizedPattern || normalizedCandidate.startsWith(`${normalizedPattern}/`);\n}\n\nfunction matchesAny(patterns: readonly string[], candidate: string): boolean {\n\treturn patterns.some((pattern) => matchesPattern(pattern, candidate));\n}\n\nfunction resolvedDecisionPath(resolved: ResolvedSandboxPath): string | undefined {\n\treturn resolved.realPath ?? resolved.nearestExistingParentRealPath;\n}\n\nfunction rootEscaped(policy: SandboxPolicy, resolved: ResolvedSandboxPath): boolean {\n\tconst candidate = resolvedDecisionPath(resolved);\n\treturn candidate === undefined || !isInside(policy.filesystem.root, candidate);\n}\n\nexport function decidePathAccess(\n\tpolicy: SandboxPolicy,\n\trequest: PathAccessRequest,\n\tresolver: SandboxPathResolver,\n): SandboxDecision {\n\tif (policy.mode === \"off\") {\n\t\treturn decision(true, \"sandbox.off\", \"Sandbox policy is disabled.\");\n\t}\n\n\tif (request.path.includes(\"\\0\")) {\n\t\treturn decision(false, \"path.invalid\", \"Path contains a NUL byte.\");\n\t}\n\n\tif (isSensitivePath(request.path)) {\n\t\treturn decision(false, \"path.secret\", \"Sensitive credential-like paths are denied.\");\n\t}\n\n\tconst resolved = resolver(request.path);\n\tif (resolved.error) {\n\t\treturn decision(false, \"path.resolve_error\", resolved.error);\n\t}\n\tif (rootEscaped(policy, resolved)) {\n\t\treturn decision(false, \"path.root_escape\", \"Resolved path is outside the sandbox root.\");\n\t}\n\tif (resolved.isSymlink && !policy.filesystem.followSymlinks) {\n\t\treturn decision(false, \"path.symlink\", \"Symlink traversal is denied by policy.\");\n\t}\n\n\tconst candidate = resolvedDecisionPath(resolved) ?? request.path;\n\tif (request.kind === \"read\") {\n\t\tif (matchesAny(policy.filesystem.readDeny, candidate) || matchesAny(policy.filesystem.readDeny, request.path)) {\n\t\t\treturn decision(false, \"path.read_deny\", \"Read path matches a deny rule.\");\n\t\t}\n\t\tif (!matchesAny(policy.filesystem.readAllow, candidate)) {\n\t\t\treturn decision(false, \"path.read_not_allowed\", \"Read path is not in an allow rule.\");\n\t\t}\n\t\treturn decision(true, \"path.read_allow\", \"Read path is allowed.\");\n\t}\n\n\tif (matchesAny(policy.filesystem.denyWrite, candidate) || matchesAny(policy.filesystem.denyWrite, request.path)) {\n\t\treturn decision(false, \"path.deny_write\", \"Write path matches an unoverrideable denyWrite rule.\");\n\t}\n\tif (matchesAny(policy.filesystem.writeAllow, candidate) || matchesAny(policy.filesystem.tempWrite, candidate)) {\n\t\treturn decision(true, \"path.write_allow\", \"Write path is allowed.\");\n\t}\n\treturn decision(false, \"path.write_not_allowed\", \"Write path is not in an allow rule.\");\n}\n\nfunction hostFromRequest(request: NetworkAccessRequest): string | undefined {\n\tif (request.host) return request.host.toLowerCase();\n\tif (!request.url) return undefined;\n\ttry {\n\t\treturn new URL(request.url).hostname.toLowerCase();\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction hostMatches(patterns: readonly string[], host: string): boolean {\n\treturn patterns.some((pattern) => {\n\t\tconst normalized = pattern.toLowerCase();\n\t\treturn host === normalized || host.endsWith(`.${normalized}`);\n\t});\n}\n\nexport function decideNetworkAccess(policy: SandboxPolicy, request: NetworkAccessRequest): SandboxDecision {\n\tif (policy.mode === \"off\") return decision(true, \"sandbox.off\", \"Sandbox policy is disabled.\");\n\tif (request.browser || request.unixSocketPath) {\n\t\treturn decision(false, \"network.socket_or_browser\", \"Browser and Unix socket access is denied by default.\");\n\t}\n\tif (policy.network.mode === \"none\") {\n\t\treturn decision(false, \"network.none\", \"Network access is disabled by policy.\");\n\t}\n\tif (request.loopback) {\n\t\treturn policy.network.mode === \"loopback\" ||\n\t\t\tpolicy.network.mode === \"domain-allowlist\" ||\n\t\t\tpolicy.network.mode === \"all-explicit\"\n\t\t\t? decision(true, \"network.loopback\", \"Loopback access is allowed.\")\n\t\t\t: decision(false, \"network.loopback_denied\", \"Loopback access is denied.\");\n\t}\n\tconst host = hostFromRequest(request);\n\tif (!host) return decision(false, \"network.unknown_host\", \"Network host could not be resolved from request.\");\n\tif (hostMatches(policy.network.deniedDomains, host)) {\n\t\treturn decision(false, \"network.domain_deny\", \"Host matches a denied domain.\");\n\t}\n\tif (policy.network.mode === \"all-explicit\") {\n\t\treturn decision(true, \"network.explicit_all\", \"Explicit network profile allows outbound host access.\");\n\t}\n\tif (policy.network.mode === \"domain-allowlist\" && hostMatches(policy.network.allowedDomains, host)) {\n\t\treturn decision(true, \"network.domain_allow\", \"Host matches an allowed domain.\");\n\t}\n\treturn decision(false, \"network.domain_not_allowed\", \"Host is not allowed by network policy.\");\n}\n\nexport function decideSandboxFallback(policy: SandboxPolicy, backend: SandboxBackendStatus): SandboxFallbackDecision {\n\tif (policy.mode === \"off\") {\n\t\treturn {\n\t\t\t...decision(true, \"sandbox.off\", \"Sandbox policy is disabled.\"),\n\t\t\tallowShell: true,\n\t\t\tallowExec: true,\n\t\t\tallowReadOnlyTools: true,\n\t\t};\n\t}\n\tif (backend.backendAvailable) {\n\t\treturn {\n\t\t\t...decision(true, \"sandbox.backend_available\", \"Sandbox backend is available.\"),\n\t\t\tallowShell: policy.process.allowShell,\n\t\t\tallowExec: policy.process.allowExec,\n\t\t\tallowReadOnlyTools: true,\n\t\t};\n\t}\n\tif (policy.mode === \"audit\") {\n\t\treturn {\n\t\t\t...decision(true, \"sandbox.audit_fallback\", \"Audit mode permits execution without backend.\"),\n\t\t\tallowShell: true,\n\t\t\tallowExec: true,\n\t\t\tallowReadOnlyTools: true,\n\t\t};\n\t}\n\tconst backendDetail = backend.unavailableReason ? ` ${backend.unavailableReason}` : \"\";\n\treturn {\n\t\t...decision(\n\t\t\tfalse,\n\t\t\t\"sandbox.backend_missing\",\n\t\t\t`Enforce mode blocks shell and exec when no backend is available.${backendDetail}`,\n\t\t),\n\t\tallowShell: false,\n\t\tallowExec: false,\n\t\tallowReadOnlyTools: true,\n\t};\n}\n\nexport function mergeSandboxPolicy(\n\tbase: SandboxPolicy,\n\toverride: Partial<SandboxPolicy>,\n\toptions: { allowBroaden?: boolean } = {},\n): SandboxPolicy {\n\tconst allowBroaden = options.allowBroaden === true;\n\treturn {\n\t\tmode: override.mode ?? base.mode,\n\t\tprofile: override.profile ?? base.profile,\n\t\tfilesystem: {\n\t\t\troot: override.filesystem?.root ?? base.filesystem.root,\n\t\t\treadAllow: allowBroaden\n\t\t\t\t? (override.filesystem?.readAllow ?? base.filesystem.readAllow)\n\t\t\t\t: intersectOrBase(base.filesystem.readAllow, override.filesystem?.readAllow),\n\t\t\treadDeny: union(base.filesystem.readDeny, override.filesystem?.readDeny),\n\t\t\twriteAllow: allowBroaden\n\t\t\t\t? (override.filesystem?.writeAllow ?? base.filesystem.writeAllow)\n\t\t\t\t: intersectOrBase(base.filesystem.writeAllow, override.filesystem?.writeAllow),\n\t\t\tdenyWrite: union(base.filesystem.denyWrite, override.filesystem?.denyWrite),\n\t\t\ttempWrite: allowBroaden\n\t\t\t\t? (override.filesystem?.tempWrite ?? base.filesystem.tempWrite)\n\t\t\t\t: intersectOrBase(base.filesystem.tempWrite, override.filesystem?.tempWrite),\n\t\t\tfollowSymlinks: false,\n\t\t},\n\t\tnetwork: {\n\t\t\tmode: allowBroaden\n\t\t\t\t? (override.network?.mode ?? base.network.mode)\n\t\t\t\t: narrowNetworkMode(base.network.mode, override.network?.mode),\n\t\t\tallowedDomains: allowBroaden\n\t\t\t\t? (override.network?.allowedDomains ?? base.network.allowedDomains)\n\t\t\t\t: intersectOrBase(base.network.allowedDomains, override.network?.allowedDomains),\n\t\t\tdeniedDomains: union(base.network.deniedDomains, override.network?.deniedDomains),\n\t\t\tallowUnixSockets: allowBroaden\n\t\t\t\t? (override.network?.allowUnixSockets ?? base.network.allowUnixSockets)\n\t\t\t\t: intersectOrBase(base.network.allowUnixSockets, override.network?.allowUnixSockets),\n\t\t\tallowBrowser: false,\n\t\t},\n\t\tprocess: {\n\t\t\tallowExec: allowBroaden\n\t\t\t\t? (override.process?.allowExec ?? base.process.allowExec)\n\t\t\t\t: base.process.allowExec && (override.process?.allowExec ?? true),\n\t\t\tallowShell: allowBroaden\n\t\t\t\t? (override.process?.allowShell ?? base.process.allowShell)\n\t\t\t\t: base.process.allowShell && (override.process?.allowShell ?? true),\n\t\t\tallowPrivilege: false,\n\t\t},\n\t};\n}\n\nfunction union(base: readonly string[], override: readonly string[] | undefined): string[] {\n\treturn [...new Set([...base, ...(override ?? [])])];\n}\n\nfunction intersectOrBase(base: readonly string[], override: readonly string[] | undefined): string[] {\n\tif (override === undefined) return [...base];\n\tconst result: string[] = [];\n\tfor (const candidate of override) {\n\t\tfor (const entry of base) {\n\t\t\tif (normalize(candidate) === normalize(entry)) {\n\t\t\t\tresult.push(candidate);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (matchesPattern(entry, candidate)) {\n\t\t\t\tresult.push(candidate);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (matchesPattern(candidate, entry)) {\n\t\t\t\tresult.push(entry);\n\t\t\t}\n\t\t}\n\t}\n\treturn [...new Set(result)];\n}\n\nfunction narrowNetworkMode(base: NetworkMode, override: NetworkMode | undefined): NetworkMode {\n\tif (override === undefined) return base;\n\tconst rank: Record<NetworkMode, number> = { none: 0, loopback: 1, \"domain-allowlist\": 2, \"all-explicit\": 3 };\n\treturn rank[override] <= rank[base] ? override : base;\n}\n\nexport interface BashSpawnPreflightContext {\n\tcommand: string;\n\tcwd: string;\n}\n\nexport interface BashSpawnPreflightDecision extends SandboxDecision {\n\tallowShell: boolean;\n}\n\n/**\n * Final pure preflight for a local bash spawn. Combines the backend-availability\n * fallback (deny shell when an OS sandbox backend is missing under enforce mode)\n * with a working-directory containment check (deny when cwd resolves outside the\n * sandbox root). This helper does not itself wrap the process in an OS sandbox; it\n * only decides whether the spawn may proceed.\n */\nexport function preflightBashSpawn(\n\tpolicy: SandboxPolicy,\n\tbackend: SandboxBackendStatus,\n\tcontext: BashSpawnPreflightContext,\n\tresolver?: SandboxPathResolver,\n): BashSpawnPreflightDecision {\n\tif (policy.mode === \"off\") {\n\t\treturn { ...decision(true, \"sandbox.off\", \"Sandbox policy is disabled.\"), allowShell: true };\n\t}\n\tif (!policy.process.allowShell) {\n\t\treturn {\n\t\t\t...decision(false, \"process.shell_denied\", \"Shell execution is disabled by policy.\"),\n\t\t\tallowShell: false,\n\t\t};\n\t}\n\tconst fallback = decideSandboxFallback(policy, backend);\n\tif (!fallback.allowShell) {\n\t\treturn { ...decision(false, fallback.rule, fallback.reason), allowShell: false };\n\t}\n\tif (context.cwd.includes(\"\\0\")) {\n\t\treturn { ...decision(false, \"cwd.invalid\", \"Working directory contains a NUL byte.\"), allowShell: false };\n\t}\n\tconst resolved = resolver?.(context.cwd);\n\tif (resolved?.error) {\n\t\treturn { ...decision(false, \"cwd.resolve_error\", resolved.error), allowShell: false };\n\t}\n\tif (resolved?.isSymlink) {\n\t\treturn {\n\t\t\t...decision(false, \"cwd.symlink\", \"Working directory is a symlink and traversal is denied.\"),\n\t\t\tallowShell: false,\n\t\t};\n\t}\n\tconst candidate = resolved ? (resolvedDecisionPath(resolved) ?? context.cwd) : context.cwd;\n\tif (!isInside(policy.filesystem.root, candidate)) {\n\t\treturn {\n\t\t\t...decision(false, \"cwd.root_escape\", \"Working directory is outside the sandbox root.\"),\n\t\t\tallowShell: false,\n\t\t};\n\t}\n\treturn {\n\t\t...decision(true, \"sandbox.shell_preflight_ok\", \"Bash spawn is permitted by sandbox policy.\"),\n\t\tallowShell: true,\n\t};\n}\n"]}
@@ -29,6 +29,8 @@ export interface CreateAgentSessionOptions {
29
29
  model: Model<any>;
30
30
  thinkingLevel?: ThinkingLevel;
31
31
  }>;
32
+ /** True when the user pinned `--model` / `--provider` for this process. */
33
+ modelPinned?: boolean;
32
34
  /**
33
35
  * Optional default tool suppression mode when no explicit allowlist is provided.
34
36
  *
@@ -1 +1 @@
1
- {"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../../src/core/sdk.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { join } from \"node:path\";\nimport {\n\tAgent,\n\ttype AgentMessage,\n\tinspectTranscriptIntegrity,\n\trepairTranscriptIntegrity,\n\ttype ThinkingLevel,\n} from \"omk-agent-core\";\nimport { createNodeResourceKeyResolver } from \"omk-agent-core/node\";\nimport { clampThinkingLevel, type Message, type Model, streamSimple, type ToolResultMessage } from \"omk-ai\";\nimport { getAgentDir } from \"../config.ts\";\nimport { ReplayLedgerManager } from \"../guardrails/evidence-system.ts\";\nimport { resolvePath } from \"../utils/paths.ts\";\nimport { AgentSession, type SessionTranscriptRepair } from \"./agent-session.ts\";\nimport { resolveAgentToolSettings } from \"./agent-tool-settings.ts\";\nimport { formatNoModelsAvailableMessage } from \"./auth-guidance.ts\";\nimport { AuthStorage } from \"./auth-storage.ts\";\nimport { DEFAULT_THINKING_LEVEL } from \"./defaults.ts\";\nimport { createDomainDispatchRuntimeSession, isDomainRoutingEnabled, tryDomainDispatch } from \"./domain-dispatch.ts\";\nimport type { ExtensionRunner, LoadExtensionsResult, SessionStartEvent, ToolDefinition } from \"./extensions/index.ts\";\nimport { tryGrokHarnessDispatch } from \"./grok-harness-dispatch.ts\";\nimport type { LoadoutAccessPolicy } from \"./loadout-access-policy.ts\";\nimport { convertToLlm } from \"./messages.ts\";\nimport { ModelRegistry } from \"./model-registry.ts\";\nimport { findInitialModel } from \"./model-resolver.ts\";\nimport { mergeProviderAttributionHeaders } from \"./provider-attribution.ts\";\nimport { recordClaudePassiveUsage, recordCodexPassiveUsage } from \"./provider-usage.ts\";\nimport type { ResourceLoader } from \"./resource-loader.ts\";\nimport { DefaultResourceLoader } from \"./resource-loader.ts\";\nimport { getDefaultSessionDir, SessionManager } from \"./session-manager.ts\";\nimport { classifySessionTermination, SessionTerminationError } from \"./session-termination.ts\";\nimport { SettingsManager } from \"./settings-manager.ts\";\nimport { time } from \"./timings.ts\";\nimport type { BashSandboxPreflight } from \"./tools/bash.ts\";\nimport {\n\tallToolNames,\n\tcreateBashTool,\n\tcreateCodingTools,\n\tcreateEditTool,\n\tcreateFindTool,\n\tcreateGrepTool,\n\tcreateLsTool,\n\tcreateReadOnlyTools,\n\tcreateReadTool,\n\tcreateWriteTool,\n\ttype ToolName,\n\twithFileMutationQueue,\n} from \"./tools/index.ts\";\n\nexport interface CreateAgentSessionOptions {\n\t/** Working directory for project-local discovery. Default: process.cwd() */\n\tcwd?: string;\n\t/** Global config directory. Default: ~/.omk/agent */\n\tagentDir?: string;\n\n\t/** Auth storage for credentials. Default: AuthStorage.create(agentDir/auth.json) */\n\tauthStorage?: AuthStorage;\n\t/** Model registry. Default: ModelRegistry.create(authStorage, agentDir/models.json) */\n\tmodelRegistry?: ModelRegistry;\n\n\t/** Model to use. Default: from settings, else first available */\n\tmodel?: Model<any>;\n\t/** Thinking level. Default: from settings, else 'medium' (clamped to model capabilities) */\n\tthinkingLevel?: ThinkingLevel;\n\t/** Models available for cycling (Ctrl+P in interactive mode) */\n\tscopedModels?: Array<{ model: Model<any>; thinkingLevel?: ThinkingLevel }>;\n\n\t/**\n\t * Optional default tool suppression mode when no explicit allowlist is provided.\n\t *\n\t * - \"all\": start with no tools enabled\n\t * - \"builtin\": disable the default built-in tools (read, bash, edit, write)\n\t * but keep extension/custom tools enabled\n\t */\n\tnoTools?: \"all\" | \"builtin\";\n\t/**\n\t * Optional allowlist of tool names.\n\t *\n\t * When omitted, OMK enables the default built-in tools (read, bash, edit, write)\n\t * and leaves extension/custom tools enabled unless `noTools` changes that default.\n\t * When provided, only the listed tool names are enabled.\n\t */\n\ttools?: string[];\n\t/** Optional denylist of tool names to disable. Applies after `tools` when both are provided. */\n\texcludeTools?: string[];\n\t/** Custom tools to register (in addition to built-in tools). */\n\tcustomTools?: ToolDefinition[];\n\n\t/** Resource loader. When omitted, DefaultResourceLoader is used. */\n\tresourceLoader?: ResourceLoader;\n\n\t/** Session manager. Default: SessionManager.create(cwd) */\n\tsessionManager?: SessionManager;\n\n\t/** Settings manager. Default: SettingsManager.create(cwd, agentDir) */\n\tsettingsManager?: SettingsManager;\n\t/** Session start event metadata for extension runtime startup. */\n\tsessionStartEvent?: SessionStartEvent;\n\t/**\n\t * Optional prompt hint used only for opt-in domain routing (`OMK_DOMAIN_ROUTING=1`).\n\t * This text only selects a loadout policy and is never sent to the model.\n\t */\n\tdomainRoutingPrompt?: string;\n\t/** Optional loadout enforcement policy for SDK-created sessions. */\n\tloadoutAccessPolicy?: LoadoutAccessPolicy;\n\t/** Trusted sandbox preflight used for built-in local bash execution. */\n\tbashSandboxPreflight?: BashSandboxPreflight;\n\t/** Optional replay ledger shared with VerifiedEvidenceExecutor freshness checks. */\n\treplayLedger?: ReplayLedgerManager;\n\t/** Goal binding for AgentSession replay events; defaults to replayLedger.goalId. */\n\treplayGoalId?: string;\n\t/** Optional lane binding for AgentSession replay events. */\n\treplayLaneId?: string;\n}\n\n/** Result from createAgentSession */\nexport interface CreateAgentSessionResult {\n\t/** The created session */\n\tsession: AgentSession;\n\t/** Extensions result (for UI context setup in interactive mode) */\n\textensionsResult: LoadExtensionsResult;\n\t/** Warning if session was restored with a different model than saved */\n\tmodelFallbackMessage?: string;\n}\n\n// Re-exports\n\nexport * from \"./agent-session-runtime.ts\";\nexport type {\n\tExtensionAPI,\n\tExtensionCommandContext,\n\tExtensionContext,\n\tExtensionFactory,\n\tSlashCommandInfo,\n\tSlashCommandSource,\n\tToolDefinition,\n} from \"./extensions/index.ts\";\nexport type { PromptTemplate } from \"./prompt-templates.ts\";\nexport type { Skill } from \"./skills.ts\";\nexport type { Tool } from \"./tools/index.ts\";\n\nexport {\n\twithFileMutationQueue,\n\t// Tool factories (for custom cwd)\n\tcreateCodingTools,\n\tcreateReadOnlyTools,\n\tcreateReadTool,\n\tcreateBashTool,\n\tcreateEditTool,\n\tcreateWriteTool,\n\tcreateGrepTool,\n\tcreateFindTool,\n\tcreateLsTool,\n};\n\n// Helper Functions\n\nfunction getDefaultAgentDir(): string {\n\treturn getAgentDir();\n}\n\ninterface SessionTranscriptRecovery {\n\tmessages: AgentMessage[];\n\trepair?: SessionTranscriptRepair;\n}\n\n/**\n * ALG001-A: inspect the restored transcript on every live session open/resume.\n *\n * - A clean transcript passes through untouched.\n * - Unambiguous missing-only tail results are auto-repaired: synthetic\n * terminal results (tool-result/v2 envelope) are persisted to the session\n * so the repair is durable and idempotent, together with a durable\n * `transcript_repaired` audit entry.\n * - Any duplicate/orphan/duplicate-call/interleaved corruption marks the\n * session (`session_corrupt` entry, once) and fails closed.\n */\nfunction recoverSessionTranscriptOnOpen(\n\tsessionManager: SessionManager,\n\tmessages: AgentMessage[],\n): SessionTranscriptRecovery {\n\tconst report = inspectTranscriptIntegrity(messages);\n\tif (report.ok) {\n\t\treturn { messages };\n\t}\n\n\tlet repaired: AgentMessage[];\n\ttry {\n\t\trepaired = repairTranscriptIntegrity(messages, \"Tool result missing; synthesized during session resume recovery\");\n\t} catch {\n\t\tconst alreadyMarked = sessionManager\n\t\t\t.getBranch()\n\t\t\t.some((entry) => entry.type === \"custom\" && entry.customType === \"session_corrupt\");\n\t\tif (!alreadyMarked) {\n\t\t\tsessionManager.appendCustomEntry(\"session_corrupt\", {\n\t\t\t\tissues: report.issues.map((issue) => ({\n\t\t\t\t\tkind: issue.kind,\n\t\t\t\t\ttoolCallId: issue.toolCallId,\n\t\t\t\t\t...(issue.toolName === undefined ? {} : { toolName: issue.toolName }),\n\t\t\t\t})),\n\t\t\t});\n\t\t}\n\t\tconst summary = report.issues.map((issue) => `${issue.kind}:${issue.toolCallId}`).join(\", \");\n\t\tconst firstIssue = report.issues[0]?.kind ?? \"missing_result\";\n\t\tconst termination = classifySessionTermination({\n\t\t\tsessionId: sessionManager.getSessionId(),\n\t\t\trunId: `resume-${randomUUID()}`,\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tsource: \"inferred_on_resume\",\n\t\t\tmessage: \"Session transcript has ambiguous tool-result corruption.\",\n\t\t\tcause: { area: \"transcript\", code: firstIssue },\n\t\t\tsideEffects: \"possible\",\n\t\t});\n\t\tthrow new SessionTerminationError(\n\t\t\t`Cannot open session: corrupt tool transcript (${summary}). ` +\n\t\t\t\t\"The session has been marked corrupt; repair it explicitly before resuming.\",\n\t\t\ttermination,\n\t\t);\n\t}\n\n\tconst inserted = repaired.slice(messages.length) as ToolResultMessage[];\n\tif (inserted.length === 0) {\n\t\treturn { messages: repaired };\n\t}\n\tfor (const message of inserted) {\n\t\tsessionManager.appendMessage(message);\n\t}\n\tconst insertedToolCallIds = inserted.map((message) => message.toolCallId);\n\tsessionManager.appendCustomEntry(\"transcript_repaired\", { insertedToolCallIds, reason: \"resume\" });\n\treturn { messages: repaired, repair: { insertedToolCallIds, reason: \"resume\" } };\n}\n\nfunction getDomainDispatchBaseToolNames(options: CreateAgentSessionOptions): string[] {\n\tif (!options.tools && (options.noTools === \"all\" || options.noTools === \"builtin\")) {\n\t\treturn [];\n\t}\n\treturn [...allToolNames];\n}\n\nfunction getAllowedToolNamesForDomainDispatch(options: CreateAgentSessionOptions): string[] | undefined {\n\treturn options.tools ?? (options.noTools === \"all\" ? [] : undefined);\n}\n\nfunction createEffectiveLoadoutAccessPolicy(\n\toptions: CreateAgentSessionOptions,\n\tresourceLoader: ResourceLoader,\n\tcwd: string,\n\tagentDir: string,\n\tprovider?: string,\n): LoadoutAccessPolicy | undefined {\n\tif (options.loadoutAccessPolicy) {\n\t\treturn options.loadoutAccessPolicy;\n\t}\n\n\tconst session = createDomainDispatchRuntimeSession({\n\t\tbaseToolNames: getDomainDispatchBaseToolNames(options),\n\t\tresourceLoader,\n\t\tcustomTools: options.customTools,\n\t\tallowedToolNames: getAllowedToolNamesForDomainDispatch(options),\n\t\texcludedToolNames: options.excludeTools,\n\t});\n\n\t// Grok OAuth: apply grok-harness domain loadout without requiring OMK_DOMAIN_ROUTING=1.\n\tif (provider) {\n\t\ttry {\n\t\t\tconst grokDispatch = tryGrokHarnessDispatch({\n\t\t\t\tprovider,\n\t\t\t\tsession,\n\t\t\t\tresourceLoader,\n\t\t\t\tcwd,\n\t\t\t\tagentDir,\n\t\t\t\tenv: process.env,\n\t\t\t});\n\t\t\tif (grokDispatch.loadoutAccessPolicy) {\n\t\t\t\treturn grokDispatch.loadoutAccessPolicy;\n\t\t\t}\n\t\t} catch {\n\t\t\t// Fall through to opt-in domain routing.\n\t\t}\n\t}\n\n\tconst domainRoutingPrompt = options.domainRoutingPrompt?.trim();\n\tif (!domainRoutingPrompt || !isDomainRoutingEnabled(process.env)) {\n\t\treturn undefined;\n\t}\n\n\ttry {\n\t\tconst dispatch = tryDomainDispatch({\n\t\t\trole: \"coder\",\n\t\t\tinitialPrompt: domainRoutingPrompt,\n\t\t\tsession,\n\t\t\tresourceLoader,\n\t\t\tcwd,\n\t\t\tagentDir,\n\t\t\tenv: process.env,\n\t\t});\n\t\treturn dispatch.loadoutAccessPolicy;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/**\n * Create an AgentSession with the specified options.\n *\n * @example\n * ```typescript\n * // Minimal - uses defaults\n * const { session } = await createAgentSession();\n *\n * // With explicit model\n * import { getModel } from 'omk-ai';\n * const { session } = await createAgentSession({\n * model: getModel('anthropic', 'claude-opus-4-5'),\n * thinkingLevel: 'high',\n * });\n *\n * // Continue previous session\n * const { session, modelFallbackMessage } = await createAgentSession({\n * continueSession: true,\n * });\n *\n * // Full control\n * const loader = new DefaultResourceLoader({\n * cwd: process.cwd(),\n * agentDir: getAgentDir(),\n * settingsManager: SettingsManager.create(),\n * });\n * await loader.reload();\n * const { session } = await createAgentSession({\n * model: myModel,\n * tools: [\"read\", \"bash\"],\n * resourceLoader: loader,\n * sessionManager: SessionManager.inMemory(),\n * });\n * ```\n */\nexport async function createAgentSession(options: CreateAgentSessionOptions = {}): Promise<CreateAgentSessionResult> {\n\tconst cwd = resolvePath(options.cwd ?? options.sessionManager?.getCwd() ?? process.cwd());\n\tconst agentDir = options.agentDir ? resolvePath(options.agentDir) : getDefaultAgentDir();\n\tlet resourceLoader = options.resourceLoader;\n\n\t// Use provided or create AuthStorage and ModelRegistry\n\tconst authPath = options.agentDir ? join(agentDir, \"auth.json\") : undefined;\n\tconst modelsPath = options.agentDir ? join(agentDir, \"models.json\") : undefined;\n\tconst authStorage = options.authStorage ?? AuthStorage.create(authPath);\n\tconst modelRegistry = options.modelRegistry ?? ModelRegistry.create(authStorage, modelsPath);\n\n\tconst settingsManager = options.settingsManager ?? SettingsManager.create(cwd, agentDir);\n\tconst agentToolSettings = resolveAgentToolSettings(settingsManager);\n\tconst sessionManager = options.sessionManager ?? SessionManager.create(cwd, getDefaultSessionDir(cwd, agentDir));\n\tlet replayLedger = options.replayLedger;\n\tlet replayGoalId = options.replayGoalId;\n\tconst sessionFile = sessionManager.getSessionFile();\n\tif (!replayLedger && sessionManager.isPersisted() && sessionFile) {\n\t\treplayGoalId ??=\n\t\t\tsessionManager\n\t\t\t\t.getSessionId()\n\t\t\t\t.replace(/[^A-Za-z0-9._-]+/g, \"-\")\n\t\t\t\t.replace(/^[._-]+|[._-]+$/g, \"\") || \"session\";\n\t\treplayLedger = new ReplayLedgerManager(replayGoalId, `${sessionFile}.replay.jsonl`);\n\t}\n\n\tif (!resourceLoader) {\n\t\tresourceLoader = new DefaultResourceLoader({ cwd, agentDir, settingsManager });\n\t\tawait resourceLoader.reload();\n\t\ttime(\"resourceLoader.reload\");\n\t}\n\n\t// Check if session has existing data to restore. Live open/resume always\n\t// inspects the transcript and either auto-repairs missing-only tail results\n\t// or fails closed on ambiguous corruption (ALG001-A).\n\tconst existingSession = sessionManager.buildSessionContext();\n\tconst transcriptRecovery = recoverSessionTranscriptOnOpen(sessionManager, existingSession.messages);\n\tconst hasExistingSession = existingSession.messages.length > 0;\n\tconst hasThinkingEntry = sessionManager.getBranch().some((entry) => entry.type === \"thinking_level_change\");\n\n\tlet model = options.model;\n\tlet modelFallbackMessage: string | undefined;\n\n\t// If session has data, try to restore model from it\n\tif (!model && hasExistingSession && existingSession.model) {\n\t\tconst restoredModel = modelRegistry.find(existingSession.model.provider, existingSession.model.modelId);\n\t\tif (restoredModel && modelRegistry.hasConfiguredAuth(restoredModel)) {\n\t\t\tmodel = restoredModel;\n\t\t}\n\t\tif (!model) {\n\t\t\tmodelFallbackMessage = `Could not restore model ${existingSession.model.provider}/${existingSession.model.modelId}`;\n\t\t}\n\t}\n\n\t// If still no model, use findInitialModel (checks settings default, then provider defaults)\n\tif (!model) {\n\t\tconst result = await findInitialModel({\n\t\t\tscopedModels: [],\n\t\t\tisContinuing: hasExistingSession,\n\t\t\tdefaultProvider: settingsManager.getDefaultProvider(),\n\t\t\tdefaultModelId: settingsManager.getDefaultModel(),\n\t\t\tdefaultThinkingLevel: settingsManager.getDefaultThinkingLevel(),\n\t\t\tmodelRegistry,\n\t\t});\n\t\tmodel = result.model;\n\t\tif (!model) {\n\t\t\tmodelFallbackMessage = formatNoModelsAvailableMessage();\n\t\t} else if (modelFallbackMessage) {\n\t\t\tmodelFallbackMessage += `. Using ${model.provider}/${model.id}`;\n\t\t}\n\t}\n\n\t// Loadout policy after model resolution so Grok OAuth can auto-apply grok-harness.\n\tconst effectiveLoadoutAccessPolicy = createEffectiveLoadoutAccessPolicy(\n\t\toptions,\n\t\tresourceLoader,\n\t\tcwd,\n\t\tagentDir,\n\t\tmodel?.provider,\n\t);\n\n\tlet thinkingLevel = options.thinkingLevel;\n\n\t// If session has data, restore thinking level from it\n\tif (thinkingLevel === undefined && hasExistingSession) {\n\t\tthinkingLevel = hasThinkingEntry\n\t\t\t? (existingSession.thinkingLevel as ThinkingLevel)\n\t\t\t: (settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL);\n\t}\n\n\t// Fall back to settings default\n\tif (thinkingLevel === undefined) {\n\t\tthinkingLevel = settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL;\n\t}\n\n\t// Clamp to model capabilities\n\tif (!model) {\n\t\tthinkingLevel = \"off\";\n\t} else {\n\t\tthinkingLevel = clampThinkingLevel(model, thinkingLevel) as ThinkingLevel;\n\t}\n\n\tconst defaultActiveToolNames: ToolName[] = [\"read\", \"bash\", \"edit\", \"write\", \"diagnostics\"];\n\tconst allowedToolNames = options.tools ?? (options.noTools === \"all\" ? [] : undefined);\n\tconst excludedToolNames = options.excludeTools;\n\tconst excludedToolNameSet = excludedToolNames ? new Set(excludedToolNames) : undefined;\n\tconst initialActiveToolNames: string[] = (\n\t\toptions.tools ? [...options.tools] : options.noTools ? [] : defaultActiveToolNames\n\t).filter((name) => !excludedToolNameSet?.has(name));\n\n\tlet agent: Agent;\n\n\t// Create convertToLlm wrapper that filters images if blockImages is enabled (defense-in-depth)\n\tconst convertToLlmWithBlockImages = (messages: AgentMessage[]): Message[] => {\n\t\tconst converted = convertToLlm(messages);\n\t\t// Check setting dynamically so mid-session changes take effect\n\t\tif (!settingsManager.getBlockImages()) {\n\t\t\treturn converted;\n\t\t}\n\t\t// Filter out ImageContent from all messages, replacing with text placeholder\n\t\treturn converted.map((msg) => {\n\t\t\tif (msg.role === \"user\" || msg.role === \"toolResult\") {\n\t\t\t\tconst content = msg.content;\n\t\t\t\tif (Array.isArray(content)) {\n\t\t\t\t\tconst hasImages = content.some((c) => c.type === \"image\");\n\t\t\t\t\tif (hasImages) {\n\t\t\t\t\t\tconst filteredContent = content\n\t\t\t\t\t\t\t.map((c) =>\n\t\t\t\t\t\t\t\tc.type === \"image\" ? { type: \"text\" as const, text: \"Image reading is disabled.\" } : c,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t.filter(\n\t\t\t\t\t\t\t\t(c, i, arr) =>\n\t\t\t\t\t\t\t\t\t// Dedupe consecutive \"Image reading is disabled.\" texts\n\t\t\t\t\t\t\t\t\t!(\n\t\t\t\t\t\t\t\t\t\tc.type === \"text\" &&\n\t\t\t\t\t\t\t\t\t\tc.text === \"Image reading is disabled.\" &&\n\t\t\t\t\t\t\t\t\t\ti > 0 &&\n\t\t\t\t\t\t\t\t\t\tarr[i - 1].type === \"text\" &&\n\t\t\t\t\t\t\t\t\t\t(arr[i - 1] as { type: \"text\"; text: string }).text === \"Image reading is disabled.\"\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\treturn { ...msg, content: filteredContent };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn msg;\n\t\t});\n\t};\n\n\tconst extensionRunnerRef: { current?: ExtensionRunner } = {};\n\n\tagent = new Agent({\n\t\tinitialState: {\n\t\t\tsystemPrompt: \"\",\n\t\t\tmodel,\n\t\t\tthinkingLevel,\n\t\t\ttools: [],\n\t\t},\n\t\tconvertToLlm: convertToLlmWithBlockImages,\n\t\tstreamFn: async (model, context, options) => {\n\t\t\tconst auth = await modelRegistry.getApiKeyAndHeaders(model);\n\t\t\tif (!auth.ok) {\n\t\t\t\tthrow new Error(auth.error);\n\t\t\t}\n\t\t\tconst providerRetrySettings = settingsManager.getProviderRetrySettings();\n\t\t\tconst httpIdleTimeoutMs = settingsManager.getHttpIdleTimeoutMs();\n\t\t\t// SDKs treat timeout=0 as 0ms (immediate timeout), not \"no timeout\".\n\t\t\t// Use max int32 to effectively disable the timeout.\n\t\t\tconst effectiveTimeoutMs = httpIdleTimeoutMs === 0 ? 2147483647 : httpIdleTimeoutMs;\n\t\t\tconst timeoutMs = options?.timeoutMs ?? providerRetrySettings.timeoutMs ?? effectiveTimeoutMs;\n\t\t\tconst websocketConnectTimeoutMs =\n\t\t\t\toptions?.websocketConnectTimeoutMs ?? settingsManager.getWebSocketConnectTimeoutMs();\n\t\t\treturn streamSimple(model, context, {\n\t\t\t\t...options,\n\t\t\t\tapiKey: auth.apiKey,\n\t\t\t\ttimeoutMs,\n\t\t\t\twebsocketConnectTimeoutMs,\n\t\t\t\tmaxRetries: options?.maxRetries ?? providerRetrySettings.maxRetries,\n\t\t\t\tmaxRetryDelayMs: options?.maxRetryDelayMs ?? providerRetrySettings.maxRetryDelayMs,\n\t\t\t\tonRateLimit: async (snapshot, responseModel) => {\n\t\t\t\t\tif (auth.apiKey && /^openai-codex(?:-|$)/.test(responseModel.provider)) {\n\t\t\t\t\t\trecordCodexPassiveUsage(auth.apiKey, snapshot);\n\t\t\t\t\t} else if (auth.apiKey && responseModel.provider === \"anthropic\") {\n\t\t\t\t\t\trecordClaudePassiveUsage(auth.apiKey, snapshot);\n\t\t\t\t\t}\n\t\t\t\t\tawait options?.onRateLimit?.(snapshot, responseModel);\n\t\t\t\t},\n\t\t\t\theaders: mergeProviderAttributionHeaders(\n\t\t\t\t\tmodel,\n\t\t\t\t\tsettingsManager,\n\t\t\t\t\toptions?.sessionId,\n\t\t\t\t\tauth.headers,\n\t\t\t\t\toptions?.headers,\n\t\t\t\t),\n\t\t\t});\n\t\t},\n\t\tonPayload: async (payload, _model) => {\n\t\t\tconst runner = extensionRunnerRef.current;\n\t\t\tif (!runner?.hasHandlers(\"before_provider_request\")) {\n\t\t\t\treturn payload;\n\t\t\t}\n\t\t\treturn runner.emitBeforeProviderRequest(payload);\n\t\t},\n\t\tonResponse: async (response, _model) => {\n\t\t\tconst runner = extensionRunnerRef.current;\n\t\t\tif (!runner?.hasHandlers(\"after_provider_response\")) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait runner.emit({\n\t\t\t\ttype: \"after_provider_response\",\n\t\t\t\tstatus: response.status,\n\t\t\t\theaders: response.headers,\n\t\t\t});\n\t\t},\n\t\tsessionId: sessionManager.getSessionId(),\n\t\ttransformContext: async (messages) => {\n\t\t\tconst runner = extensionRunnerRef.current;\n\t\t\tif (!runner) return messages;\n\t\t\treturn runner.emitContext(messages);\n\t\t},\n\t\tsteeringMode: settingsManager.getSteeringMode(),\n\t\tfollowUpMode: settingsManager.getFollowUpMode(),\n\t\ttransport: settingsManager.getTransport(),\n\t\tthinkingBudgets: settingsManager.getThinkingBudgets(),\n\t\tmaxRetryDelayMs: settingsManager.getProviderRetrySettings().maxRetryDelayMs,\n\t\ttoolTimeoutMs: agentToolSettings.toolTimeoutMs,\n\t\ttoolTimeouts: agentToolSettings.toolTimeouts,\n\t\ttoolScheduler: agentToolSettings.toolScheduler,\n\t\tmaxToolConcurrency: agentToolSettings.maxToolConcurrency,\n\t\tstrictExtensionClaims: agentToolSettings.strictExtensionClaims,\n\t\tcwd,\n\t\t// §5.5 Node identity resolver: symlink/hardlink/drive/UNC path aliases\n\t\t// conflict in the dag-v2 scheduler instead of racing (ALG002-A).\n\t\tresourceKeyResolver: createNodeResourceKeyResolver(),\n\t});\n\n\t// Restore messages if session has existing data\n\tif (hasExistingSession) {\n\t\tagent.state.messages = transcriptRecovery.messages;\n\t\tif (!hasThinkingEntry) {\n\t\t\tsessionManager.appendThinkingLevelChange(thinkingLevel);\n\t\t}\n\t} else {\n\t\t// Save initial model and thinking level for new sessions so they can be restored on resume\n\t\tif (model) {\n\t\t\tsessionManager.appendModelChange(model.provider, model.id);\n\t\t}\n\t\tsessionManager.appendThinkingLevelChange(thinkingLevel);\n\t}\n\n\tconst session = new AgentSession({\n\t\tagent,\n\t\tsessionManager,\n\t\tsettingsManager,\n\t\tcwd,\n\t\tscopedModels: options.scopedModels,\n\t\tresourceLoader,\n\t\tcustomTools: options.customTools,\n\t\tmodelRegistry,\n\t\tinitialActiveToolNames,\n\t\tallowedToolNames,\n\t\texcludedToolNames,\n\t\textensionRunnerRef,\n\t\tloadoutAccessPolicy: effectiveLoadoutAccessPolicy,\n\t\tbashSandboxPreflight: options.bashSandboxPreflight,\n\t\tsessionStartEvent: options.sessionStartEvent,\n\t\ttranscriptRepair: transcriptRecovery.repair,\n\t\treplayLedger,\n\t\treplayGoalId,\n\t\treplayLaneId: options.replayLaneId,\n\t});\n\tconst extensionsResult = resourceLoader.getExtensions();\n\n\treturn {\n\t\tsession,\n\t\textensionsResult,\n\t\tmodelFallbackMessage,\n\t};\n}\n"]}
1
+ {"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../../src/core/sdk.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { join } from \"node:path\";\nimport {\n\tAgent,\n\ttype AgentMessage,\n\tinspectTranscriptIntegrity,\n\trepairTranscriptIntegrity,\n\ttype ThinkingLevel,\n} from \"omk-agent-core\";\nimport { createNodeResourceKeyResolver } from \"omk-agent-core/node\";\nimport { clampThinkingLevel, type Message, type Model, streamSimple, type ToolResultMessage } from \"omk-ai\";\nimport { getAgentDir } from \"../config.ts\";\nimport { ReplayLedgerManager } from \"../guardrails/evidence-system.ts\";\nimport { resolvePath } from \"../utils/paths.ts\";\nimport { AgentSession, type SessionTranscriptRepair } from \"./agent-session.ts\";\nimport { resolveAgentToolSettings } from \"./agent-tool-settings.ts\";\nimport { formatNoModelsAvailableMessage } from \"./auth-guidance.ts\";\nimport { AuthStorage } from \"./auth-storage.ts\";\nimport { DEFAULT_THINKING_LEVEL } from \"./defaults.ts\";\nimport { createDomainDispatchRuntimeSession, isDomainRoutingEnabled, tryDomainDispatch } from \"./domain-dispatch.ts\";\nimport type { ExtensionRunner, LoadExtensionsResult, SessionStartEvent, ToolDefinition } from \"./extensions/index.ts\";\nimport { tryGrokHarnessDispatch } from \"./grok-harness-dispatch.ts\";\nimport type { LoadoutAccessPolicy } from \"./loadout-access-policy.ts\";\nimport { convertToLlm } from \"./messages.ts\";\nimport { ModelRegistry } from \"./model-registry.ts\";\nimport { findInitialModel } from \"./model-resolver.ts\";\nimport { mergeProviderAttributionHeaders } from \"./provider-attribution.ts\";\nimport { recordClaudePassiveUsage, recordCodexPassiveUsage } from \"./provider-usage.ts\";\nimport type { ResourceLoader } from \"./resource-loader.ts\";\nimport { DefaultResourceLoader } from \"./resource-loader.ts\";\nimport { getDefaultSessionDir, SessionManager } from \"./session-manager.ts\";\nimport { classifySessionTermination, SessionTerminationError } from \"./session-termination.ts\";\nimport { SettingsManager } from \"./settings-manager.ts\";\nimport { time } from \"./timings.ts\";\nimport type { BashSandboxPreflight } from \"./tools/bash.ts\";\nimport {\n\tallToolNames,\n\tcreateBashTool,\n\tcreateCodingTools,\n\tcreateEditTool,\n\tcreateFindTool,\n\tcreateGrepTool,\n\tcreateLsTool,\n\tcreateReadOnlyTools,\n\tcreateReadTool,\n\tcreateWriteTool,\n\ttype ToolName,\n\twithFileMutationQueue,\n} from \"./tools/index.ts\";\n\nexport interface CreateAgentSessionOptions {\n\t/** Working directory for project-local discovery. Default: process.cwd() */\n\tcwd?: string;\n\t/** Global config directory. Default: ~/.omk/agent */\n\tagentDir?: string;\n\n\t/** Auth storage for credentials. Default: AuthStorage.create(agentDir/auth.json) */\n\tauthStorage?: AuthStorage;\n\t/** Model registry. Default: ModelRegistry.create(authStorage, agentDir/models.json) */\n\tmodelRegistry?: ModelRegistry;\n\n\t/** Model to use. Default: from settings, else first available */\n\tmodel?: Model<any>;\n\t/** Thinking level. Default: from settings, else 'medium' (clamped to model capabilities) */\n\tthinkingLevel?: ThinkingLevel;\n\t/** Models available for cycling (Ctrl+P in interactive mode) */\n\tscopedModels?: Array<{ model: Model<any>; thinkingLevel?: ThinkingLevel }>;\n\t/** True when the user pinned `--model` / `--provider` for this process. */\n\tmodelPinned?: boolean;\n\n\t/**\n\t * Optional default tool suppression mode when no explicit allowlist is provided.\n\t *\n\t * - \"all\": start with no tools enabled\n\t * - \"builtin\": disable the default built-in tools (read, bash, edit, write)\n\t * but keep extension/custom tools enabled\n\t */\n\tnoTools?: \"all\" | \"builtin\";\n\t/**\n\t * Optional allowlist of tool names.\n\t *\n\t * When omitted, OMK enables the default built-in tools (read, bash, edit, write)\n\t * and leaves extension/custom tools enabled unless `noTools` changes that default.\n\t * When provided, only the listed tool names are enabled.\n\t */\n\ttools?: string[];\n\t/** Optional denylist of tool names to disable. Applies after `tools` when both are provided. */\n\texcludeTools?: string[];\n\t/** Custom tools to register (in addition to built-in tools). */\n\tcustomTools?: ToolDefinition[];\n\n\t/** Resource loader. When omitted, DefaultResourceLoader is used. */\n\tresourceLoader?: ResourceLoader;\n\n\t/** Session manager. Default: SessionManager.create(cwd) */\n\tsessionManager?: SessionManager;\n\n\t/** Settings manager. Default: SettingsManager.create(cwd, agentDir) */\n\tsettingsManager?: SettingsManager;\n\t/** Session start event metadata for extension runtime startup. */\n\tsessionStartEvent?: SessionStartEvent;\n\t/**\n\t * Optional prompt hint used only for opt-in domain routing (`OMK_DOMAIN_ROUTING=1`).\n\t * This text only selects a loadout policy and is never sent to the model.\n\t */\n\tdomainRoutingPrompt?: string;\n\t/** Optional loadout enforcement policy for SDK-created sessions. */\n\tloadoutAccessPolicy?: LoadoutAccessPolicy;\n\t/** Trusted sandbox preflight used for built-in local bash execution. */\n\tbashSandboxPreflight?: BashSandboxPreflight;\n\t/** Optional replay ledger shared with VerifiedEvidenceExecutor freshness checks. */\n\treplayLedger?: ReplayLedgerManager;\n\t/** Goal binding for AgentSession replay events; defaults to replayLedger.goalId. */\n\treplayGoalId?: string;\n\t/** Optional lane binding for AgentSession replay events. */\n\treplayLaneId?: string;\n}\n\n/** Result from createAgentSession */\nexport interface CreateAgentSessionResult {\n\t/** The created session */\n\tsession: AgentSession;\n\t/** Extensions result (for UI context setup in interactive mode) */\n\textensionsResult: LoadExtensionsResult;\n\t/** Warning if session was restored with a different model than saved */\n\tmodelFallbackMessage?: string;\n}\n\n// Re-exports\n\nexport * from \"./agent-session-runtime.ts\";\nexport type {\n\tExtensionAPI,\n\tExtensionCommandContext,\n\tExtensionContext,\n\tExtensionFactory,\n\tSlashCommandInfo,\n\tSlashCommandSource,\n\tToolDefinition,\n} from \"./extensions/index.ts\";\nexport type { PromptTemplate } from \"./prompt-templates.ts\";\nexport type { Skill } from \"./skills.ts\";\nexport type { Tool } from \"./tools/index.ts\";\n\nexport {\n\twithFileMutationQueue,\n\t// Tool factories (for custom cwd)\n\tcreateCodingTools,\n\tcreateReadOnlyTools,\n\tcreateReadTool,\n\tcreateBashTool,\n\tcreateEditTool,\n\tcreateWriteTool,\n\tcreateGrepTool,\n\tcreateFindTool,\n\tcreateLsTool,\n};\n\n// Helper Functions\n\nfunction getDefaultAgentDir(): string {\n\treturn getAgentDir();\n}\n\ninterface SessionTranscriptRecovery {\n\tmessages: AgentMessage[];\n\trepair?: SessionTranscriptRepair;\n}\n\n/**\n * ALG001-A: inspect the restored transcript on every live session open/resume.\n *\n * - A clean transcript passes through untouched.\n * - Unambiguous missing-only tail results are auto-repaired: synthetic\n * terminal results (tool-result/v2 envelope) are persisted to the session\n * so the repair is durable and idempotent, together with a durable\n * `transcript_repaired` audit entry.\n * - Any duplicate/orphan/duplicate-call/interleaved corruption marks the\n * session (`session_corrupt` entry, once) and fails closed.\n */\nfunction recoverSessionTranscriptOnOpen(\n\tsessionManager: SessionManager,\n\tmessages: AgentMessage[],\n): SessionTranscriptRecovery {\n\tconst report = inspectTranscriptIntegrity(messages);\n\tif (report.ok) {\n\t\treturn { messages };\n\t}\n\n\tlet repaired: AgentMessage[];\n\ttry {\n\t\trepaired = repairTranscriptIntegrity(messages, \"Tool result missing; synthesized during session resume recovery\");\n\t} catch {\n\t\tconst alreadyMarked = sessionManager\n\t\t\t.getBranch()\n\t\t\t.some((entry) => entry.type === \"custom\" && entry.customType === \"session_corrupt\");\n\t\tif (!alreadyMarked) {\n\t\t\tsessionManager.appendCustomEntry(\"session_corrupt\", {\n\t\t\t\tissues: report.issues.map((issue) => ({\n\t\t\t\t\tkind: issue.kind,\n\t\t\t\t\ttoolCallId: issue.toolCallId,\n\t\t\t\t\t...(issue.toolName === undefined ? {} : { toolName: issue.toolName }),\n\t\t\t\t})),\n\t\t\t});\n\t\t}\n\t\tconst summary = report.issues.map((issue) => `${issue.kind}:${issue.toolCallId}`).join(\", \");\n\t\tconst firstIssue = report.issues[0]?.kind ?? \"missing_result\";\n\t\tconst termination = classifySessionTermination({\n\t\t\tsessionId: sessionManager.getSessionId(),\n\t\t\trunId: `resume-${randomUUID()}`,\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tsource: \"inferred_on_resume\",\n\t\t\tmessage: \"Session transcript has ambiguous tool-result corruption.\",\n\t\t\tcause: { area: \"transcript\", code: firstIssue },\n\t\t\tsideEffects: \"possible\",\n\t\t});\n\t\tthrow new SessionTerminationError(\n\t\t\t`Cannot open session: corrupt tool transcript (${summary}). ` +\n\t\t\t\t\"The session has been marked corrupt; repair it explicitly before resuming.\",\n\t\t\ttermination,\n\t\t);\n\t}\n\n\tconst inserted = repaired.slice(messages.length) as ToolResultMessage[];\n\tif (inserted.length === 0) {\n\t\treturn { messages: repaired };\n\t}\n\tfor (const message of inserted) {\n\t\tsessionManager.appendMessage(message);\n\t}\n\tconst insertedToolCallIds = inserted.map((message) => message.toolCallId);\n\tsessionManager.appendCustomEntry(\"transcript_repaired\", { insertedToolCallIds, reason: \"resume\" });\n\treturn { messages: repaired, repair: { insertedToolCallIds, reason: \"resume\" } };\n}\n\nfunction getDomainDispatchBaseToolNames(options: CreateAgentSessionOptions): string[] {\n\tif (!options.tools && (options.noTools === \"all\" || options.noTools === \"builtin\")) {\n\t\treturn [];\n\t}\n\treturn [...allToolNames];\n}\n\nfunction getAllowedToolNamesForDomainDispatch(options: CreateAgentSessionOptions): string[] | undefined {\n\treturn options.tools ?? (options.noTools === \"all\" ? [] : undefined);\n}\n\nfunction createEffectiveLoadoutAccessPolicy(\n\toptions: CreateAgentSessionOptions,\n\tresourceLoader: ResourceLoader,\n\tcwd: string,\n\tagentDir: string,\n\tprovider?: string,\n): LoadoutAccessPolicy | undefined {\n\tif (options.loadoutAccessPolicy) {\n\t\treturn options.loadoutAccessPolicy;\n\t}\n\n\tconst session = createDomainDispatchRuntimeSession({\n\t\tbaseToolNames: getDomainDispatchBaseToolNames(options),\n\t\tresourceLoader,\n\t\tcustomTools: options.customTools,\n\t\tallowedToolNames: getAllowedToolNamesForDomainDispatch(options),\n\t\texcludedToolNames: options.excludeTools,\n\t});\n\n\t// Grok OAuth: apply grok-harness domain loadout without requiring OMK_DOMAIN_ROUTING=1.\n\tif (provider) {\n\t\ttry {\n\t\t\tconst grokDispatch = tryGrokHarnessDispatch({\n\t\t\t\tprovider,\n\t\t\t\tsession,\n\t\t\t\tresourceLoader,\n\t\t\t\tcwd,\n\t\t\t\tagentDir,\n\t\t\t\tenv: process.env,\n\t\t\t});\n\t\t\tif (grokDispatch.loadoutAccessPolicy) {\n\t\t\t\treturn grokDispatch.loadoutAccessPolicy;\n\t\t\t}\n\t\t} catch {\n\t\t\t// Fall through to opt-in domain routing.\n\t\t}\n\t}\n\n\tconst domainRoutingPrompt = options.domainRoutingPrompt?.trim();\n\tif (!domainRoutingPrompt || !isDomainRoutingEnabled(process.env)) {\n\t\treturn undefined;\n\t}\n\n\ttry {\n\t\tconst dispatch = tryDomainDispatch({\n\t\t\trole: \"coder\",\n\t\t\tinitialPrompt: domainRoutingPrompt,\n\t\t\tsession,\n\t\t\tresourceLoader,\n\t\t\tcwd,\n\t\t\tagentDir,\n\t\t\tenv: process.env,\n\t\t});\n\t\treturn dispatch.loadoutAccessPolicy;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/**\n * Create an AgentSession with the specified options.\n *\n * @example\n * ```typescript\n * // Minimal - uses defaults\n * const { session } = await createAgentSession();\n *\n * // With explicit model\n * import { getModel } from 'omk-ai';\n * const { session } = await createAgentSession({\n * model: getModel('anthropic', 'claude-opus-4-5'),\n * thinkingLevel: 'high',\n * });\n *\n * // Continue previous session\n * const { session, modelFallbackMessage } = await createAgentSession({\n * continueSession: true,\n * });\n *\n * // Full control\n * const loader = new DefaultResourceLoader({\n * cwd: process.cwd(),\n * agentDir: getAgentDir(),\n * settingsManager: SettingsManager.create(),\n * });\n * await loader.reload();\n * const { session } = await createAgentSession({\n * model: myModel,\n * tools: [\"read\", \"bash\"],\n * resourceLoader: loader,\n * sessionManager: SessionManager.inMemory(),\n * });\n * ```\n */\nexport async function createAgentSession(options: CreateAgentSessionOptions = {}): Promise<CreateAgentSessionResult> {\n\tconst cwd = resolvePath(options.cwd ?? options.sessionManager?.getCwd() ?? process.cwd());\n\tconst agentDir = options.agentDir ? resolvePath(options.agentDir) : getDefaultAgentDir();\n\tlet resourceLoader = options.resourceLoader;\n\n\t// Use provided or create AuthStorage and ModelRegistry\n\tconst authPath = options.agentDir ? join(agentDir, \"auth.json\") : undefined;\n\tconst modelsPath = options.agentDir ? join(agentDir, \"models.json\") : undefined;\n\tconst authStorage = options.authStorage ?? AuthStorage.create(authPath);\n\tconst modelRegistry = options.modelRegistry ?? ModelRegistry.create(authStorage, modelsPath);\n\n\tconst settingsManager = options.settingsManager ?? SettingsManager.create(cwd, agentDir);\n\tconst agentToolSettings = resolveAgentToolSettings(settingsManager);\n\tconst sessionManager = options.sessionManager ?? SessionManager.create(cwd, getDefaultSessionDir(cwd, agentDir));\n\tlet replayLedger = options.replayLedger;\n\tlet replayGoalId = options.replayGoalId;\n\tconst sessionFile = sessionManager.getSessionFile();\n\tif (!replayLedger && sessionManager.isPersisted() && sessionFile) {\n\t\treplayGoalId ??=\n\t\t\tsessionManager\n\t\t\t\t.getSessionId()\n\t\t\t\t.replace(/[^A-Za-z0-9._-]+/g, \"-\")\n\t\t\t\t.replace(/^[._-]+|[._-]+$/g, \"\") || \"session\";\n\t\treplayLedger = new ReplayLedgerManager(replayGoalId, `${sessionFile}.replay.jsonl`);\n\t}\n\n\tif (!resourceLoader) {\n\t\tresourceLoader = new DefaultResourceLoader({ cwd, agentDir, settingsManager });\n\t\tawait resourceLoader.reload();\n\t\ttime(\"resourceLoader.reload\");\n\t}\n\n\t// Check if session has existing data to restore. Live open/resume always\n\t// inspects the transcript and either auto-repairs missing-only tail results\n\t// or fails closed on ambiguous corruption (ALG001-A).\n\tconst existingSession = sessionManager.buildSessionContext();\n\tconst transcriptRecovery = recoverSessionTranscriptOnOpen(sessionManager, existingSession.messages);\n\tconst hasExistingSession = existingSession.messages.length > 0;\n\tconst hasThinkingEntry = sessionManager.getBranch().some((entry) => entry.type === \"thinking_level_change\");\n\n\tlet model = options.model;\n\tlet modelFallbackMessage: string | undefined;\n\n\t// If session has data, try to restore model from it\n\tif (!model && hasExistingSession && existingSession.model) {\n\t\tconst restoredModel = modelRegistry.find(existingSession.model.provider, existingSession.model.modelId);\n\t\tif (restoredModel && modelRegistry.hasConfiguredAuth(restoredModel)) {\n\t\t\tmodel = restoredModel;\n\t\t}\n\t\tif (!model) {\n\t\t\tmodelFallbackMessage = `Could not restore model ${existingSession.model.provider}/${existingSession.model.modelId}`;\n\t\t}\n\t}\n\n\t// If still no model, use findInitialModel (checks settings default, then provider defaults)\n\tif (!model) {\n\t\tconst result = await findInitialModel({\n\t\t\tscopedModels: [],\n\t\t\tisContinuing: hasExistingSession,\n\t\t\tdefaultProvider: settingsManager.getDefaultProvider(),\n\t\t\tdefaultModelId: settingsManager.getDefaultModel(),\n\t\t\tdefaultThinkingLevel: settingsManager.getDefaultThinkingLevel(),\n\t\t\tmodelRegistry,\n\t\t});\n\t\tmodel = result.model;\n\t\tif (!model) {\n\t\t\tmodelFallbackMessage = formatNoModelsAvailableMessage();\n\t\t} else if (modelFallbackMessage) {\n\t\t\tmodelFallbackMessage += `. Using ${model.provider}/${model.id}`;\n\t\t}\n\t}\n\n\t// Loadout policy after model resolution so Grok OAuth can auto-apply grok-harness.\n\tconst effectiveLoadoutAccessPolicy = createEffectiveLoadoutAccessPolicy(\n\t\toptions,\n\t\tresourceLoader,\n\t\tcwd,\n\t\tagentDir,\n\t\tmodel?.provider,\n\t);\n\n\tlet thinkingLevel = options.thinkingLevel;\n\n\t// If session has data, restore thinking level from it\n\tif (thinkingLevel === undefined && hasExistingSession) {\n\t\tthinkingLevel = hasThinkingEntry\n\t\t\t? (existingSession.thinkingLevel as ThinkingLevel)\n\t\t\t: (settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL);\n\t}\n\n\t// Fall back to settings default\n\tif (thinkingLevel === undefined) {\n\t\tthinkingLevel = settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL;\n\t}\n\n\t// Clamp to model capabilities\n\tif (!model) {\n\t\tthinkingLevel = \"off\";\n\t} else {\n\t\tthinkingLevel = clampThinkingLevel(model, thinkingLevel) as ThinkingLevel;\n\t}\n\n\tconst defaultActiveToolNames: ToolName[] = [\"read\", \"bash\", \"edit\", \"write\", \"diagnostics\"];\n\tconst allowedToolNames = options.tools ?? (options.noTools === \"all\" ? [] : undefined);\n\tconst excludedToolNames = options.excludeTools;\n\tconst excludedToolNameSet = excludedToolNames ? new Set(excludedToolNames) : undefined;\n\tconst initialActiveToolNames: string[] = (\n\t\toptions.tools ? [...options.tools] : options.noTools ? [] : defaultActiveToolNames\n\t).filter((name) => !excludedToolNameSet?.has(name));\n\n\tlet agent: Agent;\n\n\t// Create convertToLlm wrapper that filters images if blockImages is enabled (defense-in-depth)\n\tconst convertToLlmWithBlockImages = (messages: AgentMessage[]): Message[] => {\n\t\tconst converted = convertToLlm(messages);\n\t\t// Check setting dynamically so mid-session changes take effect\n\t\tif (!settingsManager.getBlockImages()) {\n\t\t\treturn converted;\n\t\t}\n\t\t// Filter out ImageContent from all messages, replacing with text placeholder\n\t\treturn converted.map((msg) => {\n\t\t\tif (msg.role === \"user\" || msg.role === \"toolResult\") {\n\t\t\t\tconst content = msg.content;\n\t\t\t\tif (Array.isArray(content)) {\n\t\t\t\t\tconst hasImages = content.some((c) => c.type === \"image\");\n\t\t\t\t\tif (hasImages) {\n\t\t\t\t\t\tconst filteredContent = content\n\t\t\t\t\t\t\t.map((c) =>\n\t\t\t\t\t\t\t\tc.type === \"image\" ? { type: \"text\" as const, text: \"Image reading is disabled.\" } : c,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t.filter(\n\t\t\t\t\t\t\t\t(c, i, arr) =>\n\t\t\t\t\t\t\t\t\t// Dedupe consecutive \"Image reading is disabled.\" texts\n\t\t\t\t\t\t\t\t\t!(\n\t\t\t\t\t\t\t\t\t\tc.type === \"text\" &&\n\t\t\t\t\t\t\t\t\t\tc.text === \"Image reading is disabled.\" &&\n\t\t\t\t\t\t\t\t\t\ti > 0 &&\n\t\t\t\t\t\t\t\t\t\tarr[i - 1].type === \"text\" &&\n\t\t\t\t\t\t\t\t\t\t(arr[i - 1] as { type: \"text\"; text: string }).text === \"Image reading is disabled.\"\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\treturn { ...msg, content: filteredContent };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn msg;\n\t\t});\n\t};\n\n\tconst extensionRunnerRef: { current?: ExtensionRunner } = {};\n\n\tagent = new Agent({\n\t\tinitialState: {\n\t\t\tsystemPrompt: \"\",\n\t\t\tmodel,\n\t\t\tthinkingLevel,\n\t\t\ttools: [],\n\t\t},\n\t\tconvertToLlm: convertToLlmWithBlockImages,\n\t\tstreamFn: async (model, context, options) => {\n\t\t\tconst auth = await modelRegistry.getApiKeyAndHeaders(model);\n\t\t\tif (!auth.ok) {\n\t\t\t\tthrow new Error(auth.error);\n\t\t\t}\n\t\t\tconst providerRetrySettings = settingsManager.getProviderRetrySettings();\n\t\t\tconst httpIdleTimeoutMs = settingsManager.getHttpIdleTimeoutMs();\n\t\t\t// SDKs treat timeout=0 as 0ms (immediate timeout), not \"no timeout\".\n\t\t\t// Use max int32 to effectively disable the timeout.\n\t\t\tconst effectiveTimeoutMs = httpIdleTimeoutMs === 0 ? 2147483647 : httpIdleTimeoutMs;\n\t\t\tconst timeoutMs = options?.timeoutMs ?? providerRetrySettings.timeoutMs ?? effectiveTimeoutMs;\n\t\t\tconst websocketConnectTimeoutMs =\n\t\t\t\toptions?.websocketConnectTimeoutMs ?? settingsManager.getWebSocketConnectTimeoutMs();\n\t\t\treturn streamSimple(model, context, {\n\t\t\t\t...options,\n\t\t\t\tapiKey: auth.apiKey,\n\t\t\t\ttimeoutMs,\n\t\t\t\twebsocketConnectTimeoutMs,\n\t\t\t\tmaxRetries: options?.maxRetries ?? providerRetrySettings.maxRetries,\n\t\t\t\tmaxRetryDelayMs: options?.maxRetryDelayMs ?? providerRetrySettings.maxRetryDelayMs,\n\t\t\t\tonRateLimit: async (snapshot, responseModel) => {\n\t\t\t\t\tif (auth.apiKey && /^openai-codex(?:-|$)/.test(responseModel.provider)) {\n\t\t\t\t\t\trecordCodexPassiveUsage(auth.apiKey, snapshot);\n\t\t\t\t\t} else if (auth.apiKey && responseModel.provider === \"anthropic\") {\n\t\t\t\t\t\trecordClaudePassiveUsage(auth.apiKey, snapshot);\n\t\t\t\t\t}\n\t\t\t\t\tawait options?.onRateLimit?.(snapshot, responseModel);\n\t\t\t\t},\n\t\t\t\theaders: mergeProviderAttributionHeaders(\n\t\t\t\t\tmodel,\n\t\t\t\t\tsettingsManager,\n\t\t\t\t\toptions?.sessionId,\n\t\t\t\t\tauth.headers,\n\t\t\t\t\toptions?.headers,\n\t\t\t\t),\n\t\t\t});\n\t\t},\n\t\tonPayload: async (payload, _model) => {\n\t\t\tconst runner = extensionRunnerRef.current;\n\t\t\tif (!runner?.hasHandlers(\"before_provider_request\")) {\n\t\t\t\treturn payload;\n\t\t\t}\n\t\t\treturn runner.emitBeforeProviderRequest(payload);\n\t\t},\n\t\tonResponse: async (response, _model) => {\n\t\t\tconst runner = extensionRunnerRef.current;\n\t\t\tif (!runner?.hasHandlers(\"after_provider_response\")) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait runner.emit({\n\t\t\t\ttype: \"after_provider_response\",\n\t\t\t\tstatus: response.status,\n\t\t\t\theaders: response.headers,\n\t\t\t});\n\t\t},\n\t\tsessionId: sessionManager.getSessionId(),\n\t\ttransformContext: async (messages) => {\n\t\t\tconst runner = extensionRunnerRef.current;\n\t\t\tif (!runner) return messages;\n\t\t\treturn runner.emitContext(messages);\n\t\t},\n\t\tsteeringMode: settingsManager.getSteeringMode(),\n\t\tfollowUpMode: settingsManager.getFollowUpMode(),\n\t\ttransport: settingsManager.getTransport(),\n\t\tthinkingBudgets: settingsManager.getThinkingBudgets(),\n\t\tmaxRetryDelayMs: settingsManager.getProviderRetrySettings().maxRetryDelayMs,\n\t\ttoolTimeoutMs: agentToolSettings.toolTimeoutMs,\n\t\ttoolTimeouts: agentToolSettings.toolTimeouts,\n\t\ttoolScheduler: agentToolSettings.toolScheduler,\n\t\tmaxToolConcurrency: agentToolSettings.maxToolConcurrency,\n\t\tstrictExtensionClaims: agentToolSettings.strictExtensionClaims,\n\t\tcwd,\n\t\t// §5.5 Node identity resolver: symlink/hardlink/drive/UNC path aliases\n\t\t// conflict in the dag-v2 scheduler instead of racing (ALG002-A).\n\t\tresourceKeyResolver: createNodeResourceKeyResolver(),\n\t});\n\n\t// Restore messages if session has existing data\n\tif (hasExistingSession) {\n\t\tagent.state.messages = transcriptRecovery.messages;\n\t\tif (!hasThinkingEntry) {\n\t\t\tsessionManager.appendThinkingLevelChange(thinkingLevel);\n\t\t}\n\t} else {\n\t\t// Save initial model and thinking level for new sessions so they can be restored on resume\n\t\tif (model) {\n\t\t\tsessionManager.appendModelChange(model.provider, model.id);\n\t\t}\n\t\tsessionManager.appendThinkingLevelChange(thinkingLevel);\n\t}\n\n\tconst session = new AgentSession({\n\t\tagent,\n\t\tsessionManager,\n\t\tsettingsManager,\n\t\tcwd,\n\t\tscopedModels: options.scopedModels,\n\t\tresourceLoader,\n\t\tcustomTools: options.customTools,\n\t\tmodelRegistry,\n\t\tinitialActiveToolNames,\n\t\tallowedToolNames,\n\t\texcludedToolNames,\n\t\textensionRunnerRef,\n\t\tloadoutAccessPolicy: effectiveLoadoutAccessPolicy,\n\t\tbashSandboxPreflight: options.bashSandboxPreflight,\n\t\tsessionStartEvent: options.sessionStartEvent,\n\t\tmodelPinned: options.modelPinned,\n\t\ttranscriptRepair: transcriptRecovery.repair,\n\t\treplayLedger,\n\t\treplayGoalId,\n\t\treplayLaneId: options.replayLaneId,\n\t});\n\tconst extensionsResult = resourceLoader.getExtensions();\n\n\treturn {\n\t\tsession,\n\t\textensionsResult,\n\t\tmodelFallbackMessage,\n\t};\n}\n"]}
package/dist/core/sdk.js CHANGED
@@ -416,6 +416,7 @@ export async function createAgentSession(options = {}) {
416
416
  loadoutAccessPolicy: effectiveLoadoutAccessPolicy,
417
417
  bashSandboxPreflight: options.bashSandboxPreflight,
418
418
  sessionStartEvent: options.sessionStartEvent,
419
+ modelPinned: options.modelPinned,
419
420
  transcriptRepair: transcriptRecovery.repair,
420
421
  replayLedger,
421
422
  replayGoalId,