@bastani/atomic 0.9.41 → 0.9.42-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (270) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/dist/builtin/intercom/CHANGELOG.md +13 -0
  3. package/dist/builtin/intercom/README.md +7 -3
  4. package/dist/builtin/intercom/broker/broker-status.ts +48 -0
  5. package/dist/builtin/intercom/broker/broker.ts +90 -9
  6. package/dist/builtin/intercom/broker/client.ts +112 -12
  7. package/dist/builtin/intercom/broker/control.ts +225 -0
  8. package/dist/builtin/intercom/broker/multinode-router.ts +32 -39
  9. package/dist/builtin/intercom/broker/paths.ts +4 -0
  10. package/dist/builtin/intercom/broker/pending-send-registry.ts +27 -5
  11. package/dist/builtin/intercom/broker/shared-storage-setting.ts +60 -0
  12. package/dist/builtin/intercom/broker/spawn.ts +13 -4
  13. package/dist/builtin/intercom/broker/sql-lease-store.ts +23 -0
  14. package/dist/builtin/intercom/broker/watchdog.ts +105 -0
  15. package/dist/builtin/intercom/index.bundle.mjs +200 -22
  16. package/dist/builtin/intercom/package.json +1 -1
  17. package/dist/builtin/intercom/types.ts +34 -2
  18. package/dist/builtin/mcp/package.json +1 -1
  19. package/dist/builtin/subagents/package.json +1 -1
  20. package/dist/builtin/subagents/src/extension/index.bundle.mjs +1 -0
  21. package/dist/builtin/web-access/package.json +1 -1
  22. package/dist/builtin/workflows/CHANGELOG.md +12 -0
  23. package/dist/builtin/workflows/README.md +10 -0
  24. package/dist/builtin/workflows/builtin/{chunk-txbc93t6.js → chunk-3s95f7ry.js} +1 -1
  25. package/dist/builtin/workflows/builtin/{chunk-n2gkzkty.js → chunk-edhf30ef.js} +1 -1
  26. package/dist/builtin/workflows/builtin/{chunk-8x61e0s7.js → chunk-frqxd4d1.js} +18 -9
  27. package/dist/builtin/workflows/builtin/goal.js +2 -2
  28. package/dist/builtin/workflows/builtin/index.js +3 -3
  29. package/dist/builtin/workflows/builtin/ralph.js +2 -2
  30. package/dist/builtin/workflows/package.json +6 -1
  31. package/dist/builtin/workflows/src/authoring.d.ts +3 -1
  32. package/dist/builtin/workflows/src/durable/journal/internal.d.ts +13 -0
  33. package/dist/builtin/workflows/src/durable/journal/resolution.d.ts +2 -0
  34. package/dist/builtin/workflows/src/durable/journal/sql-run-journal.d.ts +1 -0
  35. package/dist/builtin/workflows/src/extension/index.bundle.mjs +88554 -82820
  36. package/dist/builtin/workflows/src/extension/local-worker-entry.mjs +72635 -0
  37. package/dist/builtin/workflows/src/index.js +53824 -5907
  38. package/dist/builtin/workflows/src/local-worker-entry.mjs +72635 -0
  39. package/dist/builtin/workflows/src/shared/authoring-contract-stage.d.ts +7 -0
  40. package/dist/builtin/workflows/src/shared/authoring-contract-ui.d.ts +2 -0
  41. package/dist/builtin/workflows/src/workers/public-options.d.ts +31 -0
  42. package/dist/bun/app-host-modules.d.ts +9 -0
  43. package/dist/bun/app-host-modules.d.ts.map +1 -0
  44. package/dist/bun/app-host-modules.js +10 -0
  45. package/dist/bun/app-host-modules.js.map +1 -0
  46. package/dist/bun/cli.d.ts +3 -1
  47. package/dist/bun/cli.d.ts.map +1 -1
  48. package/dist/bun/cli.js +15 -1
  49. package/dist/bun/cli.js.map +1 -1
  50. package/dist/bun/internal-intercom-broker.d.ts +17 -0
  51. package/dist/bun/internal-intercom-broker.d.ts.map +1 -1
  52. package/dist/bun/internal-intercom-broker.js +29 -0
  53. package/dist/bun/internal-intercom-broker.js.map +1 -1
  54. package/dist/bun/internal-workflow-worker.d.ts +4 -0
  55. package/dist/bun/internal-workflow-worker.d.ts.map +1 -0
  56. package/dist/bun/internal-workflow-worker.js +37 -0
  57. package/dist/bun/internal-workflow-worker.js.map +1 -0
  58. package/dist/bun/split-loader.js +10 -2
  59. package/dist/bun/split-loader.js.map +1 -1
  60. package/dist/cli/args.d.ts.map +1 -1
  61. package/dist/cli/args.js +3 -1
  62. package/dist/cli/args.js.map +1 -1
  63. package/dist/cli/intercom-command.d.ts +15 -0
  64. package/dist/cli/intercom-command.d.ts.map +1 -0
  65. package/dist/cli/intercom-command.js +87 -0
  66. package/dist/cli/intercom-command.js.map +1 -0
  67. package/dist/cli/storage-command.d.ts +39 -0
  68. package/dist/cli/storage-command.d.ts.map +1 -0
  69. package/dist/cli/storage-command.js +367 -0
  70. package/dist/cli/storage-command.js.map +1 -0
  71. package/dist/core/agent-session-events.d.ts.map +1 -1
  72. package/dist/core/agent-session-events.js +5 -1
  73. package/dist/core/agent-session-events.js.map +1 -1
  74. package/dist/core/agent-session-methods.d.ts +1 -0
  75. package/dist/core/agent-session-methods.d.ts.map +1 -1
  76. package/dist/core/agent-session-methods.js.map +1 -1
  77. package/dist/core/agent-session-tasks.d.ts.map +1 -1
  78. package/dist/core/agent-session-tasks.js +1 -0
  79. package/dist/core/agent-session-tasks.js.map +1 -1
  80. package/dist/core/agent-session-types.d.ts +1 -0
  81. package/dist/core/agent-session-types.d.ts.map +1 -1
  82. package/dist/core/agent-session-types.js.map +1 -1
  83. package/dist/core/agent-session.d.ts +1 -0
  84. package/dist/core/agent-session.d.ts.map +1 -1
  85. package/dist/core/agent-session.js +2 -0
  86. package/dist/core/agent-session.js.map +1 -1
  87. package/dist/core/builtin-install-layout.d.ts +6 -0
  88. package/dist/core/builtin-install-layout.d.ts.map +1 -1
  89. package/dist/core/builtin-install-layout.js +9 -0
  90. package/dist/core/builtin-install-layout.js.map +1 -1
  91. package/dist/core/child-session-options.d.ts.map +1 -1
  92. package/dist/core/child-session-options.js +3 -0
  93. package/dist/core/child-session-options.js.map +1 -1
  94. package/dist/core/exec.js +2 -2
  95. package/dist/core/exec.js.map +1 -1
  96. package/dist/core/export-html/template.css +9 -0
  97. package/dist/core/export-html/template.js +165 -45
  98. package/dist/core/extensions/loader-host-modules.d.ts +6 -0
  99. package/dist/core/extensions/loader-host-modules.d.ts.map +1 -1
  100. package/dist/core/extensions/loader-host-modules.js +12 -1
  101. package/dist/core/extensions/loader-host-modules.js.map +1 -1
  102. package/dist/core/extensions/loader-virtual-modules.d.ts +6 -0
  103. package/dist/core/extensions/loader-virtual-modules.d.ts.map +1 -1
  104. package/dist/core/extensions/loader-virtual-modules.js +18 -0
  105. package/dist/core/extensions/loader-virtual-modules.js.map +1 -1
  106. package/dist/core/extensions/message-types.d.ts +4 -0
  107. package/dist/core/extensions/message-types.d.ts.map +1 -1
  108. package/dist/core/extensions/message-types.js.map +1 -1
  109. package/dist/core/fast-model-routing-transport.d.ts.map +1 -1
  110. package/dist/core/fast-model-routing-transport.js +5 -1
  111. package/dist/core/fast-model-routing-transport.js.map +1 -1
  112. package/dist/core/intercom-broker-control.d.ts +67 -0
  113. package/dist/core/intercom-broker-control.d.ts.map +1 -0
  114. package/dist/core/intercom-broker-control.js +17 -0
  115. package/dist/core/intercom-broker-control.js.map +1 -0
  116. package/dist/core/model-runtime-streaming.d.ts.map +1 -1
  117. package/dist/core/model-runtime-streaming.js +38 -1
  118. package/dist/core/model-runtime-streaming.js.map +1 -1
  119. package/dist/core/package-manager-git.d.ts +2 -2
  120. package/dist/core/package-manager-git.d.ts.map +1 -1
  121. package/dist/core/package-manager-git.js.map +1 -1
  122. package/dist/core/package-manager-manifest.d.ts.map +1 -1
  123. package/dist/core/package-manager-manifest.js +9 -1
  124. package/dist/core/package-manager-manifest.js.map +1 -1
  125. package/dist/core/package-manager-npm.d.ts +2 -2
  126. package/dist/core/package-manager-npm.d.ts.map +1 -1
  127. package/dist/core/package-manager-npm.js.map +1 -1
  128. package/dist/core/package-manager-paths.d.ts +7 -7
  129. package/dist/core/package-manager-paths.d.ts.map +1 -1
  130. package/dist/core/package-manager-paths.js.map +1 -1
  131. package/dist/core/package-manager-storage-adapters.d.ts +7 -0
  132. package/dist/core/package-manager-storage-adapters.d.ts.map +1 -0
  133. package/dist/core/package-manager-storage-adapters.js +76 -0
  134. package/dist/core/package-manager-storage-adapters.js.map +1 -0
  135. package/dist/core/package-manager-types.d.ts +4 -0
  136. package/dist/core/package-manager-types.d.ts.map +1 -1
  137. package/dist/core/package-manager-types.js.map +1 -1
  138. package/dist/core/provider-rate-limits.d.ts +23 -0
  139. package/dist/core/provider-rate-limits.d.ts.map +1 -0
  140. package/dist/core/provider-rate-limits.js +113 -0
  141. package/dist/core/provider-rate-limits.js.map +1 -0
  142. package/dist/core/provider-request-admission.d.ts +7 -0
  143. package/dist/core/provider-request-admission.d.ts.map +1 -0
  144. package/dist/core/provider-request-admission.js +91 -0
  145. package/dist/core/provider-request-admission.js.map +1 -0
  146. package/dist/core/resource-admission-scope.d.ts +4 -0
  147. package/dist/core/resource-admission-scope.d.ts.map +1 -0
  148. package/dist/core/resource-admission-scope.js +9 -0
  149. package/dist/core/resource-admission-scope.js.map +1 -0
  150. package/dist/core/sdk-types.d.ts +16 -0
  151. package/dist/core/sdk-types.d.ts.map +1 -1
  152. package/dist/core/sdk-types.js.map +1 -1
  153. package/dist/core/sdk.d.ts +2 -1
  154. package/dist/core/sdk.d.ts.map +1 -1
  155. package/dist/core/sdk.js +18 -2
  156. package/dist/core/sdk.js.map +1 -1
  157. package/dist/core/session-lifecycle-work.d.ts +1 -0
  158. package/dist/core/session-lifecycle-work.d.ts.map +1 -1
  159. package/dist/core/session-lifecycle-work.js +1 -0
  160. package/dist/core/session-lifecycle-work.js.map +1 -1
  161. package/dist/core/settings-manager-basic-accessors.d.ts +3 -0
  162. package/dist/core/settings-manager-basic-accessors.d.ts.map +1 -1
  163. package/dist/core/settings-manager-basic-accessors.js +6 -0
  164. package/dist/core/settings-manager-basic-accessors.js.map +1 -1
  165. package/dist/core/settings-schema.d.ts +22 -0
  166. package/dist/core/settings-schema.d.ts.map +1 -1
  167. package/dist/core/settings-schema.js +18 -1
  168. package/dist/core/settings-schema.js.map +1 -1
  169. package/dist/core/storage/index.d.ts +1 -0
  170. package/dist/core/storage/index.d.ts.map +1 -1
  171. package/dist/core/storage/index.js +1 -0
  172. package/dist/core/storage/index.js.map +1 -1
  173. package/dist/core/storage/node-sqlite.d.ts +2 -1
  174. package/dist/core/storage/node-sqlite.d.ts.map +1 -1
  175. package/dist/core/storage/node-sqlite.js +5 -4
  176. package/dist/core/storage/node-sqlite.js.map +1 -1
  177. package/dist/core/storage/provider.d.ts +6 -0
  178. package/dist/core/storage/provider.d.ts.map +1 -1
  179. package/dist/core/storage/provider.js +27 -7
  180. package/dist/core/storage/provider.js.map +1 -1
  181. package/dist/core/storage/sqlite-facade.d.ts +49 -0
  182. package/dist/core/storage/sqlite-facade.d.ts.map +1 -0
  183. package/dist/core/storage/sqlite-facade.js +180 -0
  184. package/dist/core/storage/sqlite-facade.js.map +1 -0
  185. package/dist/core/storage/storage-status.d.ts +81 -0
  186. package/dist/core/storage/storage-status.d.ts.map +1 -0
  187. package/dist/core/storage/storage-status.js +409 -0
  188. package/dist/core/storage/storage-status.js.map +1 -0
  189. package/dist/core/tasks/supervisor.d.ts +2 -1
  190. package/dist/core/tasks/supervisor.d.ts.map +1 -1
  191. package/dist/core/tasks/supervisor.js +105 -4
  192. package/dist/core/tasks/supervisor.js.map +1 -1
  193. package/dist/core/tools/bash-pty-native.d.ts.map +1 -1
  194. package/dist/core/tools/bash-pty-native.js +1 -1
  195. package/dist/core/tools/bash-pty-native.js.map +1 -1
  196. package/dist/index.d.ts +2 -1
  197. package/dist/index.d.ts.map +1 -1
  198. package/dist/index.js +2 -1
  199. package/dist/index.js.map +1 -1
  200. package/dist/main.d.ts.map +1 -1
  201. package/dist/main.js +28 -2
  202. package/dist/main.js.map +1 -1
  203. package/dist/modes/interactive/components/assistant-message.d.ts +3 -1
  204. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  205. package/dist/modes/interactive/components/assistant-message.js +7 -3
  206. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  207. package/dist/modes/interactive/components/chat-message-renderer.d.ts +9 -1
  208. package/dist/modes/interactive/components/chat-message-renderer.d.ts.map +1 -1
  209. package/dist/modes/interactive/components/chat-message-renderer.js +16 -8
  210. package/dist/modes/interactive/components/chat-message-renderer.js.map +1 -1
  211. package/dist/modes/interactive/components/login-dialog.d.ts +5 -0
  212. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  213. package/dist/modes/interactive/components/login-dialog.js +69 -2
  214. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  215. package/dist/modes/interactive/components/markdown-transform.d.ts +1 -1
  216. package/dist/modes/interactive/components/markdown-transform.d.ts.map +1 -1
  217. package/dist/modes/interactive/components/markdown-transform.js +2 -2
  218. package/dist/modes/interactive/components/markdown-transform.js.map +1 -1
  219. package/dist/modes/interactive/components/session-selector-search.d.ts.map +1 -1
  220. package/dist/modes/interactive/components/session-selector-search.js +80 -22
  221. package/dist/modes/interactive/components/session-selector-search.js.map +1 -1
  222. package/dist/modes/interactive/components/user-message.d.ts +3 -1
  223. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  224. package/dist/modes/interactive/components/user-message.js +6 -2
  225. package/dist/modes/interactive/components/user-message.js.map +1 -1
  226. package/dist/modes/interactive/interactive-mode-deps.d.ts +1 -1
  227. package/dist/modes/interactive/interactive-mode-deps.d.ts.map +1 -1
  228. package/dist/modes/interactive/interactive-mode-deps.js.map +1 -1
  229. package/dist/modes/interactive/interactive-render-chat.js +6 -2
  230. package/dist/modes/interactive/interactive-render-chat.js.map +1 -1
  231. package/dist/modes/interactive-engine/engine-health.d.ts +8 -4
  232. package/dist/modes/interactive-engine/engine-health.d.ts.map +1 -1
  233. package/dist/modes/interactive-engine/engine-health.js +41 -6
  234. package/dist/modes/interactive-engine/engine-health.js.map +1 -1
  235. package/dist/modes/interactive-engine/isolated-runtime.d.ts.map +1 -1
  236. package/dist/modes/interactive-engine/isolated-runtime.js +19 -17
  237. package/dist/modes/interactive-engine/isolated-runtime.js.map +1 -1
  238. package/dist/modes/interactive-engine/remote-queue-pause.js +1 -1
  239. package/dist/modes/interactive-engine/remote-queue-pause.js.map +1 -1
  240. package/dist/modes/rpc/rpc-input-scheduler.d.ts +1 -0
  241. package/dist/modes/rpc/rpc-input-scheduler.d.ts.map +1 -1
  242. package/dist/modes/rpc/rpc-input-scheduler.js +4 -1
  243. package/dist/modes/rpc/rpc-input-scheduler.js.map +1 -1
  244. package/dist/modes/rpc/rpc-input.d.ts.map +1 -1
  245. package/dist/modes/rpc/rpc-input.js +29 -7
  246. package/dist/modes/rpc/rpc-input.js.map +1 -1
  247. package/dist/utils/shell.d.ts.map +1 -1
  248. package/dist/utils/shell.js +14 -0
  249. package/dist/utils/shell.js.map +1 -1
  250. package/docs/custom-provider/oauth.md +8 -1
  251. package/docs/extensions/api-reference.md +2 -0
  252. package/docs/intercom/operations.md +17 -8
  253. package/docs/intercom/reference.md +1 -1
  254. package/docs/keybindings.md +1 -1
  255. package/docs/packages/authoring.md +15 -0
  256. package/docs/packages.md +3 -2
  257. package/docs/providers.md +2 -0
  258. package/docs/reference/cli.md +17 -0
  259. package/docs/sdk.md +37 -0
  260. package/docs/settings.md +33 -2
  261. package/docs/storage.md +191 -219
  262. package/docs/usage.md +1 -1
  263. package/docs/workflows/api-reference.md +50 -0
  264. package/docs/workflows/operations.md +44 -1
  265. package/docs/workflows.md +25 -0
  266. package/examples/extensions/gondolin/package-lock.json +14 -24
  267. package/examples/extensions/gondolin/package.json +1 -1
  268. package/npm-shrinkwrap.json +35 -35
  269. package/package.json +4 -4
  270. package/schemas/settings.schema.json +87 -1
@@ -292,6 +292,20 @@ export function killProcessTree(pid) {
292
292
  }
293
293
  }
294
294
  else {
295
+ if (process.platform === "darwin") {
296
+ // macOS signals group members individually after releasing the group lock. A killed
297
+ // child can wake its waiting shell before that shell is signalled, letting `a; b`
298
+ // start b during cancellation (pi #11252). Stop the group first to reduce this race;
299
+ // this is best-effort, not an atomic freeze. SIGKILL also kills stopped processes.
300
+ // Linux holds tasklist_lock across group signalling; child exit notification needs
301
+ // its write lock, so the shell is signalled before observing the killed child's exit.
302
+ try {
303
+ process.kill(-pid, "SIGSTOP");
304
+ }
305
+ catch {
306
+ // A failed stop must not prevent the kill attempt or its single-process fallback.
307
+ }
308
+ }
295
309
  // Use SIGKILL on Unix/Linux/Mac
296
310
  try {
297
311
  process.kill(-pid, "SIGKILL");
@@ -1 +1 @@
1
- {"version":3,"file":"shell.js","sourceRoot":"","sources":["../../src/utils/shell.ts"],"names":[],"mappings":"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","sourcesContent":["import { accessSync, constants } from \"node:fs\";\nimport { delimiter, join, win32 } from \"node:path\";\nimport { Worker } from \"node:worker_threads\";\nimport { spawn, spawnSync } from \"child_process\";\nimport { getBinDir } from \"../config.js\";\nimport { createChildProcessEnvironment } from \"./child-process.ts\";\n\nexport interface ShellConfig {\n\tshell: string;\n\targs: string[];\n\tcommandTransport?: \"argv\" | \"stdin\";\n}\n\nfunction isLegacyWslBashPath(path: string): boolean {\n\tconst normalized = win32.normalize(path).toLowerCase();\n\tconst systemRoot = process.env.SystemRoot;\n\tif (systemRoot) {\n\t\tif (!/^[a-z]:[\\\\/]/i.test(systemRoot)) return false;\n\t\treturn [\"System32\", \"Sysnative\"].some(\n\t\t\t(directory) => normalized === win32.join(systemRoot, directory, \"bash.exe\").toLowerCase(),\n\t\t);\n\t}\n\t// Fall back to conventional launcher paths only when the Windows directory is unknown.\n\treturn /^[a-z]:\\\\windows\\\\(?:system32|sysnative)\\\\bash\\.exe$/.test(normalized);\n}\n\nfunction getBashShellConfig(shell: string): ShellConfig {\n\treturn isLegacyWslBashPath(shell) ? { shell, args: [\"-s\"], commandTransport: \"stdin\" } : { shell, args: [\"-c\"] };\n}\n\n// On Unix this is identical to existsSync() (both are access(F_OK)), just\n// spelled with try/catch. On Windows we explicitly avoid existsSync(): its\n// extra stat check rejects runnable Store aliases (nodejs/node#36790).\nfunction shellPathExists(path: string): boolean {\n\ttry {\n\t\taccessSync(path, constants.F_OK);\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nconst MISSING_EXECUTABLE_RECHECK_MS = 30_000;\n\ninterface ExecutableLookup {\n\tpath: string | null;\n\tcheckedAt: number;\n}\n\nconst executablePathLookups = new Map<string, ExecutableLookup>();\n\nfunction isReusableLookup(lookup: ExecutableLookup): boolean {\n\treturn lookup.path === null\n\t\t? Date.now() - lookup.checkedAt < MISSING_EXECUTABLE_RECHECK_MS\n\t\t: shellPathExists(lookup.path);\n}\n\n/**\n * Find an executable on PATH (cross-platform), memoized per PATH value.\n * A missing executable is looked up again once its result is 30 seconds old.\n */\nfunction findExecutableOnPath(executable: string): string | null {\n\tconst key = `${executable}\\0${process.env.PATH ?? \"\"}`;\n\tconst cached = executablePathLookups.get(key);\n\tif (cached && isReusableLookup(cached)) return cached.path;\n\tconst path = lookupExecutableOnPath(executable);\n\texecutablePathLookups.set(key, { path, checkedAt: Date.now() });\n\treturn path;\n}\n\nfunction lookupExecutableOnPath(executable: string): string | null {\n\tif (process.platform === \"win32\") {\n\t\t// Windows: use 'where' and check the returned path.\n\t\ttry {\n\t\t\tconst result = spawnSync(\"where\", [executable], {\n\t\t\t\tencoding: \"utf-8\",\n\t\t\t\ttimeout: 5000,\n\t\t\t\tenv: createChildProcessEnvironment(),\n\t\t\t});\n\t\t\tif (result.status === 0 && result.stdout) {\n\t\t\t\tconst firstMatch = result.stdout.trim().split(/\\r?\\n/)[0];\n\t\t\t\tif (firstMatch && shellPathExists(firstMatch)) {\n\t\t\t\t\treturn firstMatch;\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore errors\n\t\t}\n\t\treturn null;\n\t}\n\n\t// Unix: Use 'which' and trust its output (handles Termux and special filesystems)\n\ttry {\n\t\tconst result = spawnSync(\"which\", [executable], {\n\t\t\tencoding: \"utf-8\",\n\t\t\ttimeout: 5000,\n\t\t\tenv: createChildProcessEnvironment(),\n\t\t});\n\t\tif (result.status === 0 && result.stdout) {\n\t\t\tconst firstMatch = result.stdout.trim().split(/\\r?\\n/)[0];\n\t\t\tif (firstMatch) {\n\t\t\t\treturn firstMatch;\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// Ignore errors\n\t}\n\treturn null;\n}\n\n/**\n * Resolve shell configuration based on platform and an optional explicit shell path.\n * Resolution order:\n * 1. User-specified shellPath\n * 2. On Windows: Git Bash in known locations, then bash on PATH\n * 3. On Unix: /bin/bash, then bash on PATH, then fallback to sh\n */\nexport function getShellConfig(customShellPath?: string): ShellConfig {\n\t// 1. Check user-specified shell path\n\tif (customShellPath) {\n\t\tif (shellPathExists(customShellPath)) {\n\t\t\treturn getBashShellConfig(customShellPath);\n\t\t}\n\t\tthrow new Error(`Custom shell path not found: ${customShellPath}`);\n\t}\n\n\tif (process.platform === \"win32\") {\n\t\t// 2. Try Git Bash in known locations\n\t\tconst paths: string[] = [];\n\t\tconst programFiles = process.env.ProgramFiles;\n\t\tif (programFiles) {\n\t\t\tpaths.push(`${programFiles}\\\\Git\\\\bin\\\\bash.exe`);\n\t\t}\n\t\tconst programFilesX86 = process.env[\"ProgramFiles(x86)\"];\n\t\tif (programFilesX86) {\n\t\t\tpaths.push(`${programFilesX86}\\\\Git\\\\bin\\\\bash.exe`);\n\t\t}\n\n\t\tfor (const path of paths) {\n\t\t\tif (shellPathExists(path)) {\n\t\t\t\treturn getBashShellConfig(path);\n\t\t\t}\n\t\t}\n\n\t\t// 3. Fallback: search bash.exe on PATH (Cygwin, MSYS2, WSL, etc.)\n\t\tconst bashOnPath = findExecutableOnPath(\"bash.exe\");\n\t\tif (bashOnPath) {\n\t\t\treturn getBashShellConfig(bashOnPath);\n\t\t}\n\n\t\tthrow new Error(\n\t\t\t`No bash shell found. Options:\\n` +\n\t\t\t\t` 1. Install Git for Windows: https://git-scm.com/download/win\\n` +\n\t\t\t\t` 2. Add your bash to PATH (Cygwin, MSYS2, etc.)\\n` +\n\t\t\t\t\" 3. Set shellPath in settings.json\\n\\n\" +\n\t\t\t\t`Searched Git Bash in:\\n${paths.map((p) => ` ${p}`).join(\"\\n\")}`,\n\t\t);\n\t}\n\n\t// Unix: try /bin/bash, then bash on PATH, then fallback to sh\n\tif (shellPathExists(\"/bin/bash\")) {\n\t\treturn getBashShellConfig(\"/bin/bash\");\n\t}\n\n\tconst bashOnPath = findExecutableOnPath(\"bash\");\n\tif (bashOnPath) {\n\t\treturn getBashShellConfig(bashOnPath);\n\t}\n\n\treturn { shell: \"sh\", args: [\"-c\"] };\n}\n\nexport const POWERSHELL_ARGS = [\"-NoProfile\", \"-NonInteractive\", \"-ExecutionPolicy\", \"Bypass\", \"-Command\"] as const;\n\nexport function getPowerShellConfig(): ShellConfig {\n\tif (process.platform !== \"win32\") throw new Error(\"The powershell tool is only available on Windows.\");\n\tlet shell = findExecutableOnPath(\"pwsh.exe\");\n\t// Installation directories may be on any drive and need not be on PATH.\n\tconst programFiles = process.env.ProgramFiles;\n\tif (!shell && programFiles) {\n\t\tconst pwsh7 = win32.join(programFiles, \"PowerShell\", \"7\", \"pwsh.exe\");\n\t\tif (shellPathExists(pwsh7)) shell = pwsh7;\n\t}\n\n\tshell ??= findExecutableOnPath(\"powershell.exe\");\n\tconst systemRoot = process.env.SystemRoot;\n\tif (!shell && systemRoot) {\n\t\tconst windowsPowerShell = win32.join(systemRoot, \"System32\", \"WindowsPowerShell\", \"v1.0\", \"powershell.exe\");\n\t\tif (shellPathExists(windowsPowerShell)) shell = windowsPowerShell;\n\t}\n\n\tif (!shell) throw new Error(\"No PowerShell executable found. Install PowerShell or add it to PATH.\");\n\treturn { shell, args: [...POWERSHELL_ARGS] };\n}\n\nexport function isPowerShellAvailable(): boolean {\n\ttry {\n\t\tgetPowerShellConfig();\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nexport function getShellEnv(): NodeJS.ProcessEnv {\n\tconst binDir = getBinDir();\n\tconst pathKey = Object.keys(process.env).find((key) => key.toLowerCase() === \"path\") ?? \"PATH\";\n\tconst currentPath = process.env[pathKey] ?? \"\";\n\tconst pathEntries = currentPath.split(delimiter).filter(Boolean);\n\tconst hasBinDir = pathEntries.includes(binDir);\n\tconst updatedPath = hasBinDir ? currentPath : [binDir, currentPath].filter(Boolean).join(delimiter);\n\n\treturn {\n\t\t...process.env,\n\t\t[pathKey]: updatedPath,\n\t};\n}\n\n/**\n * Sanitize binary output for display/storage.\n * Removes characters that crash string-width or cause display issues:\n * - Control characters (except tab, newline, carriage return)\n * - Unicode interlinear annotation characters U+FFF9..U+FFFB\n */\nexport function sanitizeBinaryOutput(str: string): string {\n\t// Removed characters are single UTF-16 code units; surrogate pairs remain intact.\n\treturn str.replace(/[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F\\uFFF9-\\uFFFB]/g, \"\");\n}\n\ninterface DetachedChildGuardian {\n\tpostMessage(message: { action: \"track\" | \"untrack\"; pid: number }): void;\n\tterminate(): Promise<number>;\n\tunref(): void;\n}\n\nlet detachedChildGuardian: DetachedChildGuardian | undefined;\n\nconst PARENT_GUARDIAN_SOURCE = `\nconst { parentPort, workerData } = require(\"node:worker_threads\");\nconst { spawn } = require(\"node:child_process\");\nconst { existsSync } = require(\"node:fs\");\nconst { join } = require(\"node:path\");\nconst tracked = new Set();\nparentPort.on(\"message\", ({ action, pid }) => {\n if (action === \"track\") tracked.add(pid);\n else tracked.delete(pid);\n});\nfunction killTree(pid) {\n if (process.platform === \"win32\") {\n // Same System32 hardening as killProcessTree: PATH is not trusted here, and an\n // unconsumed async \"error\" from a failed spawn would take the guardian thread\n // down and leak every detached child it exists to reap.\n try {\n const child = spawn(join(process.env.SystemRoot || \"C:\\\\\\\\Windows\", \"System32\", \"taskkill.exe\"), [\"/F\", \"/T\", \"/PID\", String(pid)], { stdio: \"ignore\", detached: true, windowsHide: true, env: { ...process.env, AI_AGENT: workerData.aiAgent } });\n child.once(\"error\", () => {});\n } catch {}\n return;\n }\n try { process.kill(-pid, \"SIGKILL\"); }\n catch { try { process.kill(pid, \"SIGKILL\"); } catch {} }\n}\nconst timer = setInterval(() => {\n let parentAlive = true;\n try { process.kill(workerData.parentPid, 0); } catch { parentAlive = false; }\n if (parentAlive && (!workerData.stopFile || !existsSync(workerData.stopFile))) return;\n for (const pid of tracked) killTree(pid);\n killTree(process.pid);\n}, 50);\ntimer.unref?.();\n`;\n\nexport function startParentProcessGuardian(parentPid: number, stopFile?: string): () => Promise<void> {\n\tif (detachedChildGuardian) return async () => {};\n\tconst guardian = new Worker(PARENT_GUARDIAN_SOURCE, {\n\t\teval: true,\n\t\tworkerData: { parentPid, stopFile, aiAgent: createChildProcessEnvironment().AI_AGENT },\n\t});\n\tdetachedChildGuardian = guardian;\n\tfor (const pid of trackedDetachedChildPids) guardian.postMessage({ action: \"track\", pid });\n\tguardian.unref();\n\treturn async () => {\n\t\tif (detachedChildGuardian !== guardian) return;\n\t\tdetachedChildGuardian = undefined;\n\t\tawait guardian.terminate();\n\t};\n}\n\n/**\n * Detached child processes must be tracked so they can be killed on parent\n * shutdown signals (SIGHUP/SIGTERM).\n */\nconst trackedDetachedChildPids = new Set<number>();\n\nexport function trackDetachedChildPid(pid: number): void {\n\ttrackedDetachedChildPids.add(pid);\n\tdetachedChildGuardian?.postMessage({ action: \"track\", pid });\n}\n\nexport function untrackDetachedChildPid(pid: number): void {\n\ttrackedDetachedChildPids.delete(pid);\n\tdetachedChildGuardian?.postMessage({ action: \"untrack\", pid });\n}\n\nexport function killTrackedDetachedChildren(): void {\n\tfor (const pid of trackedDetachedChildPids) {\n\t\tkillProcessTree(pid);\n\t\tdetachedChildGuardian?.postMessage({ action: \"untrack\", pid });\n\t}\n\ttrackedDetachedChildPids.clear();\n}\n\n/**\n * Kill a process and all its children (cross-platform)\n */\nexport function killProcessTree(pid: number): void {\n\tif (process.platform === \"win32\") {\n\t\t// Use the trusted System32 executable so cleanup does not depend on PATH.\n\t\ttry {\n\t\t\tconst child = spawn(\n\t\t\t\tjoin(process.env.SystemRoot || \"C:\\\\Windows\", \"System32\", \"taskkill.exe\"),\n\t\t\t\t[\"/F\", \"/T\", \"/PID\", String(pid)],\n\t\t\t\t{\n\t\t\t\t\tstdio: \"ignore\",\n\t\t\t\t\tdetached: true,\n\t\t\t\t\t// `detached` gives the child its own console on Windows unless this is\n\t\t\t\t\t// set, so omitting it both flashes a window and pays for a console\n\t\t\t\t\t// allocation on every tree kill. Every other spawn in the fork sets it.\n\t\t\t\t\twindowsHide: true,\n\t\t\t\t\tenv: createChildProcessEnvironment(),\n\t\t\t\t},\n\t\t\t);\n\t\t\t// A failed spawn emits \"error\" asynchronously; consume it to avoid crashing Node.\n\t\t\tchild.once(\"error\", () => {});\n\t\t} catch {\n\t\t\t// Ignore errors if taskkill fails.\n\t\t}\n\t} else {\n\t\t// Use SIGKILL on Unix/Linux/Mac\n\t\ttry {\n\t\t\tprocess.kill(-pid, \"SIGKILL\");\n\t\t} catch {\n\t\t\t// Fallback to killing just the child if process group kill fails\n\t\t\ttry {\n\t\t\t\tprocess.kill(pid, \"SIGKILL\");\n\t\t\t} catch {\n\t\t\t\t// Process already dead\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"shell.js","sourceRoot":"","sources":["../../src/utils/shell.ts"],"names":[],"mappings":"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","sourcesContent":["import { accessSync, constants } from \"node:fs\";\nimport { delimiter, join, win32 } from \"node:path\";\nimport { Worker } from \"node:worker_threads\";\nimport { spawn, spawnSync } from \"child_process\";\nimport { getBinDir } from \"../config.js\";\nimport { createChildProcessEnvironment } from \"./child-process.ts\";\n\nexport interface ShellConfig {\n\tshell: string;\n\targs: string[];\n\tcommandTransport?: \"argv\" | \"stdin\";\n}\n\nfunction isLegacyWslBashPath(path: string): boolean {\n\tconst normalized = win32.normalize(path).toLowerCase();\n\tconst systemRoot = process.env.SystemRoot;\n\tif (systemRoot) {\n\t\tif (!/^[a-z]:[\\\\/]/i.test(systemRoot)) return false;\n\t\treturn [\"System32\", \"Sysnative\"].some(\n\t\t\t(directory) => normalized === win32.join(systemRoot, directory, \"bash.exe\").toLowerCase(),\n\t\t);\n\t}\n\t// Fall back to conventional launcher paths only when the Windows directory is unknown.\n\treturn /^[a-z]:\\\\windows\\\\(?:system32|sysnative)\\\\bash\\.exe$/.test(normalized);\n}\n\nfunction getBashShellConfig(shell: string): ShellConfig {\n\treturn isLegacyWslBashPath(shell) ? { shell, args: [\"-s\"], commandTransport: \"stdin\" } : { shell, args: [\"-c\"] };\n}\n\n// On Unix this is identical to existsSync() (both are access(F_OK)), just\n// spelled with try/catch. On Windows we explicitly avoid existsSync(): its\n// extra stat check rejects runnable Store aliases (nodejs/node#36790).\nfunction shellPathExists(path: string): boolean {\n\ttry {\n\t\taccessSync(path, constants.F_OK);\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nconst MISSING_EXECUTABLE_RECHECK_MS = 30_000;\n\ninterface ExecutableLookup {\n\tpath: string | null;\n\tcheckedAt: number;\n}\n\nconst executablePathLookups = new Map<string, ExecutableLookup>();\n\nfunction isReusableLookup(lookup: ExecutableLookup): boolean {\n\treturn lookup.path === null\n\t\t? Date.now() - lookup.checkedAt < MISSING_EXECUTABLE_RECHECK_MS\n\t\t: shellPathExists(lookup.path);\n}\n\n/**\n * Find an executable on PATH (cross-platform), memoized per PATH value.\n * A missing executable is looked up again once its result is 30 seconds old.\n */\nfunction findExecutableOnPath(executable: string): string | null {\n\tconst key = `${executable}\\0${process.env.PATH ?? \"\"}`;\n\tconst cached = executablePathLookups.get(key);\n\tif (cached && isReusableLookup(cached)) return cached.path;\n\tconst path = lookupExecutableOnPath(executable);\n\texecutablePathLookups.set(key, { path, checkedAt: Date.now() });\n\treturn path;\n}\n\nfunction lookupExecutableOnPath(executable: string): string | null {\n\tif (process.platform === \"win32\") {\n\t\t// Windows: use 'where' and check the returned path.\n\t\ttry {\n\t\t\tconst result = spawnSync(\"where\", [executable], {\n\t\t\t\tencoding: \"utf-8\",\n\t\t\t\ttimeout: 5000,\n\t\t\t\tenv: createChildProcessEnvironment(),\n\t\t\t});\n\t\t\tif (result.status === 0 && result.stdout) {\n\t\t\t\tconst firstMatch = result.stdout.trim().split(/\\r?\\n/)[0];\n\t\t\t\tif (firstMatch && shellPathExists(firstMatch)) {\n\t\t\t\t\treturn firstMatch;\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore errors\n\t\t}\n\t\treturn null;\n\t}\n\n\t// Unix: Use 'which' and trust its output (handles Termux and special filesystems)\n\ttry {\n\t\tconst result = spawnSync(\"which\", [executable], {\n\t\t\tencoding: \"utf-8\",\n\t\t\ttimeout: 5000,\n\t\t\tenv: createChildProcessEnvironment(),\n\t\t});\n\t\tif (result.status === 0 && result.stdout) {\n\t\t\tconst firstMatch = result.stdout.trim().split(/\\r?\\n/)[0];\n\t\t\tif (firstMatch) {\n\t\t\t\treturn firstMatch;\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// Ignore errors\n\t}\n\treturn null;\n}\n\n/**\n * Resolve shell configuration based on platform and an optional explicit shell path.\n * Resolution order:\n * 1. User-specified shellPath\n * 2. On Windows: Git Bash in known locations, then bash on PATH\n * 3. On Unix: /bin/bash, then bash on PATH, then fallback to sh\n */\nexport function getShellConfig(customShellPath?: string): ShellConfig {\n\t// 1. Check user-specified shell path\n\tif (customShellPath) {\n\t\tif (shellPathExists(customShellPath)) {\n\t\t\treturn getBashShellConfig(customShellPath);\n\t\t}\n\t\tthrow new Error(`Custom shell path not found: ${customShellPath}`);\n\t}\n\n\tif (process.platform === \"win32\") {\n\t\t// 2. Try Git Bash in known locations\n\t\tconst paths: string[] = [];\n\t\tconst programFiles = process.env.ProgramFiles;\n\t\tif (programFiles) {\n\t\t\tpaths.push(`${programFiles}\\\\Git\\\\bin\\\\bash.exe`);\n\t\t}\n\t\tconst programFilesX86 = process.env[\"ProgramFiles(x86)\"];\n\t\tif (programFilesX86) {\n\t\t\tpaths.push(`${programFilesX86}\\\\Git\\\\bin\\\\bash.exe`);\n\t\t}\n\n\t\tfor (const path of paths) {\n\t\t\tif (shellPathExists(path)) {\n\t\t\t\treturn getBashShellConfig(path);\n\t\t\t}\n\t\t}\n\n\t\t// 3. Fallback: search bash.exe on PATH (Cygwin, MSYS2, WSL, etc.)\n\t\tconst bashOnPath = findExecutableOnPath(\"bash.exe\");\n\t\tif (bashOnPath) {\n\t\t\treturn getBashShellConfig(bashOnPath);\n\t\t}\n\n\t\tthrow new Error(\n\t\t\t`No bash shell found. Options:\\n` +\n\t\t\t\t` 1. Install Git for Windows: https://git-scm.com/download/win\\n` +\n\t\t\t\t` 2. Add your bash to PATH (Cygwin, MSYS2, etc.)\\n` +\n\t\t\t\t\" 3. Set shellPath in settings.json\\n\\n\" +\n\t\t\t\t`Searched Git Bash in:\\n${paths.map((p) => ` ${p}`).join(\"\\n\")}`,\n\t\t);\n\t}\n\n\t// Unix: try /bin/bash, then bash on PATH, then fallback to sh\n\tif (shellPathExists(\"/bin/bash\")) {\n\t\treturn getBashShellConfig(\"/bin/bash\");\n\t}\n\n\tconst bashOnPath = findExecutableOnPath(\"bash\");\n\tif (bashOnPath) {\n\t\treturn getBashShellConfig(bashOnPath);\n\t}\n\n\treturn { shell: \"sh\", args: [\"-c\"] };\n}\n\nexport const POWERSHELL_ARGS = [\"-NoProfile\", \"-NonInteractive\", \"-ExecutionPolicy\", \"Bypass\", \"-Command\"] as const;\n\nexport function getPowerShellConfig(): ShellConfig {\n\tif (process.platform !== \"win32\") throw new Error(\"The powershell tool is only available on Windows.\");\n\tlet shell = findExecutableOnPath(\"pwsh.exe\");\n\t// Installation directories may be on any drive and need not be on PATH.\n\tconst programFiles = process.env.ProgramFiles;\n\tif (!shell && programFiles) {\n\t\tconst pwsh7 = win32.join(programFiles, \"PowerShell\", \"7\", \"pwsh.exe\");\n\t\tif (shellPathExists(pwsh7)) shell = pwsh7;\n\t}\n\n\tshell ??= findExecutableOnPath(\"powershell.exe\");\n\tconst systemRoot = process.env.SystemRoot;\n\tif (!shell && systemRoot) {\n\t\tconst windowsPowerShell = win32.join(systemRoot, \"System32\", \"WindowsPowerShell\", \"v1.0\", \"powershell.exe\");\n\t\tif (shellPathExists(windowsPowerShell)) shell = windowsPowerShell;\n\t}\n\n\tif (!shell) throw new Error(\"No PowerShell executable found. Install PowerShell or add it to PATH.\");\n\treturn { shell, args: [...POWERSHELL_ARGS] };\n}\n\nexport function isPowerShellAvailable(): boolean {\n\ttry {\n\t\tgetPowerShellConfig();\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nexport function getShellEnv(): NodeJS.ProcessEnv {\n\tconst binDir = getBinDir();\n\tconst pathKey = Object.keys(process.env).find((key) => key.toLowerCase() === \"path\") ?? \"PATH\";\n\tconst currentPath = process.env[pathKey] ?? \"\";\n\tconst pathEntries = currentPath.split(delimiter).filter(Boolean);\n\tconst hasBinDir = pathEntries.includes(binDir);\n\tconst updatedPath = hasBinDir ? currentPath : [binDir, currentPath].filter(Boolean).join(delimiter);\n\n\treturn {\n\t\t...process.env,\n\t\t[pathKey]: updatedPath,\n\t};\n}\n\n/**\n * Sanitize binary output for display/storage.\n * Removes characters that crash string-width or cause display issues:\n * - Control characters (except tab, newline, carriage return)\n * - Unicode interlinear annotation characters U+FFF9..U+FFFB\n */\nexport function sanitizeBinaryOutput(str: string): string {\n\t// Removed characters are single UTF-16 code units; surrogate pairs remain intact.\n\treturn str.replace(/[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F\\uFFF9-\\uFFFB]/g, \"\");\n}\n\ninterface DetachedChildGuardian {\n\tpostMessage(message: { action: \"track\" | \"untrack\"; pid: number }): void;\n\tterminate(): Promise<number>;\n\tunref(): void;\n}\n\nlet detachedChildGuardian: DetachedChildGuardian | undefined;\n\nconst PARENT_GUARDIAN_SOURCE = `\nconst { parentPort, workerData } = require(\"node:worker_threads\");\nconst { spawn } = require(\"node:child_process\");\nconst { existsSync } = require(\"node:fs\");\nconst { join } = require(\"node:path\");\nconst tracked = new Set();\nparentPort.on(\"message\", ({ action, pid }) => {\n if (action === \"track\") tracked.add(pid);\n else tracked.delete(pid);\n});\nfunction killTree(pid) {\n if (process.platform === \"win32\") {\n // Same System32 hardening as killProcessTree: PATH is not trusted here, and an\n // unconsumed async \"error\" from a failed spawn would take the guardian thread\n // down and leak every detached child it exists to reap.\n try {\n const child = spawn(join(process.env.SystemRoot || \"C:\\\\\\\\Windows\", \"System32\", \"taskkill.exe\"), [\"/F\", \"/T\", \"/PID\", String(pid)], { stdio: \"ignore\", detached: true, windowsHide: true, env: { ...process.env, AI_AGENT: workerData.aiAgent } });\n child.once(\"error\", () => {});\n } catch {}\n return;\n }\n try { process.kill(-pid, \"SIGKILL\"); }\n catch { try { process.kill(pid, \"SIGKILL\"); } catch {} }\n}\nconst timer = setInterval(() => {\n let parentAlive = true;\n try { process.kill(workerData.parentPid, 0); } catch { parentAlive = false; }\n if (parentAlive && (!workerData.stopFile || !existsSync(workerData.stopFile))) return;\n for (const pid of tracked) killTree(pid);\n killTree(process.pid);\n}, 50);\ntimer.unref?.();\n`;\n\nexport function startParentProcessGuardian(parentPid: number, stopFile?: string): () => Promise<void> {\n\tif (detachedChildGuardian) return async () => {};\n\tconst guardian = new Worker(PARENT_GUARDIAN_SOURCE, {\n\t\teval: true,\n\t\tworkerData: { parentPid, stopFile, aiAgent: createChildProcessEnvironment().AI_AGENT },\n\t});\n\tdetachedChildGuardian = guardian;\n\tfor (const pid of trackedDetachedChildPids) guardian.postMessage({ action: \"track\", pid });\n\tguardian.unref();\n\treturn async () => {\n\t\tif (detachedChildGuardian !== guardian) return;\n\t\tdetachedChildGuardian = undefined;\n\t\tawait guardian.terminate();\n\t};\n}\n\n/**\n * Detached child processes must be tracked so they can be killed on parent\n * shutdown signals (SIGHUP/SIGTERM).\n */\nconst trackedDetachedChildPids = new Set<number>();\n\nexport function trackDetachedChildPid(pid: number): void {\n\ttrackedDetachedChildPids.add(pid);\n\tdetachedChildGuardian?.postMessage({ action: \"track\", pid });\n}\n\nexport function untrackDetachedChildPid(pid: number): void {\n\ttrackedDetachedChildPids.delete(pid);\n\tdetachedChildGuardian?.postMessage({ action: \"untrack\", pid });\n}\n\nexport function killTrackedDetachedChildren(): void {\n\tfor (const pid of trackedDetachedChildPids) {\n\t\tkillProcessTree(pid);\n\t\tdetachedChildGuardian?.postMessage({ action: \"untrack\", pid });\n\t}\n\ttrackedDetachedChildPids.clear();\n}\n\n/**\n * Kill a process and all its children (cross-platform)\n */\nexport function killProcessTree(pid: number): void {\n\tif (process.platform === \"win32\") {\n\t\t// Use the trusted System32 executable so cleanup does not depend on PATH.\n\t\ttry {\n\t\t\tconst child = spawn(\n\t\t\t\tjoin(process.env.SystemRoot || \"C:\\\\Windows\", \"System32\", \"taskkill.exe\"),\n\t\t\t\t[\"/F\", \"/T\", \"/PID\", String(pid)],\n\t\t\t\t{\n\t\t\t\t\tstdio: \"ignore\",\n\t\t\t\t\tdetached: true,\n\t\t\t\t\t// `detached` gives the child its own console on Windows unless this is\n\t\t\t\t\t// set, so omitting it both flashes a window and pays for a console\n\t\t\t\t\t// allocation on every tree kill. Every other spawn in the fork sets it.\n\t\t\t\t\twindowsHide: true,\n\t\t\t\t\tenv: createChildProcessEnvironment(),\n\t\t\t\t},\n\t\t\t);\n\t\t\t// A failed spawn emits \"error\" asynchronously; consume it to avoid crashing Node.\n\t\t\tchild.once(\"error\", () => {});\n\t\t} catch {\n\t\t\t// Ignore errors if taskkill fails.\n\t\t}\n\t} else {\n\t\tif (process.platform === \"darwin\") {\n\t\t\t// macOS signals group members individually after releasing the group lock. A killed\n\t\t\t// child can wake its waiting shell before that shell is signalled, letting `a; b`\n\t\t\t// start b during cancellation (pi #11252). Stop the group first to reduce this race;\n\t\t\t// this is best-effort, not an atomic freeze. SIGKILL also kills stopped processes.\n\t\t\t// Linux holds tasklist_lock across group signalling; child exit notification needs\n\t\t\t// its write lock, so the shell is signalled before observing the killed child's exit.\n\t\t\ttry {\n\t\t\t\tprocess.kill(-pid, \"SIGSTOP\");\n\t\t\t} catch {\n\t\t\t\t// A failed stop must not prevent the kill attempt or its single-process fallback.\n\t\t\t}\n\t\t}\n\t\t// Use SIGKILL on Unix/Linux/Mac\n\t\ttry {\n\t\t\tprocess.kill(-pid, \"SIGKILL\");\n\t\t} catch {\n\t\t\t// Fallback to killing just the child if process group kill fails\n\t\t\ttry {\n\t\t\t\tprocess.kill(pid, \"SIGKILL\");\n\t\t\t} catch {\n\t\t\t\t// Process already dead\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
@@ -156,13 +156,20 @@ interface OAuthLoginCallbacks {
156
156
  onAuth(params: { url: string }): void;
157
157
 
158
158
  // Show device code (for device authorization flow)
159
- onDeviceCode(params: { userCode: string; verificationUri: string }): void;
159
+ onDeviceCode(params: {
160
+ userCode: string;
161
+ verificationUri: string;
162
+ openBrowserOnConfirm?: boolean; // default true
163
+ copyCodeOnConfirm?: boolean; // default true
164
+ }): void;
160
165
 
161
166
  // Prompt user for input (for manual token entry)
162
167
  onPrompt(params: { message: string }): Promise<string>;
163
168
  }
164
169
  ```
165
170
 
171
+ In `/login`, press Enter (`tui.select.confirm`) to open the device-login URL and copy the code, or log in manually. Providers can disable either action with `openBrowserOnConfirm: false` or `copyCodeOnConfirm: false` in `onDeviceCode` or native `AuthEvent` notifications.
172
+
166
173
  ### OAuthCredentials
167
174
 
168
175
  Credentials are persisted in `~/.atomic/agent/auth.json` (legacy `~/.pi/agent/auth.json` may be read for compatibility):
@@ -800,6 +800,8 @@ The transformer receives the Markdown string and a context with:
800
800
  - `messageType` — `"user"`, `"assistant"`, or `"assistant-thinking"`
801
801
  - `isStreaming` — `true` for partial assistant updates; `false` for user, finalized assistant, and restored messages
802
802
  - `availableWidth` — exact terminal columns available for the transformed Markdown content
803
+ - `messageId` — the session entry id when the message is rendered from the saved session (resume, `/tree`, rebuilds); `undefined` for live messages
804
+ - `timestamp` — the session entry's ISO timestamp under the same conditions; `undefined` for live messages
803
805
 
804
806
  Return the transformed Markdown:
805
807
 
@@ -18,6 +18,7 @@ Start with the state you can observe. The existing sections below explain the co
18
18
  | Terminal stage delivery failure | The stage fails without spending a model retry or fallback. | Fix the delivery failure rather than changing models. The typed failure applies to every model candidate. See [How it works](#how-it-works). |
19
19
  | Destination-side admission failure | A blocking non-parent asker receives a correlated error instead of waiting for the reply timeout. | Act on the returned destination error, then send again when the destination can admit the message. See [Workflow and subagent notifications](#workflow-and-subagent-notifications). |
20
20
  | Broker restart or concurrent startup | Sessions disappear briefly, or several sessions invoke Intercom at the same time. | Usually no action is needed. Clients reconnect, and the spawn lock prevents duplicate brokers. See [How it works](#how-it-works). |
21
+ | Broker accepts connections but stops answering | List, send, or workflow route requests time out. | Allow about a minute for automatic broker replacement, then reconnect backoff. A request timeout can add up to five seconds for a health check. Check the logs below if it repeats. See [How it works](#how-it-works). |
21
22
  | Broker does not start | The startup or readiness error quotes the broker log path and includes a bounded tail. | Read `~/.atomic/agent/intercom/broker.log`, or the equivalent path below `ATOMIC_CODING_AGENT_DIR`. See [How it works](#how-it-works). |
22
23
  | Half-closed peer or undeliverable send | The send returns `Session not found`; its message id remains retryable. | Refresh the session list and retry after the target reconnects. See [How it works](#how-it-works). |
23
24
  | Subagent being restored after a restart | A send or ask to a background subagent that its owner will restore, or is restoring, waits instead of returning `Session not found`. For a plain session's subagent this holds from the moment its reopened session starts. For a subagent of an interrupted workflow stage it holds from the moment the session that launched the run is back and the run is addressable, until the stage, once running again, restores it. It holds for the owner and for any session in the subagent's group, whether the message uses the subagent's name, its Atomic session ID, or the Intercom session ID it had before the restart. The name and Atomic session ID are held from the first moment; the pre-restart ID is held once the owner has read it from the subagent's own session, usually within a fraction of a second. Each waiting message is delivered once, in the order it was sent, when the subagent reconnects. If the subagent has not reconnected within 20 seconds, the sender gets a "still being restored" error. If the subagent did not survive the restart, the sender gets an explicit "did not survive the restart and is gone" error. If another Atomic session takes over the interrupted run, the senders waiting for its subagents get an error saying so. None of these returns `Session not found`. | Retry later if the subagent has not reconnected yet, launch a new subagent if the old one is gone, or send again to the session that took over the run. See [How it works](#how-it-works). |
@@ -59,6 +60,8 @@ A stage name reused by a later occurrence in the same run (for example `reviewer
59
60
 
60
61
  The local broker starts on first use and exits after five seconds without registered sessions. Clients reconnect automatically using delays of 1, 2, 5, 10, then 30 seconds. Explicit connection failures remain visible while background recovery continues.
61
62
 
63
+ If the broker stops answering health requests for about a minute, it terminates itself so clients can start a replacement. A single slow request does not restart a responsive broker. This protection starts with a newly launched broker; a broker still running from an older Atomic version must restart before it gains it. A process suspended by the operating system cannot run its own recovery. If it remains stuck, verify which process owns the broker before stopping it manually. Do not delete the Intercom database or key files.
64
+
62
65
  If workflow-stage warm-up exhausts its retries, a stage with queued messages fails without consuming them or spending a model retry. Restore broker availability before retrying the work. Protocol, authentication, configuration, and terminal delivery failures require attention rather than a model change.
63
66
 
64
67
  For `send`, `ask`, and `reply`, the tool owns reconnect recovery. One invocation makes the initial attempt and up to three retries, waiting 1, 2, then 5 seconds between attempts. Retries preserve the original message ID, caller arguments, attachment order and presence, and reply thread. The model neither supplies nor receives a retry token. Every new invocation is a fresh intentional operation, even with identical text. Existing integrations must stop passing `retryToken`; caller-supplied tokens are refused without sending.
@@ -77,7 +80,7 @@ Custom hosts may declare an optional `recipientPurpose` on session registration
77
80
 
78
81
  Configuration and diagnostics live in `~/.atomic/agent/intercom/`, or under `ATOMIC_CODING_AGENT_DIR`. The legacy `PI_CODING_AGENT_DIR` applies when the Atomic variable is unset. Edit `config.json` for [Intercom settings](/intercom/reference#configuration).
79
82
 
80
- When startup fails, read the `broker.log` path printed in the error. The log is replaced on each broker spawn and ordinary broker diagnostics are capped at 8 KiB, so capture relevant output before retrying.
83
+ When startup fails, read the `broker.log` path printed in the error. Ordinary diagnostics are capped at 8 KiB per broker. The next spawn keeps a bounded copy in `broker.log.previous`; `broker-watchdog.log` separately retains the latest automatic termination's time, PID, and reason. Preserve these files when reporting repeated stalls or restarts. They live under the Intercom directory above.
81
84
 
82
85
  ## Workflow and Subagent Notifications
83
86
 
@@ -128,9 +131,9 @@ By default, each machine's broker serves only the sessions on that machine. With
128
131
 
129
132
  ### Turning it on
130
133
 
131
- Give every machine the same shared top-level `storage` selection, including the same `prefix`. To reach other machines, the selection must be an [adapter](/storage#selecting-an-adapter) whose module implements `openDatabase` and connects to a database that every machine can reach, as described in [Sharing storage across hosts](/storage#sharing-storage-across-hosts). Install the adapter module on every machine.
134
+ Give every machine the same shared top-level `storage` selection, including the same `prefix`. To reach other machines, the selection must be an [adapter](/storage#selecting-an-adapter) whose module implements `openDatabase` and connects to a database that every machine can reach, as described in [Sharing storage across hosts](/storage#sharing-storage-across-hosts). Install the adapter on every machine, for example as an [Atomic package](/storage#adapters-from-atomic-packages) so that `atomic install` also brings its driver.
132
135
 
133
- Multi-node routing needs Atomic installed from npm (`npm install -g @bastani/atomic`). The standalone binary's broker cannot load storage adapters, so routing stays off there even with a shared `storage` selection, and sessions on other machines cannot be reached. Use an npm installation on every machine that should take part.
136
+ Both the npm installation and the standalone binary run multi-node routing and load storage adapters.
134
137
 
135
138
  The broker takes its selection from one of two places:
136
139
 
@@ -148,14 +151,20 @@ The broker takes its selection from one of two places:
148
151
  }
149
152
  ```
150
153
 
151
- One broker serves every session in an agent directory and keeps the selection it started with until it exits. After changing the selection, close the Atomic sessions on that machine. The broker exits five seconds after its last session disconnects, and the next session that uses Intercom starts one with the new selection.
154
+ One broker serves every session in an agent directory and keeps the selection it started with until it exits. After changing the selection, run `atomic intercom restart`. It asks the broker to shut down; the sessions connected to it reconnect within a few seconds, and the first one starts a broker with the new selection. The command waits for that broker and prints its multi-node routing state. When no session is connected, the next session that uses Intercom starts the new broker. A broker started by an older Atomic version, which does not answer the request, is stopped with SIGTERM.
155
+
156
+ `atomic intercom status` shows whether the broker is running, how many sessions it serves, whether it uses the current selection, and its multi-node routing state with the number of other brokers live in the shared database. `atomic storage status` shows the same together with the selection for sessions and workflow runs; see [Checking the selected storage](/storage#checking-the-selected-storage). In a session, `intercom({ action: "status" })` adds a `Multi-node routing:` line, for example `Multi-node routing: on (adapter @bastani/atomic-storage-libsql, prefix none), 1 peer broker live (build-host)`.
157
+
158
+ Each broker writes its multi-node state to `broker.log` when it starts: `Intercom multi-node routing is on (adapter <adapter>, prefix <prefix>)` once it has opened the shared database, or `Intercom multi-node routing is off: <reason>`. `<adapter>` is `sqlite` for a `path` selection.
152
159
 
153
160
  Multi-node routing stays off, and Intercom behaves exactly as on a single machine, when the selection is:
154
161
 
155
162
  - absent, including when only project settings (`.atomic/settings.json`), `sessions.storage`, `workflows.journal`, `ATOMIC_SESSIONS_STORAGE`, or `ATOMIC_WORKFLOWS_JOURNAL` select storage;
156
163
  - in memory: `{ "memory": true }` or `{ "path": ":memory:" }`;
157
164
  - an adapter that implements only `openSessionStore` or `openRunJournal` and has no `openDatabase`;
158
- - invalid, for example an `env` secret that is not set in the broker's environment, or an adapter module that cannot be loaded. The broker then writes `Intercom multi-node routing is off: <reason>` to `broker.log`.
165
+ - invalid, for example an `env` secret that is not set in the broker's environment, or an adapter module that cannot be loaded. Sessions warn at startup when the Atomic they run cannot load the adapter.
166
+
167
+ In each case the broker writes `Intercom multi-node routing is off: <reason>` to `broker.log`.
159
168
 
160
169
  A `path` selection, including the default storage file, is a local SQLite file. Only brokers on the same machine that open the same file share it. Do not put it on a network filesystem.
161
170
 
@@ -204,15 +213,15 @@ When a whole machine or its broker stops, its sessions still look connected for
204
213
 
205
214
  **A session on another machine does not appear in `intercom list`.**
206
215
 
207
- - Check that both machines use the same storage selection and the same `prefix`, from settings or from the process that started each broker. A broker started before you changed the selection keeps the old one until it exits.
208
- - Check `broker.log` on both machines for `Intercom multi-node routing is off: <reason>` or `Intercom multi-node routing is unavailable: <reason>`. An absent, in-memory, or domain-only selection stays off without a message, and a `path` selection reaches only brokers on the same machine.
216
+ - Run `atomic storage status` on both machines. Both must use the same storage selection and the same `prefix`, from settings or from the process that started each broker, and the broker must report `storage: matches the selection above`. A broker started before you changed the selection keeps the old one until `atomic intercom restart` replaces it.
217
+ - Check that each broker reports multi-node routing `on` with the other broker live, in `atomic intercom status` or in `broker.log`. `Intercom multi-node routing is off: <reason>` or `Intercom multi-node routing is unavailable: <reason>` there gives the cause. A `path` selection reaches only brokers on the same machine, and a machine whose broker is not running is not live.
209
218
  - A session appears only after it connects to Intercom, which happens lazily. Ask it to use Intercom once, for example `intercom({ action: "status" })`.
210
219
  - Both sessions must share a group.
211
220
  - When the database is slow to answer, `intercom list` shows only local sessions. Try again.
212
221
 
213
222
  **A send stays queued.** The recipient is offline. It receives the message when the same Atomic session connects to Intercom again with the same home group, on a machine that uses the same storage and prefix. A session reopened on a machine with a different selection never sees it.
214
223
 
215
- **The database is unavailable.** Messages between sessions on the same machine keep working. A send to a session on another machine fails with `Session not found on this node, and Intercom could not reach the shared storage database to look for it on other nodes. Try again once the database is available.` If the database cannot be opened when the broker starts, the broker logs `Intercom multi-node routing is unavailable: <reason>` and retries after 1 second, doubling up to every 30 seconds. Intercom never falls back to in-memory storage. If sends keep failing after the database is back, close the sessions on that machine so that a new broker starts.
224
+ **The database is unavailable.** Messages between sessions on the same machine keep working. A send to a session on another machine fails with `Session not found on this node, and Intercom could not reach the shared storage database to look for it on other nodes. Try again once the database is available.` If the database cannot be opened when the broker starts, the broker logs `Intercom multi-node routing is unavailable: <reason>` and retries after 1 second, doubling up to every 30 seconds. Intercom never falls back to in-memory storage. If sends keep failing after the database is back, run `atomic intercom restart` so that a new broker starts.
216
225
 
217
226
  **An ask to a session on another machine never gets an answer.** If that session disconnects before it answers, the asker is not told, and waits until the ask times out after 10 minutes.
218
227
 
@@ -28,7 +28,7 @@ description: The intercom tool contract and every intercom setting.
28
28
  | `ask` | Sends a message and blocks until a live recipient replies (10-minute timeout). An ask to a known workflow stage whose session has not initialized is refused with `pending_stage_ask_unsupported` and recommends ordinary `send`; holding a waiter until a stage eventually starts would be unbounded. A live recipient disconnect fails promptly. Parallel children continue in the same execution after a correlated reply; single-child parent handoffs remain unchanged. |
29
29
  | `reply` | Replies to the intercom-triggered message of the current turn; otherwise falls back to the single unresolved inbound ask. With multiple pending asks, pass `to` or inspect with `pending` first. |
30
30
  | `pending` | Lists unresolved inbound asks with sender, message ID, elapsed time, and a short preview. |
31
- | `status` | Shows connection status, session ID, every group this session belongs to, and the count of active sessions visible through those memberships. A `group` filter remains a read-only peek. |
31
+ | `status` | Shows connection status, session ID, every group this session belongs to, the count of active sessions visible through those memberships, and the broker's multi-node routing state with the number of other brokers live in the shared database. A `group` filter remains a read-only peek. |
32
32
 
33
33
  To give two plain chat sessions a private shared membership, have both call:
34
34
 
@@ -179,7 +179,7 @@ Both keys are recognized by their physical identity, not by the configured `app.
179
179
 
180
180
  Ctrl+C is the host's escape hatch whenever an engine-owned `ctx.ui.custom()` component or overlay holds input: those forward every key to the engine, so a component that never resolves would swallow Ctrl+C. Which component gets the press is decided per mount, in this order:
181
181
 
182
- 1. If the engine is provably not answering, the first press terminates and replaces it — a wedged child cannot run the component's own handler either. "Not answering" means the watchdog has declared it unresponsive, a cooperative abort has gone unanswered past the same one-second threshold, a replacement has been waiting for readiness past it, or a replacement failed. A failed replacement keeps Ctrl+C armed so another press can try again; Atomic never retries on its own.
182
+ 1. If the engine stops answering, the first press terminates and replaces it. This includes a queue clear or cancellation of chat, a shell command, compaction, or a retry unanswered for over one second, a failed clear or cancellation, a replacement waiting over one second for readiness, or a failed replacement. Engine heartbeats do not disarm recovery for a stalled control request. A failed replacement keeps Ctrl+C available for another attempt; a control failure never triggers an automatic restart.
183
183
  2. Otherwise, if the component declared `handlesCtrlC` when it was mounted, it receives the press and keeps its own Skip, Close, or cancel behavior. The bundled workflow surfaces declare it. If the same component is still holding input on the next press, that press closes it.
184
184
  3. Otherwise the first press closes that one component, exactly as if it had been cancelled: its `ctx.ui.custom()` promise resolves with `undefined`, the editor comes back, and the engine — along with everything else it has mounted or is running — is left alone.
185
185
 
@@ -52,6 +52,21 @@ Entries use the same schema as `mcp.json`, including URL environment-variable in
52
52
 
53
53
  Contributed servers have the lowest precedence: users can replace or disable one by name in their own MCP configuration, and filter them with `mcpServers` patterns in settings. Project packages contribute servers only in trusted projects. For a server whose configuration is computed at runtime, call [`pi.registerMcpServer()`](/extensions/api-reference#pi-registermcpserver-name-config) from an extension.
54
54
 
55
+ ### Storage Adapters
56
+
57
+ A package can provide [storage adapters](/storage#selecting-an-adapter) with `storageAdapters`, a list of adapter module paths relative to the package root. Put the database driver in `dependencies` so `atomic install` installs it with the package, and list `@bastani/atomic` in `peerDependencies`:
58
+
59
+ ```json
60
+ {
61
+ "name": "@example/atomic-libsql",
62
+ "dependencies": { "@libsql/client": "0.18.0" },
63
+ "peerDependencies": { "@bastani/atomic": "*" },
64
+ "atomic": { "storageAdapters": ["./libsql-adapter.ts"] }
65
+ }
66
+ ```
67
+
68
+ Users select the adapter with the package name, for example `"storage": { "adapter": "@example/atomic-libsql" }`, or with `<package>/<name>` when the package lists several adapters, where `<name>` is the adapter file's name without its extension. Storage adapters are only read from the manifest, never from a convention directory, and only from packages in user settings. See [Adapters from Atomic packages](/storage#adapters-from-atomic-packages).
69
+
55
70
  ### Gallery Metadata
56
71
 
57
72
  The package gallery currently recognizes legacy `pi-package` metadata, while new Atomic packages should also include `atomic-package`. Add `video` or `image` fields to show a preview:
package/docs/packages.md CHANGED
@@ -1,13 +1,13 @@
1
1
  ---
2
2
  title: "Atomic packages"
3
- description: "Install, manage, and share Atomic packages that bundle extensions, skills, prompt templates, themes, workflows, and MCP servers."
3
+ description: "Install, manage, and share Atomic packages that bundle extensions, skills, prompt templates, themes, workflows, MCP servers, and storage adapters."
4
4
  ---
5
5
 
6
6
  > Atomic can help you create packages. Ask it to bundle your extensions, skills, prompt templates, or themes.
7
7
 
8
8
  # Atomic Packages
9
9
 
10
- Atomic packages bundle extensions, skills, prompt templates, themes, workflow definitions, and MCP servers so you can share them through npm or git. Declare resources in `package.json` under the `atomic` key, or use conventional directories.
10
+ Atomic packages bundle extensions, skills, prompt templates, themes, workflow definitions, MCP servers, and [storage adapters](/storage#adapters-from-atomic-packages) so you can share them through npm or git. Declare resources in `package.json` under the `atomic` key, or use conventional directories.
11
11
 
12
12
  ## Where to go next
13
13
 
@@ -28,6 +28,7 @@ Read this page to install and manage packages, then continue:
28
28
  - [Creating an Atomic Package](/packages/authoring#creating-an-atomic-package)
29
29
  - [Gallery Metadata](/packages/authoring#gallery-metadata)
30
30
  - [MCP Servers](/packages/authoring#mcp-servers)
31
+ - [Storage Adapters](/packages/authoring#storage-adapters)
31
32
  - [Package Structure](/packages/authoring#package-structure)
32
33
  - [Convention Directories](/packages/authoring#convention-directories)
33
34
  - [Dependencies](/packages/authoring#dependencies)
package/docs/providers.md CHANGED
@@ -39,6 +39,8 @@ OpenRouter opens its browser PKCE flow and asks whether to mint a new API key. C
39
39
 
40
40
  Kimi Code displays its device URL/code and polls until approval, then refreshes expired tokens automatically.
41
41
 
42
+ Device-code logins (Kimi Code, GitHub Copilot, Meta, and extension providers) show the URL and code without opening a browser. Press Enter to open the URL and copy the code, or open it yourself and type the code.
43
+
42
44
  Built-in and extension-provided OAuth work in both direct and isolated sessions. Login completes once credentials are saved; model-catalog refresh runs separately.
43
45
 
44
46
  Escape or Ctrl+C quietly cancels the matching login, including immediate/pre-device native aborts, and leaves the previously committed credential and catalog unchanged. Provider denial, device expiry, timeout, browser/network/protocol failure, malformed responses, token exchange, and persistence failures remain visible. Atomic claims success when the provider flow and credential persistence complete; it does not wait for model-catalog or ambient-availability refresh work.
@@ -90,6 +90,23 @@ Once the complete credential reaches stdout, the command has succeeded. If the s
90
90
 
91
91
  `auth check --credentials --json` may instead write a credential-free JSON status object on a non-zero check result. See [Security](/security#credential-export) before wiring this into a script.
92
92
 
93
+ ## Storage and Intercom Commands
94
+
95
+ ```bash
96
+ atomic storage status [--json] # Selection, source, adapter and connection per domain
97
+ atomic storage join <url> [--token-file <path>] [--prefix <prefix>] # Use a shared libSQL / Turso database
98
+ atomic intercom status # This machine's Intercom broker and multi-node state
99
+ atomic intercom restart # Replace the broker so it reads the current selection
100
+ ```
101
+
102
+ `atomic storage status` reports, for sessions, workflow runs, and Intercom, the selected storage, where the selection comes from (an environment variable, project or global settings, or the default) and the outranked selections, whether the adapter loads, and whether the database answers a `SELECT 1`. It also shows the running Intercom broker: whether it uses the current selection, its multi-node routing state, and the other brokers live in the shared database. It creates and changes nothing, and exits `1` when a selection is invalid, an adapter cannot load, or a database does not answer.
103
+
104
+ `atomic storage join` checks the token and the connection with `@bastani/atomic-storage-libsql`, stores the trimmed token in a new `storage-token.<id>` file in the agent directory with mode 600, writes only the global `storage` setting, and lists the environment variables and settings that override it. Previous token files are never overwritten; a settings-write failure removes only the new token. It creates no tables. `--token-file` is required except for absolute `file:` URLs; relative file URLs and URLs that contain the token itself (a user name, a password, or an `authToken` parameter) are refused. When the package is not installed, it prints the install command. See [Storage](/storage#joining-a-machine-to-a-shared-database).
105
+
106
+ `atomic intercom restart` asks the broker to shut down (an older broker gets SIGTERM), waits for it to exit, and then for a reconnecting session to start a new broker, whose state it prints. Without connected sessions, the next session that uses Intercom starts one.
107
+
108
+ See [Joining a machine to a shared database](/storage#joining-a-machine-to-a-shared-database) and [Running Intercom across machines](/intercom/operations#running-intercom-across-machines).
109
+
93
110
  ## Modes
94
111
 
95
112
  | Flag | Description |
package/docs/sdk.md CHANGED
@@ -162,6 +162,39 @@ const { session } = await createAgentSession({
162
162
  });
163
163
  ```
164
164
 
165
+ ### Shared resource admission
166
+
167
+ SDK hosts can pass `resourceAdmission` to `createAgentSession()` to share their own capacity policy across sessions and managed shell commands:
168
+
169
+ ```typescript
170
+ import type { ResourceAdmission } from "@bastani/atomic";
171
+
172
+ const resourceAdmission: ResourceAdmission = {
173
+ async acquire(kind, signal, request) {
174
+ if (request?.delegated) {
175
+ const permit = capacity.tryAcquire(kind);
176
+ if (!permit) throw new Error("No capacity left for a delegated child session");
177
+ return async () => permit.release();
178
+ }
179
+ const permit = await capacity.acquire(kind, { signal });
180
+ return async () => permit.release();
181
+ },
182
+ };
183
+ const { session } = await createAgentSession({ resourceAdmission });
184
+ ```
185
+
186
+ `capacity` is your host's admission service. `kind` is `"session"` or `"process"`. Make acquisition cancellation-aware and the returned release function idempotent. `resourceAdmissionSignal` cancels a wait for session capacity; children inherit the admission service, not that signal. Pass the same service whenever your runtime factory creates a replacement session.
187
+
188
+ A session holds its permit until clean disposal. A managed command holds its permit through background execution and confirmed process cleanup, even after an observation wait returns. Failed launches release their permits after rollback. Cleanup failures keep capacity reserved; inspect and resolve them instead of recycling a slot on a timer. Arbitrary processes created outside Atomic's managed command APIs are not counted.
189
+
190
+ Your admission service must reconcile abandoned holders after a host crash. Atomic does not treat lease expiry as cleanup proof or clear a service's retained permits automatically.
191
+
192
+ On Unix, Atomic calls `permit.bindProcess({ kind: "unix-process-group", id })` on a process permit once the command's process group exists and before the launch returns, if the permit provides it. Persist the identity with the permit so a replacement host can check whether the group is still alive before it frees the capacity. The identity only names the group and gives no right to signal it. Windows commands report no identity, so keep those permits reserved until normal cleanup confirms them. If `bindProcess` rejects, Atomic cancels the command, fails the launch, and releases the permit only after cleanup is confirmed.
193
+
194
+ A parent session keeps its permit while it waits for a child, so a child that queued for a permit could wait on its own parent. Atomic marks child sessions (subagents and other sessions built from `getChildSessionOptions()`) with `request.delegated`. For those requests, grant a permit immediately or throw. Never queue them or exceed the cap. A cap of one, or a cap filled by parents, therefore rejects delegation with your error instead of stalling. Set the cap above the number of parents that can be live at once if you need children to run.
195
+
196
+ Use `withResourceAdmission(resourceAdmission, async () => { ... })` to apply a service to SDK sessions and managed commands launched within an asynchronous operation, including commands using an existing task owner. An explicit session service wins for that session; commands in the operation use its scoped service.
197
+
165
198
  ### Session storage
166
199
 
167
200
  `createAgentSession()` persists sessions in `~/.atomic/workflows/journal.sqlite` by default, sharing the file with workflow runs under separate `session_` and `wf_` table prefixes. Pass `sessionStore` to choose storage explicitly:
@@ -297,6 +330,10 @@ Without a `journal` option, the session's own `workflows.journal` setting applie
297
330
 
298
331
  Without a per-run or per-session option, `ATOMIC_WORKFLOWS_JOURNAL` or a non-empty `ATOMIC_WORKFLOW_JOURNAL_PATH` wins, then `ATOMIC_STORAGE`, then `workflows.journal`, then shared top-level `storage`. Setting both journal variables is a conflict. Nearer-scope settings replace whole selections. Relative settings paths resolve against Atomic home. The default journal is `~/.atomic/workflows/journal.sqlite`; an empty shared selection uses `~/.atomic/storage.sqlite`. Keep the original journal and working directory to resume saved runs. Invalid configuration and storage failures reject rather than switching to memory. See [Workflow storage settings](/settings#workflow-storage) and [Selecting storage per process](/storage#selecting-storage-per-process).
299
332
 
333
+ ### Intercom in standalone workflows
334
+
335
+ Workflow stages run by the standalone `run()` API can use Intercom when the stage has access to the `intercom` tool, including with `executionMode: "non_interactive"`. Atomic opens Intercom when an eligible stage starts and closes the workflow route when the run settles. Intercom remains unavailable when a stage excludes the tool or disables its builtin package. A local session that needs to steer a standalone run can join `workflow:<rootRunId>` and send to a stage or `workflow:<rootRunId>/**`. See [Programmatic usage](/workflows/api-reference#programmatic-usage) for details.
336
+
300
337
  ### Workflow run control
301
338
 
302
339
  `session.workflows` controls this session's runs without slash commands or the model-facing `workflow` tool. A fresh session can inspect a durable root by its full UUID within the same working directory and journal. Inspection stays read-only and does not add the run to `listRuns()`.
package/docs/settings.md CHANGED
@@ -577,13 +577,44 @@ Workflow runs use a SQLite journal, by default the local file `~/.atomic/workflo
577
577
  }
578
578
  ```
579
579
 
580
+ #### Local worker pools
581
+
582
+ Local worker pools are disabled unless `workflows.localWorkers` is set. With the option omitted, Atomic creates no worker tables, processes, or polling loops. A worker definition must be importable from its module path and export name. Only first-level `ctx.workflow(...)` child subtrees run in the pool; a root closure stays in the main process, and nested children stay on their assigned worker.
583
+
584
+ ```json
585
+ {
586
+ "workflows": {
587
+ "localWorkers": {
588
+ "count": 4,
589
+ "definitions": [
590
+ { "path": "./workflows/research.ts", "exportName": "research" }
591
+ ]
592
+ },
593
+ "admission": {
594
+ "limits": { "session": 4, "process": 8 },
595
+ "userLimits": { "session": 8, "process": 16 },
596
+ "userDatabase": "/Users/alex/.atomic/workflows/journal.sqlite",
597
+ "host": { "maxLoadPerCpu": 2, "minFreeMemoryBytes": 2147483648 }
598
+ }
599
+ }
600
+ }
601
+ ```
602
+
603
+ `count` is a positive integer. Paths in the settings object resolve against the invocation working directory. Worker settings and entry-module digests are saved with the root; resume refuses changed settings or definitions. The entry-module digest does not include imported dependencies.
604
+
605
+ `limits` caps agent sessions and managed tool processes for one root. Session permits cover creation through disposal, including fallback and delegated sessions. Process permits last until confirmed cleanup; yielding an observation wait does not free one. Processes outside Atomic's managed tools are not counted. `host` load and memory thresholds gate launches but do not impose hard OS limits.
606
+
607
+ `userLimits` counts across runs only when all participating runs use the same canonical local SQLite journal. Set `userDatabase` to that exact path, and pass the admission options to participating non-pooled SDK runs. This coordinates processes for one local user; it is not a tenant isolation boundary.
608
+
609
+ Workers require native process and boot identity and are currently supported only on Linux and macOS. Windows and hosts without those identities refuse activation, even if no process cap is configured. Remote workers are not supported. SDK callers cannot combine the pool with process-bound adapters, UI, persistence, or similar overrides. An unregistered or non-importable child fails; Atomic does not execute it locally as a fallback. Recovery may repeat external effects that occurred after a checkpoint, so use stable idempotency keys for operations that must not be repeated.
610
+
580
611
  Relative settings paths resolve against the Atomic home directory, normally `~/.atomic`, not the project directory. Precedence is SDK `RunOpts.journal` (or `createAgentSession({ journal })` for a session's `workflow` tool), `--storage` or `--storage-file`, `ATOMIC_WORKFLOWS_JOURNAL` or non-empty `ATOMIC_WORKFLOW_JOURNAL_PATH`, `ATOMIC_STORAGE`, `workflows.journal`, shared top-level `storage`, then the default journal file. An empty environment variable is ignored. SDK sessions read `workflows.journal` from the settings they were given, so `SettingsManager.inMemory(...)` selects a journal per session. A present empty journal block selects the journal default rather than inheriting shared storage. See [Selecting storage per process](/storage#selecting-storage-per-process).
581
612
 
582
613
  To keep workflow records only in memory, set `"journal": { "memory": true }`. Do not combine `memory: true` with a non-empty `path`. Memory must be selected explicitly and cannot recover runs after the process exits; an environment path still takes precedence over this setting.
583
614
 
584
615
  Project `workflows.journal` and `storage` each replace their global selection as a whole, including options, secrets, and prefix. For example, a project can select `{ "memory": true }` over a global path. Invalid selected settings raise `StorageConfigError` instead of trying a lower-precedence selection. Combining `memory: true` with a non-empty `path` is an error.
585
616
 
586
- To use your own database driver, install an adapter module and select it by module specifier:
617
+ To use your own database driver, install an adapter module, for example as an [Atomic package](/storage#adapters-from-atomic-packages), and select it by package name or module path:
587
618
 
588
619
  ```json
589
620
  {
@@ -596,7 +627,7 @@ To use your own database driver, install an adapter module and select it by modu
596
627
  }
597
628
  ```
598
629
 
599
- The shared `storage` setting supplies defaults for both sessions and the journal. Use `sessions.storage` or `workflows.journal` for a domain-only override. A shared empty selection uses `~/.atomic/storage.sqlite`. Atomic bundles no remote driver. Adapter modules execute code, so review them before trusting project settings. Secret references accept `{ "env": "VARIABLE" }` or `{ "file": "token.txt" }`; relative secret files resolve against Atomic home and their contents are passed verbatim. See [Storage adapters](/storage#selecting-an-adapter).
630
+ The shared `storage` setting supplies defaults for both sessions and the journal. Use `sessions.storage` or `workflows.journal` for a domain-only override. A shared empty selection uses `~/.atomic/storage.sqlite`. Atomic bundles no remote driver. Adapter modules execute code, so review them before trusting project settings. Secret references accept `{ "env": "VARIABLE" }` or `{ "file": "token.txt" }`; relative secret files resolve against Atomic home, and their contents are passed as written except for one trailing line ending (`\n` or `\r\n`), which is removed. See [Storage adapters](/storage#selecting-an-adapter).
600
631
 
601
632
  Changing the journal selects a different run history. Preserve the original file to inspect or resume its runs. The default local file needs no separate server; a remote database requires an adapter, and every host that shares runs must select the same database and prefix. See [Sharing storage across hosts](/storage#sharing-storage-across-hosts). If the selected journal cannot be opened or written, the action fails with a typed storage error rather than selecting memory. Restore access to the store and inspect the retained run before retrying. See [Durable workflows](/workflows/operations#durable-workflows-and-cross-session-resume).
602
633