@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
@@ -0,0 +1,219 @@
1
+ import {
2
+ mkdirSync,
3
+ mkdtempSync,
4
+ realpathSync,
5
+ rmSync,
6
+ writeFileSync,
7
+ } from "node:fs";
8
+ import { tmpdir } from "node:os";
9
+ import { join } from "node:path";
10
+ import { afterEach, describe, expect, test } from "bun:test";
11
+
12
+ import {
13
+ isToolActiveForContext,
14
+ type SkillProjectionContext,
15
+ SUBAGENT_ONLY_TOOL_NAMES,
16
+ } from "../daemon/conversation-tool-setup.js";
17
+ import { fileListTool } from "../tools/filesystem/list.js";
18
+ import {
19
+ FileSystemOps,
20
+ type PathPolicy,
21
+ } from "../tools/shared/filesystem/file-ops-service.js";
22
+ import { sandboxPolicy } from "../tools/shared/filesystem/path-policy.js";
23
+ import type { ToolContext } from "../tools/types.js";
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // Helpers
27
+ // ---------------------------------------------------------------------------
28
+
29
+ const testDirs: string[] = [];
30
+
31
+ function makeTempDir(): string {
32
+ const dir = realpathSync(mkdtempSync(join(tmpdir(), "file-list-test-")));
33
+ testDirs.push(dir);
34
+ return dir;
35
+ }
36
+
37
+ afterEach(() => {
38
+ for (const dir of testDirs.splice(0)) {
39
+ rmSync(dir, { recursive: true, force: true });
40
+ }
41
+ });
42
+
43
+ /** Build a sandbox-bound PathPolicy for the given directory. */
44
+ function sandboxPolicyFor(boundary: string): PathPolicy {
45
+ return (rawPath, options) => sandboxPolicy(rawPath, boundary, options);
46
+ }
47
+
48
+ // ---------------------------------------------------------------------------
49
+ // listDirSafe
50
+ // ---------------------------------------------------------------------------
51
+
52
+ describe("FileSystemOps.listDirSafe", () => {
53
+ test("lists directory contents with type indicators (dirs end with /)", () => {
54
+ const dir = makeTempDir();
55
+ mkdirSync(join(dir, "subdir-a"));
56
+ mkdirSync(join(dir, "subdir-b"));
57
+ writeFileSync(join(dir, "file-a.txt"), "hello");
58
+ writeFileSync(join(dir, "file-b.md"), "world");
59
+
60
+ const ops = new FileSystemOps(sandboxPolicyFor(dir));
61
+ const result = ops.listDirSafe({ path: dir });
62
+ expect(result.ok).toBe(true);
63
+ if (!result.ok) return;
64
+
65
+ const lines = result.value.listing.split("\n");
66
+ // Directories first with trailing /
67
+ expect(lines[0]).toBe("subdir-a/");
68
+ expect(lines[1]).toBe("subdir-b/");
69
+ // Files after directories, with size info
70
+ expect(lines[2]).toMatch(/^file-a\.txt\s+\d+\s*B$/);
71
+ expect(lines[3]).toMatch(/^file-b\.md\s+\d+\s*B$/);
72
+ });
73
+
74
+ test("glob filtering works (e.g. '*.md' only returns .md files)", () => {
75
+ const dir = makeTempDir();
76
+ writeFileSync(join(dir, "readme.md"), "# Title");
77
+ writeFileSync(join(dir, "notes.md"), "some notes");
78
+ writeFileSync(join(dir, "app.ts"), "console.log('hi')");
79
+ writeFileSync(join(dir, "config.json"), "{}");
80
+
81
+ const ops = new FileSystemOps(sandboxPolicyFor(dir));
82
+ const result = ops.listDirSafe({ path: dir, glob: "*.md" });
83
+ expect(result.ok).toBe(true);
84
+ if (!result.ok) return;
85
+
86
+ const lines = result.value.listing.split("\n");
87
+ expect(lines.length).toBe(2);
88
+ expect(lines[0]).toMatch(/^notes\.md/);
89
+ expect(lines[1]).toMatch(/^readme\.md/);
90
+ });
91
+
92
+ test("returns NOT_A_DIRECTORY error for file paths", () => {
93
+ const dir = makeTempDir();
94
+ writeFileSync(join(dir, "regular-file.txt"), "content");
95
+
96
+ const ops = new FileSystemOps(sandboxPolicyFor(dir));
97
+ const result = ops.listDirSafe({ path: join(dir, "regular-file.txt") });
98
+ expect(result.ok).toBe(false);
99
+ if (result.ok) return;
100
+ expect(result.error.code).toBe("NOT_A_DIRECTORY");
101
+ });
102
+
103
+ test("returns NOT_FOUND error for nonexistent paths", () => {
104
+ const dir = makeTempDir();
105
+
106
+ const ops = new FileSystemOps(sandboxPolicyFor(dir));
107
+ const result = ops.listDirSafe({ path: join(dir, "nonexistent") });
108
+ expect(result.ok).toBe(false);
109
+ if (result.ok) return;
110
+ expect(result.error.code).toBe("NOT_FOUND");
111
+ });
112
+
113
+ test("sandbox policy rejects paths outside boundary", () => {
114
+ const dir = makeTempDir();
115
+
116
+ const ops = new FileSystemOps(sandboxPolicyFor(dir));
117
+ const result = ops.listDirSafe({ path: "../../../etc" });
118
+ expect(result.ok).toBe(false);
119
+ if (result.ok) return;
120
+ expect(result.error.code).toBe("PATH_OUT_OF_BOUNDS");
121
+ });
122
+ });
123
+
124
+ // ---------------------------------------------------------------------------
125
+ // FileListTool integration tests
126
+ // ---------------------------------------------------------------------------
127
+
128
+ /** Build a minimal ToolContext with the given workingDir. */
129
+ function makeToolContext(workingDir: string): ToolContext {
130
+ return {
131
+ workingDir,
132
+ conversationId: "test-conv",
133
+ trustClass: "guardian",
134
+ };
135
+ }
136
+
137
+ describe("FileListTool", () => {
138
+ test("execute() returns formatted listing for a temp directory", async () => {
139
+ const dir = makeTempDir();
140
+ mkdirSync(join(dir, "src"));
141
+ writeFileSync(join(dir, "README.md"), "# Hello");
142
+ writeFileSync(join(dir, "index.ts"), "export {}");
143
+
144
+ const result = await fileListTool.execute(
145
+ { path: dir, activity: "listing test dir" },
146
+ makeToolContext(dir),
147
+ );
148
+
149
+ expect(result.isError).toBe(false);
150
+ const lines = result.content.split("\n");
151
+ // Directory first with trailing /
152
+ expect(lines[0]).toBe("src/");
153
+ // Files after (alphabetical), with size info
154
+ expect(lines[1]).toMatch(/^index\.ts\s+\d+\s*B$/);
155
+ expect(lines[2]).toMatch(/^README\.md\s+\d+\s*B$/);
156
+ });
157
+
158
+ test("execute() returns error for invalid path input", async () => {
159
+ const dir = makeTempDir();
160
+ const result = await fileListTool.execute(
161
+ { path: 123, activity: "test" },
162
+ makeToolContext(dir),
163
+ );
164
+ expect(result.isError).toBe(true);
165
+ expect(result.content).toContain("path is required and must be a string");
166
+ });
167
+
168
+ test("execute() returns error for nonexistent directory", async () => {
169
+ const dir = makeTempDir();
170
+ const result = await fileListTool.execute(
171
+ { path: join(dir, "nope"), activity: "test" },
172
+ makeToolContext(dir),
173
+ );
174
+ expect(result.isError).toBe(true);
175
+ expect(result.content).toContain("not found");
176
+ });
177
+ });
178
+
179
+ // ---------------------------------------------------------------------------
180
+ // Subagent-only visibility
181
+ // ---------------------------------------------------------------------------
182
+
183
+ describe("file_list subagent visibility", () => {
184
+ test("file_list is in SUBAGENT_ONLY_TOOL_NAMES", () => {
185
+ expect(SUBAGENT_ONLY_TOOL_NAMES.has("file_list")).toBe(true);
186
+ });
187
+
188
+ test("isToolActiveForContext hides file_list when isSubagent is false", () => {
189
+ const ctx: SkillProjectionContext = {
190
+ skillProjectionState: new Map(),
191
+ skillProjectionCache: {},
192
+ coreToolNames: new Set(["file_list"]),
193
+ toolsDisabledDepth: 0,
194
+ };
195
+ expect(isToolActiveForContext("file_list", ctx)).toBe(false);
196
+ });
197
+
198
+ test("isToolActiveForContext hides file_list when isSubagent is undefined", () => {
199
+ const ctx: SkillProjectionContext = {
200
+ skillProjectionState: new Map(),
201
+ skillProjectionCache: {},
202
+ coreToolNames: new Set(["file_list"]),
203
+ toolsDisabledDepth: 0,
204
+ isSubagent: undefined,
205
+ };
206
+ expect(isToolActiveForContext("file_list", ctx)).toBe(false);
207
+ });
208
+
209
+ test("isToolActiveForContext shows file_list when isSubagent is true", () => {
210
+ const ctx: SkillProjectionContext = {
211
+ skillProjectionState: new Map(),
212
+ skillProjectionCache: {},
213
+ coreToolNames: new Set(["file_list"]),
214
+ toolsDisabledDepth: 0,
215
+ isSubagent: true,
216
+ };
217
+ expect(isToolActiveForContext("file_list", ctx)).toBe(true);
218
+ });
219
+ });
@@ -53,7 +53,7 @@ describe("first-greeting", () => {
53
53
  const greeting = getCannedFirstGreeting();
54
54
  expect(greeting).toBe(CANNED_FIRST_GREETING);
55
55
  expect(greeting).toContain("brand new");
56
- expect(greeting).toContain("no name, no memories");
56
+ expect(greeting).toContain("No name, no memories");
57
57
  });
58
58
  });
59
59
  });
@@ -1,4 +1,4 @@
1
- import { rmSync, writeFileSync } from "node:fs";
1
+ import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
4
4
 
@@ -18,6 +18,9 @@ let mockConfig = {
18
18
  mock.module("../config/loader.js", () => ({
19
19
  getConfig: () => mockConfig,
20
20
  loadConfig: () => mockConfig,
21
+ loadRawConfig: () => ({}),
22
+ saveRawConfig: () => {},
23
+ invalidateConfigCache: () => {},
21
24
  }));
22
25
 
23
26
  // Mock conversation store
@@ -43,6 +46,7 @@ mock.module("../memory/conversation-crud.js", () => ({
43
46
  totalEstimatedCost: 0,
44
47
  title: null,
45
48
  }),
49
+ getMessageById: () => null,
46
50
  provenanceFromTrustContext: () => ({
47
51
  source: "user",
48
52
  trustContext: undefined,
@@ -72,7 +76,16 @@ mock.module("../memory/conversation-title-service.js", () => ({
72
76
  }));
73
77
 
74
78
  // Import after mocks are set up
75
- const { HeartbeatService } = await import("../heartbeat/heartbeat-service.js");
79
+ const { HeartbeatService, isShallowProfile } =
80
+ await import("../heartbeat/heartbeat-service.js");
81
+
82
+ // Read the bundled template files so we can write them into the test workspace
83
+ const templatesDir = join(import.meta.dirname!, "..", "prompts", "templates");
84
+ const IDENTITY_TEMPLATE = readFileSync(
85
+ join(templatesDir, "IDENTITY.md"),
86
+ "utf-8",
87
+ );
88
+ const USER_TEMPLATE = readFileSync(join(templatesDir, "USER.md"), "utf-8");
76
89
 
77
90
  describe("HeartbeatService", () => {
78
91
  let processMessageCalls: Array<{
@@ -83,8 +96,11 @@ describe("HeartbeatService", () => {
83
96
  let alerterCalls: Array<{ type: string; title: string; body: string }>;
84
97
 
85
98
  afterEach(() => {
86
- // Clean up HEARTBEAT.md between tests so file-existence tests don't leak
99
+ // Clean up workspace files between tests so file-existence tests don't leak
87
100
  rmSync(join(testWorkspaceDir, "HEARTBEAT.md"), { force: true });
101
+ rmSync(join(testWorkspaceDir, "IDENTITY.md"), { force: true });
102
+ rmSync(join(testWorkspaceDir, "USER.md"), { force: true });
103
+ rmSync(join(testWorkspaceDir, ".reengagement-ts"), { force: true });
88
104
  });
89
105
 
90
106
  beforeEach(() => {
@@ -228,6 +244,24 @@ describe("HeartbeatService", () => {
228
244
  expect(processMessageCalls).toHaveLength(0);
229
245
  });
230
246
 
247
+ test("active hours skip still advances nextRunAt", async () => {
248
+ mockConfig.heartbeat.activeHoursStart = 9;
249
+ mockConfig.heartbeat.activeHoursEnd = 17;
250
+
251
+ const service = createService({ getCurrentHour: () => 3 });
252
+ service.start();
253
+
254
+ const before = Date.now();
255
+ await service.runOnce();
256
+
257
+ expect(processMessageCalls).toHaveLength(0);
258
+ expect(service.nextRunAt).not.toBeNull();
259
+ expect(service.nextRunAt!).toBeGreaterThanOrEqual(
260
+ before + mockConfig.heartbeat.intervalMs,
261
+ );
262
+ service.stop();
263
+ });
264
+
231
265
  test("active hours guard allows within window", async () => {
232
266
  mockConfig.heartbeat.activeHoursStart = 9;
233
267
  mockConfig.heartbeat.activeHoursEnd = 17;
@@ -360,6 +394,22 @@ describe("HeartbeatService", () => {
360
394
  expect(alerterCalls[0].body).toBe("LLM timeout");
361
395
  });
362
396
 
397
+ test("successful run updates lastRunAt and nextRunAt", async () => {
398
+ const service = createService();
399
+ expect(service.lastRunAt).toBeNull();
400
+ expect(service.nextRunAt).toBeNull();
401
+
402
+ const before = Date.now();
403
+ await service.runOnce();
404
+
405
+ expect(service.lastRunAt).not.toBeNull();
406
+ expect(service.lastRunAt!).toBeGreaterThanOrEqual(before);
407
+ expect(service.nextRunAt).not.toBeNull();
408
+ expect(service.nextRunAt!).toBeGreaterThanOrEqual(
409
+ before + mockConfig.heartbeat.intervalMs,
410
+ );
411
+ });
412
+
363
413
  test("alerts on conversation creation failure", async () => {
364
414
  // Override createConversation to throw via a fresh import trick:
365
415
  // Since createConversation is mocked at module level, we simulate
@@ -442,4 +492,131 @@ describe("HeartbeatService", () => {
442
492
  expect(processMessageCalls).toHaveLength(1);
443
493
  expect(processMessageCalls[0].options?.speed).toBe("fast");
444
494
  });
495
+
496
+ describe("isShallowProfile", () => {
497
+ test("returns true when both IDENTITY.md and USER.md are unmodified templates", () => {
498
+ writeFileSync(join(testWorkspaceDir, "IDENTITY.md"), IDENTITY_TEMPLATE);
499
+ writeFileSync(join(testWorkspaceDir, "USER.md"), USER_TEMPLATE);
500
+
501
+ expect(isShallowProfile()).toBe(true);
502
+ });
503
+
504
+ test("returns false when IDENTITY.md has been customized", () => {
505
+ writeFileSync(
506
+ join(testWorkspaceDir, "IDENTITY.md"),
507
+ "# IDENTITY.md\n\n- **Name:** Jarvis\n",
508
+ );
509
+ writeFileSync(join(testWorkspaceDir, "USER.md"), USER_TEMPLATE);
510
+
511
+ expect(isShallowProfile()).toBe(false);
512
+ });
513
+
514
+ test("returns false when USER.md has been customized", () => {
515
+ writeFileSync(join(testWorkspaceDir, "IDENTITY.md"), IDENTITY_TEMPLATE);
516
+ writeFileSync(
517
+ join(testWorkspaceDir, "USER.md"),
518
+ "# USER.md\n\n- Preferred name/reference: Alice\n",
519
+ );
520
+
521
+ expect(isShallowProfile()).toBe(false);
522
+ });
523
+
524
+ test("returns false when neither file exists", () => {
525
+ expect(isShallowProfile()).toBe(false);
526
+ });
527
+ });
528
+
529
+ describe("relationship-depth prompt injection", () => {
530
+ test("includes <relationship-depth> when profile is shallow", () => {
531
+ writeFileSync(join(testWorkspaceDir, "IDENTITY.md"), IDENTITY_TEMPLATE);
532
+ writeFileSync(join(testWorkspaceDir, "USER.md"), USER_TEMPLATE);
533
+
534
+ const service = createService();
535
+ const { prompt, includedReengagement } =
536
+ service.buildPrompt("- Check things");
537
+
538
+ expect(prompt).toContain("<relationship-depth>");
539
+ expect(prompt).toContain("profile is still sparse");
540
+ expect(includedReengagement).toBe(true);
541
+ });
542
+
543
+ test("omits <relationship-depth> when profile is not shallow", () => {
544
+ writeFileSync(
545
+ join(testWorkspaceDir, "IDENTITY.md"),
546
+ "# IDENTITY.md\n\n- **Name:** Jarvis\n",
547
+ );
548
+ writeFileSync(join(testWorkspaceDir, "USER.md"), USER_TEMPLATE);
549
+
550
+ const service = createService();
551
+ const { prompt, includedReengagement } =
552
+ service.buildPrompt("- Check things");
553
+
554
+ expect(prompt).not.toContain("<relationship-depth>");
555
+ expect(includedReengagement).toBe(false);
556
+ });
557
+
558
+ test("omits <relationship-depth> when cooldown has not elapsed", () => {
559
+ writeFileSync(join(testWorkspaceDir, "IDENTITY.md"), IDENTITY_TEMPLATE);
560
+ writeFileSync(join(testWorkspaceDir, "USER.md"), USER_TEMPLATE);
561
+ // Write a recent timestamp to simulate cooldown not elapsed
562
+ writeFileSync(
563
+ join(testWorkspaceDir, ".reengagement-ts"),
564
+ Date.now().toString(),
565
+ );
566
+
567
+ const service = createService();
568
+ const { prompt, includedReengagement } =
569
+ service.buildPrompt("- Check things");
570
+
571
+ expect(prompt).not.toContain("<relationship-depth>");
572
+ expect(includedReengagement).toBe(false);
573
+ });
574
+
575
+ test("includes <relationship-depth> when cooldown has elapsed", () => {
576
+ writeFileSync(join(testWorkspaceDir, "IDENTITY.md"), IDENTITY_TEMPLATE);
577
+ writeFileSync(join(testWorkspaceDir, "USER.md"), USER_TEMPLATE);
578
+ // Write a timestamp from 19 hours ago
579
+ const nineteenHoursAgo = Date.now() - 19 * 60 * 60 * 1000;
580
+ writeFileSync(
581
+ join(testWorkspaceDir, ".reengagement-ts"),
582
+ nineteenHoursAgo.toString(),
583
+ );
584
+
585
+ const service = createService();
586
+ const { prompt, includedReengagement } =
587
+ service.buildPrompt("- Check things");
588
+
589
+ expect(prompt).toContain("<relationship-depth>");
590
+ expect(includedReengagement).toBe(true);
591
+ });
592
+
593
+ test("does not record timestamp when processMessage fails", async () => {
594
+ writeFileSync(join(testWorkspaceDir, "IDENTITY.md"), IDENTITY_TEMPLATE);
595
+ writeFileSync(join(testWorkspaceDir, "USER.md"), USER_TEMPLATE);
596
+
597
+ const service = createService({
598
+ processMessage: async () => {
599
+ throw new Error("LLM timeout");
600
+ },
601
+ });
602
+
603
+ await service.runOnce();
604
+
605
+ // The reengagement timestamp file should NOT exist since delivery failed
606
+ const tsPath = join(testWorkspaceDir, ".reengagement-ts");
607
+ expect(existsSync(tsPath)).toBe(false);
608
+ });
609
+
610
+ test("records timestamp after successful delivery", async () => {
611
+ writeFileSync(join(testWorkspaceDir, "IDENTITY.md"), IDENTITY_TEMPLATE);
612
+ writeFileSync(join(testWorkspaceDir, "USER.md"), USER_TEMPLATE);
613
+
614
+ const service = createService();
615
+ await service.runOnce();
616
+
617
+ // The reengagement timestamp file should exist after successful delivery
618
+ const tsPath = join(testWorkspaceDir, ".reengagement-ts");
619
+ expect(existsSync(tsPath)).toBe(true);
620
+ });
621
+ });
445
622
  });