@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,597 @@
1
+ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { ScratchpadBusyError, StalenessBanner } from '@otto/coworker-scratchpad';
4
+ import { LocalDataVault } from '@otto/coworker-vault';
5
+ import { validateName, readCellsJsonl, readPersistedLeaf } from './helpers.js';
6
+ import { projectTree, formatTreeText } from '@otto/coworker-scratchpad';
7
+ import { sessionSidecarPath, writeSessionSidecar, deleteSessionSidecar, readSessionSidecar } from './session-sidecar.js';
8
+ import { detectWorkspaceRoot } from './workspace-root.js';
9
+ import { workspaceHash, workspacePointerPath, writeWorkspacePointer } from './workspace-pointer.js';
10
+ import { showRecoveryNotesBanner, showDivergenceBanner, formatNoteLine } from './attach-banners.js';
11
+ import { formatRelativeAge } from './format-age.js';
12
+ const VERBS = ['list', 'new', 'attach', 'reset', 'view', 'remove', 'tree', 'fork', 'save', 'detach', 'clear-history', 'notes', 'evict', 'use', 'unuse'];
13
+ /**
14
+ * Phase 2 Task 16: module-level singleton so banner one-shot state survives
15
+ * across multiple /sp invocations within one process. Reset on /sp reset for
16
+ * the affected scratchpad (the kernel respawns ⇒ stale tracking restarts).
17
+ *
18
+ * Per-test isolation: tests create distinct scratchpad names (or new tmp
19
+ * roots) so the per-(scratchpad, session, ref) key set never collides across
20
+ * tests. No reset-all escape hatch is exposed.
21
+ */
22
+ const stalenessBanner = new StalenessBanner();
23
+ function readBindingsFromMeta(metaPath) {
24
+ if (!existsSync(metaPath))
25
+ return [];
26
+ try {
27
+ const meta = JSON.parse(readFileSync(metaPath, 'utf8'));
28
+ return Array.isArray(meta.bindings) ? meta.bindings : [];
29
+ }
30
+ catch {
31
+ return [];
32
+ }
33
+ }
34
+ function ensureCurrent(deps) {
35
+ let current = deps.getCurrentName();
36
+ if (!current) {
37
+ current = 'default';
38
+ deps.setCurrentName(current);
39
+ }
40
+ return current;
41
+ }
42
+ function listExistingScratchpads(root) {
43
+ if (!existsSync(root))
44
+ return [];
45
+ const names = [];
46
+ for (const entry of readdirSync(root)) {
47
+ const dir = join(root, entry);
48
+ try {
49
+ if (statSync(dir).isDirectory() && existsSync(join(dir, 'meta.json')))
50
+ names.push(entry);
51
+ }
52
+ catch {
53
+ // entry vanished -> skip
54
+ }
55
+ }
56
+ return names.sort();
57
+ }
58
+ function formatCellSummary(rec) {
59
+ const head = rec.ok ? `cell ${rec.id} [ok]` : `cell ${rec.id} [err]`;
60
+ const value = rec.ok ? ` value=${JSON.stringify(rec.value)}` : ` error=${rec.error?.message ?? ''}`;
61
+ return `${head} ${rec.code.split('\n')[0].slice(0, 80)} ${value}`;
62
+ }
63
+ function persistWorkspacePointer(deps, name) {
64
+ const wsRoot = detectWorkspaceRoot(deps.getWorkspaceCwd());
65
+ const wsHash = workspaceHash(wsRoot);
66
+ const wsPath = workspacePointerPath(deps.rootDir(), wsHash);
67
+ const wsPayload = {
68
+ schema_version: 1,
69
+ workspace_hash: wsHash,
70
+ workspace_root: wsRoot,
71
+ last_session_id: deps.getSessionId(),
72
+ last_current_name: name,
73
+ last_attached_at: new Date().toISOString(),
74
+ };
75
+ writeWorkspacePointer(wsPath, wsPayload);
76
+ }
77
+ function joinQuotedArg(parts, startIdx) {
78
+ if (startIdx >= parts.length)
79
+ return null;
80
+ const first = parts[startIdx];
81
+ if (!first)
82
+ return null;
83
+ if (!first.startsWith('"'))
84
+ return first;
85
+ // Quoted: walk forward until we find a part ending with "
86
+ if (first.length > 1 && first.endsWith('"')) {
87
+ return first.slice(1, -1); // single-token quoted reason
88
+ }
89
+ const collected = [first.slice(1)]; // strip opening quote
90
+ for (let i = startIdx + 1; i < parts.length; i++) {
91
+ const p = parts[i] ?? '';
92
+ if (p.endsWith('"')) {
93
+ collected.push(p.slice(0, -1));
94
+ return collected.join(' ');
95
+ }
96
+ collected.push(p);
97
+ }
98
+ return collected.join(' '); // no closing quote — take rest
99
+ }
100
+ export function registerSpCommand(pi, deps) {
101
+ pi.registerCommand('sp', {
102
+ description: 'Manage scratchpads: /sp [list|new|attach|reset|view|remove|tree|fork|save|detach|clear-history|notes|evict|use|unuse] [name]',
103
+ getArgumentCompletions: (prefix) => {
104
+ // Split on whitespace but preserve whether the prefix ends with a space
105
+ // (trailing space = user typed the verb and hit space, ready for name completion).
106
+ const trimmed = prefix.trimStart();
107
+ const parts = trimmed.split(/\s+/);
108
+ // If trailing space: user has finished typing the verb, want name completions.
109
+ const trailingSpace = prefix.endsWith(' ');
110
+ if (parts.length <= 1 && !trailingSpace) {
111
+ return VERBS.filter((v) => v.startsWith(parts[0] ?? '')).map((v) => ({ value: v, label: v }));
112
+ }
113
+ const verb = parts[0];
114
+ if (verb === 'attach' || verb === 'reset' || verb === 'view' || verb === 'remove') {
115
+ const namePrefix = trailingSpace && parts.length === 1 ? '' : (parts[1] ?? '');
116
+ return listExistingScratchpads(deps.rootDir())
117
+ .filter((n) => n.startsWith(namePrefix))
118
+ .map((n) => ({ value: `${verb} ${n}`, label: n }));
119
+ }
120
+ return [];
121
+ },
122
+ handler: async (args, ctx) => {
123
+ const trimmed = args.trim();
124
+ const parts = trimmed.length === 0 ? [] : trimmed.split(/\s+/);
125
+ const verb = parts[0] ?? 'list';
126
+ const name = parts[1];
127
+ try {
128
+ switch (verb) {
129
+ case 'list': {
130
+ const mgr = deps.getManager();
131
+ const live = mgr.list();
132
+ const liveByName = new Map(live.map((e) => [e.name, e]));
133
+ const onDisk = listExistingScratchpads(deps.rootDir());
134
+ const all = Array.from(new Set([...liveByName.keys(), ...onDisk])).sort();
135
+ const cur = deps.getCurrentName();
136
+ if (all.length === 0) {
137
+ ctx.ui.notify('No scratchpads yet. Use /sp new <name> to create one.', 'info');
138
+ return;
139
+ }
140
+ // Task D: render idle-age column for warm entries. Compute `now` once so
141
+ // every row shares the same baseline.
142
+ const now = Date.now();
143
+ // Pad name column so the age column lines up. Cold entries have no age.
144
+ const maxNameLen = all.reduce((m, n) => Math.max(m, n.length), 0);
145
+ const lines = all.map((n) => {
146
+ const l = liveByName.get(n);
147
+ const state = l?.live ? '● live' : '○ cold';
148
+ const marker = n === cur ? ' (current)' : '';
149
+ let age = '';
150
+ if (l?.live) {
151
+ age = l.hasActiveCell ? 'active' : formatRelativeAge(now - l.lastUsedAt);
152
+ }
153
+ const namePadded = age ? n.padEnd(maxNameLen) : n;
154
+ const ageCol = age ? ` ${age}` : '';
155
+ // Phase 2 Task 16: binding count read from meta.json. Renders as
156
+ // `uses:N` after the age column so the existing live/cold and
157
+ // active/idle assertions stay intact. Hidden when 0 to avoid
158
+ // visual noise for scratchpads that aren't bound.
159
+ const bindingCount = readBindingsFromMeta(join(deps.rootDir(), n, 'meta.json')).length;
160
+ const bindingsCol = bindingCount > 0 ? ` uses:${bindingCount}` : '';
161
+ return ` ${state} ${namePadded}${ageCol}${bindingsCol}${marker}`;
162
+ });
163
+ ctx.ui.notify(['scratchpads:', ...lines].join('\n'), 'info');
164
+ return;
165
+ }
166
+ case 'new': {
167
+ if (!name) {
168
+ ctx.ui.notify('Usage: /sp new <name> [--use <engine:name>] ...', 'error');
169
+ return;
170
+ }
171
+ validateName(name);
172
+ // Phase 2 Task 16: parse --use flag. Repeatable: `--use jira:prod --use foo:bar`.
173
+ // Each ref is validated via LocalDataVault.parseRef before persisting; a single
174
+ // malformed ref aborts the whole create so we don't end up with a half-bound
175
+ // scratchpad on disk.
176
+ const bindings = [];
177
+ for (let i = 2; i < parts.length; i++) {
178
+ if (parts[i] === '--use') {
179
+ const ref = parts[i + 1];
180
+ if (!ref) {
181
+ ctx.ui.notify('Usage: /sp new <name> --use <engine:name>', 'error');
182
+ return;
183
+ }
184
+ try {
185
+ LocalDataVault.parseRef(ref);
186
+ }
187
+ catch (err) {
188
+ ctx.ui.notify(err.message, 'error');
189
+ return;
190
+ }
191
+ bindings.push(ref);
192
+ i++; // skip ref token
193
+ }
194
+ }
195
+ await deps.getManager().create(name, bindings.length > 0 ? { bindings } : {});
196
+ deps.setCurrentName(name);
197
+ writeSessionSidecar(sessionSidecarPath(deps.rootDir(), deps.getSessionId()), {
198
+ schema_version: 1,
199
+ session_id: deps.getSessionId(),
200
+ current_name: name,
201
+ attached_at: new Date().toISOString(),
202
+ });
203
+ persistWorkspacePointer(deps, name);
204
+ const suffix = bindings.length > 0 ? ` (bindings: ${bindings.join(', ')})` : '';
205
+ ctx.ui.notify(`created scratchpad: ${name} (now current)${suffix}`, 'info');
206
+ return;
207
+ }
208
+ case 'use': {
209
+ const target = name;
210
+ const ref = parts[2];
211
+ if (!target || !ref) {
212
+ ctx.ui.notify('Usage: /sp use <name> <engine:name>', 'error');
213
+ return;
214
+ }
215
+ validateName(target);
216
+ try {
217
+ LocalDataVault.parseRef(ref);
218
+ }
219
+ catch (err) {
220
+ ctx.ui.notify(err.message, 'error');
221
+ return;
222
+ }
223
+ try {
224
+ const { added } = await deps.getManager().addBinding(target, ref);
225
+ if (!added) {
226
+ ctx.ui.notify(`binding already present: ${ref} → ${target}`, 'info');
227
+ }
228
+ else {
229
+ // Hint /sp reset because the live kernel was spawned without this
230
+ // env block; the binding only takes effect on the next respawn.
231
+ ctx.ui.notify(`binding added: ${ref} → ${target}. /sp reset to inject into the live kernel.`, 'info');
232
+ }
233
+ }
234
+ catch (err) {
235
+ ctx.ui.notify(err.message, 'error');
236
+ }
237
+ return;
238
+ }
239
+ case 'unuse': {
240
+ const target = name;
241
+ const ref = parts[2];
242
+ if (!target || !ref) {
243
+ ctx.ui.notify('Usage: /sp unuse <name> <engine:name>', 'error');
244
+ return;
245
+ }
246
+ validateName(target);
247
+ try {
248
+ const { removed } = await deps.getManager().removeBinding(target, ref);
249
+ if (!removed) {
250
+ ctx.ui.notify(`binding not present: ${ref} → ${target}`, 'info');
251
+ }
252
+ else {
253
+ ctx.ui.notify(`binding removed: ${ref} from ${target}. /sp reset to drop the env block from the live kernel.`, 'info');
254
+ }
255
+ }
256
+ catch (err) {
257
+ ctx.ui.notify(err.message, 'error');
258
+ }
259
+ return;
260
+ }
261
+ case 'attach': {
262
+ if (!name) {
263
+ ctx.ui.notify('Usage: /sp attach <name> [--force-takeover] [--reason "<text>"]', 'error');
264
+ return;
265
+ }
266
+ validateName(name);
267
+ // Slash-command path is strict: error on typo instead of silently auto-creating
268
+ // (the LLM-tool path via cw_scratchpad action=exec stays permissive).
269
+ const metaPath = join(deps.rootDir(), name, 'meta.json');
270
+ if (!existsSync(metaPath)) {
271
+ ctx.ui.notify(`scratchpad not found: ${name}. Use /sp new ${name} to create it.`, 'error');
272
+ return;
273
+ }
274
+ const forceFlag = parts.includes('--force-takeover');
275
+ const reasonIdx = parts.indexOf('--reason');
276
+ const reasonArg = reasonIdx >= 0 ? joinQuotedArg(parts, reasonIdx + 1) : null;
277
+ let attached = false;
278
+ // Phase 2 Task 16: capture the runtime returned by getOrAttach so we
279
+ // can use its spawnTime as the staleness banner's clock. Defensive
280
+ // typing because some tests return null from a stubbed manager.
281
+ let runtime = null;
282
+ try {
283
+ runtime = (await deps.getManager().getOrAttach(name));
284
+ attached = true;
285
+ }
286
+ catch (err) {
287
+ if (!(err instanceof ScratchpadBusyError)) {
288
+ ctx.ui.notify(err.message, 'error');
289
+ return;
290
+ }
291
+ const holder = err.holder;
292
+ const proceed = forceFlag || await ctx.ui.confirm('Force takeover?', `${name}: lock held by pid ${holder.pid} on host ${holder.host} (acquired ${holder.acquired_at}). Take it?`);
293
+ if (!proceed) {
294
+ ctx.ui.notify('cancelled', 'info');
295
+ return;
296
+ }
297
+ let reason = reasonArg;
298
+ if (reason === null) {
299
+ const input = await ctx.ui.input('Takeover reason', 'why are you taking over?');
300
+ if (input === undefined) {
301
+ ctx.ui.notify('cancelled', 'info');
302
+ return;
303
+ }
304
+ reason = input.trim() || '(no reason given)';
305
+ }
306
+ try {
307
+ runtime = (await deps.getManager().getOrAttach(name, { forceTakeover: true, takeoverReason: reason }));
308
+ attached = true;
309
+ }
310
+ catch (retryErr) {
311
+ ctx.ui.notify(retryErr.message, 'error');
312
+ return;
313
+ }
314
+ }
315
+ if (!attached)
316
+ return;
317
+ deps.setCurrentName(name);
318
+ writeSessionSidecar(sessionSidecarPath(deps.rootDir(), deps.getSessionId()), {
319
+ schema_version: 1,
320
+ session_id: deps.getSessionId(),
321
+ current_name: name,
322
+ attached_at: new Date().toISOString(),
323
+ });
324
+ persistWorkspacePointer(deps, name);
325
+ ctx.ui.notify(`attached to scratchpad: ${name}`, 'info');
326
+ // §2 + §4 banners (1g2):
327
+ const { markSeen } = showRecoveryNotesBanner(name, deps.rootDir(), ctx.ui);
328
+ if (markSeen) {
329
+ await deps.getManager().markRecoveryNotesSeen(name);
330
+ }
331
+ showDivergenceBanner(name, deps.rootDir(), ctx.ui);
332
+ // Phase 2 Task 16: staleness banner. If a vault is wired AND this
333
+ // scratchpad has bindings, check each ref's last-modified vs the
334
+ // kernel's spawnTime; emit one-shot warning if any are stale.
335
+ // Skipped silently when no vault is configured — the scratchpad
336
+ // extension still works without coworker-vault present.
337
+ //
338
+ // spawnTime source: prefer the runtime's stamped Date from
339
+ // ChildProcessRuntime.start() (Task 13). Fallback to meta.json's
340
+ // mtime — also rewritten on every attach by writeMeta — when the
341
+ // runtime is null (stubbed manager paths in tests) or missing the
342
+ // spawnTime field. Both anchors converge on the same user-visible
343
+ // semantic: "your creds changed since you started this kernel."
344
+ if (deps.getStalenessVault) {
345
+ const vault = deps.getStalenessVault();
346
+ const bindings = readBindingsFromMeta(join(deps.rootDir(), name, 'meta.json'));
347
+ if (vault && bindings.length > 0) {
348
+ try {
349
+ const spawnTime = runtime?.spawnTime instanceof Date && runtime.spawnTime.getTime() > 0
350
+ ? runtime.spawnTime
351
+ : new Date(statSync(join(deps.rootDir(), name, 'meta.json')).mtime);
352
+ const banner = await stalenessBanner.check({
353
+ scratchpadName: name,
354
+ sessionId: deps.getSessionId(),
355
+ bindings,
356
+ spawnTime,
357
+ lookupLastModified: (ref) => vault.lookupLastModified(ref),
358
+ });
359
+ if (banner)
360
+ ctx.ui.notify(banner, 'warning');
361
+ }
362
+ catch {
363
+ // Banner emission must never block /sp attach — swallow errors.
364
+ }
365
+ }
366
+ }
367
+ return;
368
+ }
369
+ case 'reset': {
370
+ const target = name ?? ensureCurrent(deps);
371
+ validateName(target);
372
+ const mgr = deps.getManager();
373
+ // Phase 2 Task 16: preserve bindings across reset. remove() wipes
374
+ // the dir, then create() rewrites a fresh meta.json — without this
375
+ // pre-read the new meta.bindings would be []. Resetting is "respawn
376
+ // the kernel with current config", so the binding list is part of
377
+ // config that must survive.
378
+ const preservedBindings = mgr.readBindings(target);
379
+ await mgr.remove(target);
380
+ await mgr.create(target, preservedBindings.length > 0 ? { bindings: preservedBindings } : {});
381
+ // Phase 2 Task 16: clear the staleness-banner one-shot state for
382
+ // this scratchpad — the new kernel has a fresh spawnTime, so old
383
+ // "stale" status is no longer relevant.
384
+ stalenessBanner.resetForRespawn(target);
385
+ // currentName preserved if it was the reset target; otherwise unchanged
386
+ ctx.ui.notify(`reset scratchpad: ${target}`, 'info');
387
+ return;
388
+ }
389
+ case 'view': {
390
+ const target = name ?? ensureCurrent(deps);
391
+ validateName(target);
392
+ const { cells, total_cells } = readCellsJsonl(join(deps.rootDir(), target));
393
+ if (total_cells === 0) {
394
+ ctx.ui.notify(`${target}: no cells yet`, 'info');
395
+ return;
396
+ }
397
+ const tail = cells.slice(-10);
398
+ const lines = tail.map((c) => formatCellSummary(c));
399
+ ctx.ui.notify([`${target} (${total_cells} cells, last 10):`, ...lines].join('\n'), 'info');
400
+ return;
401
+ }
402
+ case 'remove': {
403
+ if (!name) {
404
+ ctx.ui.notify('Usage: /sp remove <name> [--yes]', 'error');
405
+ return;
406
+ }
407
+ const force = parts.includes('--yes');
408
+ validateName(name);
409
+ if (name === deps.getCurrentName() && !force) {
410
+ const confirmed = await ctx.ui.confirm('Remove current scratchpad?', `${name} is your current scratchpad. Remove it? This deletes kernel.db, namespace.json, and the cell journal.`);
411
+ if (!confirmed) {
412
+ ctx.ui.notify('cancelled', 'info');
413
+ return;
414
+ }
415
+ }
416
+ const wasCurrent = name === deps.getCurrentName();
417
+ await deps.getManager().remove(name);
418
+ if (wasCurrent) {
419
+ deleteSessionSidecar(sessionSidecarPath(deps.rootDir(), deps.getSessionId()));
420
+ deps.setCurrentName(null);
421
+ }
422
+ ctx.ui.notify(`removed scratchpad: ${name}`, 'info');
423
+ return;
424
+ }
425
+ case 'evict': {
426
+ if (!name) {
427
+ ctx.ui.notify('Usage: /sp evict <name> [--force]', 'error');
428
+ return;
429
+ }
430
+ const force = parts.includes('--force');
431
+ validateName(name);
432
+ try {
433
+ const { interrupted } = await deps.getManager().evict(name, { force });
434
+ const msg = interrupted
435
+ ? `interrupted active cell and evicted ${name}`
436
+ : `evicted ${name} (still on disk; /sp attach ${name} to re-warm)`;
437
+ ctx.ui.notify(msg, 'info');
438
+ }
439
+ catch (e) {
440
+ ctx.ui.notify(e.message, 'error');
441
+ }
442
+ return;
443
+ }
444
+ case 'tree': {
445
+ // Usage: /sp tree [<name>] [--to <id>]
446
+ const flagIdx = parts.indexOf('--to');
447
+ let target;
448
+ if (flagIdx === -1) {
449
+ target = name ?? ensureCurrent(deps);
450
+ }
451
+ else {
452
+ target = flagIdx === 1 ? ensureCurrent(deps) : parts[1];
453
+ const toId = Number(parts[flagIdx + 1]);
454
+ if (!Number.isInteger(toId) || toId <= 0) {
455
+ ctx.ui.notify('Usage: /sp tree [<name>] --to <id>', 'error');
456
+ return;
457
+ }
458
+ validateName(target);
459
+ await deps.getManager().setLeaf(target, toId);
460
+ ctx.ui.notify(`set leaf of ${target} to cell ${toId}`, 'info');
461
+ return;
462
+ }
463
+ validateName(target);
464
+ const { cells } = readCellsJsonl(join(deps.rootDir(), target));
465
+ if (cells.length === 0) {
466
+ ctx.ui.notify(`${target}: no cells yet`, 'info');
467
+ return;
468
+ }
469
+ const tree = projectTree(cells);
470
+ const leaf = readPersistedLeaf(join(deps.rootDir(), target, 'meta.json'));
471
+ ctx.ui.notify(`${target} cell tree:\n${formatTreeText(tree, leaf)}`, 'info');
472
+ return;
473
+ }
474
+ case 'fork': {
475
+ // Usage: /sp fork <src> <dst>
476
+ if (parts.length < 3) {
477
+ ctx.ui.notify('Usage: /sp fork <src> <dst>', 'error');
478
+ return;
479
+ }
480
+ const src = parts[1];
481
+ const dst = parts[2];
482
+ validateName(src);
483
+ validateName(dst);
484
+ await deps.getManager().fork(src, dst);
485
+ ctx.ui.notify(`forked ${src} → ${dst}`, 'info');
486
+ return;
487
+ }
488
+ case 'save': {
489
+ const target = name ?? deps.getCurrentName();
490
+ if (!target) {
491
+ ctx.ui.notify('Usage: /sp save [<name>] — no current scratchpad', 'error');
492
+ return;
493
+ }
494
+ validateName(target);
495
+ await deps.getManager().save(target);
496
+ ctx.ui.notify(`saved ${target}`, 'info');
497
+ return;
498
+ }
499
+ case 'detach': {
500
+ const target = deps.getCurrentName();
501
+ if (!target) {
502
+ ctx.ui.notify('not attached to any scratchpad', 'error');
503
+ return;
504
+ }
505
+ await deps.getManager().detach(target, deps.getSessionId());
506
+ deleteSessionSidecar(sessionSidecarPath(deps.rootDir(), deps.getSessionId()));
507
+ deps.setCurrentName(null);
508
+ ctx.ui.notify(`detached from ${target}`, 'info');
509
+ return;
510
+ }
511
+ case 'clear-history': {
512
+ const target = name ?? deps.getCurrentName();
513
+ if (!target) {
514
+ ctx.ui.notify('Usage: /sp clear-history [<name>] — no current scratchpad', 'error');
515
+ return;
516
+ }
517
+ validateName(target);
518
+ const confirmed = await ctx.ui.confirm('Clear cell history?', `Clear cell history for ${target}? kernel.db + namespace.json are preserved.`);
519
+ if (!confirmed) {
520
+ ctx.ui.notify('cancelled', 'info');
521
+ return;
522
+ }
523
+ await deps.getManager().clearHistory(target);
524
+ ctx.ui.notify(`cleared cell history for ${target}`, 'info');
525
+ return;
526
+ }
527
+ case 'notes': {
528
+ const target = name ?? deps.getCurrentName();
529
+ if (!target) {
530
+ ctx.ui.notify('Usage: /sp notes [<name>] (no current scratchpad)', 'error');
531
+ return;
532
+ }
533
+ validateName(target);
534
+ const metaPath = join(deps.rootDir(), target, 'meta.json');
535
+ if (!existsSync(metaPath)) {
536
+ ctx.ui.notify(`scratchpad not found: ${target}`, 'error');
537
+ return;
538
+ }
539
+ let meta;
540
+ try {
541
+ meta = JSON.parse(readFileSync(metaPath, 'utf8'));
542
+ }
543
+ catch {
544
+ ctx.ui.notify(`${target}: meta.json unreadable`, 'error');
545
+ return;
546
+ }
547
+ const notes = Array.isArray(meta.recovery_notes) ? meta.recovery_notes : [];
548
+ if (notes.length === 0) {
549
+ ctx.ui.notify(`no recovery notes for ${target}`, 'info');
550
+ return;
551
+ }
552
+ const lines = notes.map(formatNoteLine);
553
+ ctx.ui.notify(`${target} recovery notes (${notes.length}):\n${lines.join('\n')}`, 'info');
554
+ // Deliberately does NOT update recovery_notes_seen_at — re-view path is read-only.
555
+ return;
556
+ }
557
+ default: {
558
+ ctx.ui.notify(`unknown verb: ${verb}. Try one of: ${VERBS.join(', ')}`, 'error');
559
+ }
560
+ }
561
+ }
562
+ catch (err) {
563
+ ctx.ui.notify(err.message, 'error');
564
+ }
565
+ },
566
+ });
567
+ }
568
+ /**
569
+ * Phase 3 Task 18: exposes a session→scratchpad-name lookup for cross-pillar
570
+ * consumers (memory's MemoryRecorder uses this to derive the default Room).
571
+ *
572
+ * Reads the per-session sidecar written by /sp new and /sp attach. Returns
573
+ * null when no scratchpad is currently attached for `sessionId`, when
574
+ * `sessionId` is empty/undefined, or on any IO/parse error — callers must
575
+ * tolerate null (the caller-side fallback is "use workspace as Room").
576
+ *
577
+ * Decoupled from `SpDeps` so consumers outside the slash-command lifecycle
578
+ * (e.g. recorders constructed at extension activation) can call it without
579
+ * holding a reference to the scratchpad manager.
580
+ *
581
+ * Note on the sidecar reader's actual signature: `readSessionSidecar(path)`
582
+ * takes a fully-resolved path, not `{scratchpadsRoot, sessionId}`. We compose
583
+ * with `sessionSidecarPath(rootDir, sessionId)` to bridge.
584
+ */
585
+ export function createCurrentScratchpadProvider(opts) {
586
+ return (sessionId) => {
587
+ if (!sessionId)
588
+ return null;
589
+ try {
590
+ const sidecar = readSessionSidecar(sessionSidecarPath(opts.scratchpadsRoot, sessionId));
591
+ return sidecar?.current_name ?? null;
592
+ }
593
+ catch {
594
+ return null;
595
+ }
596
+ };
597
+ }
@@ -0,0 +1,41 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
3
+ import { dirname, join } from 'node:path';
4
+ export const WORKSPACE_POINTER_STALE_MS = 7 * 24 * 60 * 60 * 1000;
5
+ export function workspaceHash(workspaceRoot) {
6
+ return createHash('sha256').update(workspaceRoot).digest('hex').slice(0, 16);
7
+ }
8
+ export function workspacePointerPath(rootDir, hash) {
9
+ return join(rootDir, '_workspaces', `${hash}.json`);
10
+ }
11
+ export function readWorkspacePointer(path) {
12
+ if (!existsSync(path))
13
+ return null;
14
+ try {
15
+ const parsed = JSON.parse(readFileSync(path, 'utf8'));
16
+ if (parsed.schema_version === 1 &&
17
+ typeof parsed.workspace_hash === 'string' &&
18
+ typeof parsed.workspace_root === 'string' &&
19
+ typeof parsed.last_session_id === 'string' &&
20
+ typeof parsed.last_current_name === 'string' &&
21
+ typeof parsed.last_attached_at === 'string') {
22
+ return parsed;
23
+ }
24
+ return null;
25
+ }
26
+ catch {
27
+ return null;
28
+ }
29
+ }
30
+ export function writeWorkspacePointer(path, payload) {
31
+ mkdirSync(dirname(path), { recursive: true });
32
+ const tmp = `${path}.tmp`;
33
+ writeFileSync(tmp, JSON.stringify(payload, null, 2));
34
+ renameSync(tmp, path);
35
+ }
36
+ export function isPointerFresh(pointer, now) {
37
+ const attachedAt = Date.parse(pointer.last_attached_at);
38
+ if (Number.isNaN(attachedAt))
39
+ return false;
40
+ return (now - attachedAt) < WORKSPACE_POINTER_STALE_MS;
41
+ }
@@ -0,0 +1,17 @@
1
+ import { execSync } from 'node:child_process';
2
+ export function detectWorkspaceRoot(cwd) {
3
+ try {
4
+ const out = execSync('git rev-parse --show-toplevel', {
5
+ cwd,
6
+ stdio: ['ignore', 'pipe', 'ignore'],
7
+ encoding: 'utf8',
8
+ timeout: 500,
9
+ }).trim();
10
+ if (out)
11
+ return out;
12
+ }
13
+ catch {
14
+ /* not a git repo, git not installed, or timeout — fall through */
15
+ }
16
+ return cwd;
17
+ }