@cmetech/otto 1.1.0 → 1.2.4

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 (425) 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-paths.js +8 -0
  7. package/dist/resources/extensions/coworker-artifacts/artifacts-command.js +31 -0
  8. package/dist/resources/extensions/coworker-artifacts/artifacts-singleton.js +17 -0
  9. package/dist/resources/extensions/coworker-artifacts/extension-manifest.json +13 -0
  10. package/dist/resources/extensions/coworker-artifacts/index.js +125 -0
  11. package/dist/resources/extensions/coworker-artifacts/list-tool.js +27 -0
  12. package/dist/resources/extensions/coworker-artifacts/open-tool.js +25 -0
  13. package/dist/resources/extensions/coworker-memory/extension-manifest.json +13 -0
  14. package/dist/resources/extensions/coworker-memory/index.js +219 -0
  15. package/dist/resources/extensions/coworker-memory/memorize-tool.js +10 -0
  16. package/dist/resources/extensions/coworker-memory/memory-command.js +157 -0
  17. package/dist/resources/extensions/coworker-memory/memory-singleton.js +55 -0
  18. package/dist/resources/extensions/coworker-memory/recall-tool.js +18 -0
  19. package/dist/resources/extensions/coworker-memory/session-hooks.js +45 -0
  20. package/dist/resources/extensions/coworker-scratchpad/attach-banners.js +53 -0
  21. package/dist/resources/extensions/coworker-scratchpad/extension-manifest.json +13 -0
  22. package/dist/resources/extensions/coworker-scratchpad/format-age.js +9 -0
  23. package/dist/resources/extensions/coworker-scratchpad/helpers.js +38 -0
  24. package/dist/resources/extensions/coworker-scratchpad/index.js +199 -0
  25. package/dist/resources/extensions/coworker-scratchpad/mime-bundle.js +20 -0
  26. package/dist/resources/extensions/coworker-scratchpad/scratchpad-tool.js +118 -0
  27. package/dist/resources/extensions/coworker-scratchpad/session-sidecar.js +60 -0
  28. package/dist/resources/extensions/coworker-scratchpad/sp-command.js +597 -0
  29. package/dist/resources/extensions/coworker-scratchpad/workspace-pointer.js +41 -0
  30. package/dist/resources/extensions/coworker-scratchpad/workspace-root.js +17 -0
  31. package/dist/resources/extensions/coworker-vault/audit-command.js +35 -0
  32. package/dist/resources/extensions/coworker-vault/connect-command.js +42 -0
  33. package/dist/resources/extensions/coworker-vault/datasource-command.js +50 -0
  34. package/dist/resources/extensions/coworker-vault/extension-manifest.json +12 -0
  35. package/dist/resources/extensions/coworker-vault/index.js +171 -0
  36. package/dist/resources/extensions/coworker-vault/test-helpers.js +86 -0
  37. package/dist/resources/extensions/coworker-vault/vault-singleton.js +24 -0
  38. package/dist/resources/extensions/otto/commands/release-notes/_data.js +82 -0
  39. package/dist/resources/extensions/otto/commands/release-notes/command.js +15 -4
  40. package/dist/resources/extensions/subagent/index.js +8 -1
  41. package/dist/resources/extensions/subagent/launch.js +37 -5
  42. package/dist/resources/extensions/subagent/run-store.js +1 -0
  43. package/dist/resources/extensions/workflow/bootstrap/register-extension.js +2 -0
  44. package/dist/resources/extensions/workflow/bootstrap/register-hooks.js +10 -0
  45. package/dist/resources/extensions/workflow/persona-status.js +87 -0
  46. package/dist/update-cmd.d.ts +19 -0
  47. package/dist/update-cmd.js +177 -6
  48. package/package.json +25 -10
  49. package/packages/contracts/package.json +1 -1
  50. package/packages/coworker-artifacts/dist/artifact-store.d.ts +25 -0
  51. package/packages/coworker-artifacts/dist/artifact-store.js +187 -0
  52. package/packages/coworker-artifacts/dist/dir-snapshot.d.ts +7 -0
  53. package/packages/coworker-artifacts/dist/dir-snapshot.js +54 -0
  54. package/packages/coworker-artifacts/dist/errors.d.ts +18 -0
  55. package/packages/coworker-artifacts/dist/errors.js +37 -0
  56. package/packages/coworker-artifacts/dist/index.d.ts +7 -0
  57. package/packages/coworker-artifacts/dist/index.js +7 -0
  58. package/packages/coworker-artifacts/dist/readme-renderer.d.ts +5 -0
  59. package/packages/coworker-artifacts/dist/readme-renderer.js +47 -0
  60. package/packages/coworker-artifacts/dist/resolve-uri.d.ts +3 -0
  61. package/packages/coworker-artifacts/dist/resolve-uri.js +29 -0
  62. package/packages/coworker-artifacts/dist/slug.d.ts +4 -0
  63. package/packages/coworker-artifacts/dist/slug.js +32 -0
  64. package/packages/coworker-artifacts/dist/types.d.ts +52 -0
  65. package/packages/coworker-artifacts/dist/types.js +1 -0
  66. package/packages/coworker-artifacts/package.json +20 -0
  67. package/packages/coworker-artifacts/src/artifact-store.test.ts +188 -0
  68. package/packages/coworker-artifacts/src/artifact-store.ts +206 -0
  69. package/packages/coworker-artifacts/src/artifacts-integration.test.ts +109 -0
  70. package/packages/coworker-artifacts/src/dir-snapshot.test.ts +71 -0
  71. package/packages/coworker-artifacts/src/dir-snapshot.ts +52 -0
  72. package/packages/coworker-artifacts/src/errors.test.ts +37 -0
  73. package/packages/coworker-artifacts/src/errors.ts +28 -0
  74. package/packages/coworker-artifacts/src/index.test.ts +22 -0
  75. package/packages/coworker-artifacts/src/index.ts +7 -0
  76. package/packages/coworker-artifacts/src/readme-renderer.test.ts +72 -0
  77. package/packages/coworker-artifacts/src/readme-renderer.ts +56 -0
  78. package/packages/coworker-artifacts/src/resolve-uri.test.ts +46 -0
  79. package/packages/coworker-artifacts/src/resolve-uri.ts +29 -0
  80. package/packages/coworker-artifacts/src/slug.test.ts +47 -0
  81. package/packages/coworker-artifacts/src/slug.ts +31 -0
  82. package/packages/coworker-artifacts/src/types.ts +61 -0
  83. package/packages/coworker-artifacts/tsconfig.json +15 -0
  84. package/packages/coworker-artifacts/tsconfig.publish.json +4 -0
  85. package/packages/coworker-memory/dist/context-injection.d.ts +9 -0
  86. package/packages/coworker-memory/dist/context-injection.js +41 -0
  87. package/packages/coworker-memory/dist/errors.d.ts +25 -0
  88. package/packages/coworker-memory/dist/errors.js +51 -0
  89. package/packages/coworker-memory/dist/index.d.ts +12 -0
  90. package/packages/coworker-memory/dist/index.js +12 -0
  91. package/packages/coworker-memory/dist/layer-a-store.d.ts +16 -0
  92. package/packages/coworker-memory/dist/layer-a-store.js +78 -0
  93. package/packages/coworker-memory/dist/local-sqlite-backend.d.ts +28 -0
  94. package/packages/coworker-memory/dist/local-sqlite-backend.js +167 -0
  95. package/packages/coworker-memory/dist/memory-backend.d.ts +14 -0
  96. package/packages/coworker-memory/dist/memory-backend.js +1 -0
  97. package/packages/coworker-memory/dist/memory-recorder.d.ts +50 -0
  98. package/packages/coworker-memory/dist/memory-recorder.js +69 -0
  99. package/packages/coworker-memory/dist/migrations/001-init.sql +38 -0
  100. package/packages/coworker-memory/dist/migrations/002-artifact-kind.sql +50 -0
  101. package/packages/coworker-memory/dist/paste-detector.d.ts +5 -0
  102. package/packages/coworker-memory/dist/paste-detector.js +14 -0
  103. package/packages/coworker-memory/dist/persona-seed.d.ts +10 -0
  104. package/packages/coworker-memory/dist/persona-seed.js +38 -0
  105. package/packages/coworker-memory/dist/recall-formatter.d.ts +2 -0
  106. package/packages/coworker-memory/dist/recall-formatter.js +14 -0
  107. package/packages/coworker-memory/dist/scope-resolver.d.ts +9 -0
  108. package/packages/coworker-memory/dist/scope-resolver.js +10 -0
  109. package/packages/coworker-memory/dist/types.d.ts +51 -0
  110. package/packages/coworker-memory/dist/types.js +2 -0
  111. package/packages/coworker-memory/dist/workspace-id.d.ts +3 -0
  112. package/packages/coworker-memory/dist/workspace-id.js +54 -0
  113. package/packages/coworker-memory/package.json +35 -0
  114. package/packages/coworker-memory/src/activator-integration.test.ts +141 -0
  115. package/packages/coworker-memory/src/context-injection.test.ts +72 -0
  116. package/packages/coworker-memory/src/context-injection.ts +57 -0
  117. package/packages/coworker-memory/src/errors.test.ts +45 -0
  118. package/packages/coworker-memory/src/errors.ts +42 -0
  119. package/packages/coworker-memory/src/index.test.ts +21 -0
  120. package/packages/coworker-memory/src/index.ts +12 -0
  121. package/packages/coworker-memory/src/layer-a-store.test.ts +85 -0
  122. package/packages/coworker-memory/src/layer-a-store.ts +88 -0
  123. package/packages/coworker-memory/src/local-sqlite-backend.test.ts +110 -0
  124. package/packages/coworker-memory/src/local-sqlite-backend.ts +185 -0
  125. package/packages/coworker-memory/src/memory-backend.ts +10 -0
  126. package/packages/coworker-memory/src/memory-integration.test.ts +89 -0
  127. package/packages/coworker-memory/src/memory-recorder.test.ts +101 -0
  128. package/packages/coworker-memory/src/memory-recorder.ts +95 -0
  129. package/packages/coworker-memory/src/migrations/001-init.sql +38 -0
  130. package/packages/coworker-memory/src/migrations/002-artifact-kind.sql +50 -0
  131. package/packages/coworker-memory/src/paste-detector.test.ts +23 -0
  132. package/packages/coworker-memory/src/paste-detector.ts +18 -0
  133. package/packages/coworker-memory/src/persona-seed.test.ts +57 -0
  134. package/packages/coworker-memory/src/persona-seed.ts +46 -0
  135. package/packages/coworker-memory/src/recall-formatter.test.ts +34 -0
  136. package/packages/coworker-memory/src/recall-formatter.ts +15 -0
  137. package/packages/coworker-memory/src/scope-resolver.test.ts +23 -0
  138. package/packages/coworker-memory/src/scope-resolver.ts +18 -0
  139. package/packages/coworker-memory/src/types.ts +61 -0
  140. package/packages/coworker-memory/src/workspace-id.test.ts +48 -0
  141. package/packages/coworker-memory/src/workspace-id.ts +56 -0
  142. package/packages/coworker-memory/tsconfig.json +15 -0
  143. package/packages/coworker-memory/tsconfig.publish.json +4 -0
  144. package/packages/coworker-persona/dist/commands.d.ts +7 -0
  145. package/packages/coworker-persona/dist/commands.js +35 -0
  146. package/packages/coworker-persona/dist/defaults/manifest.yaml +12 -0
  147. package/packages/coworker-persona/dist/defaults/steering/identity.md +3 -0
  148. package/packages/coworker-persona/dist/index.d.ts +3 -0
  149. package/packages/coworker-persona/dist/index.js +3 -0
  150. package/packages/coworker-persona/dist/manifest.d.ts +24 -0
  151. package/packages/coworker-persona/dist/manifest.js +21 -0
  152. package/packages/coworker-persona/dist/registry.d.ts +22 -0
  153. package/packages/coworker-persona/dist/registry.js +142 -0
  154. package/packages/coworker-persona/package.json +28 -0
  155. package/packages/coworker-persona/scripts/copy-defaults.cjs +17 -0
  156. package/packages/coworker-persona/src/commands.ts +47 -0
  157. package/packages/coworker-persona/src/defaults/manifest.yaml +12 -0
  158. package/packages/coworker-persona/src/defaults/steering/identity.md +3 -0
  159. package/packages/coworker-persona/src/index.ts +3 -0
  160. package/packages/coworker-persona/src/manifest.test.ts +67 -0
  161. package/packages/coworker-persona/src/manifest.ts +49 -0
  162. package/packages/coworker-persona/src/registry.test.ts +89 -0
  163. package/packages/coworker-persona/src/registry.ts +147 -0
  164. package/packages/coworker-persona/tsconfig.json +15 -0
  165. package/packages/coworker-persona/tsconfig.publish.json +4 -0
  166. package/packages/coworker-scratchpad/dist/cell-archive.d.ts +39 -0
  167. package/packages/coworker-scratchpad/dist/cell-archive.js +77 -0
  168. package/packages/coworker-scratchpad/dist/cell-tree.d.ts +14 -0
  169. package/packages/coworker-scratchpad/dist/cell-tree.js +72 -0
  170. package/packages/coworker-scratchpad/dist/child-process-runtime.d.ts +129 -0
  171. package/packages/coworker-scratchpad/dist/child-process-runtime.js +427 -0
  172. package/packages/coworker-scratchpad/dist/collector-registry.d.ts +12 -0
  173. package/packages/coworker-scratchpad/dist/collector-registry.js +29 -0
  174. package/packages/coworker-scratchpad/dist/detect-kind.d.ts +3 -0
  175. package/packages/coworker-scratchpad/dist/detect-kind.js +19 -0
  176. package/packages/coworker-scratchpad/dist/file-collector.d.ts +15 -0
  177. package/packages/coworker-scratchpad/dist/file-collector.js +99 -0
  178. package/packages/coworker-scratchpad/dist/index.d.ts +13 -0
  179. package/packages/coworker-scratchpad/dist/index.js +13 -0
  180. package/packages/coworker-scratchpad/dist/kernel-bindings.d.ts +49 -0
  181. package/packages/coworker-scratchpad/dist/kernel-bindings.js +220 -0
  182. package/packages/coworker-scratchpad/dist/kernel-entry.d.ts +1 -0
  183. package/packages/coworker-scratchpad/dist/kernel-entry.js +355 -0
  184. package/packages/coworker-scratchpad/dist/kernel-protocol.d.ts +171 -0
  185. package/packages/coworker-scratchpad/dist/kernel-protocol.js +48 -0
  186. package/packages/coworker-scratchpad/dist/kernel-spawn.d.ts +3 -0
  187. package/packages/coworker-scratchpad/dist/kernel-spawn.js +54 -0
  188. package/packages/coworker-scratchpad/dist/namespace-codec.d.ts +22 -0
  189. package/packages/coworker-scratchpad/dist/namespace-codec.js +61 -0
  190. package/packages/coworker-scratchpad/dist/scratchpad-lock.d.ts +24 -0
  191. package/packages/coworker-scratchpad/dist/scratchpad-lock.js +86 -0
  192. package/packages/coworker-scratchpad/dist/scratchpad-manager.d.ts +193 -0
  193. package/packages/coworker-scratchpad/dist/scratchpad-manager.js +866 -0
  194. package/packages/coworker-scratchpad/dist/staleness-banner.d.ts +12 -0
  195. package/packages/coworker-scratchpad/dist/staleness-banner.js +27 -0
  196. package/packages/coworker-scratchpad/package.json +31 -0
  197. package/packages/coworker-scratchpad/src/cell-archive.test.ts +150 -0
  198. package/packages/coworker-scratchpad/src/cell-archive.ts +97 -0
  199. package/packages/coworker-scratchpad/src/cell-tree.test.ts +105 -0
  200. package/packages/coworker-scratchpad/src/cell-tree.ts +90 -0
  201. package/packages/coworker-scratchpad/src/child-process-runtime.test.ts +413 -0
  202. package/packages/coworker-scratchpad/src/child-process-runtime.ts +493 -0
  203. package/packages/coworker-scratchpad/src/collector-registry.test.ts +69 -0
  204. package/packages/coworker-scratchpad/src/collector-registry.ts +33 -0
  205. package/packages/coworker-scratchpad/src/detect-kind.test.ts +33 -0
  206. package/packages/coworker-scratchpad/src/detect-kind.ts +22 -0
  207. package/packages/coworker-scratchpad/src/file-collector.test.ts +109 -0
  208. package/packages/coworker-scratchpad/src/file-collector.ts +114 -0
  209. package/packages/coworker-scratchpad/src/index.ts +74 -0
  210. package/packages/coworker-scratchpad/src/kernel-bindings.test.ts +188 -0
  211. package/packages/coworker-scratchpad/src/kernel-bindings.ts +279 -0
  212. package/packages/coworker-scratchpad/src/kernel-entry.test.ts +123 -0
  213. package/packages/coworker-scratchpad/src/kernel-entry.ts +390 -0
  214. package/packages/coworker-scratchpad/src/kernel-protocol.test.ts +105 -0
  215. package/packages/coworker-scratchpad/src/kernel-protocol.ts +230 -0
  216. package/packages/coworker-scratchpad/src/kernel-spawn.test.ts +60 -0
  217. package/packages/coworker-scratchpad/src/kernel-spawn.ts +54 -0
  218. package/packages/coworker-scratchpad/src/namespace-codec.test.ts +102 -0
  219. package/packages/coworker-scratchpad/src/namespace-codec.ts +90 -0
  220. package/packages/coworker-scratchpad/src/scratchpad-lock.test.ts +98 -0
  221. package/packages/coworker-scratchpad/src/scratchpad-lock.ts +102 -0
  222. package/packages/coworker-scratchpad/src/scratchpad-manager.test.ts +1343 -0
  223. package/packages/coworker-scratchpad/src/scratchpad-manager.ts +891 -0
  224. package/packages/coworker-scratchpad/src/staleness-banner.test.ts +53 -0
  225. package/packages/coworker-scratchpad/src/staleness-banner.ts +33 -0
  226. package/packages/coworker-scratchpad/src/vault-integration.test.ts +221 -0
  227. package/packages/coworker-scratchpad/tsconfig.json +15 -0
  228. package/packages/coworker-scratchpad/tsconfig.publish.json +4 -0
  229. package/packages/coworker-types/dist/artifacts.d.ts +31 -0
  230. package/packages/coworker-types/dist/artifacts.js +2 -0
  231. package/packages/coworker-types/dist/contracts.d.ts +32 -0
  232. package/packages/coworker-types/dist/contracts.js +1 -0
  233. package/packages/coworker-types/dist/index.d.ts +5 -0
  234. package/packages/coworker-types/dist/index.js +5 -0
  235. package/packages/coworker-types/dist/memory.d.ts +61 -0
  236. package/packages/coworker-types/dist/memory.js +3 -0
  237. package/packages/coworker-types/dist/scratchpad.d.ts +43 -0
  238. package/packages/coworker-types/dist/scratchpad.js +2 -0
  239. package/packages/coworker-types/dist/vault.d.ts +34 -0
  240. package/packages/coworker-types/dist/vault.js +2 -0
  241. package/packages/coworker-types/package.json +24 -0
  242. package/packages/coworker-types/src/artifacts.test.ts +52 -0
  243. package/packages/coworker-types/src/artifacts.ts +35 -0
  244. package/packages/coworker-types/src/contracts.test.ts +43 -0
  245. package/packages/coworker-types/src/contracts.ts +36 -0
  246. package/packages/coworker-types/src/index.ts +5 -0
  247. package/packages/coworker-types/src/memory.test.ts +50 -0
  248. package/packages/coworker-types/src/memory.ts +79 -0
  249. package/packages/coworker-types/src/scratchpad.test.ts +46 -0
  250. package/packages/coworker-types/src/scratchpad.ts +51 -0
  251. package/packages/coworker-types/src/smoke.test.ts +34 -0
  252. package/packages/coworker-types/src/vault.test.ts +49 -0
  253. package/packages/coworker-types/src/vault.ts +40 -0
  254. package/packages/coworker-types/tsconfig.json +15 -0
  255. package/packages/coworker-types/tsconfig.publish.json +4 -0
  256. package/packages/coworker-utils/dist/audit-log.d.ts +34 -0
  257. package/packages/coworker-utils/dist/audit-log.js +88 -0
  258. package/packages/coworker-utils/dist/index.d.ts +6 -0
  259. package/packages/coworker-utils/dist/index.js +6 -0
  260. package/packages/coworker-utils/dist/lease.d.ts +7 -0
  261. package/packages/coworker-utils/dist/lease.js +67 -0
  262. package/packages/coworker-utils/dist/logger.d.ts +13 -0
  263. package/packages/coworker-utils/dist/logger.js +26 -0
  264. package/packages/coworker-utils/dist/migration-runner.d.ts +7 -0
  265. package/packages/coworker-utils/dist/migration-runner.js +36 -0
  266. package/packages/coworker-utils/dist/ndjson-channel.d.ts +3 -0
  267. package/packages/coworker-utils/dist/ndjson-channel.js +38 -0
  268. package/packages/coworker-utils/dist/secret-scanner.d.ts +10 -0
  269. package/packages/coworker-utils/dist/secret-scanner.js +42 -0
  270. package/packages/coworker-utils/package.json +24 -0
  271. package/packages/coworker-utils/src/audit-log.test.ts +140 -0
  272. package/packages/coworker-utils/src/audit-log.ts +107 -0
  273. package/packages/coworker-utils/src/index.ts +6 -0
  274. package/packages/coworker-utils/src/lease.test.ts +64 -0
  275. package/packages/coworker-utils/src/lease.ts +76 -0
  276. package/packages/coworker-utils/src/logger.test.ts +50 -0
  277. package/packages/coworker-utils/src/logger.ts +45 -0
  278. package/packages/coworker-utils/src/migration-runner.test.ts +65 -0
  279. package/packages/coworker-utils/src/migration-runner.ts +50 -0
  280. package/packages/coworker-utils/src/ndjson-channel.test.ts +76 -0
  281. package/packages/coworker-utils/src/ndjson-channel.ts +41 -0
  282. package/packages/coworker-utils/src/secret-scanner.test.ts +61 -0
  283. package/packages/coworker-utils/src/secret-scanner.ts +56 -0
  284. package/packages/coworker-utils/tsconfig.json +15 -0
  285. package/packages/coworker-utils/tsconfig.publish.json +4 -0
  286. package/packages/coworker-vault/dist/data-vault.d.ts +41 -0
  287. package/packages/coworker-vault/dist/data-vault.js +223 -0
  288. package/packages/coworker-vault/dist/engine-registry.d.ts +34 -0
  289. package/packages/coworker-vault/dist/engine-registry.js +90 -0
  290. package/packages/coworker-vault/dist/engines/jira.yaml +17 -0
  291. package/packages/coworker-vault/dist/errors.d.ts +28 -0
  292. package/packages/coworker-vault/dist/errors.js +57 -0
  293. package/packages/coworker-vault/dist/index.d.ts +6 -0
  294. package/packages/coworker-vault/dist/index.js +6 -0
  295. package/packages/coworker-vault/dist/injector.d.ts +19 -0
  296. package/packages/coworker-vault/dist/injector.js +77 -0
  297. package/packages/coworker-vault/dist/types.d.ts +28 -0
  298. package/packages/coworker-vault/dist/types.js +1 -0
  299. package/packages/coworker-vault/dist/vault-keep.d.ts +4 -0
  300. package/packages/coworker-vault/dist/vault-keep.js +21 -0
  301. package/packages/coworker-vault/package.json +29 -0
  302. package/packages/coworker-vault/src/data-vault.test.ts +199 -0
  303. package/packages/coworker-vault/src/data-vault.ts +257 -0
  304. package/packages/coworker-vault/src/engine-registry.test.ts +120 -0
  305. package/packages/coworker-vault/src/engine-registry.ts +107 -0
  306. package/packages/coworker-vault/src/engines/jira.yaml +17 -0
  307. package/packages/coworker-vault/src/errors.test.ts +58 -0
  308. package/packages/coworker-vault/src/errors.ts +50 -0
  309. package/packages/coworker-vault/src/index.test.ts +24 -0
  310. package/packages/coworker-vault/src/index.ts +6 -0
  311. package/packages/coworker-vault/src/injector.test.ts +109 -0
  312. package/packages/coworker-vault/src/injector.ts +98 -0
  313. package/packages/coworker-vault/src/types.ts +33 -0
  314. package/packages/coworker-vault/src/vault-keep.test.ts +49 -0
  315. package/packages/coworker-vault/src/vault-keep.ts +31 -0
  316. package/packages/coworker-vault/tsconfig.json +15 -0
  317. package/packages/coworker-vault/tsconfig.publish.json +4 -0
  318. package/packages/daemon/package.json +3 -3
  319. package/packages/mcp-server/package.json +3 -3
  320. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  321. package/packages/native/package.json +1 -1
  322. package/packages/native/tsconfig.tsbuildinfo +1 -1
  323. package/packages/pi-agent-core/package.json +1 -1
  324. package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
  325. package/packages/pi-ai/package.json +1 -1
  326. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  327. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +6 -1
  328. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  329. package/packages/pi-coding-agent/dist/core/extensions/runner.js +22 -3
  330. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  331. package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js +11 -0
  332. package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js.map +1 -1
  333. package/packages/pi-coding-agent/dist/modes/rpc/raw-stdout.d.ts +47 -0
  334. package/packages/pi-coding-agent/dist/modes/rpc/raw-stdout.d.ts.map +1 -0
  335. package/packages/pi-coding-agent/dist/modes/rpc/raw-stdout.js +107 -0
  336. package/packages/pi-coding-agent/dist/modes/rpc/raw-stdout.js.map +1 -0
  337. package/packages/pi-coding-agent/dist/modes/rpc/raw-stdout.regression.test.d.ts +19 -0
  338. package/packages/pi-coding-agent/dist/modes/rpc/raw-stdout.regression.test.d.ts.map +1 -0
  339. package/packages/pi-coding-agent/dist/modes/rpc/raw-stdout.regression.test.js +121 -0
  340. package/packages/pi-coding-agent/dist/modes/rpc/raw-stdout.regression.test.js.map +1 -0
  341. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  342. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js +17 -1
  343. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
  344. package/packages/pi-coding-agent/package.json +2 -2
  345. package/packages/pi-coding-agent/src/core/extensions/runner.ts +22 -3
  346. package/packages/pi-coding-agent/src/core/resolve-config-value.test.ts +11 -0
  347. package/packages/pi-coding-agent/src/modes/rpc/raw-stdout.regression.test.ts +129 -0
  348. package/packages/pi-coding-agent/src/modes/rpc/raw-stdout.ts +117 -0
  349. package/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +18 -1
  350. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  351. package/packages/pi-tui/package.json +1 -1
  352. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  353. package/packages/rpc-client/package.json +2 -2
  354. package/packages/rpc-client/tsconfig.tsbuildinfo +1 -1
  355. package/pkg/package.json +1 -1
  356. package/scripts/install.js +6 -5
  357. package/src/resources/extensions/_coworker-paths.test.ts +40 -0
  358. package/src/resources/extensions/_coworker-paths.ts +10 -0
  359. package/src/resources/extensions/coworker-artifacts/artifacts-command.test.ts +54 -0
  360. package/src/resources/extensions/coworker-artifacts/artifacts-command.ts +43 -0
  361. package/src/resources/extensions/coworker-artifacts/artifacts-singleton.test.ts +25 -0
  362. package/src/resources/extensions/coworker-artifacts/artifacts-singleton.ts +29 -0
  363. package/src/resources/extensions/coworker-artifacts/extension-manifest.json +13 -0
  364. package/src/resources/extensions/coworker-artifacts/index.test.ts +46 -0
  365. package/src/resources/extensions/coworker-artifacts/index.ts +154 -0
  366. package/src/resources/extensions/coworker-artifacts/list-tool.test.ts +29 -0
  367. package/src/resources/extensions/coworker-artifacts/list-tool.ts +53 -0
  368. package/src/resources/extensions/coworker-artifacts/open-tool.test.ts +30 -0
  369. package/src/resources/extensions/coworker-artifacts/open-tool.ts +43 -0
  370. package/src/resources/extensions/coworker-memory/extension-manifest.json +13 -0
  371. package/src/resources/extensions/coworker-memory/index.test.ts +137 -0
  372. package/src/resources/extensions/coworker-memory/index.ts +257 -0
  373. package/src/resources/extensions/coworker-memory/memorize-tool.test.ts +41 -0
  374. package/src/resources/extensions/coworker-memory/memorize-tool.ts +20 -0
  375. package/src/resources/extensions/coworker-memory/memory-command.test.ts +134 -0
  376. package/src/resources/extensions/coworker-memory/memory-command.ts +131 -0
  377. package/src/resources/extensions/coworker-memory/memory-singleton.test.ts +41 -0
  378. package/src/resources/extensions/coworker-memory/memory-singleton.ts +89 -0
  379. package/src/resources/extensions/coworker-memory/recall-tool.test.ts +50 -0
  380. package/src/resources/extensions/coworker-memory/recall-tool.ts +35 -0
  381. package/src/resources/extensions/coworker-memory/session-hooks.test.ts +77 -0
  382. package/src/resources/extensions/coworker-memory/session-hooks.ts +61 -0
  383. package/src/resources/extensions/coworker-scratchpad/attach-banners.test.ts +124 -0
  384. package/src/resources/extensions/coworker-scratchpad/attach-banners.ts +67 -0
  385. package/src/resources/extensions/coworker-scratchpad/extension-manifest.json +13 -0
  386. package/src/resources/extensions/coworker-scratchpad/format-age.test.ts +30 -0
  387. package/src/resources/extensions/coworker-scratchpad/format-age.ts +6 -0
  388. package/src/resources/extensions/coworker-scratchpad/helpers.test.ts +93 -0
  389. package/src/resources/extensions/coworker-scratchpad/helpers.ts +42 -0
  390. package/src/resources/extensions/coworker-scratchpad/index.test.ts +514 -0
  391. package/src/resources/extensions/coworker-scratchpad/index.ts +207 -0
  392. package/src/resources/extensions/coworker-scratchpad/mime-bundle.test.ts +61 -0
  393. package/src/resources/extensions/coworker-scratchpad/mime-bundle.ts +23 -0
  394. package/src/resources/extensions/coworker-scratchpad/scratchpad-tool.test.ts +137 -0
  395. package/src/resources/extensions/coworker-scratchpad/scratchpad-tool.ts +165 -0
  396. package/src/resources/extensions/coworker-scratchpad/session-sidecar.test.ts +133 -0
  397. package/src/resources/extensions/coworker-scratchpad/session-sidecar.ts +68 -0
  398. package/src/resources/extensions/coworker-scratchpad/sp-command.test.ts +836 -0
  399. package/src/resources/extensions/coworker-scratchpad/sp-command.ts +602 -0
  400. package/src/resources/extensions/coworker-scratchpad/workspace-pointer.test.ts +74 -0
  401. package/src/resources/extensions/coworker-scratchpad/workspace-pointer.ts +55 -0
  402. package/src/resources/extensions/coworker-scratchpad/workspace-root.test.ts +51 -0
  403. package/src/resources/extensions/coworker-scratchpad/workspace-root.ts +16 -0
  404. package/src/resources/extensions/coworker-vault/audit-command.test.ts +109 -0
  405. package/src/resources/extensions/coworker-vault/audit-command.ts +56 -0
  406. package/src/resources/extensions/coworker-vault/connect-command.test.ts +103 -0
  407. package/src/resources/extensions/coworker-vault/connect-command.ts +69 -0
  408. package/src/resources/extensions/coworker-vault/datasource-command.test.ts +80 -0
  409. package/src/resources/extensions/coworker-vault/datasource-command.ts +81 -0
  410. package/src/resources/extensions/coworker-vault/extension-manifest.json +12 -0
  411. package/src/resources/extensions/coworker-vault/index.test.ts +82 -0
  412. package/src/resources/extensions/coworker-vault/index.ts +181 -0
  413. package/src/resources/extensions/coworker-vault/test-helpers.ts +120 -0
  414. package/src/resources/extensions/coworker-vault/vault-singleton.test.ts +27 -0
  415. package/src/resources/extensions/coworker-vault/vault-singleton.ts +40 -0
  416. package/src/resources/extensions/otto/commands/release-notes/_data.ts +96 -0
  417. package/src/resources/extensions/otto/commands/release-notes/command.ts +16 -3
  418. package/src/resources/extensions/subagent/index.ts +9 -0
  419. package/src/resources/extensions/subagent/launch.test.ts +97 -0
  420. package/src/resources/extensions/subagent/launch.ts +42 -5
  421. package/src/resources/extensions/subagent/run-store.ts +3 -1
  422. package/src/resources/extensions/workflow/bootstrap/register-extension.ts +2 -0
  423. package/src/resources/extensions/workflow/bootstrap/register-hooks.ts +10 -0
  424. package/src/resources/extensions/workflow/persona-status.ts +109 -0
  425. package/src/resources/extensions/workflow/tests/auto-recovery.test.ts +34 -0
@@ -0,0 +1,230 @@
1
+ // NDJSON wire protocol between the scratchpad parent runtime and the kernel child.
2
+ // One JSON object per line (\n terminated). See spec §2.4 + §6.3.
3
+
4
+ export type { SkippedKey } from './namespace-codec.js';
5
+ import type { SkippedKey } from './namespace-codec.js';
6
+
7
+ export interface RunRequest {
8
+ id: number;
9
+ type: 'run';
10
+ code: string;
11
+ }
12
+ export interface SnapshotRequest {
13
+ id: number;
14
+ type: 'snapshot';
15
+ }
16
+ export type KernelRequest = RunRequest | SnapshotRequest;
17
+
18
+ export interface ResultOk {
19
+ id: number;
20
+ type: 'result';
21
+ ok: true;
22
+ value: unknown;
23
+ stdout: string;
24
+ }
25
+ export interface ResultErr {
26
+ id: number;
27
+ type: 'result';
28
+ ok: false;
29
+ error: { name: string; message: string; stack?: string };
30
+ }
31
+ export type ResultResponse = ResultOk | ResultErr;
32
+
33
+ // 1d2 — snapshot request/response. Parent triggers, kernel sync-writes
34
+ // <scratchpadDir>/namespace.json and ACKs. Parent records the result into
35
+ // meta.json before disposing the kernel.
36
+ export interface SnapshotResultOk {
37
+ id: number;
38
+ type: 'snapshot_result';
39
+ ok: true;
40
+ skipped: SkippedKey[];
41
+ snapshotted_at: string;
42
+ }
43
+ export interface SnapshotResultErr {
44
+ id: number;
45
+ type: 'snapshot_result';
46
+ ok: false;
47
+ error: { name: string; message: string; stack?: string };
48
+ }
49
+ export type SnapshotResult = SnapshotResultOk | SnapshotResultErr;
50
+
51
+ export type RecoveryNote =
52
+ | { kind: 'namespace-absent' }
53
+ | { kind: 'namespace-corrupt'; message: string }
54
+ | { kind: 'cells-since-snapshot'; n: number }
55
+ | { kind: 'snapshot-failed'; message: string };
56
+
57
+ // Layer-B drawer payload recorded when a cell loads data through a collector.
58
+ // `schema` is always null in Phase 1b; real schema introspection arrives with
59
+ // DuckDB in sub-plan 1d.
60
+ export interface DataLoadDrawer {
61
+ kind: 'data_load';
62
+ collector: string;
63
+ uri: string;
64
+ bytes: number | null;
65
+ rows_loaded: number | null;
66
+ loaded_at: string;
67
+ schema: null;
68
+ }
69
+
70
+ export interface ReadyEvent {
71
+ type: 'event';
72
+ event: 'ready';
73
+ recovery_notes?: RecoveryNote[];
74
+ }
75
+ export interface DataLoadEvent {
76
+ type: 'event';
77
+ event: 'data_load';
78
+ drawer: DataLoadDrawer;
79
+ }
80
+ export interface ProgressEvent {
81
+ type: 'event';
82
+ event: 'progress';
83
+ message?: string;
84
+ }
85
+ export interface StartupErrorEvent {
86
+ type: 'event';
87
+ event: 'startup_error';
88
+ kind: string;
89
+ error: { name: string; message: string; stack?: string };
90
+ }
91
+ // Phase 4 Task 9 — artifact RPC + event
92
+
93
+ // Layer-B drawer payload recorded when a cell creates an artifact. Mirrors the
94
+ // shape of DataLoadDrawer; persisted to memory by the manager (Task 10).
95
+ export interface ArtifactCreateDrawer {
96
+ kind: 'artifact';
97
+ slug: string;
98
+ artifact_kind: string; // 'report' in v1
99
+ uri: string;
100
+ primary_path: string;
101
+ created_at: string;
102
+ }
103
+
104
+ // Kernel→manager request to create an artifact. Manager constructs the
105
+ // ArtifactStore and writes the response back to kernel stdin.
106
+ export interface ArtifactCreateRequest {
107
+ type: 'request';
108
+ request: 'artifact_create';
109
+ id: string;
110
+ kind: string; // 'report'
111
+ name: string;
112
+ }
113
+ export interface ArtifactCreateResponseOk {
114
+ type: 'response';
115
+ request: 'artifact_create';
116
+ id: string;
117
+ ok: true;
118
+ slug: string;
119
+ uri: string;
120
+ primary_path: string;
121
+ }
122
+ export interface ArtifactCreateResponseErr {
123
+ type: 'response';
124
+ request: 'artifact_create';
125
+ id: string;
126
+ ok: false;
127
+ error: string;
128
+ }
129
+ export type ArtifactCreateResponse = ArtifactCreateResponseOk | ArtifactCreateResponseErr;
130
+
131
+ // Kernel→manager request to write files into an existing artifact dir.
132
+ export interface ArtifactUpdateRequest {
133
+ type: 'request';
134
+ request: 'artifact_update';
135
+ id: string;
136
+ slug: string;
137
+ files: Array<{ path: string; content: string }>;
138
+ }
139
+ export interface ArtifactUpdateResponseOk {
140
+ type: 'response';
141
+ request: 'artifact_update';
142
+ id: string;
143
+ ok: true;
144
+ files_touched: string[];
145
+ }
146
+ export interface ArtifactUpdateResponseErr {
147
+ type: 'response';
148
+ request: 'artifact_update';
149
+ id: string;
150
+ ok: false;
151
+ error: string;
152
+ }
153
+ export type ArtifactUpdateResponse = ArtifactUpdateResponseOk | ArtifactUpdateResponseErr;
154
+
155
+ // Event the kernel emits after a successful artifact_create RPC so the manager
156
+ // can record the drawer into memory (mirrors DataLoadEvent).
157
+ export interface ArtifactCreateEvent {
158
+ type: 'event';
159
+ event: 'artifact_create';
160
+ drawer: ArtifactCreateDrawer;
161
+ }
162
+
163
+ export type KernelRpcRequest = ArtifactCreateRequest | ArtifactUpdateRequest;
164
+ export type KernelRpcResponse = ArtifactCreateResponse | ArtifactUpdateResponse;
165
+
166
+ export type KernelEvent =
167
+ | ReadyEvent
168
+ | DataLoadEvent
169
+ | ProgressEvent
170
+ | StartupErrorEvent
171
+ | ArtifactCreateEvent;
172
+
173
+ // Phase 4 Task 10: widened to include KernelRpcRequest so the manager-side
174
+ // dispatch (child-process-runtime.ts readLoop) can narrow inbound artifact
175
+ // RPC requests alongside results/events/snapshot_results. The dispatcher must
176
+ // guard the ResultResponse branch with a discriminant check on `type === 'result'`
177
+ // before touching `frame.id` (which is `string` on RPC frames and `number`
178
+ // elsewhere).
179
+ export type KernelFrame = ResultResponse | KernelEvent | SnapshotResult | KernelRpcRequest;
180
+
181
+ export function isDataLoadEvent(frame: KernelFrame): frame is DataLoadEvent {
182
+ return frame.type === 'event' && frame.event === 'data_load';
183
+ }
184
+
185
+ export function isProgressEvent(frame: KernelFrame): frame is ProgressEvent {
186
+ return frame.type === 'event' && frame.event === 'progress';
187
+ }
188
+
189
+ export function isStartupErrorEvent(frame: KernelFrame): frame is StartupErrorEvent {
190
+ return frame.type === 'event' && frame.event === 'startup_error';
191
+ }
192
+
193
+ export function isSnapshotResult(frame: KernelFrame): frame is SnapshotResult {
194
+ return frame.type === 'snapshot_result';
195
+ }
196
+
197
+ export function isArtifactCreateEvent(frame: unknown): frame is ArtifactCreateEvent {
198
+ if (typeof frame !== 'object' || frame === null) return false;
199
+ const f = frame as { type?: unknown; event?: unknown };
200
+ return f.type === 'event' && f.event === 'artifact_create';
201
+ }
202
+
203
+ export function isArtifactCreateRequest(frame: unknown): frame is ArtifactCreateRequest {
204
+ if (typeof frame !== 'object' || frame === null) return false;
205
+ const f = frame as { type?: unknown; request?: unknown };
206
+ return f.type === 'request' && f.request === 'artifact_create';
207
+ }
208
+
209
+ export function isArtifactUpdateRequest(frame: unknown): frame is ArtifactUpdateRequest {
210
+ if (typeof frame !== 'object' || frame === null) return false;
211
+ const f = frame as { type?: unknown; request?: unknown };
212
+ return f.type === 'request' && f.request === 'artifact_update';
213
+ }
214
+
215
+ export function isArtifactCreateResponse(frame: unknown): frame is ArtifactCreateResponse {
216
+ if (typeof frame !== 'object' || frame === null) return false;
217
+ const f = frame as { type?: unknown; request?: unknown };
218
+ return f.type === 'response' && f.request === 'artifact_create';
219
+ }
220
+
221
+ export function isArtifactUpdateResponse(frame: unknown): frame is ArtifactUpdateResponse {
222
+ if (typeof frame !== 'object' || frame === null) return false;
223
+ const f = frame as { type?: unknown; request?: unknown };
224
+ return f.type === 'response' && f.request === 'artifact_update';
225
+ }
226
+
227
+ /** True for any `{type:'response'}` frame addressed to the kernel. */
228
+ export function isKernelRpcResponse(frame: unknown): frame is KernelRpcResponse {
229
+ return isArtifactCreateResponse(frame) || isArtifactUpdateResponse(frame);
230
+ }
@@ -0,0 +1,60 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { basename, isAbsolute } from 'node:path';
4
+ import { filterEnv, kernelExecArgv, resolveKernelEntry } from './kernel-spawn.js';
5
+
6
+ describe('filterEnv', () => {
7
+ it('keeps allowlisted vars, allow-prefixed vars, and strips everything else', () => {
8
+ const out = filterEnv({
9
+ PATH: '/usr/bin',
10
+ HOME: '/home/x',
11
+ LC_ALL: 'en_US.UTF-8',
12
+ OTTO_DS_servicenow_prod__token: 'injected',
13
+ NODE_OPTIONS: '--max-old-space-size=512',
14
+ RANDOM_THING: 'nope',
15
+ ANTHROPIC_API_KEY: 'secret',
16
+ });
17
+ assert.equal(out.PATH, '/usr/bin');
18
+ assert.equal(out.HOME, '/home/x');
19
+ assert.equal(out.LC_ALL, 'en_US.UTF-8');
20
+ assert.equal(out.OTTO_DS_servicenow_prod__token, 'injected');
21
+ assert.equal(out.NODE_OPTIONS, '--max-old-space-size=512');
22
+ assert.equal(out.RANDOM_THING, undefined);
23
+ assert.equal(out.ANTHROPIC_API_KEY, undefined);
24
+ });
25
+
26
+ it('strips denylisted API keys even though no allow-rule would admit them', () => {
27
+ const out = filterEnv({ OPENAI_API_KEY: 'x', LOOP24_GATEWAY_KEY: 'y' });
28
+ assert.equal(out.OPENAI_API_KEY, undefined);
29
+ assert.equal(out.LOOP24_GATEWAY_KEY, undefined);
30
+ });
31
+ });
32
+
33
+ describe('kernelExecArgv', () => {
34
+ it('forwards loader flags (with their values) and drops --test', () => {
35
+ const out = kernelExecArgv([
36
+ '--import',
37
+ './src/resources/extensions/workflow/tests/resolve-ts.mjs',
38
+ '--experimental-strip-types',
39
+ '--test',
40
+ ]);
41
+ assert.deepEqual(out, [
42
+ '--import',
43
+ './src/resources/extensions/workflow/tests/resolve-ts.mjs',
44
+ '--experimental-strip-types',
45
+ ]);
46
+ });
47
+
48
+ it('handles --flag=value form and returns empty for a bare --test', () => {
49
+ assert.deepEqual(kernelExecArgv(['--import=./x.mjs', '--test']), ['--import=./x.mjs']);
50
+ assert.deepEqual(kernelExecArgv(['--test']), []);
51
+ });
52
+ });
53
+
54
+ describe('resolveKernelEntry', () => {
55
+ it('returns an absolute path to a kernel-entry module', () => {
56
+ const entry = resolveKernelEntry();
57
+ assert.equal(isAbsolute(entry), true);
58
+ assert.ok(['kernel-entry.js', 'kernel-entry.ts'].includes(basename(entry)));
59
+ });
60
+ });
@@ -0,0 +1,54 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { fileURLToPath } from 'node:url';
3
+
4
+ const ENV_ALLOW = new Set([
5
+ 'PATH', 'HOME', 'TERM', 'SHELL', 'TMPDIR', 'TMP', 'TEMP',
6
+ 'LANG', 'LANGUAGE', 'PWD', 'USER', 'LOGNAME',
7
+ ]);
8
+ const ENV_ALLOW_PREFIXES = ['LC_', 'XDG_', 'OTTO_', 'NODE_'];
9
+ const ENV_DENY = new Set([
10
+ 'LOOP24_GATEWAY_KEY', 'ANTHROPIC_API_KEY', 'OPENAI_API_KEY',
11
+ 'AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'GITHUB_TOKEN',
12
+ ]);
13
+
14
+ export function filterEnv(source: NodeJS.ProcessEnv = process.env): NodeJS.ProcessEnv {
15
+ const out: NodeJS.ProcessEnv = {};
16
+ for (const [key, value] of Object.entries(source)) {
17
+ if (value === undefined) continue;
18
+ if (ENV_DENY.has(key)) continue; // denylist overrides any allow-rule
19
+ if (ENV_ALLOW.has(key) || ENV_ALLOW_PREFIXES.some((p) => key.startsWith(p))) {
20
+ out[key] = value;
21
+ }
22
+ }
23
+ return out;
24
+ }
25
+
26
+ const FORWARD_FLAGS_WITH_VALUE = new Set([
27
+ '--import', '--loader', '--experimental-loader', '--require', '-r', '--conditions',
28
+ ]);
29
+ const FORWARD_FLAGS_BOOLEAN = new Set([
30
+ '--experimental-strip-types', '--experimental-transform-types', '--no-warnings',
31
+ ]);
32
+
33
+ export function kernelExecArgv(execArgv: string[] = process.execArgv): string[] {
34
+ const out: string[] = [];
35
+ for (let i = 0; i < execArgv.length; i++) {
36
+ const arg = execArgv[i];
37
+ const eq = arg.indexOf('=');
38
+ const flag = eq >= 0 ? arg.slice(0, eq) : arg;
39
+ if (FORWARD_FLAGS_WITH_VALUE.has(flag)) {
40
+ out.push(arg);
41
+ if (eq < 0 && i + 1 < execArgv.length) out.push(execArgv[++i]);
42
+ } else if (FORWARD_FLAGS_BOOLEAN.has(flag)) {
43
+ out.push(arg);
44
+ }
45
+ // Everything else (--test, --watch, --test-*, …) is dropped.
46
+ }
47
+ return out;
48
+ }
49
+
50
+ export function resolveKernelEntry(): string {
51
+ const js = fileURLToPath(new URL('./kernel-entry.js', import.meta.url));
52
+ if (existsSync(js)) return js;
53
+ return fileURLToPath(new URL('./kernel-entry.ts', import.meta.url));
54
+ }
@@ -0,0 +1,102 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {
4
+ NAMESPACE_SCHEMA_VERSION,
5
+ encodeNamespace,
6
+ decodeNamespace,
7
+ } from './namespace-codec.js';
8
+
9
+ describe('namespace-codec', () => {
10
+ it('round-trips primitives and plain objects', () => {
11
+ const { envelope } = encodeNamespace({ x: 1, s: 'hi', o: { a: [1, 2, 3] } }, () => 0);
12
+ assert.equal(envelope.schema_version, NAMESPACE_SCHEMA_VERSION);
13
+ assert.equal(typeof envelope.snapshot_b64, 'string');
14
+ assert.deepEqual(envelope.skipped, []);
15
+ const { values, skipped } = decodeNamespace(JSON.stringify(envelope));
16
+ assert.equal(values.x, 1);
17
+ assert.equal(values.s, 'hi');
18
+ assert.deepEqual(values.o, { a: [1, 2, 3] });
19
+ assert.deepEqual(skipped, []);
20
+ });
21
+
22
+ it('round-trips Date / Map / Set / BigInt with type identity preserved', () => {
23
+ const m = new Map<string, number>([['a', 1], ['b', 2]]);
24
+ const s = new Set<number>([10, 20, 30]);
25
+ const d = new Date(1717180800000);
26
+ const big = 9007199254740993n; // > Number.MAX_SAFE_INTEGER
27
+ const { envelope } = encodeNamespace({ m, s, d, big }, () => 0);
28
+ const { values } = decodeNamespace(JSON.stringify(envelope));
29
+ assert.ok(values.m instanceof Map);
30
+ assert.equal((values.m as Map<string, number>).get('b'), 2);
31
+ assert.ok(values.s instanceof Set);
32
+ assert.equal((values.s as Set<number>).has(20), true);
33
+ assert.ok(values.d instanceof Date);
34
+ assert.equal((values.d as Date).getTime(), 1717180800000);
35
+ assert.equal(values.big, 9007199254740993n);
36
+ });
37
+
38
+ it('records non-serializable values in skipped[] without aborting', () => {
39
+ // A function is not v8-serializable.
40
+ const fn = (): number => 1;
41
+ const { envelope, skipped } = encodeNamespace({ ok: 1, badFn: fn }, () => 0);
42
+ assert.equal(skipped.length, 1);
43
+ assert.equal(skipped[0].key, 'badFn');
44
+ assert.equal(skipped[0].ctor, 'Function');
45
+ assert.match(skipped[0].reason, /./); // non-empty reason
46
+ assert.equal(envelope.skipped.length, 1);
47
+ assert.equal(envelope.skipped[0].key, 'badFn');
48
+ assert.equal(envelope.skipped[0].ctor, 'Function');
49
+ const { values } = decodeNamespace(JSON.stringify(envelope));
50
+ assert.equal(values.ok, 1);
51
+ assert.equal('badFn' in values, false);
52
+ });
53
+
54
+ it('encodes ts from the injected clock', () => {
55
+ const { envelope } = encodeNamespace({ x: 1 }, () => 1717180800000);
56
+ assert.equal(envelope.ts, new Date(1717180800000).toISOString());
57
+ });
58
+
59
+ it('decodeNamespace throws on a wrong schema_version', () => {
60
+ const bad = JSON.stringify({ schema_version: 99, snapshot_b64: 'AAAA', skipped: [], ts: '...' });
61
+ assert.throws(() => decodeNamespace(bad), /schema_version/);
62
+ });
63
+
64
+ it('decodeNamespace throws on a malformed envelope (not JSON)', () => {
65
+ assert.throws(() => decodeNamespace('{not json'), /./);
66
+ });
67
+
68
+ it('decodeNamespace throws on a base64 payload that is not a valid v8 buffer', () => {
69
+ const bad = JSON.stringify({
70
+ schema_version: NAMESPACE_SCHEMA_VERSION,
71
+ snapshot_b64: Buffer.from('not-a-v8-buffer').toString('base64'),
72
+ skipped: [],
73
+ ts: '2026-05-31T00:00:00.000Z',
74
+ });
75
+ assert.throws(() => decodeNamespace(bad), /./);
76
+ });
77
+
78
+ it('demotes survivors to skipped[] when bulk serialize throws (defensive — never throws)', () => {
79
+ // A getter that throws only on its second invocation: passes the per-key probe,
80
+ // explodes on the final bulk serialize. The codec must demote all survivors and
81
+ // return cleanly rather than letting the exception propagate.
82
+ const evilObj: Record<string, unknown> = {};
83
+ let calls = 0;
84
+ Object.defineProperty(evilObj, 'x', {
85
+ enumerable: true,
86
+ configurable: true,
87
+ get() {
88
+ calls++;
89
+ if (calls > 1) throw new Error('second-call boom');
90
+ return 1;
91
+ },
92
+ });
93
+ const result = encodeNamespace({ evil: evilObj, plain: 42 }, () => 0);
94
+ assert.ok(result.skipped.length >= 1);
95
+ assert.ok(result.skipped.some((s) => s.reason.startsWith('bulk-serialize-failed')));
96
+ // The envelope is still well-formed — empty snapshot, well-typed.
97
+ assert.equal(typeof result.envelope.snapshot_b64, 'string');
98
+ const { values } = decodeNamespace(JSON.stringify(result.envelope));
99
+ assert.equal('evil' in values, false);
100
+ assert.equal('plain' in values, false); // demoted alongside in the bulk fallback
101
+ });
102
+ });
@@ -0,0 +1,90 @@
1
+ import { serialize, deserialize } from 'node:v8';
2
+
3
+ export const NAMESPACE_SCHEMA_VERSION = 1;
4
+
5
+ export interface SkippedKey {
6
+ key: string;
7
+ ctor: string | null;
8
+ reason: string;
9
+ }
10
+
11
+ export interface NamespaceEnvelope {
12
+ schema_version: number;
13
+ snapshot_b64: string;
14
+ skipped: SkippedKey[];
15
+ ts: string;
16
+ }
17
+
18
+ export interface EncodeResult {
19
+ envelope: NamespaceEnvelope;
20
+ skipped: SkippedKey[];
21
+ }
22
+
23
+ export interface DecodeResult {
24
+ values: Record<string, unknown>;
25
+ skipped: SkippedKey[];
26
+ }
27
+
28
+ function ctorName(value: unknown): string | null {
29
+ if (value === null || value === undefined) return null;
30
+ const c = (value as { constructor?: { name?: string } }).constructor;
31
+ return c?.name ?? null;
32
+ }
33
+
34
+ export function encodeNamespace(
35
+ values: Record<string, unknown>,
36
+ now: () => number,
37
+ ): EncodeResult {
38
+ const survivors: Record<string, unknown> = {};
39
+ const skipped: SkippedKey[] = [];
40
+ for (const key of Object.keys(values)) {
41
+ const value = values[key];
42
+ try {
43
+ // Probe by serializing a single-key wrapper. Cheaper than per-key buffers
44
+ // because the survivors map is re-serialized once below.
45
+ serialize({ [key]: value });
46
+ survivors[key] = value;
47
+ } catch (err) {
48
+ skipped.push({
49
+ key,
50
+ ctor: ctorName(value),
51
+ reason: (err as Error).message,
52
+ });
53
+ }
54
+ }
55
+ let snapshot_b64: string;
56
+ try {
57
+ snapshot_b64 = serialize(survivors).toString('base64');
58
+ } catch (err) {
59
+ // A value passed the per-key probe but threw on bulk serialize (e.g. a getter
60
+ // that throws on its second invocation). Demote every survivor to skipped so
61
+ // the caller still gets a usable envelope; the snapshot for this round is empty.
62
+ const reason = `bulk-serialize-failed: ${(err as Error).message}`;
63
+ for (const key of Object.keys(survivors)) {
64
+ skipped.push({ key, ctor: ctorName(survivors[key]), reason });
65
+ }
66
+ snapshot_b64 = serialize({}).toString('base64');
67
+ }
68
+ const envelope: NamespaceEnvelope = {
69
+ schema_version: NAMESPACE_SCHEMA_VERSION,
70
+ snapshot_b64,
71
+ skipped,
72
+ ts: new Date(now()).toISOString(),
73
+ };
74
+ return { envelope, skipped };
75
+ }
76
+
77
+ export function decodeNamespace(json: string): DecodeResult {
78
+ const parsed = JSON.parse(json) as Partial<NamespaceEnvelope>;
79
+ if (parsed.schema_version !== NAMESPACE_SCHEMA_VERSION) {
80
+ throw new Error(
81
+ `namespace-codec: unsupported schema_version ${String(parsed.schema_version)} (expected ${NAMESPACE_SCHEMA_VERSION})`,
82
+ );
83
+ }
84
+ if (typeof parsed.snapshot_b64 !== 'string') {
85
+ throw new Error('namespace-codec: missing snapshot_b64');
86
+ }
87
+ const buf = Buffer.from(parsed.snapshot_b64, 'base64');
88
+ const values = deserialize(buf) as Record<string, unknown>;
89
+ return { values, skipped: Array.isArray(parsed.skipped) ? parsed.skipped : [] };
90
+ }
@@ -0,0 +1,98 @@
1
+ import { describe, it, beforeEach, afterEach } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { mkdtemp, rm, writeFile, mkdir } from 'node:fs/promises';
4
+ import { existsSync, readFileSync } from 'node:fs';
5
+ import { tmpdir, hostname } from 'node:os';
6
+ import { join } from 'node:path';
7
+ import process from 'node:process';
8
+ import { spawn } from 'node:child_process';
9
+ import {
10
+ acquireLock,
11
+ releaseLock,
12
+ readLock,
13
+ isStaleLock,
14
+ ScratchpadBusyError,
15
+ } from './scratchpad-lock.js';
16
+ import type { LockInfo } from './scratchpad-lock.js';
17
+
18
+ let root: string;
19
+ let dir: string;
20
+
21
+ // A real, definitely-dead PID: spawn a node that exits immediately, await its exit, reuse its PID.
22
+ async function deadPid(): Promise<number> {
23
+ const c = spawn(process.execPath, ['-e', '']);
24
+ const pid = c.pid as number;
25
+ await new Promise<void>((r) => c.on('exit', () => r()));
26
+ return pid;
27
+ }
28
+
29
+ describe('scratchpad-lock', () => {
30
+ beforeEach(async () => {
31
+ root = await mkdtemp(join(tmpdir(), 'sp-lock-'));
32
+ dir = join(root, 'p1');
33
+ });
34
+
35
+ afterEach(async () => {
36
+ await rm(root, { recursive: true, force: true });
37
+ });
38
+
39
+ it('acquires a fresh lock holding this pid + host', () => {
40
+ const lock = acquireLock(dir);
41
+ assert.equal(lock.pid, process.pid);
42
+ assert.equal(lock.host, hostname());
43
+ assert.ok(existsSync(join(dir, 'lock.json')));
44
+ });
45
+
46
+ it('throws ScratchpadBusyError when a live holder already owns the lock', () => {
47
+ acquireLock(dir);
48
+ assert.throws(() => acquireLock(dir), (err: unknown) => {
49
+ assert.ok(err instanceof ScratchpadBusyError);
50
+ assert.match((err as Error).message, /scratchpad p1 is busy in another session/);
51
+ return true;
52
+ });
53
+ });
54
+
55
+ it('clears a stale lock (dead holder pid) and re-acquires', async () => {
56
+ await mkdir(dir, { recursive: true });
57
+ const stale: LockInfo = { pid: await deadPid(), host: hostname(), acquired_at: '2026-01-01T00:00:00.000Z' };
58
+ await writeFile(join(dir, 'lock.json'), JSON.stringify(stale));
59
+ const lock = acquireLock(dir);
60
+ assert.equal(lock.pid, process.pid);
61
+ assert.equal(JSON.parse(readFileSync(join(dir, 'lock.json'), 'utf8')).pid, process.pid);
62
+ });
63
+
64
+ it('force-takeover overwrites a live lock and records takeover_from', () => {
65
+ const prior = acquireLock(dir);
66
+ const taken = acquireLock(dir, { forceTakeover: true, takeoverReason: 'unit-test' });
67
+ assert.equal(taken.pid, process.pid);
68
+ assert.ok(taken.takeover_from);
69
+ assert.equal(taken.takeover_from!.pid, prior.pid);
70
+ assert.equal(taken.takeover_from!.reason, 'unit-test');
71
+ });
72
+
73
+ it('releaseLock removes our own lock so it can be re-acquired', () => {
74
+ acquireLock(dir);
75
+ releaseLock(dir);
76
+ assert.equal(existsSync(join(dir, 'lock.json')), false);
77
+ assert.doesNotThrow(() => acquireLock(dir));
78
+ });
79
+
80
+ it('releaseLock leaves a lock owned by another holder', async () => {
81
+ await mkdir(dir, { recursive: true });
82
+ const other: LockInfo = { pid: await deadPid(), host: hostname(), acquired_at: '2026-01-01T00:00:00.000Z' };
83
+ await writeFile(join(dir, 'lock.json'), JSON.stringify(other));
84
+ releaseLock(dir);
85
+ assert.ok(existsSync(join(dir, 'lock.json'))); // not ours -> untouched
86
+ });
87
+
88
+ it('isStaleLock is true for a dead holder and false for a live one', async () => {
89
+ const dead: LockInfo = { pid: await deadPid(), host: hostname(), acquired_at: '2026-01-01T00:00:00.000Z' };
90
+ const live: LockInfo = { pid: process.pid, host: hostname(), acquired_at: '2026-01-01T00:00:00.000Z' };
91
+ assert.equal(isStaleLock(dead), true);
92
+ assert.equal(isStaleLock(live), false);
93
+ });
94
+
95
+ it('readLock returns null when no lock exists', () => {
96
+ assert.equal(readLock(dir), null);
97
+ });
98
+ });