@cmetech/otto 1.1.1 → 1.2.5

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 (433) hide show
  1. package/dist/coworker/persona-commands.d.ts +1 -0
  2. package/dist/coworker/persona-commands.js +5 -0
  3. package/dist/coworker/persona-commands.test.d.ts +1 -0
  4. package/dist/coworker/persona-commands.test.js +45 -0
  5. package/dist/resources/.managed-resources-content-hash +1 -1
  6. package/dist/resources/extensions/coworker-artifacts/artifacts-command.js +31 -0
  7. package/dist/resources/extensions/coworker-artifacts/artifacts-singleton.js +17 -0
  8. package/dist/resources/extensions/coworker-artifacts/extension-manifest.json +13 -0
  9. package/dist/resources/extensions/coworker-artifacts/index.js +125 -0
  10. package/dist/resources/extensions/coworker-artifacts/list-tool.js +27 -0
  11. package/dist/resources/extensions/coworker-artifacts/open-tool.js +25 -0
  12. package/dist/resources/extensions/coworker-memory/extension-manifest.json +13 -0
  13. package/dist/resources/extensions/coworker-memory/index.js +219 -0
  14. package/dist/resources/extensions/coworker-memory/memorize-tool.js +10 -0
  15. package/dist/resources/extensions/coworker-memory/memory-command.js +157 -0
  16. package/dist/resources/extensions/coworker-memory/memory-singleton.js +55 -0
  17. package/dist/resources/extensions/coworker-memory/recall-tool.js +18 -0
  18. package/dist/resources/extensions/coworker-memory/session-hooks.js +45 -0
  19. package/dist/resources/extensions/coworker-scratchpad/attach-banners.js +53 -0
  20. package/dist/resources/extensions/coworker-scratchpad/extension-manifest.json +13 -0
  21. package/dist/resources/extensions/coworker-scratchpad/format-age.js +9 -0
  22. package/dist/resources/extensions/coworker-scratchpad/helpers.js +38 -0
  23. package/dist/resources/extensions/coworker-scratchpad/index.js +199 -0
  24. package/dist/resources/extensions/coworker-scratchpad/mime-bundle.js +20 -0
  25. package/dist/resources/extensions/coworker-scratchpad/scratchpad-tool.js +118 -0
  26. package/dist/resources/extensions/coworker-scratchpad/session-sidecar.js +60 -0
  27. package/dist/resources/extensions/coworker-scratchpad/sp-command.js +597 -0
  28. package/dist/resources/extensions/coworker-scratchpad/workspace-pointer.js +41 -0
  29. package/dist/resources/extensions/coworker-scratchpad/workspace-root.js +17 -0
  30. package/dist/resources/extensions/coworker-vault/audit-command.js +35 -0
  31. package/dist/resources/extensions/coworker-vault/connect-command.js +42 -0
  32. package/dist/resources/extensions/coworker-vault/datasource-command.js +50 -0
  33. package/dist/resources/extensions/coworker-vault/extension-manifest.json +12 -0
  34. package/dist/resources/extensions/coworker-vault/index.js +171 -0
  35. package/dist/resources/extensions/coworker-vault/test-helpers.js +86 -0
  36. package/dist/resources/extensions/coworker-vault/vault-singleton.js +24 -0
  37. package/dist/resources/extensions/otto/commands/release-notes/_data.js +83 -0
  38. package/dist/resources/extensions/otto/commands/release-notes/command.js +15 -4
  39. package/dist/resources/extensions/otto/index.js +31 -6
  40. package/dist/resources/extensions/shared/coworker-paths.js +8 -0
  41. package/dist/resources/extensions/slash-commands/{audit.js → audit-codebase.js} +4 -4
  42. package/dist/resources/extensions/slash-commands/extension-manifest.json +1 -1
  43. package/dist/resources/extensions/slash-commands/index.js +2 -2
  44. package/dist/resources/extensions/subagent/index.js +8 -1
  45. package/dist/resources/extensions/subagent/launch.js +37 -5
  46. package/dist/resources/extensions/subagent/run-store.js +1 -0
  47. package/dist/resources/extensions/workflow/bootstrap/register-extension.js +2 -0
  48. package/dist/resources/extensions/workflow/bootstrap/register-hooks.js +10 -0
  49. package/dist/resources/extensions/workflow/health-widget-core.js +1 -1
  50. package/dist/resources/extensions/workflow/persona-status.js +87 -0
  51. package/package.json +26 -10
  52. package/packages/contracts/package.json +1 -1
  53. package/packages/coworker-artifacts/dist/artifact-store.d.ts +25 -0
  54. package/packages/coworker-artifacts/dist/artifact-store.js +187 -0
  55. package/packages/coworker-artifacts/dist/dir-snapshot.d.ts +7 -0
  56. package/packages/coworker-artifacts/dist/dir-snapshot.js +54 -0
  57. package/packages/coworker-artifacts/dist/errors.d.ts +18 -0
  58. package/packages/coworker-artifacts/dist/errors.js +37 -0
  59. package/packages/coworker-artifacts/dist/index.d.ts +7 -0
  60. package/packages/coworker-artifacts/dist/index.js +7 -0
  61. package/packages/coworker-artifacts/dist/readme-renderer.d.ts +5 -0
  62. package/packages/coworker-artifacts/dist/readme-renderer.js +47 -0
  63. package/packages/coworker-artifacts/dist/resolve-uri.d.ts +3 -0
  64. package/packages/coworker-artifacts/dist/resolve-uri.js +29 -0
  65. package/packages/coworker-artifacts/dist/slug.d.ts +4 -0
  66. package/packages/coworker-artifacts/dist/slug.js +32 -0
  67. package/packages/coworker-artifacts/dist/types.d.ts +52 -0
  68. package/packages/coworker-artifacts/dist/types.js +1 -0
  69. package/packages/coworker-artifacts/package.json +20 -0
  70. package/packages/coworker-artifacts/src/artifact-store.test.ts +188 -0
  71. package/packages/coworker-artifacts/src/artifact-store.ts +206 -0
  72. package/packages/coworker-artifacts/src/artifacts-integration.test.ts +109 -0
  73. package/packages/coworker-artifacts/src/dir-snapshot.test.ts +71 -0
  74. package/packages/coworker-artifacts/src/dir-snapshot.ts +52 -0
  75. package/packages/coworker-artifacts/src/errors.test.ts +37 -0
  76. package/packages/coworker-artifacts/src/errors.ts +28 -0
  77. package/packages/coworker-artifacts/src/index.test.ts +22 -0
  78. package/packages/coworker-artifacts/src/index.ts +7 -0
  79. package/packages/coworker-artifacts/src/readme-renderer.test.ts +72 -0
  80. package/packages/coworker-artifacts/src/readme-renderer.ts +56 -0
  81. package/packages/coworker-artifacts/src/resolve-uri.test.ts +46 -0
  82. package/packages/coworker-artifacts/src/resolve-uri.ts +29 -0
  83. package/packages/coworker-artifacts/src/slug.test.ts +47 -0
  84. package/packages/coworker-artifacts/src/slug.ts +31 -0
  85. package/packages/coworker-artifacts/src/types.ts +61 -0
  86. package/packages/coworker-artifacts/tsconfig.json +15 -0
  87. package/packages/coworker-artifacts/tsconfig.publish.json +4 -0
  88. package/packages/coworker-memory/dist/context-injection.d.ts +9 -0
  89. package/packages/coworker-memory/dist/context-injection.js +41 -0
  90. package/packages/coworker-memory/dist/errors.d.ts +25 -0
  91. package/packages/coworker-memory/dist/errors.js +51 -0
  92. package/packages/coworker-memory/dist/index.d.ts +12 -0
  93. package/packages/coworker-memory/dist/index.js +12 -0
  94. package/packages/coworker-memory/dist/layer-a-store.d.ts +16 -0
  95. package/packages/coworker-memory/dist/layer-a-store.js +78 -0
  96. package/packages/coworker-memory/dist/local-sqlite-backend.d.ts +28 -0
  97. package/packages/coworker-memory/dist/local-sqlite-backend.js +167 -0
  98. package/packages/coworker-memory/dist/memory-backend.d.ts +14 -0
  99. package/packages/coworker-memory/dist/memory-backend.js +1 -0
  100. package/packages/coworker-memory/dist/memory-recorder.d.ts +50 -0
  101. package/packages/coworker-memory/dist/memory-recorder.js +69 -0
  102. package/packages/coworker-memory/dist/migrations/001-init.sql +38 -0
  103. package/packages/coworker-memory/dist/migrations/002-artifact-kind.sql +50 -0
  104. package/packages/coworker-memory/dist/paste-detector.d.ts +5 -0
  105. package/packages/coworker-memory/dist/paste-detector.js +14 -0
  106. package/packages/coworker-memory/dist/persona-seed.d.ts +10 -0
  107. package/packages/coworker-memory/dist/persona-seed.js +38 -0
  108. package/packages/coworker-memory/dist/recall-formatter.d.ts +2 -0
  109. package/packages/coworker-memory/dist/recall-formatter.js +14 -0
  110. package/packages/coworker-memory/dist/scope-resolver.d.ts +9 -0
  111. package/packages/coworker-memory/dist/scope-resolver.js +10 -0
  112. package/packages/coworker-memory/dist/types.d.ts +51 -0
  113. package/packages/coworker-memory/dist/types.js +2 -0
  114. package/packages/coworker-memory/dist/workspace-id.d.ts +3 -0
  115. package/packages/coworker-memory/dist/workspace-id.js +54 -0
  116. package/packages/coworker-memory/package.json +35 -0
  117. package/packages/coworker-memory/src/activator-integration.test.ts +141 -0
  118. package/packages/coworker-memory/src/context-injection.test.ts +72 -0
  119. package/packages/coworker-memory/src/context-injection.ts +57 -0
  120. package/packages/coworker-memory/src/errors.test.ts +45 -0
  121. package/packages/coworker-memory/src/errors.ts +42 -0
  122. package/packages/coworker-memory/src/index.test.ts +21 -0
  123. package/packages/coworker-memory/src/index.ts +12 -0
  124. package/packages/coworker-memory/src/layer-a-store.test.ts +85 -0
  125. package/packages/coworker-memory/src/layer-a-store.ts +88 -0
  126. package/packages/coworker-memory/src/local-sqlite-backend.test.ts +110 -0
  127. package/packages/coworker-memory/src/local-sqlite-backend.ts +185 -0
  128. package/packages/coworker-memory/src/memory-backend.ts +10 -0
  129. package/packages/coworker-memory/src/memory-integration.test.ts +89 -0
  130. package/packages/coworker-memory/src/memory-recorder.test.ts +101 -0
  131. package/packages/coworker-memory/src/memory-recorder.ts +95 -0
  132. package/packages/coworker-memory/src/migrations/001-init.sql +38 -0
  133. package/packages/coworker-memory/src/migrations/002-artifact-kind.sql +50 -0
  134. package/packages/coworker-memory/src/paste-detector.test.ts +23 -0
  135. package/packages/coworker-memory/src/paste-detector.ts +18 -0
  136. package/packages/coworker-memory/src/persona-seed.test.ts +57 -0
  137. package/packages/coworker-memory/src/persona-seed.ts +46 -0
  138. package/packages/coworker-memory/src/recall-formatter.test.ts +34 -0
  139. package/packages/coworker-memory/src/recall-formatter.ts +15 -0
  140. package/packages/coworker-memory/src/scope-resolver.test.ts +23 -0
  141. package/packages/coworker-memory/src/scope-resolver.ts +18 -0
  142. package/packages/coworker-memory/src/types.ts +61 -0
  143. package/packages/coworker-memory/src/workspace-id.test.ts +48 -0
  144. package/packages/coworker-memory/src/workspace-id.ts +56 -0
  145. package/packages/coworker-memory/tsconfig.json +15 -0
  146. package/packages/coworker-memory/tsconfig.publish.json +4 -0
  147. package/packages/coworker-persona/dist/commands.d.ts +7 -0
  148. package/packages/coworker-persona/dist/commands.js +35 -0
  149. package/packages/coworker-persona/dist/defaults/manifest.yaml +12 -0
  150. package/packages/coworker-persona/dist/defaults/steering/identity.md +3 -0
  151. package/packages/coworker-persona/dist/index.d.ts +3 -0
  152. package/packages/coworker-persona/dist/index.js +3 -0
  153. package/packages/coworker-persona/dist/manifest.d.ts +24 -0
  154. package/packages/coworker-persona/dist/manifest.js +21 -0
  155. package/packages/coworker-persona/dist/registry.d.ts +22 -0
  156. package/packages/coworker-persona/dist/registry.js +142 -0
  157. package/packages/coworker-persona/package.json +28 -0
  158. package/packages/coworker-persona/scripts/copy-defaults.cjs +17 -0
  159. package/packages/coworker-persona/src/commands.ts +47 -0
  160. package/packages/coworker-persona/src/defaults/manifest.yaml +12 -0
  161. package/packages/coworker-persona/src/defaults/steering/identity.md +3 -0
  162. package/packages/coworker-persona/src/index.ts +3 -0
  163. package/packages/coworker-persona/src/manifest.test.ts +67 -0
  164. package/packages/coworker-persona/src/manifest.ts +49 -0
  165. package/packages/coworker-persona/src/registry.test.ts +89 -0
  166. package/packages/coworker-persona/src/registry.ts +147 -0
  167. package/packages/coworker-persona/tsconfig.json +15 -0
  168. package/packages/coworker-persona/tsconfig.publish.json +4 -0
  169. package/packages/coworker-scratchpad/dist/cell-archive.d.ts +39 -0
  170. package/packages/coworker-scratchpad/dist/cell-archive.js +77 -0
  171. package/packages/coworker-scratchpad/dist/cell-tree.d.ts +14 -0
  172. package/packages/coworker-scratchpad/dist/cell-tree.js +72 -0
  173. package/packages/coworker-scratchpad/dist/child-process-runtime.d.ts +129 -0
  174. package/packages/coworker-scratchpad/dist/child-process-runtime.js +427 -0
  175. package/packages/coworker-scratchpad/dist/collector-registry.d.ts +12 -0
  176. package/packages/coworker-scratchpad/dist/collector-registry.js +29 -0
  177. package/packages/coworker-scratchpad/dist/detect-kind.d.ts +3 -0
  178. package/packages/coworker-scratchpad/dist/detect-kind.js +19 -0
  179. package/packages/coworker-scratchpad/dist/file-collector.d.ts +15 -0
  180. package/packages/coworker-scratchpad/dist/file-collector.js +99 -0
  181. package/packages/coworker-scratchpad/dist/index.d.ts +13 -0
  182. package/packages/coworker-scratchpad/dist/index.js +13 -0
  183. package/packages/coworker-scratchpad/dist/kernel-bindings.d.ts +49 -0
  184. package/packages/coworker-scratchpad/dist/kernel-bindings.js +220 -0
  185. package/packages/coworker-scratchpad/dist/kernel-entry.d.ts +1 -0
  186. package/packages/coworker-scratchpad/dist/kernel-entry.js +355 -0
  187. package/packages/coworker-scratchpad/dist/kernel-protocol.d.ts +171 -0
  188. package/packages/coworker-scratchpad/dist/kernel-protocol.js +48 -0
  189. package/packages/coworker-scratchpad/dist/kernel-spawn.d.ts +3 -0
  190. package/packages/coworker-scratchpad/dist/kernel-spawn.js +54 -0
  191. package/packages/coworker-scratchpad/dist/namespace-codec.d.ts +22 -0
  192. package/packages/coworker-scratchpad/dist/namespace-codec.js +61 -0
  193. package/packages/coworker-scratchpad/dist/scratchpad-lock.d.ts +24 -0
  194. package/packages/coworker-scratchpad/dist/scratchpad-lock.js +86 -0
  195. package/packages/coworker-scratchpad/dist/scratchpad-manager.d.ts +193 -0
  196. package/packages/coworker-scratchpad/dist/scratchpad-manager.js +866 -0
  197. package/packages/coworker-scratchpad/dist/staleness-banner.d.ts +12 -0
  198. package/packages/coworker-scratchpad/dist/staleness-banner.js +27 -0
  199. package/packages/coworker-scratchpad/package.json +31 -0
  200. package/packages/coworker-scratchpad/src/cell-archive.test.ts +150 -0
  201. package/packages/coworker-scratchpad/src/cell-archive.ts +97 -0
  202. package/packages/coworker-scratchpad/src/cell-tree.test.ts +105 -0
  203. package/packages/coworker-scratchpad/src/cell-tree.ts +90 -0
  204. package/packages/coworker-scratchpad/src/child-process-runtime.test.ts +413 -0
  205. package/packages/coworker-scratchpad/src/child-process-runtime.ts +493 -0
  206. package/packages/coworker-scratchpad/src/collector-registry.test.ts +69 -0
  207. package/packages/coworker-scratchpad/src/collector-registry.ts +33 -0
  208. package/packages/coworker-scratchpad/src/detect-kind.test.ts +33 -0
  209. package/packages/coworker-scratchpad/src/detect-kind.ts +22 -0
  210. package/packages/coworker-scratchpad/src/file-collector.test.ts +109 -0
  211. package/packages/coworker-scratchpad/src/file-collector.ts +114 -0
  212. package/packages/coworker-scratchpad/src/index.ts +74 -0
  213. package/packages/coworker-scratchpad/src/kernel-bindings.test.ts +188 -0
  214. package/packages/coworker-scratchpad/src/kernel-bindings.ts +279 -0
  215. package/packages/coworker-scratchpad/src/kernel-entry.test.ts +123 -0
  216. package/packages/coworker-scratchpad/src/kernel-entry.ts +390 -0
  217. package/packages/coworker-scratchpad/src/kernel-protocol.test.ts +105 -0
  218. package/packages/coworker-scratchpad/src/kernel-protocol.ts +230 -0
  219. package/packages/coworker-scratchpad/src/kernel-spawn.test.ts +60 -0
  220. package/packages/coworker-scratchpad/src/kernel-spawn.ts +54 -0
  221. package/packages/coworker-scratchpad/src/namespace-codec.test.ts +102 -0
  222. package/packages/coworker-scratchpad/src/namespace-codec.ts +90 -0
  223. package/packages/coworker-scratchpad/src/scratchpad-lock.test.ts +98 -0
  224. package/packages/coworker-scratchpad/src/scratchpad-lock.ts +102 -0
  225. package/packages/coworker-scratchpad/src/scratchpad-manager.test.ts +1343 -0
  226. package/packages/coworker-scratchpad/src/scratchpad-manager.ts +891 -0
  227. package/packages/coworker-scratchpad/src/staleness-banner.test.ts +53 -0
  228. package/packages/coworker-scratchpad/src/staleness-banner.ts +33 -0
  229. package/packages/coworker-scratchpad/src/vault-integration.test.ts +221 -0
  230. package/packages/coworker-scratchpad/tsconfig.json +15 -0
  231. package/packages/coworker-scratchpad/tsconfig.publish.json +4 -0
  232. package/packages/coworker-types/dist/artifacts.d.ts +31 -0
  233. package/packages/coworker-types/dist/artifacts.js +2 -0
  234. package/packages/coworker-types/dist/contracts.d.ts +32 -0
  235. package/packages/coworker-types/dist/contracts.js +1 -0
  236. package/packages/coworker-types/dist/index.d.ts +5 -0
  237. package/packages/coworker-types/dist/index.js +5 -0
  238. package/packages/coworker-types/dist/memory.d.ts +61 -0
  239. package/packages/coworker-types/dist/memory.js +3 -0
  240. package/packages/coworker-types/dist/scratchpad.d.ts +43 -0
  241. package/packages/coworker-types/dist/scratchpad.js +2 -0
  242. package/packages/coworker-types/dist/vault.d.ts +34 -0
  243. package/packages/coworker-types/dist/vault.js +2 -0
  244. package/packages/coworker-types/package.json +24 -0
  245. package/packages/coworker-types/src/artifacts.test.ts +52 -0
  246. package/packages/coworker-types/src/artifacts.ts +35 -0
  247. package/packages/coworker-types/src/contracts.test.ts +43 -0
  248. package/packages/coworker-types/src/contracts.ts +36 -0
  249. package/packages/coworker-types/src/index.ts +5 -0
  250. package/packages/coworker-types/src/memory.test.ts +50 -0
  251. package/packages/coworker-types/src/memory.ts +79 -0
  252. package/packages/coworker-types/src/scratchpad.test.ts +46 -0
  253. package/packages/coworker-types/src/scratchpad.ts +51 -0
  254. package/packages/coworker-types/src/smoke.test.ts +34 -0
  255. package/packages/coworker-types/src/vault.test.ts +49 -0
  256. package/packages/coworker-types/src/vault.ts +40 -0
  257. package/packages/coworker-types/tsconfig.json +15 -0
  258. package/packages/coworker-types/tsconfig.publish.json +4 -0
  259. package/packages/coworker-utils/dist/audit-log.d.ts +34 -0
  260. package/packages/coworker-utils/dist/audit-log.js +88 -0
  261. package/packages/coworker-utils/dist/index.d.ts +6 -0
  262. package/packages/coworker-utils/dist/index.js +6 -0
  263. package/packages/coworker-utils/dist/lease.d.ts +7 -0
  264. package/packages/coworker-utils/dist/lease.js +67 -0
  265. package/packages/coworker-utils/dist/logger.d.ts +13 -0
  266. package/packages/coworker-utils/dist/logger.js +26 -0
  267. package/packages/coworker-utils/dist/migration-runner.d.ts +7 -0
  268. package/packages/coworker-utils/dist/migration-runner.js +36 -0
  269. package/packages/coworker-utils/dist/ndjson-channel.d.ts +3 -0
  270. package/packages/coworker-utils/dist/ndjson-channel.js +38 -0
  271. package/packages/coworker-utils/dist/secret-scanner.d.ts +10 -0
  272. package/packages/coworker-utils/dist/secret-scanner.js +42 -0
  273. package/packages/coworker-utils/package.json +24 -0
  274. package/packages/coworker-utils/src/audit-log.test.ts +140 -0
  275. package/packages/coworker-utils/src/audit-log.ts +107 -0
  276. package/packages/coworker-utils/src/index.ts +6 -0
  277. package/packages/coworker-utils/src/lease.test.ts +64 -0
  278. package/packages/coworker-utils/src/lease.ts +76 -0
  279. package/packages/coworker-utils/src/logger.test.ts +50 -0
  280. package/packages/coworker-utils/src/logger.ts +45 -0
  281. package/packages/coworker-utils/src/migration-runner.test.ts +65 -0
  282. package/packages/coworker-utils/src/migration-runner.ts +50 -0
  283. package/packages/coworker-utils/src/ndjson-channel.test.ts +76 -0
  284. package/packages/coworker-utils/src/ndjson-channel.ts +41 -0
  285. package/packages/coworker-utils/src/secret-scanner.test.ts +61 -0
  286. package/packages/coworker-utils/src/secret-scanner.ts +56 -0
  287. package/packages/coworker-utils/tsconfig.json +15 -0
  288. package/packages/coworker-utils/tsconfig.publish.json +4 -0
  289. package/packages/coworker-vault/dist/data-vault.d.ts +41 -0
  290. package/packages/coworker-vault/dist/data-vault.js +223 -0
  291. package/packages/coworker-vault/dist/engine-registry.d.ts +34 -0
  292. package/packages/coworker-vault/dist/engine-registry.js +90 -0
  293. package/packages/coworker-vault/dist/engines/jira.yaml +17 -0
  294. package/packages/coworker-vault/dist/errors.d.ts +28 -0
  295. package/packages/coworker-vault/dist/errors.js +57 -0
  296. package/packages/coworker-vault/dist/index.d.ts +6 -0
  297. package/packages/coworker-vault/dist/index.js +6 -0
  298. package/packages/coworker-vault/dist/injector.d.ts +19 -0
  299. package/packages/coworker-vault/dist/injector.js +77 -0
  300. package/packages/coworker-vault/dist/types.d.ts +28 -0
  301. package/packages/coworker-vault/dist/types.js +1 -0
  302. package/packages/coworker-vault/dist/vault-keep.d.ts +4 -0
  303. package/packages/coworker-vault/dist/vault-keep.js +21 -0
  304. package/packages/coworker-vault/package.json +29 -0
  305. package/packages/coworker-vault/src/data-vault.test.ts +199 -0
  306. package/packages/coworker-vault/src/data-vault.ts +257 -0
  307. package/packages/coworker-vault/src/engine-registry.test.ts +120 -0
  308. package/packages/coworker-vault/src/engine-registry.ts +107 -0
  309. package/packages/coworker-vault/src/engines/jira.yaml +17 -0
  310. package/packages/coworker-vault/src/errors.test.ts +58 -0
  311. package/packages/coworker-vault/src/errors.ts +50 -0
  312. package/packages/coworker-vault/src/index.test.ts +24 -0
  313. package/packages/coworker-vault/src/index.ts +6 -0
  314. package/packages/coworker-vault/src/injector.test.ts +109 -0
  315. package/packages/coworker-vault/src/injector.ts +98 -0
  316. package/packages/coworker-vault/src/types.ts +33 -0
  317. package/packages/coworker-vault/src/vault-keep.test.ts +49 -0
  318. package/packages/coworker-vault/src/vault-keep.ts +31 -0
  319. package/packages/coworker-vault/tsconfig.json +15 -0
  320. package/packages/coworker-vault/tsconfig.publish.json +4 -0
  321. package/packages/daemon/package.json +3 -3
  322. package/packages/mcp-server/package.json +3 -3
  323. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  324. package/packages/native/package.json +1 -1
  325. package/packages/native/tsconfig.tsbuildinfo +1 -1
  326. package/packages/pi-agent-core/package.json +1 -1
  327. package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
  328. package/packages/pi-ai/package.json +1 -1
  329. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  330. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +6 -1
  331. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  332. package/packages/pi-coding-agent/dist/core/extensions/runner.js +22 -3
  333. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  334. package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js +11 -0
  335. package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js.map +1 -1
  336. package/packages/pi-coding-agent/dist/modes/rpc/raw-stdout.d.ts +47 -0
  337. package/packages/pi-coding-agent/dist/modes/rpc/raw-stdout.d.ts.map +1 -0
  338. package/packages/pi-coding-agent/dist/modes/rpc/raw-stdout.js +107 -0
  339. package/packages/pi-coding-agent/dist/modes/rpc/raw-stdout.js.map +1 -0
  340. package/packages/pi-coding-agent/dist/modes/rpc/raw-stdout.regression.test.d.ts +19 -0
  341. package/packages/pi-coding-agent/dist/modes/rpc/raw-stdout.regression.test.d.ts.map +1 -0
  342. package/packages/pi-coding-agent/dist/modes/rpc/raw-stdout.regression.test.js +121 -0
  343. package/packages/pi-coding-agent/dist/modes/rpc/raw-stdout.regression.test.js.map +1 -0
  344. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  345. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js +17 -1
  346. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
  347. package/packages/pi-coding-agent/package.json +2 -2
  348. package/packages/pi-coding-agent/src/core/extensions/runner.ts +22 -3
  349. package/packages/pi-coding-agent/src/core/resolve-config-value.test.ts +11 -0
  350. package/packages/pi-coding-agent/src/modes/rpc/raw-stdout.regression.test.ts +129 -0
  351. package/packages/pi-coding-agent/src/modes/rpc/raw-stdout.ts +117 -0
  352. package/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +18 -1
  353. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  354. package/packages/pi-tui/package.json +1 -1
  355. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  356. package/packages/rpc-client/package.json +2 -2
  357. package/packages/rpc-client/tsconfig.tsbuildinfo +1 -1
  358. package/pkg/package.json +1 -1
  359. package/scripts/install.js +6 -5
  360. package/src/resources/extensions/coworker-artifacts/artifacts-command.test.ts +54 -0
  361. package/src/resources/extensions/coworker-artifacts/artifacts-command.ts +43 -0
  362. package/src/resources/extensions/coworker-artifacts/artifacts-singleton.test.ts +25 -0
  363. package/src/resources/extensions/coworker-artifacts/artifacts-singleton.ts +29 -0
  364. package/src/resources/extensions/coworker-artifacts/extension-manifest.json +13 -0
  365. package/src/resources/extensions/coworker-artifacts/index.test.ts +46 -0
  366. package/src/resources/extensions/coworker-artifacts/index.ts +154 -0
  367. package/src/resources/extensions/coworker-artifacts/list-tool.test.ts +29 -0
  368. package/src/resources/extensions/coworker-artifacts/list-tool.ts +53 -0
  369. package/src/resources/extensions/coworker-artifacts/open-tool.test.ts +30 -0
  370. package/src/resources/extensions/coworker-artifacts/open-tool.ts +43 -0
  371. package/src/resources/extensions/coworker-memory/extension-manifest.json +13 -0
  372. package/src/resources/extensions/coworker-memory/index.test.ts +137 -0
  373. package/src/resources/extensions/coworker-memory/index.ts +257 -0
  374. package/src/resources/extensions/coworker-memory/memorize-tool.test.ts +41 -0
  375. package/src/resources/extensions/coworker-memory/memorize-tool.ts +20 -0
  376. package/src/resources/extensions/coworker-memory/memory-command.test.ts +134 -0
  377. package/src/resources/extensions/coworker-memory/memory-command.ts +131 -0
  378. package/src/resources/extensions/coworker-memory/memory-singleton.test.ts +41 -0
  379. package/src/resources/extensions/coworker-memory/memory-singleton.ts +89 -0
  380. package/src/resources/extensions/coworker-memory/recall-tool.test.ts +50 -0
  381. package/src/resources/extensions/coworker-memory/recall-tool.ts +35 -0
  382. package/src/resources/extensions/coworker-memory/session-hooks.test.ts +77 -0
  383. package/src/resources/extensions/coworker-memory/session-hooks.ts +61 -0
  384. package/src/resources/extensions/coworker-scratchpad/attach-banners.test.ts +124 -0
  385. package/src/resources/extensions/coworker-scratchpad/attach-banners.ts +67 -0
  386. package/src/resources/extensions/coworker-scratchpad/extension-manifest.json +13 -0
  387. package/src/resources/extensions/coworker-scratchpad/format-age.test.ts +30 -0
  388. package/src/resources/extensions/coworker-scratchpad/format-age.ts +6 -0
  389. package/src/resources/extensions/coworker-scratchpad/helpers.test.ts +93 -0
  390. package/src/resources/extensions/coworker-scratchpad/helpers.ts +42 -0
  391. package/src/resources/extensions/coworker-scratchpad/index.test.ts +514 -0
  392. package/src/resources/extensions/coworker-scratchpad/index.ts +207 -0
  393. package/src/resources/extensions/coworker-scratchpad/mime-bundle.test.ts +61 -0
  394. package/src/resources/extensions/coworker-scratchpad/mime-bundle.ts +23 -0
  395. package/src/resources/extensions/coworker-scratchpad/scratchpad-tool.test.ts +137 -0
  396. package/src/resources/extensions/coworker-scratchpad/scratchpad-tool.ts +165 -0
  397. package/src/resources/extensions/coworker-scratchpad/session-sidecar.test.ts +133 -0
  398. package/src/resources/extensions/coworker-scratchpad/session-sidecar.ts +68 -0
  399. package/src/resources/extensions/coworker-scratchpad/sp-command.test.ts +836 -0
  400. package/src/resources/extensions/coworker-scratchpad/sp-command.ts +602 -0
  401. package/src/resources/extensions/coworker-scratchpad/workspace-pointer.test.ts +74 -0
  402. package/src/resources/extensions/coworker-scratchpad/workspace-pointer.ts +55 -0
  403. package/src/resources/extensions/coworker-scratchpad/workspace-root.test.ts +51 -0
  404. package/src/resources/extensions/coworker-scratchpad/workspace-root.ts +16 -0
  405. package/src/resources/extensions/coworker-vault/audit-command.test.ts +109 -0
  406. package/src/resources/extensions/coworker-vault/audit-command.ts +56 -0
  407. package/src/resources/extensions/coworker-vault/connect-command.test.ts +103 -0
  408. package/src/resources/extensions/coworker-vault/connect-command.ts +69 -0
  409. package/src/resources/extensions/coworker-vault/datasource-command.test.ts +80 -0
  410. package/src/resources/extensions/coworker-vault/datasource-command.ts +81 -0
  411. package/src/resources/extensions/coworker-vault/extension-manifest.json +12 -0
  412. package/src/resources/extensions/coworker-vault/index.test.ts +82 -0
  413. package/src/resources/extensions/coworker-vault/index.ts +181 -0
  414. package/src/resources/extensions/coworker-vault/test-helpers.ts +120 -0
  415. package/src/resources/extensions/coworker-vault/vault-singleton.test.ts +27 -0
  416. package/src/resources/extensions/coworker-vault/vault-singleton.ts +40 -0
  417. package/src/resources/extensions/otto/commands/release-notes/_data.ts +97 -0
  418. package/src/resources/extensions/otto/commands/release-notes/command.ts +16 -3
  419. package/src/resources/extensions/otto/index.ts +29 -6
  420. package/src/resources/extensions/shared/coworker-paths.test.ts +40 -0
  421. package/src/resources/extensions/shared/coworker-paths.ts +10 -0
  422. package/src/resources/extensions/slash-commands/{audit.ts → audit-codebase.ts} +4 -4
  423. package/src/resources/extensions/slash-commands/extension-manifest.json +1 -1
  424. package/src/resources/extensions/slash-commands/index.ts +2 -2
  425. package/src/resources/extensions/subagent/index.ts +9 -0
  426. package/src/resources/extensions/subagent/launch.test.ts +97 -0
  427. package/src/resources/extensions/subagent/launch.ts +42 -5
  428. package/src/resources/extensions/subagent/run-store.ts +3 -1
  429. package/src/resources/extensions/workflow/bootstrap/register-extension.ts +2 -0
  430. package/src/resources/extensions/workflow/bootstrap/register-hooks.ts +10 -0
  431. package/src/resources/extensions/workflow/health-widget-core.ts +1 -1
  432. package/src/resources/extensions/workflow/persona-status.ts +109 -0
  433. package/src/resources/extensions/workflow/tests/auto-recovery.test.ts +34 -0
@@ -0,0 +1,493 @@
1
+ import { spawn, type ChildProcessWithoutNullStreams } from 'node:child_process';
2
+ import process from 'node:process';
3
+ import { writeNdjson, readNdjson } from '@otto/coworker-utils';
4
+ import type { CredentialInjector } from '@otto/coworker-vault';
5
+ import { filterEnv, kernelExecArgv, resolveKernelEntry } from './kernel-spawn.js';
6
+ import {
7
+ isDataLoadEvent,
8
+ isProgressEvent,
9
+ isStartupErrorEvent,
10
+ isSnapshotResult,
11
+ isArtifactCreateEvent,
12
+ isArtifactCreateRequest,
13
+ isArtifactUpdateRequest,
14
+ } from './kernel-protocol.js';
15
+ import type {
16
+ ArtifactCreateDrawer,
17
+ ArtifactCreateRequest,
18
+ ArtifactCreateResponse,
19
+ ArtifactUpdateRequest,
20
+ ArtifactUpdateResponse,
21
+ DataLoadDrawer,
22
+ KernelFrame,
23
+ RecoveryNote,
24
+ SnapshotResult,
25
+ } from './kernel-protocol.js';
26
+
27
+ export interface CellResult {
28
+ value: unknown;
29
+ stdout: string;
30
+ }
31
+
32
+ export interface ChildProcessRuntimeOptions {
33
+ workspace: string;
34
+ onDataLoad?: (drawer: DataLoadDrawer) => void;
35
+ cellTimeoutMs?: number; // total wall-clock hard cap per cell
36
+ inactivityTimeoutMs?: number; // silence cap before the first progress() heartbeat
37
+ inactivityAfterProgressMs?: number; // silence cap after a progress() heartbeat
38
+ cancelGraceMs?: number; // SIGINT -> SIGTERM/SIGKILL escalation window
39
+ entryPath?: string;
40
+ scratchpadDir?: string;
41
+ /**
42
+ * Phase 2 Task 13: optional vault credential injector. When provided alongside
43
+ * a non-empty `bindings`, the runtime adds OTTO_DS_* env vars to the spawned
44
+ * kernel's environment (after the existing env filter). Absent => no-op; the
45
+ * runtime behaves exactly as it did pre-Phase-2.
46
+ */
47
+ injector?: CredentialInjector;
48
+ bindings?: string[];
49
+ /**
50
+ * Phase 2 Task 13: identifies this runtime's scratchpad for audit records the
51
+ * injector emits. The runtime itself doesn't otherwise use this field.
52
+ */
53
+ scratchpadName?: string;
54
+ /**
55
+ * Phase 2 Task 13: session id stamped on audit records the injector emits.
56
+ * Empty string is a valid no-session value.
57
+ */
58
+ sessionId?: string;
59
+ /**
60
+ * Phase 4 Task 10: Layer-B fan-out for artifact_create events. The kernel
61
+ * emits one of these after every successful otto.artifact.create RPC; the
62
+ * runtime forwards the drawer to this callback so the manager can record it
63
+ * into memory. Absent => artifact_create events are dropped.
64
+ */
65
+ onArtifactCreate?: (drawer: ArtifactCreateDrawer) => void;
66
+ /**
67
+ * Phase 4 Task 10: parent-side handler for `artifact_create` RPC requests.
68
+ * The runtime calls this when it sees an `{type:'request', request:'artifact_create'}`
69
+ * frame on the child's stdout and writes the resolved response (or an error
70
+ * frame) back to the child's stdin. If absent, the request is rejected with
71
+ * "artifacts unavailable" so the cell fails fast instead of hanging.
72
+ */
73
+ handleArtifactCreate?: (req: { kind: string; name: string }) =>
74
+ Promise<{ slug: string; uri: string; primary_path: string }>;
75
+ /**
76
+ * Phase 4 Task 10: parent-side handler for `artifact_update` RPC requests.
77
+ * Mirror of handleArtifactCreate for the update path.
78
+ */
79
+ handleArtifactUpdate?: (req: { slug: string; files: Array<{ path: string; content: string }> }) =>
80
+ Promise<{ files_touched: string[] }>;
81
+ }
82
+
83
+ interface Pending {
84
+ resolve: (result: CellResult) => void;
85
+ reject: (err: Error) => void;
86
+ totalTimer: NodeJS.Timeout;
87
+ inactivityTimer: NodeJS.Timeout;
88
+ inactivityWindowMs: number;
89
+ }
90
+
91
+ const DEFAULT_CELL_TIMEOUT_MS = 120_000;
92
+ const DEFAULT_INACTIVITY_MS = 30_000;
93
+ const DEFAULT_INACTIVITY_AFTER_PROGRESS_MS = 60_000;
94
+ const DEFAULT_CANCEL_GRACE_MS = 2_000;
95
+ const MAX_RESTARTS_BEFORE_SUCCESS = 1;
96
+
97
+ export class ChildProcessRuntime {
98
+ private child: ChildProcessWithoutNullStreams | null = null;
99
+ private readonly pending = new Map<number, Pending>();
100
+ private readonly pendingSnapshots = new Map<number, (res: SnapshotResult) => void>();
101
+ private activeId: number | null = null;
102
+ private nextId = 1;
103
+ private alive = false;
104
+ private childReady = false;
105
+ private disposed = false;
106
+ private restartsSinceSuccess = 0;
107
+ private recoveryNotes_: RecoveryNote[] = [];
108
+ private resolveReady: () => void = () => {};
109
+ private rejectReady: (err: Error) => void = () => {};
110
+ private ready: Promise<void> = Promise.resolve();
111
+ /**
112
+ * Phase 2 Task 13: timestamp of the most recent successful spawn(). Used by
113
+ * higher-level staleness checks (Task 15) to decide whether a warm kernel's
114
+ * env-injected credentials need to be refreshed. Pre-start() value is epoch
115
+ * so callers can distinguish "never spawned" without nullable juggling.
116
+ */
117
+ public spawnTime: Date = new Date(0);
118
+
119
+ constructor(private readonly options: ChildProcessRuntimeOptions) {}
120
+
121
+ async start(): Promise<void> {
122
+ if (this.disposed) throw new Error('runtime disposed');
123
+ await this.spawnChild();
124
+ }
125
+
126
+ /**
127
+ * Phase 2 Task 13: env construction split out so the injector hop has a clean
128
+ * seam to extend. Existing semantics unchanged: filterEnv applies the kernel's
129
+ * allowlist/denylist, then (when configured) the injector overlays OTTO_DS_*
130
+ * vars from vault entries. Only the spawned child sees the result; the parent
131
+ * process.env is never mutated.
132
+ */
133
+ private async buildBaseEnv(): Promise<NodeJS.ProcessEnv> {
134
+ const base = filterEnv(process.env);
135
+ const { injector, bindings } = this.options;
136
+ if (!injector || !bindings || bindings.length === 0) return base;
137
+ return injector.injectEnv(base, bindings, {
138
+ scratchpadName: this.options.scratchpadName ?? '',
139
+ sessionId: this.options.sessionId ?? '',
140
+ pid: process.pid,
141
+ });
142
+ }
143
+
144
+ private async spawnChild(): Promise<void> {
145
+ this.ready = new Promise<void>((resolve, reject) => {
146
+ this.resolveReady = resolve;
147
+ this.rejectReady = reject;
148
+ });
149
+ const entry = this.options.entryPath ?? resolveKernelEntry();
150
+ const args = [...kernelExecArgv(), entry, this.options.workspace];
151
+ if (this.options.scratchpadDir !== undefined) args.push(this.options.scratchpadDir);
152
+ const env = await this.buildBaseEnv();
153
+ const child = spawn(
154
+ process.execPath,
155
+ args,
156
+ { stdio: ['pipe', 'pipe', 'inherit'], cwd: process.cwd(), env },
157
+ ) as unknown as ChildProcessWithoutNullStreams;
158
+ this.child = child;
159
+ this.alive = true;
160
+ this.childReady = false;
161
+ // Phase 2 Task 13: stamp spawnTime immediately after a successful spawn.
162
+ // Done synchronously so the ready promise (which the caller awaits) reflects
163
+ // a non-epoch spawnTime by the time start() returns.
164
+ this.spawnTime = new Date();
165
+ child.on('exit', (code, signal) => {
166
+ if (this.child !== child) return; // superseded by a restart
167
+ this.alive = false;
168
+ this.childReady = false;
169
+ if (this.disposed) return;
170
+ const err = new Error(`kernel exited (code=${code ?? 'null'}, signal=${signal ?? 'null'})`);
171
+ this.rejectReady(err); // no-op if ready already resolved
172
+ this.failAllPending(err);
173
+ });
174
+ void this.readLoop(child);
175
+ return this.ready;
176
+ }
177
+
178
+ private async readLoop(child: ChildProcessWithoutNullStreams): Promise<void> {
179
+ try {
180
+ for await (const raw of readNdjson(child.stdout)) {
181
+ if (this.child !== child) break; // superseded by a restart
182
+ const frame = raw as KernelFrame;
183
+ if (frame.type === 'event') {
184
+ if (frame.event === 'ready') {
185
+ if (frame.recovery_notes) this.recoveryNotes_ = [...frame.recovery_notes];
186
+ this.childReady = true;
187
+ this.resolveReady();
188
+ }
189
+ else if (isStartupErrorEvent(frame)) {
190
+ const err = new Error(frame.error.message);
191
+ err.name = `startup_error/${frame.kind}`;
192
+ this.rejectReady(err);
193
+ }
194
+ else if (isDataLoadEvent(frame)) this.options.onDataLoad?.(frame.drawer);
195
+ else if (isArtifactCreateEvent(frame)) this.options.onArtifactCreate?.(frame.drawer);
196
+ else if (isProgressEvent(frame)) this.resetInactivity();
197
+ continue;
198
+ }
199
+ if (isSnapshotResult(frame)) {
200
+ const resolver = this.pendingSnapshots.get(frame.id);
201
+ if (resolver) {
202
+ this.pendingSnapshots.delete(frame.id);
203
+ resolver(frame);
204
+ }
205
+ continue;
206
+ }
207
+ // Phase 4 Task 10: artifact RPC requests originate from the kernel and
208
+ // are serviced by the manager via handleArtifactCreate/Update. Handle
209
+ // before the result-fallthrough — these frames carry a string `id`
210
+ // (kernel mints `art-<pid>-<seq>`) and would otherwise be miskeyed
211
+ // against the numeric `pending` map.
212
+ if (isArtifactCreateRequest(frame)) {
213
+ void this.handleArtifactCreateRpc(frame);
214
+ continue;
215
+ }
216
+ if (isArtifactUpdateRequest(frame)) {
217
+ void this.handleArtifactUpdateRpc(frame);
218
+ continue;
219
+ }
220
+ if (frame.type !== 'result') continue; // defensive: unknown frame shape
221
+ const p = this.pending.get(frame.id);
222
+ if (!p) continue;
223
+ clearTimeout(p.totalTimer);
224
+ clearTimeout(p.inactivityTimer);
225
+ this.pending.delete(frame.id);
226
+ if (this.activeId === frame.id) this.activeId = null;
227
+ this.restartsSinceSuccess = 0; // a completed cell proves the kernel is healthy
228
+ if (frame.ok) {
229
+ p.resolve({ value: frame.value, stdout: frame.stdout });
230
+ } else {
231
+ const err = new Error(frame.error.message);
232
+ err.name = frame.error.name;
233
+ if (frame.error.stack) err.stack = frame.error.stack;
234
+ p.reject(err);
235
+ }
236
+ }
237
+ } catch (err) {
238
+ this.failAllPending(err as Error);
239
+ }
240
+ }
241
+
242
+ private resetInactivity(): void {
243
+ if (this.activeId === null) return;
244
+ const id = this.activeId;
245
+ const p = this.pending.get(id);
246
+ if (!p) return;
247
+ p.inactivityWindowMs =
248
+ this.options.inactivityAfterProgressMs ?? DEFAULT_INACTIVITY_AFTER_PROGRESS_MS;
249
+ clearTimeout(p.inactivityTimer);
250
+ p.inactivityTimer = setTimeout(() => this.onInactivityTimeout(id), p.inactivityWindowMs);
251
+ }
252
+
253
+ private onInactivityTimeout(id: number): void {
254
+ const p = this.pending.get(id);
255
+ if (!p) return;
256
+ clearTimeout(p.totalTimer);
257
+ this.pending.delete(id);
258
+ if (this.activeId === id) this.activeId = null;
259
+ this.markDead();
260
+ p.reject(new Error(`cell ${id} timed out after ${p.inactivityWindowMs}ms of inactivity`));
261
+ }
262
+
263
+ private onTotalTimeout(id: number, totalMs: number): void {
264
+ const p = this.pending.get(id);
265
+ if (!p) return;
266
+ clearTimeout(p.inactivityTimer);
267
+ this.pending.delete(id);
268
+ if (this.activeId === id) this.activeId = null;
269
+ this.markDead();
270
+ p.reject(new Error(`cell ${id} timed out after ${totalMs}ms (total wall-clock)`));
271
+ }
272
+
273
+ async runCell(code: string): Promise<CellResult> {
274
+ if (this.disposed) throw new Error('runtime disposed');
275
+ if (!this.alive) {
276
+ if (this.restartsSinceSuccess >= MAX_RESTARTS_BEFORE_SUCCESS) {
277
+ throw new Error(
278
+ `kernel repeatedly crashed (${this.restartsSinceSuccess} restart(s) without a successful cell); giving up`,
279
+ );
280
+ }
281
+ this.restartsSinceSuccess++;
282
+ this.spawnChild(); // start a fresh kernel; the write below waits for its ready
283
+ }
284
+ const id = this.nextId++;
285
+ const totalMs = this.options.cellTimeoutMs ?? DEFAULT_CELL_TIMEOUT_MS;
286
+ const inactivityMs = this.options.inactivityTimeoutMs ?? DEFAULT_INACTIVITY_MS;
287
+ const result = new Promise<CellResult>((resolve, reject) => {
288
+ const totalTimer = setTimeout(() => this.onTotalTimeout(id, totalMs), totalMs);
289
+ const inactivityTimer = setTimeout(() => this.onInactivityTimeout(id), inactivityMs);
290
+ this.pending.set(id, { resolve, reject, totalTimer, inactivityTimer, inactivityWindowMs: inactivityMs });
291
+ });
292
+ // Register the active cell synchronously so cancel()/timeouts can act on it even
293
+ // while a restarted kernel is still starting up. Send the code once it's ready.
294
+ this.activeId = id;
295
+ const ready = this.ready;
296
+ void ready
297
+ .then(() => {
298
+ const child = this.child;
299
+ if (child && this.pending.has(id)) return writeNdjson(child.stdin, { id, type: 'run', code });
300
+ })
301
+ .catch(() => {
302
+ // ready rejected (kernel died during startup); the exit handler/cancel rejects this cell.
303
+ });
304
+ return result;
305
+ }
306
+
307
+ async cancel(): Promise<void> {
308
+ const child = this.child;
309
+ if (!child || !this.alive) return;
310
+ const id = this.activeId;
311
+ if (!this.childReady) {
312
+ // The kernel is still starting (e.g. a fresh restart). SIGINT would race the
313
+ // child's SIG_IGN handler install and could kill it before it's installed, so
314
+ // escalate straight to a hard kill and reject the active cell as cancelled.
315
+ this.rejectActive(id, `cell ${id} cancelled`);
316
+ this.markDead();
317
+ return;
318
+ }
319
+ child.kill('SIGINT'); // child ignores SIGINT between cells; no-op for sync vm code mid-cell
320
+ if (id === null) return; // nothing running: the gentle signal is harmless
321
+ await new Promise((r) => setTimeout(r, this.options.cancelGraceMs ?? DEFAULT_CANCEL_GRACE_MS));
322
+ const p = this.pending.get(id);
323
+ if (!p) return; // settled within the grace window
324
+ clearTimeout(p.totalTimer);
325
+ clearTimeout(p.inactivityTimer);
326
+ this.pending.delete(id);
327
+ if (this.activeId === id) this.activeId = null;
328
+ this.markDead(); // escalate: SIGTERM -> SIGKILL
329
+ p.reject(new Error(`cell ${id} cancelled`));
330
+ }
331
+
332
+ async snapshot(): Promise<SnapshotResult> {
333
+ if (this.disposed) {
334
+ return { id: 0, type: 'snapshot_result', ok: false, error: { name: 'RuntimeDisposed', message: 'runtime disposed' } };
335
+ }
336
+ if (!this.alive || !this.child) {
337
+ return { id: 0, type: 'snapshot_result', ok: false, error: { name: 'RuntimeDead', message: 'kernel is not alive' } };
338
+ }
339
+ const id = this.nextId++;
340
+ const result = new Promise<SnapshotResult>((resolve) => {
341
+ this.pendingSnapshots.set(id, resolve);
342
+ });
343
+ try {
344
+ await this.ready;
345
+ const child = this.child;
346
+ if (!child) {
347
+ this.pendingSnapshots.delete(id);
348
+ return { id, type: 'snapshot_result', ok: false, error: { name: 'RuntimeDead', message: 'kernel died before snapshot' } };
349
+ }
350
+ await writeNdjson(child.stdin, { id, type: 'snapshot' });
351
+ } catch (err) {
352
+ this.pendingSnapshots.delete(id);
353
+ const e = err as Error;
354
+ return { id, type: 'snapshot_result', ok: false, error: { name: e.name, message: e.message } };
355
+ }
356
+ return result;
357
+ }
358
+
359
+ /**
360
+ * Phase 4 Task 10: service an `artifact_create` RPC request from the kernel.
361
+ * Calls the manager-supplied handler (if any), serializes the result into a
362
+ * `{type:'response', request:'artifact_create'}` frame, and writes it back
363
+ * to the child's stdin. Errors are surfaced to the kernel as `ok:false`
364
+ * frames so the awaiting cell rejects cleanly rather than hanging.
365
+ */
366
+ private async handleArtifactCreateRpc(req: ArtifactCreateRequest): Promise<void> {
367
+ const child = this.child;
368
+ if (!child) return;
369
+ let resp: ArtifactCreateResponse;
370
+ try {
371
+ const handler = this.options.handleArtifactCreate;
372
+ if (!handler) throw new Error('artifacts unavailable');
373
+ const result = await handler({ kind: req.kind, name: req.name });
374
+ resp = {
375
+ type: 'response',
376
+ request: 'artifact_create',
377
+ id: req.id,
378
+ ok: true,
379
+ slug: result.slug,
380
+ uri: result.uri,
381
+ primary_path: result.primary_path,
382
+ };
383
+ } catch (err) {
384
+ const e = err as Error;
385
+ resp = {
386
+ type: 'response',
387
+ request: 'artifact_create',
388
+ id: req.id,
389
+ ok: false,
390
+ error: e.message,
391
+ };
392
+ }
393
+ try {
394
+ await writeNdjson(child.stdin, resp);
395
+ } catch {
396
+ // child died between request and response — exit handler fails pending cells.
397
+ }
398
+ }
399
+
400
+ /**
401
+ * Phase 4 Task 10: service an `artifact_update` RPC request. Mirror of
402
+ * handleArtifactCreateRpc for the update path.
403
+ */
404
+ private async handleArtifactUpdateRpc(req: ArtifactUpdateRequest): Promise<void> {
405
+ const child = this.child;
406
+ if (!child) return;
407
+ let resp: ArtifactUpdateResponse;
408
+ try {
409
+ const handler = this.options.handleArtifactUpdate;
410
+ if (!handler) throw new Error('artifacts unavailable');
411
+ const result = await handler({ slug: req.slug, files: req.files });
412
+ resp = {
413
+ type: 'response',
414
+ request: 'artifact_update',
415
+ id: req.id,
416
+ ok: true,
417
+ files_touched: result.files_touched,
418
+ };
419
+ } catch (err) {
420
+ const e = err as Error;
421
+ resp = {
422
+ type: 'response',
423
+ request: 'artifact_update',
424
+ id: req.id,
425
+ ok: false,
426
+ error: e.message,
427
+ };
428
+ }
429
+ try {
430
+ await writeNdjson(child.stdin, resp);
431
+ } catch {
432
+ // child died between request and response — exit handler fails pending cells.
433
+ }
434
+ }
435
+
436
+ private rejectActive(id: number | null, message: string): void {
437
+ if (id === null) return;
438
+ const p = this.pending.get(id);
439
+ if (!p) return;
440
+ clearTimeout(p.totalTimer);
441
+ clearTimeout(p.inactivityTimer);
442
+ this.pending.delete(id);
443
+ if (this.activeId === id) this.activeId = null;
444
+ p.reject(new Error(message));
445
+ }
446
+
447
+ private failAllPending(err: Error): void {
448
+ for (const p of this.pending.values()) {
449
+ clearTimeout(p.totalTimer);
450
+ clearTimeout(p.inactivityTimer);
451
+ p.reject(err);
452
+ }
453
+ this.pending.clear();
454
+ this.activeId = null;
455
+ for (const resolve of this.pendingSnapshots.values()) {
456
+ resolve({ id: 0, type: 'snapshot_result', ok: false, error: { name: err.name, message: err.message } });
457
+ }
458
+ this.pendingSnapshots.clear();
459
+ }
460
+
461
+ private markDead(): void {
462
+ const child = this.child;
463
+ if (!child) return;
464
+ this.alive = false;
465
+ this.childReady = false;
466
+ child.kill('SIGTERM');
467
+ child.kill('SIGKILL');
468
+ // child stays referenced until its 'exit' fires; identity guards in readLoop/exit
469
+ // ignore the dead child once a restart reassigns this.child.
470
+ }
471
+
472
+ get hasActiveCell(): boolean {
473
+ return this.activeId !== null;
474
+ }
475
+
476
+ get recoveryNotes(): readonly RecoveryNote[] {
477
+ return this.recoveryNotes_;
478
+ }
479
+
480
+ async dispose(): Promise<void> {
481
+ if (this.disposed) return;
482
+ this.disposed = true;
483
+ this.alive = false;
484
+ this.childReady = false;
485
+ this.failAllPending(new Error('runtime disposed'));
486
+ const child = this.child;
487
+ this.child = null;
488
+ if (child) {
489
+ child.stdin.end();
490
+ child.kill('SIGTERM');
491
+ }
492
+ }
493
+ }
@@ -0,0 +1,69 @@
1
+ import { describe, it, beforeEach, afterEach } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { mkdtemp, mkdir, writeFile, rm } from 'node:fs/promises';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ import { pathToFileURL } from 'node:url';
7
+ import { DefaultCollectorRegistry, uriMatchesPattern } from './collector-registry.js';
8
+ import { FileCollector } from './file-collector.js';
9
+
10
+ describe('uriMatchesPattern', () => {
11
+ it('matches trailing-wildcard prefixes', () => {
12
+ assert.equal(uriMatchesPattern('file:///x/a.csv', 'file://*'), true);
13
+ assert.equal(uriMatchesPattern('http://x/a', 'file://*'), false);
14
+ });
15
+ it('matches exact patterns without a wildcard', () => {
16
+ assert.equal(uriMatchesPattern('mcp://res', 'mcp://res'), true);
17
+ assert.equal(uriMatchesPattern('mcp://other', 'mcp://res'), false);
18
+ });
19
+ });
20
+
21
+ describe('DefaultCollectorRegistry', () => {
22
+ it('registers, lists, and gets collectors by id', () => {
23
+ const reg = new DefaultCollectorRegistry();
24
+ const fc = new FileCollector({ workspace: '/tmp/x' });
25
+ reg.register(fc);
26
+ assert.equal(reg.get('file'), fc);
27
+ assert.equal(reg.get('nope'), null);
28
+ assert.deepEqual(reg.list().map((c) => c.id), ['file']);
29
+ });
30
+
31
+ describe('resolve()', () => {
32
+ let workspace: string;
33
+ let inputs: string;
34
+
35
+ beforeEach(async () => {
36
+ workspace = await mkdtemp(join(tmpdir(), 'reg-ws-'));
37
+ inputs = join(workspace, '.otto', 'inputs');
38
+ await mkdir(inputs, { recursive: true });
39
+ });
40
+ afterEach(async () => {
41
+ await rm(workspace, { recursive: true, force: true });
42
+ });
43
+
44
+ it('resolves a known file:// uri to its collector + ref', async () => {
45
+ await writeFile(join(inputs, 'cmdb.csv'), 'a,b\n');
46
+ const reg = new DefaultCollectorRegistry();
47
+ reg.register(new FileCollector({ workspace }));
48
+ const uri = pathToFileURL(join(inputs, 'cmdb.csv')).href;
49
+ const hit = await reg.resolve(uri);
50
+ assert.ok(hit);
51
+ assert.equal(hit.collector.id, 'file');
52
+ assert.equal(hit.ref.uri, uri);
53
+ assert.equal(hit.ref.kind, 'csv');
54
+ });
55
+
56
+ it('returns null for an unknown file under a matching collector', async () => {
57
+ const reg = new DefaultCollectorRegistry();
58
+ reg.register(new FileCollector({ workspace }));
59
+ const missing = pathToFileURL(join(inputs, 'absent.csv')).href;
60
+ assert.equal(await reg.resolve(missing), null);
61
+ });
62
+
63
+ it('returns null when no collector matches the uri scheme', async () => {
64
+ const reg = new DefaultCollectorRegistry();
65
+ reg.register(new FileCollector({ workspace }));
66
+ assert.equal(await reg.resolve('http://example.com/x.csv'), null);
67
+ });
68
+ });
69
+ });
@@ -0,0 +1,33 @@
1
+ import type { Collector, CollectorRegistry, DataSourceRef } from '@otto/coworker-types';
2
+
3
+ export function uriMatchesPattern(uri: string, pattern: string): boolean {
4
+ if (pattern.endsWith('*')) return uri.startsWith(pattern.slice(0, -1));
5
+ return uri === pattern;
6
+ }
7
+
8
+ export class DefaultCollectorRegistry implements CollectorRegistry {
9
+ private readonly collectors = new Map<string, Collector>();
10
+
11
+ register(collector: Collector): void {
12
+ this.collectors.set(collector.id, collector);
13
+ }
14
+
15
+ list(): Collector[] {
16
+ return [...this.collectors.values()];
17
+ }
18
+
19
+ get(id: string): Collector | null {
20
+ return this.collectors.get(id) ?? null;
21
+ }
22
+
23
+ async resolve(uri: string): Promise<{ collector: Collector; ref: DataSourceRef } | null> {
24
+ for (const collector of this.collectors.values()) {
25
+ const patterns = collector.describe().supports_uris;
26
+ if (!patterns.some((p) => uriMatchesPattern(uri, p))) continue;
27
+ for await (const ref of collector.list()) {
28
+ if (ref.uri === uri) return { collector, ref };
29
+ }
30
+ }
31
+ return null;
32
+ }
33
+ }
@@ -0,0 +1,33 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { detectKind, FILE_COLLECTOR_KINDS } from './detect-kind.js';
4
+
5
+ describe('detectKind', () => {
6
+ it('maps each supported extension to its DataKind', () => {
7
+ assert.equal(detectKind('/x/cmdb.csv'), 'csv');
8
+ assert.equal(detectKind('/x/report.xlsx'), 'xlsx');
9
+ assert.equal(detectKind('/x/data.json'), 'json');
10
+ assert.equal(detectKind('/x/big.parquet'), 'parquet');
11
+ assert.equal(detectKind('/x/notes.txt'), 'txt');
12
+ assert.equal(detectKind('/x/README.md'), 'md');
13
+ });
14
+
15
+ it('is case-insensitive on the extension', () => {
16
+ assert.equal(detectKind('/x/CMDB.CSV'), 'csv');
17
+ });
18
+
19
+ it('handles file:// URIs and strips query/hash', () => {
20
+ assert.equal(detectKind('file:///workspace/.otto/inputs/a.csv'), 'csv');
21
+ assert.equal(detectKind('file:///x/a.json?v=2#top'), 'json');
22
+ });
23
+
24
+ it('returns null for unsupported or extensionless paths', () => {
25
+ assert.equal(detectKind('/x/report.pdf'), null);
26
+ assert.equal(detectKind('/x/Makefile'), null);
27
+ assert.equal(detectKind('/x/archive.tar.gz'), null);
28
+ });
29
+
30
+ it('exposes the supported kinds as a stable list', () => {
31
+ assert.deepEqual([...FILE_COLLECTOR_KINDS].sort(), ['csv', 'json', 'md', 'parquet', 'txt', 'xlsx']);
32
+ });
33
+ });
@@ -0,0 +1,22 @@
1
+ import type { DataKind } from '@otto/coworker-types';
2
+
3
+ const EXT_TO_KIND: Record<string, DataKind> = {
4
+ '.csv': 'csv',
5
+ '.xlsx': 'xlsx',
6
+ '.json': 'json',
7
+ '.parquet': 'parquet',
8
+ '.txt': 'txt',
9
+ '.md': 'md',
10
+ };
11
+
12
+ // The six file kinds FileCollector enumerates. (DataKind also includes
13
+ // rest/mcp-resource/acp-stream, which belong to future non-file collectors.)
14
+ export const FILE_COLLECTOR_KINDS: readonly DataKind[] = ['csv', 'xlsx', 'json', 'parquet', 'txt', 'md'];
15
+
16
+ export function detectKind(pathOrUri: string): DataKind | null {
17
+ const clean = pathOrUri.split('?')[0].split('#')[0];
18
+ const dot = clean.lastIndexOf('.');
19
+ if (dot === -1) return null;
20
+ const ext = clean.slice(dot).toLowerCase();
21
+ return EXT_TO_KIND[ext] ?? null;
22
+ }