@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,188 @@
1
+ // packages/coworker-artifacts/src/artifact-store.test.ts
2
+ import { describe, it } from 'node:test';
3
+ import assert from 'node:assert/strict';
4
+ import { mkdtempSync, existsSync, readFileSync, readdirSync, statSync, writeFileSync, mkdirSync } from 'node:fs';
5
+ import { tmpdir } from 'node:os';
6
+ import { join } from 'node:path';
7
+ import { ArtifactStore } from './artifact-store.js';
8
+ import { ArtifactKindRejected, ArtifactNotFound, ArtifactSlugCollision } from './errors.js';
9
+
10
+ function tmpWs(): string {
11
+ return mkdtempSync(join(tmpdir(), 'art-store-'));
12
+ }
13
+
14
+ function fixedNow(): () => string {
15
+ let n = Date.parse('2026-06-02T14:00:00Z');
16
+ return () => {
17
+ const v = new Date(n).toISOString();
18
+ n += 60_000;
19
+ return v;
20
+ };
21
+ }
22
+
23
+ describe('ArtifactStore.create', () => {
24
+ it('creates dir + metadata + empty primary + initial provenance + README', async () => {
25
+ const ws = tmpWs();
26
+ const store = new ArtifactStore({ workspaceDir: ws, now: fixedNow() });
27
+ const h = await store.create('report', 'RCA: load balancer 503');
28
+ assert.equal(h.slug, 'rca-load-balancer-503');
29
+ assert.equal(h.kind, 'report');
30
+ assert.equal(h.uri, 'artifact://rca-load-balancer-503');
31
+ assert.ok(existsSync(h.dir));
32
+ assert.ok(existsSync(h.primaryPath));
33
+ assert.ok(existsSync(h.metadataPath));
34
+ assert.ok(existsSync(h.provenancePath));
35
+ assert.ok(existsSync(h.readmePath));
36
+ const meta = JSON.parse(readFileSync(h.metadataPath, 'utf8'));
37
+ assert.equal(meta._schema, 1);
38
+ assert.equal(meta.slug, 'rca-load-balancer-503');
39
+ assert.equal(meta.kind, 'report');
40
+ assert.equal(meta.primary_file, 'report.md');
41
+ assert.equal(meta.turn_count, 0);
42
+ });
43
+ it('rejects non-report kind', async () => {
44
+ const store = new ArtifactStore({ workspaceDir: tmpWs() });
45
+ await assert.rejects(() => store.create('workbook' as never, 'x'), ArtifactKindRejected);
46
+ });
47
+ it('suffixes slug on collision', async () => {
48
+ const ws = tmpWs();
49
+ const store = new ArtifactStore({ workspaceDir: ws, now: fixedNow() });
50
+ const a = await store.create('report', 'RCA');
51
+ const b = await store.create('report', 'RCA');
52
+ const c = await store.create('report', 'RCA');
53
+ assert.equal(a.slug, 'rca');
54
+ assert.equal(b.slug, 'rca-2');
55
+ assert.equal(c.slug, 'rca-3');
56
+ });
57
+ it('throws ArtifactSlugCollision after exhausting suffixes', async () => {
58
+ const ws = tmpWs();
59
+ mkdirSync(join(ws, '.otto', 'artifacts'), { recursive: true });
60
+ mkdirSync(join(ws, '.otto', 'artifacts', 'rca'));
61
+ for (let n = 2; n <= 101; n++) mkdirSync(join(ws, '.otto', 'artifacts', `rca-${n}`));
62
+ const store = new ArtifactStore({ workspaceDir: ws, now: fixedNow() });
63
+ await assert.rejects(() => store.create('report', 'RCA'), ArtifactSlugCollision);
64
+ });
65
+ it('writes files at mode 0o600 and dir at 0o700', async () => {
66
+ const ws = tmpWs();
67
+ const store = new ArtifactStore({ workspaceDir: ws, now: fixedNow() });
68
+ const h = await store.create('report', 'sec');
69
+ const dirStat = statSync(h.dir);
70
+ const fileStat = statSync(h.metadataPath);
71
+ assert.equal((dirStat.mode & 0o777), 0o700);
72
+ assert.equal((fileStat.mode & 0o777), 0o600);
73
+ });
74
+ });
75
+
76
+ describe('ArtifactStore.update', () => {
77
+ it('writes files atomically; returns files_touched', async () => {
78
+ const ws = tmpWs();
79
+ const store = new ArtifactStore({ workspaceDir: ws, now: fixedNow() });
80
+ const h = await store.create('report', 'r');
81
+ const out = await store.update(h, [{ path: 'report.md', content: '# hi\n' }]);
82
+ assert.deepEqual(out.files_touched.sort(), ['report.md']);
83
+ assert.equal(readFileSync(h.primaryPath, 'utf8'), '# hi\n');
84
+ });
85
+ it('rejects FileWrite path with .. or /', async () => {
86
+ const ws = tmpWs();
87
+ const store = new ArtifactStore({ workspaceDir: ws, now: fixedNow() });
88
+ const h = await store.create('report', 'r');
89
+ await assert.rejects(() => store.update(h, [{ path: '../escape', content: 'x' }]));
90
+ await assert.rejects(() => store.update(h, [{ path: '/abs', content: 'x' }]));
91
+ });
92
+ it('detects added + modified files via DirSnapshot diff', async () => {
93
+ const ws = tmpWs();
94
+ const store = new ArtifactStore({ workspaceDir: ws, now: fixedNow() });
95
+ const h = await store.create('report', 'r');
96
+ await store.update(h, [{ path: 'report.md', content: '# v1\n' }]);
97
+ await new Promise(r => setTimeout(r, 20));
98
+ const out = await store.update(h, [
99
+ { path: 'report.md', content: '# v2\n' },
100
+ { path: 'appendix.md', content: '## A\n' },
101
+ ]);
102
+ assert.deepEqual(out.files_touched.sort(), ['appendix.md', 'report.md']);
103
+ });
104
+ it('rejects empty FileWrite path', async () => {
105
+ const ws = tmpWs();
106
+ const store = new ArtifactStore({ workspaceDir: ws, now: fixedNow() });
107
+ const h = await store.create('report', 'r');
108
+ await assert.rejects(() => store.update(h, [{ path: '', content: 'x' }]));
109
+ });
110
+ it('bumps last_updated_at + turn_count when paired with recordTurn', async () => {
111
+ const ws = tmpWs();
112
+ const store = new ArtifactStore({ workspaceDir: ws, now: fixedNow() });
113
+ const h = await store.create('report', 'r');
114
+ const meta1 = JSON.parse(readFileSync(h.metadataPath, 'utf8'));
115
+ await store.recordTurn(h, {
116
+ action: 'create', turn_id: 't1', user_prompt: 'p1', files_touched: [],
117
+ });
118
+ const meta2 = JSON.parse(readFileSync(h.metadataPath, 'utf8'));
119
+ assert.equal(meta2.turn_count, 1);
120
+ assert.notEqual(meta1.last_updated_at, meta2.last_updated_at);
121
+ });
122
+ });
123
+
124
+ describe('ArtifactStore.recordTurn', () => {
125
+ it('appends to provenance.json', async () => {
126
+ const ws = tmpWs();
127
+ const store = new ArtifactStore({ workspaceDir: ws, now: fixedNow() });
128
+ const h = await store.create('report', 'r');
129
+ await store.recordTurn(h, {
130
+ action: 'create', turn_id: 't1', user_prompt: 'p1', files_touched: [],
131
+ });
132
+ await store.recordTurn(h, {
133
+ action: 'update', turn_id: 't2', user_prompt: 'p2', files_touched: ['report.md'],
134
+ });
135
+ const prov = JSON.parse(readFileSync(h.provenancePath, 'utf8'));
136
+ assert.equal(prov.length, 2);
137
+ assert.equal(prov[0].turn_id, 't1');
138
+ assert.equal(prov[1].turn_id, 't2');
139
+ });
140
+ });
141
+
142
+ describe('ArtifactStore.list + get + remove', () => {
143
+ it('list returns all artifact handles, sorted by created_at desc', async () => {
144
+ const ws = tmpWs();
145
+ const store = new ArtifactStore({ workspaceDir: ws, now: fixedNow() });
146
+ const a = await store.create('report', 'a');
147
+ const b = await store.create('report', 'b');
148
+ const list = await store.list();
149
+ assert.equal(list.length, 2);
150
+ // fixedNow increments — b is created after a, so b should be first
151
+ assert.equal(list[0]!.slug, 'b');
152
+ assert.equal(list[1]!.slug, 'a');
153
+ });
154
+ it('get returns handle or null', async () => {
155
+ const ws = tmpWs();
156
+ const store = new ArtifactStore({ workspaceDir: ws, now: fixedNow() });
157
+ const h = await store.create('report', 'r');
158
+ assert.equal((await store.get('r'))!.slug, 'r');
159
+ assert.equal(await store.get('missing'), null);
160
+ });
161
+ it('remove deletes directory; throws ArtifactNotFound if missing', async () => {
162
+ const ws = tmpWs();
163
+ const store = new ArtifactStore({ workspaceDir: ws, now: fixedNow() });
164
+ const h = await store.create('report', 'r');
165
+ await store.remove('r', true);
166
+ assert.equal(existsSync(h.dir), false);
167
+ await assert.rejects(() => store.remove('r', true), ArtifactNotFound);
168
+ });
169
+ it('remove rejects when confirm is not true', async () => {
170
+ const store = new ArtifactStore({ workspaceDir: tmpWs() });
171
+ await assert.rejects(() => store.remove('x', false as never));
172
+ });
173
+ it('remove rejects empty slug', async () => {
174
+ const ws = tmpWs();
175
+ const store = new ArtifactStore({ workspaceDir: ws, now: fixedNow() });
176
+ await assert.rejects(() => store.remove('', true), ArtifactNotFound);
177
+ });
178
+ it('remove rejects slug with path separator', async () => {
179
+ const ws = tmpWs();
180
+ const store = new ArtifactStore({ workspaceDir: ws, now: fixedNow() });
181
+ await assert.rejects(() => store.remove('foo/bar', true), ArtifactNotFound);
182
+ });
183
+ it('remove rejects slug containing ..', async () => {
184
+ const ws = tmpWs();
185
+ const store = new ArtifactStore({ workspaceDir: ws, now: fixedNow() });
186
+ await assert.rejects(() => store.remove('..', true), ArtifactNotFound);
187
+ });
188
+ });
@@ -0,0 +1,206 @@
1
+ // packages/coworker-artifacts/src/artifact-store.ts
2
+ import {
3
+ chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync,
4
+ rmSync, writeFileSync,
5
+ } from 'node:fs';
6
+ import { join, normalize } from 'node:path';
7
+ import type {
8
+ ArtifactHandle, ArtifactKind, ArtifactMetadata, FileWrite,
9
+ Provenance, TurnEntry,
10
+ } from './types.js';
11
+ import { ARTIFACT_KINDS } from './types.js';
12
+ import {
13
+ ArtifactKindRejected, ArtifactNotFound, ArtifactSlugCollision,
14
+ } from './errors.js';
15
+ import { deriveSlug, nextCollisionSlug } from './slug.js';
16
+ import { takeSnapshot, diffSnapshots } from './dir-snapshot.js';
17
+ import { renderReadme } from './readme-renderer.js';
18
+
19
+ export interface ArtifactStoreOptions {
20
+ workspaceDir: string;
21
+ now?: () => string;
22
+ }
23
+
24
+ const ARTIFACTS_DIR_NAME = '.otto/artifacts';
25
+ const PRIMARY_FILE = 'report.md';
26
+
27
+ export class ArtifactStore {
28
+ private readonly workspaceDir: string;
29
+ private readonly now: () => string;
30
+
31
+ constructor(opts: ArtifactStoreOptions) {
32
+ this.workspaceDir = opts.workspaceDir;
33
+ this.now = opts.now ?? (() => new Date().toISOString());
34
+ }
35
+
36
+ private rootDir(): string {
37
+ return join(this.workspaceDir, ARTIFACTS_DIR_NAME);
38
+ }
39
+
40
+ private existingSlugs(): Set<string> {
41
+ const root = this.rootDir();
42
+ if (!existsSync(root)) return new Set();
43
+ return new Set(readdirSync(root, { withFileTypes: true })
44
+ .filter(e => e.isDirectory())
45
+ .map(e => e.name));
46
+ }
47
+
48
+ private handleFor(slug: string, kind: ArtifactKind, name: string): ArtifactHandle {
49
+ const dir = join(this.rootDir(), slug);
50
+ return {
51
+ slug, kind, name, dir,
52
+ uri: `artifact://${slug}`,
53
+ primaryPath: join(dir, PRIMARY_FILE),
54
+ metadataPath: join(dir, 'metadata.json'),
55
+ provenancePath: join(dir, 'provenance.json'),
56
+ readmePath: join(dir, 'README.md'),
57
+ };
58
+ }
59
+
60
+ private atomicWrite(path: string, content: string, mode = 0o600): void {
61
+ const tmp = `${path}.tmp`;
62
+ writeFileSync(tmp, content, { mode });
63
+ chmodSync(tmp, mode);
64
+ renameSync(tmp, path);
65
+ }
66
+
67
+ private readMetadata(path: string): ArtifactMetadata {
68
+ return JSON.parse(readFileSync(path, 'utf8')) as ArtifactMetadata;
69
+ }
70
+
71
+ private readProvenance(path: string): Provenance {
72
+ if (!existsSync(path)) return [];
73
+ try {
74
+ return JSON.parse(readFileSync(path, 'utf8')) as Provenance;
75
+ } catch {
76
+ return [];
77
+ }
78
+ }
79
+
80
+ private fileStats(dir: string): Array<{ path: string; sizeBytes: number }> {
81
+ const snap = takeSnapshot(dir);
82
+ return [...snap.entries()]
83
+ .filter(([p]) => p !== 'metadata.json' && p !== 'provenance.json' && p !== 'README.md')
84
+ .map(([path, { sizeBytes }]) => ({ path, sizeBytes }));
85
+ }
86
+
87
+ async create(kind: ArtifactKind, name: string): Promise<ArtifactHandle> {
88
+ if (!ARTIFACT_KINDS.includes(kind)) throw new ArtifactKindRejected(kind);
89
+ mkdirSync(this.rootDir(), { recursive: true, mode: 0o700 });
90
+ const base = deriveSlug(name);
91
+ const slug = nextCollisionSlug(base, this.existingSlugs());
92
+ const handle = this.handleFor(slug, kind, name);
93
+ // Use mkdirSync (non-recursive) on the artifact dir for race detection,
94
+ // but the parent is created above; if this throws EEXIST, retry with bumped slug.
95
+ try { mkdirSync(handle.dir, { mode: 0o700 }); }
96
+ catch (err) {
97
+ if ((err as NodeJS.ErrnoException).code === 'EEXIST') {
98
+ // Race — recurse with refreshed slug set
99
+ return this.create(kind, name);
100
+ }
101
+ throw err;
102
+ }
103
+ const ts = this.now();
104
+ const meta: ArtifactMetadata = {
105
+ _schema: 1, slug, kind, name,
106
+ created_at: ts, last_updated_at: ts,
107
+ turn_count: 0, primary_file: PRIMARY_FILE,
108
+ uri: handle.uri,
109
+ };
110
+ this.atomicWrite(handle.metadataPath, JSON.stringify(meta, null, 2));
111
+ this.atomicWrite(handle.primaryPath, '');
112
+ this.atomicWrite(handle.provenancePath, '[]');
113
+ this.atomicWrite(handle.readmePath, renderReadme(meta, [], this.fileStats(handle.dir)));
114
+ return handle;
115
+ }
116
+
117
+ async update(handle: ArtifactHandle, files: FileWrite[]): Promise<{ files_touched: string[] }> {
118
+ if (!existsSync(handle.dir)) throw new ArtifactNotFound(handle.slug);
119
+ for (const f of files) {
120
+ const normalized = normalize(f.path);
121
+ if (!f.path || !normalized || normalized === '.' ||
122
+ normalized.startsWith('..') || normalized.startsWith('/') ||
123
+ normalized.includes('\0')) {
124
+ throw new Error(`Bad FileWrite path: ${f.path}`);
125
+ }
126
+ }
127
+ const before = takeSnapshot(handle.dir);
128
+ for (const f of files) {
129
+ const abs = join(handle.dir, f.path);
130
+ mkdirSync(join(abs, '..'), { recursive: true, mode: 0o700 });
131
+ this.atomicWrite(abs, f.content);
132
+ }
133
+ const after = takeSnapshot(handle.dir);
134
+ const diff = diffSnapshots(before, after);
135
+ const filesTouched = [...new Set([...diff.added, ...diff.modified])]
136
+ .filter(p => p !== 'metadata.json' && p !== 'provenance.json' && p !== 'README.md')
137
+ .sort();
138
+ // Bump metadata (last_updated_at; turn_count incremented by recordTurn)
139
+ const meta = this.readMetadata(handle.metadataPath);
140
+ meta.last_updated_at = this.now();
141
+ this.atomicWrite(handle.metadataPath, JSON.stringify(meta, null, 2));
142
+ const prov = this.readProvenance(handle.provenancePath);
143
+ this.atomicWrite(handle.readmePath, renderReadme(meta, prov, this.fileStats(handle.dir)));
144
+ return { files_touched: filesTouched };
145
+ }
146
+
147
+ async recordTurn(
148
+ handle: ArtifactHandle,
149
+ entry: Omit<TurnEntry, '_schema' | 'ts'> & Partial<Pick<TurnEntry, 'ts'>>,
150
+ ): Promise<void> {
151
+ if (!existsSync(handle.dir)) throw new ArtifactNotFound(handle.slug);
152
+ const prov = this.readProvenance(handle.provenancePath);
153
+ const ts = entry.ts ?? this.now();
154
+ const fullEntry: TurnEntry = {
155
+ _schema: 1, ts,
156
+ action: entry.action,
157
+ turn_id: entry.turn_id,
158
+ user_prompt: entry.user_prompt,
159
+ files_touched: entry.files_touched,
160
+ ...(entry.agent_turn_id !== undefined ? { agent_turn_id: entry.agent_turn_id } : {}),
161
+ ...(entry.scratchpad_name !== undefined ? { scratchpad_name: entry.scratchpad_name } : {}),
162
+ };
163
+ prov.push(fullEntry);
164
+ this.atomicWrite(handle.provenancePath, JSON.stringify(prov, null, 2));
165
+ // Bump metadata
166
+ const meta = this.readMetadata(handle.metadataPath);
167
+ meta.turn_count = prov.length;
168
+ meta.last_updated_at = ts;
169
+ this.atomicWrite(handle.metadataPath, JSON.stringify(meta, null, 2));
170
+ this.atomicWrite(handle.readmePath, renderReadme(meta, prov, this.fileStats(handle.dir)));
171
+ }
172
+
173
+ async list(): Promise<ArtifactHandle[]> {
174
+ const root = this.rootDir();
175
+ if (!existsSync(root)) return [];
176
+ const handles: Array<{ handle: ArtifactHandle; created_at: string }> = [];
177
+ for (const slug of this.existingSlugs()) {
178
+ const metaPath = join(root, slug, 'metadata.json');
179
+ if (!existsSync(metaPath)) continue;
180
+ try {
181
+ const meta = this.readMetadata(metaPath);
182
+ const h = this.handleFor(meta.slug, meta.kind, meta.name);
183
+ handles.push({ handle: h, created_at: meta.created_at });
184
+ } catch { /* skip malformed */ }
185
+ }
186
+ handles.sort((a, b) => b.created_at.localeCompare(a.created_at));
187
+ return handles.map(x => x.handle);
188
+ }
189
+
190
+ async get(slug: string): Promise<ArtifactHandle | null> {
191
+ const metaPath = join(this.rootDir(), slug, 'metadata.json');
192
+ if (!existsSync(metaPath)) return null;
193
+ const meta = this.readMetadata(metaPath);
194
+ return this.handleFor(meta.slug, meta.kind, meta.name);
195
+ }
196
+
197
+ async remove(slug: string, confirm: true): Promise<void> {
198
+ if (confirm !== true) throw new Error(`/artifacts remove requires --confirm`);
199
+ if (!slug || slug.includes('/') || slug.includes('..') || slug === '.') {
200
+ throw new ArtifactNotFound(slug);
201
+ }
202
+ const dir = join(this.rootDir(), slug);
203
+ if (!existsSync(dir)) throw new ArtifactNotFound(slug);
204
+ rmSync(dir, { recursive: true, force: true });
205
+ }
206
+ }
@@ -0,0 +1,109 @@
1
+ // packages/coworker-artifacts/src/artifacts-integration.test.ts
2
+ import { describe, it } from 'node:test';
3
+ import assert from 'node:assert/strict';
4
+ import { mkdtempSync, existsSync, readFileSync } from 'node:fs';
5
+ import { tmpdir } from 'node:os';
6
+ import { join } from 'node:path';
7
+
8
+ import coworkerVaultExtension from '../../../src/resources/extensions/coworker-vault/index.js';
9
+ import coworkerMemoryExtension, { getMemoryRecorder, createMemoryBundle } from '../../../src/resources/extensions/coworker-memory/index.js';
10
+ import coworkerArtifactsExtension, { getArtifactStore } from '../../../src/resources/extensions/coworker-artifacts/index.js';
11
+ import coworkerScratchpadExtension from '../../../src/resources/extensions/coworker-scratchpad/index.js';
12
+ import { makeFakeApi, fireSessionStart, fireSessionShutdown } from '../../../src/resources/extensions/coworker-vault/test-helpers.js';
13
+
14
+ describe('Phase 4 — cross-extension integration', () => {
15
+ it('artifact created by store surfaces as kind:artifact drawer in memory and persists on disk', async () => {
16
+ const global = mkdtempSync(join(tmpdir(), 'p4-g-'));
17
+ const ws = mkdtempSync(join(tmpdir(), 'p4-w-'));
18
+ const sp = mkdtempSync(join(tmpdir(), 'p4-sp-'));
19
+ process.env.OTTO_COWORKER_GLOBAL_DIR = global;
20
+ process.env.OTTO_SCRATCHPAD_ROOT = sp;
21
+ try {
22
+ const vaultApi = makeFakeApi();
23
+ const memApi = makeFakeApi();
24
+ const artApi = makeFakeApi();
25
+ const spApi = makeFakeApi();
26
+ coworkerVaultExtension(vaultApi.api);
27
+ coworkerMemoryExtension(memApi.api);
28
+ coworkerArtifactsExtension(artApi.api);
29
+ coworkerScratchpadExtension(spApi.api);
30
+
31
+ await fireSessionStart(vaultApi, { cwd: ws });
32
+ await fireSessionStart(memApi, { cwd: ws });
33
+ await fireSessionStart(artApi, { cwd: ws });
34
+ await fireSessionStart(spApi, { cwd: ws });
35
+
36
+ const store = getArtifactStore();
37
+ const recorder = getMemoryRecorder();
38
+ assert.ok(store);
39
+ assert.ok(recorder);
40
+
41
+ // Simulate kernel→manager flow: store.create + recorder.recordArtifact
42
+ const handle = await store.create('report', 'RCA: load balancer 503');
43
+ await store.update(handle, [{ path: 'report.md', content: '# RCA\n\nbody\n' }]);
44
+ await recorder.recordArtifact({
45
+ scratchpadName: 'p1-incident',
46
+ slug: handle.slug,
47
+ kind: handle.kind,
48
+ uri: handle.uri,
49
+ turnId: 'turn-abc',
50
+ });
51
+
52
+ // Disk verification
53
+ assert.ok(existsSync(handle.dir));
54
+ assert.match(readFileSync(handle.primaryPath, 'utf8'), /# RCA/);
55
+ assert.match(readFileSync(handle.metadataPath, 'utf8'), /"slug": "rca-load-balancer-503"/);
56
+
57
+ // Memory recall via peek bundle
58
+ const peek = await createMemoryBundle({
59
+ globalDir: global, workspaceDir: ws,
60
+ scopeMode: 'per-project-tagged',
61
+ currentScratchpadName: () => null,
62
+ });
63
+ try {
64
+ const r = await peek.backend.recall({ query: 'rca-load-balancer-503', kind: 'artifact' });
65
+ assert.equal(r.length, 1);
66
+ const parsed = JSON.parse(r[0]!.drawer.content);
67
+ assert.equal(parsed.slug, 'rca-load-balancer-503');
68
+ assert.equal(parsed.uri, 'artifact://rca-load-balancer-503');
69
+ assert.equal(r[0]!.drawer.room, 'p1-incident');
70
+ } finally { await peek.dispose(); }
71
+
72
+ await fireSessionShutdown(spApi);
73
+ await fireSessionShutdown(artApi);
74
+ await fireSessionShutdown(memApi);
75
+ await fireSessionShutdown(vaultApi);
76
+ } finally {
77
+ delete process.env.OTTO_COWORKER_GLOBAL_DIR;
78
+ delete process.env.OTTO_SCRATCHPAD_ROOT;
79
+ }
80
+ });
81
+
82
+ it('artifact init failure does not break memory or vault', async () => {
83
+ // Skip artifact activation; vault + memory + scratchpad activate normally
84
+ const global = mkdtempSync(join(tmpdir(), 'p4-mix-'));
85
+ const ws = mkdtempSync(join(tmpdir(), 'p4-mix-ws-'));
86
+ const sp = mkdtempSync(join(tmpdir(), 'p4-mix-sp-'));
87
+ process.env.OTTO_COWORKER_GLOBAL_DIR = global;
88
+ process.env.OTTO_SCRATCHPAD_ROOT = sp;
89
+ try {
90
+ const vaultApi = makeFakeApi();
91
+ const memApi = makeFakeApi();
92
+ const spApi = makeFakeApi();
93
+ coworkerVaultExtension(vaultApi.api);
94
+ coworkerMemoryExtension(memApi.api);
95
+ coworkerScratchpadExtension(spApi.api);
96
+ await fireSessionStart(vaultApi, { cwd: ws });
97
+ await fireSessionStart(memApi, { cwd: ws });
98
+ await fireSessionStart(spApi, { cwd: ws });
99
+ assert.equal(getArtifactStore(), null);
100
+ assert.equal(getMemoryRecorder() !== null, true);
101
+ await fireSessionShutdown(spApi);
102
+ await fireSessionShutdown(memApi);
103
+ await fireSessionShutdown(vaultApi);
104
+ } finally {
105
+ delete process.env.OTTO_COWORKER_GLOBAL_DIR;
106
+ delete process.env.OTTO_SCRATCHPAD_ROOT;
107
+ }
108
+ });
109
+ });
@@ -0,0 +1,71 @@
1
+ // packages/coworker-artifacts/src/dir-snapshot.test.ts
2
+ import { describe, it } from 'node:test';
3
+ import assert from 'node:assert/strict';
4
+ import { mkdtempSync, mkdirSync, writeFileSync, unlinkSync } from 'node:fs';
5
+ import { tmpdir } from 'node:os';
6
+ import { join } from 'node:path';
7
+ import { takeSnapshot, diffSnapshots } from './dir-snapshot.js';
8
+
9
+ function tmp(): string { return mkdtempSync(join(tmpdir(), 'ds-')); }
10
+
11
+ describe('takeSnapshot', () => {
12
+ it('returns empty map for empty dir', () => {
13
+ const snap = takeSnapshot(tmp());
14
+ assert.equal(snap.size, 0);
15
+ });
16
+ it('captures relative path + size for each file', () => {
17
+ const dir = tmp();
18
+ writeFileSync(join(dir, 'a.md'), 'hello');
19
+ writeFileSync(join(dir, 'b.md'), 'world!');
20
+ const snap = takeSnapshot(dir);
21
+ assert.equal(snap.size, 2);
22
+ assert.equal(snap.get('a.md')!.sizeBytes, 5);
23
+ assert.equal(snap.get('b.md')!.sizeBytes, 6);
24
+ assert.equal(typeof snap.get('a.md')!.mtimeNs, 'bigint');
25
+ });
26
+ it('recurses into subdirs with forward-slash paths', () => {
27
+ const dir = tmp();
28
+ mkdirSync(join(dir, 'sub'));
29
+ writeFileSync(join(dir, 'sub', 'c.md'), 'nested');
30
+ const snap = takeSnapshot(dir);
31
+ assert.ok(snap.has('sub/c.md'));
32
+ });
33
+ it('returns empty map when dir does not exist', () => {
34
+ const snap = takeSnapshot(join(tmp(), 'nope'));
35
+ assert.equal(snap.size, 0);
36
+ });
37
+ });
38
+
39
+ describe('diffSnapshots', () => {
40
+ it('detects added files', () => {
41
+ const before = new Map();
42
+ const after = new Map([['a.md', { mtimeNs: 1n, sizeBytes: 5 }]]);
43
+ const d = diffSnapshots(before, after);
44
+ assert.deepEqual(d.added, ['a.md']);
45
+ assert.deepEqual(d.modified, []);
46
+ assert.deepEqual(d.removed, []);
47
+ });
48
+ it('detects removed files', () => {
49
+ const before = new Map([['a.md', { mtimeNs: 1n, sizeBytes: 5 }]]);
50
+ const after = new Map();
51
+ const d = diffSnapshots(before, after);
52
+ assert.deepEqual(d.removed, ['a.md']);
53
+ });
54
+ it('detects modified when sizeBytes differs', () => {
55
+ const before = new Map([['a.md', { mtimeNs: 1n, sizeBytes: 5 }]]);
56
+ const after = new Map([['a.md', { mtimeNs: 2n, sizeBytes: 7 }]]);
57
+ const d = diffSnapshots(before, after);
58
+ assert.deepEqual(d.modified, ['a.md']);
59
+ });
60
+ it('detects modified when mtimeNs differs (same size)', () => {
61
+ const before = new Map([['a.md', { mtimeNs: 1n, sizeBytes: 5 }]]);
62
+ const after = new Map([['a.md', { mtimeNs: 999n, sizeBytes: 5 }]]);
63
+ assert.deepEqual(diffSnapshots(before, after).modified, ['a.md']);
64
+ });
65
+ it('no diff when identical', () => {
66
+ const a = new Map([['a.md', { mtimeNs: 1n, sizeBytes: 5 }]]);
67
+ const b = new Map([['a.md', { mtimeNs: 1n, sizeBytes: 5 }]]);
68
+ const d = diffSnapshots(a, b);
69
+ assert.equal(d.added.length + d.modified.length + d.removed.length, 0);
70
+ });
71
+ });
@@ -0,0 +1,52 @@
1
+ // packages/coworker-artifacts/src/dir-snapshot.ts
2
+ import { existsSync, readdirSync, statSync } from 'node:fs';
3
+ import { join, relative, sep } from 'node:path';
4
+ import type { DirSnapshot } from './types.js';
5
+
6
+ export function takeSnapshot(dir: string): DirSnapshot {
7
+ const out: DirSnapshot = new Map();
8
+ if (!existsSync(dir)) return out;
9
+ walk(dir, dir, out);
10
+ return out;
11
+ }
12
+
13
+ function walk(root: string, current: string, out: DirSnapshot): void {
14
+ let entries;
15
+ try { entries = readdirSync(current, { withFileTypes: true }); }
16
+ catch { return; }
17
+ for (const entry of entries) {
18
+ const abs = join(current, entry.name);
19
+ if (entry.isDirectory()) {
20
+ walk(root, abs, out);
21
+ } else if (entry.isFile()) {
22
+ const stat = statSync(abs, { bigint: true });
23
+ const rel = relative(root, abs).split(sep).join('/');
24
+ out.set(rel, {
25
+ mtimeNs: stat.mtimeNs,
26
+ sizeBytes: Number(stat.size),
27
+ });
28
+ }
29
+ }
30
+ }
31
+
32
+ export function diffSnapshots(
33
+ before: DirSnapshot,
34
+ after: DirSnapshot,
35
+ ): { added: string[]; modified: string[]; removed: string[] } {
36
+ const added: string[] = [];
37
+ const modified: string[] = [];
38
+ const removed: string[] = [];
39
+ for (const [path, a] of after) {
40
+ const b = before.get(path);
41
+ if (!b) added.push(path);
42
+ else if (b.mtimeNs !== a.mtimeNs || b.sizeBytes !== a.sizeBytes) modified.push(path);
43
+ }
44
+ for (const path of before.keys()) {
45
+ if (!after.has(path)) removed.push(path);
46
+ }
47
+ return {
48
+ added: added.sort(),
49
+ modified: modified.sort(),
50
+ removed: removed.sort(),
51
+ };
52
+ }
@@ -0,0 +1,37 @@
1
+ // packages/coworker-artifacts/src/errors.test.ts
2
+ import { describe, it } from 'node:test';
3
+ import assert from 'node:assert/strict';
4
+ import {
5
+ ArtifactNotFound, ArtifactKindRejected, ArtifactUriMalformed, ArtifactSlugCollision,
6
+ } from './errors.js';
7
+
8
+ describe('artifact errors', () => {
9
+ it('ArtifactNotFound carries slug', () => {
10
+ const e = new ArtifactNotFound('rca-1');
11
+ assert.equal(e.name, 'ArtifactNotFound');
12
+ assert.equal(e.slug, 'rca-1');
13
+ assert.match(e.message, /rca-1/);
14
+ });
15
+ it('ArtifactKindRejected carries kind', () => {
16
+ const e = new ArtifactKindRejected('workbook');
17
+ assert.equal(e.name, 'ArtifactKindRejected');
18
+ assert.equal(e.kind, 'workbook');
19
+ assert.match(e.message, /workbook/);
20
+ assert.match(e.message, /report/);
21
+ });
22
+ it('ArtifactUriMalformed carries uri + reason', () => {
23
+ const e = new ArtifactUriMalformed('artifact://../x', 'path traversal');
24
+ assert.equal(e.name, 'ArtifactUriMalformed');
25
+ assert.equal(e.uri, 'artifact://../x');
26
+ assert.equal(e.reason, 'path traversal');
27
+ assert.match(e.message, /path traversal/);
28
+ });
29
+ it('ArtifactSlugCollision carries base + attempts', () => {
30
+ const e = new ArtifactSlugCollision('rca', 100);
31
+ assert.equal(e.name, 'ArtifactSlugCollision');
32
+ assert.equal(e.base, 'rca');
33
+ assert.equal(e.attempts, 100);
34
+ assert.match(e.message, /rca/);
35
+ assert.match(e.message, /100/);
36
+ });
37
+ });