@vellumai/assistant 0.6.0 → 0.6.1

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 (285) 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 +32 -1
  6. package/docs/architecture/integrations.md +1 -1
  7. package/docs/architecture/memory.md +21 -24
  8. package/openapi.yaml +538 -3
  9. package/package.json +5 -1
  10. package/src/__tests__/anthropic-provider.test.ts +160 -95
  11. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  12. package/src/__tests__/app-executors.test.ts +47 -1
  13. package/src/__tests__/app-source-watcher.test.ts +159 -0
  14. package/src/__tests__/checker.test.ts +38 -6
  15. package/src/__tests__/config-schema.test.ts +5 -0
  16. package/src/__tests__/conversation-agent-loop-overflow.test.ts +4 -6
  17. package/src/__tests__/conversation-agent-loop.test.ts +4 -51
  18. package/src/__tests__/conversation-history-web-search.test.ts +1 -1
  19. package/src/__tests__/conversation-runtime-assembly.test.ts +653 -832
  20. package/src/__tests__/conversation-runtime-workspace.test.ts +1 -93
  21. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +17 -4
  22. package/src/__tests__/conversation-wipe.test.ts +2 -6
  23. package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -12
  24. package/src/__tests__/conversation-workspace-injection.test.ts +25 -26
  25. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
  26. package/src/__tests__/copy-composer-tc-templates.test.ts +335 -0
  27. package/src/__tests__/date-context.test.ts +76 -210
  28. package/src/__tests__/db-schedule-syntax-migration.test.ts +16 -1
  29. package/src/__tests__/file-list-tool.test.ts +219 -0
  30. package/src/__tests__/first-greeting.test.ts +1 -1
  31. package/src/__tests__/heartbeat-service.test.ts +180 -3
  32. package/src/__tests__/identity-routes.test.ts +328 -0
  33. package/src/__tests__/injection-block.test.ts +24 -0
  34. package/src/__tests__/install-skill-routing.test.ts +7 -6
  35. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +15 -14
  36. package/src/__tests__/list-messages-tool-merge.test.ts +300 -0
  37. package/src/__tests__/llm-context-normalization.test.ts +18 -18
  38. package/src/__tests__/llm-context-route-provider.test.ts +101 -0
  39. package/src/__tests__/llm-request-log-turn-query.test.ts +162 -0
  40. package/src/__tests__/log-export-workspace.test.ts +72 -105
  41. package/src/__tests__/mcp-abort-signal.test.ts +5 -0
  42. package/src/__tests__/mcp-client-auth.test.ts +5 -0
  43. package/src/__tests__/memory-recall-log-store.test.ts +132 -0
  44. package/src/__tests__/migration-export-streaming.test.ts +304 -0
  45. package/src/__tests__/migration-import-commit-http.test.ts +11 -10
  46. package/src/__tests__/mock-fetch.ts +87 -0
  47. package/src/__tests__/notification-decision-recipient-context.test.ts +282 -0
  48. package/src/__tests__/onboarding-template-contract.test.ts +62 -14
  49. package/src/__tests__/parser.test.ts +32 -0
  50. package/src/__tests__/permission-checker-host-gate.test.ts +452 -0
  51. package/src/__tests__/permission-controls-v2-flag.test.ts +55 -0
  52. package/src/__tests__/permission-mode-sse.test.ts +418 -0
  53. package/src/__tests__/permission-mode-store.test.ts +277 -0
  54. package/src/__tests__/permission-mode.test.ts +101 -0
  55. package/src/__tests__/platform-bash-auto-approve.test.ts +359 -0
  56. package/src/__tests__/profiler-routes.test.ts +502 -0
  57. package/src/__tests__/profiler-run-store.test.ts +441 -0
  58. package/src/__tests__/proxy-approval-callback.test.ts +4 -75
  59. package/src/__tests__/registry.test.ts +1 -1
  60. package/src/__tests__/sandbox-host-parity.test.ts +5 -4
  61. package/src/__tests__/scheduler-reuse-conversation.test.ts +368 -0
  62. package/src/__tests__/scrub-corrupted-image-attachments.test.ts +278 -0
  63. package/src/__tests__/search-skills-unified.test.ts +4 -3
  64. package/src/__tests__/send-endpoint-busy.test.ts +42 -3
  65. package/src/__tests__/set-permission-mode.test.ts +274 -0
  66. package/src/__tests__/skill-load-feature-flag.test.ts +12 -0
  67. package/src/__tests__/skill-memory.test.ts +2 -783
  68. package/src/__tests__/strip-memory-injections.test.ts +187 -0
  69. package/src/__tests__/subagent-detail.test.ts +84 -0
  70. package/src/__tests__/subagent-disposal.test.ts +308 -0
  71. package/src/__tests__/subagent-manager-notify.test.ts +19 -10
  72. package/src/__tests__/subagent-notify-parent.test.ts +390 -0
  73. package/src/__tests__/subagent-role-registry.test.ts +108 -0
  74. package/src/__tests__/subagent-tool-filtering.test.ts +71 -0
  75. package/src/__tests__/subagent-tools.test.ts +464 -4
  76. package/src/__tests__/system-prompt-ask-mode.test.ts +139 -0
  77. package/src/__tests__/task-memory-cleanup.test.ts +12 -12
  78. package/src/__tests__/terminal-tools.test.ts +17 -27
  79. package/src/__tests__/test-preload.ts +4 -0
  80. package/src/__tests__/tool-executor.test.ts +4 -26
  81. package/src/__tests__/tool-side-effects-slack-dm.test.ts +1 -0
  82. package/src/__tests__/top-level-renderer.test.ts +10 -13
  83. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +116 -2
  84. package/src/__tests__/workspace-migration-028-recover-conversations-from-disk-view.test.ts +387 -0
  85. package/src/agent/loop.ts +6 -0
  86. package/src/approvals/guardian-request-resolvers.ts +24 -0
  87. package/src/avatar/traits-png-sync.ts +3 -3
  88. package/src/cli/__tests__/run-assistant-command.ts +29 -0
  89. package/src/cli/commands/__tests__/email-download.test.ts +245 -0
  90. package/src/cli/commands/__tests__/email-list.test.ts +192 -0
  91. package/src/cli/commands/__tests__/email-register.test.ts +186 -0
  92. package/src/cli/commands/__tests__/email-send.test.ts +291 -0
  93. package/src/cli/commands/__tests__/email-status.test.ts +181 -0
  94. package/src/cli/commands/__tests__/email-unregister.test.ts +139 -0
  95. package/src/cli/commands/__tests__/routes.test.ts +562 -0
  96. package/src/cli/commands/conversations.ts +1 -8
  97. package/src/cli/commands/email.ts +584 -835
  98. package/src/cli/commands/memory.ts +1 -34
  99. package/src/cli/commands/notifications.ts +7 -2
  100. package/src/cli/commands/oauth/connect.ts +14 -5
  101. package/src/cli/commands/routes.ts +396 -0
  102. package/src/cli/commands/skills.ts +130 -20
  103. package/src/cli/program.ts +2 -0
  104. package/src/cli.ts +1 -120
  105. package/src/config/bundled-skills/app-builder/SKILL.md +4 -1
  106. package/src/config/bundled-skills/gmail/SKILL.md +2 -2
  107. package/src/config/bundled-skills/messaging/SKILL.md +7 -0
  108. package/src/config/bundled-skills/schedule/SKILL.md +22 -2
  109. package/src/config/bundled-skills/schedule/TOOLS.json +8 -0
  110. package/src/config/bundled-skills/settings/tools/avatar-get.ts +3 -13
  111. package/src/config/bundled-skills/settings/tools/avatar-remove.ts +2 -4
  112. package/src/config/bundled-skills/settings/tools/avatar-update.ts +5 -2
  113. package/src/config/bundled-skills/slack/SKILL.md +2 -0
  114. package/src/config/bundled-skills/subagent/SKILL.md +43 -3
  115. package/src/config/bundled-skills/subagent/TOOLS.json +29 -4
  116. package/src/config/env-registry.ts +63 -0
  117. package/src/config/feature-flag-registry.json +17 -1
  118. package/src/config/schema.ts +8 -0
  119. package/src/config/schemas/filing.ts +51 -0
  120. package/src/config/schemas/heartbeat.ts +15 -12
  121. package/src/config/schemas/memory-lifecycle.ts +12 -0
  122. package/src/config/schemas/security.ts +14 -0
  123. package/src/daemon/app-source-watcher.ts +93 -0
  124. package/src/daemon/config-watcher.ts +79 -1
  125. package/src/daemon/conversation-agent-loop-handlers.ts +20 -0
  126. package/src/daemon/conversation-agent-loop.ts +158 -65
  127. package/src/daemon/conversation-history.ts +4 -19
  128. package/src/daemon/conversation-lifecycle.ts +8 -14
  129. package/src/daemon/conversation-process.ts +13 -7
  130. package/src/daemon/conversation-runtime-assembly.ts +300 -306
  131. package/src/daemon/conversation-tool-setup.ts +44 -14
  132. package/src/daemon/conversation-workspace.ts +1 -2
  133. package/src/daemon/conversation.ts +18 -0
  134. package/src/daemon/date-context.ts +26 -53
  135. package/src/daemon/first-greeting.ts +1 -1
  136. package/src/daemon/handlers/conversations.ts +4 -7
  137. package/src/daemon/handlers/shared.test.ts +143 -0
  138. package/src/daemon/handlers/shared.ts +63 -5
  139. package/src/daemon/handlers/skills.ts +11 -18
  140. package/src/daemon/lifecycle.ts +199 -157
  141. package/src/daemon/message-types/conversations.ts +25 -6
  142. package/src/daemon/message-types/messages.ts +9 -1
  143. package/src/daemon/message-types/schedules.ts +1 -0
  144. package/src/daemon/message-types/settings.ts +6 -0
  145. package/src/daemon/profiler-run-store.ts +557 -0
  146. package/src/daemon/server.ts +89 -9
  147. package/src/daemon/shutdown-handlers.ts +5 -0
  148. package/src/daemon/tool-side-effects.ts +23 -3
  149. package/src/export/transcript-formatter.ts +148 -0
  150. package/src/filing/filing-service.ts +228 -0
  151. package/src/heartbeat/heartbeat-service.ts +96 -7
  152. package/src/mcp/client.ts +6 -0
  153. package/src/mcp/mcp-oauth-provider.ts +149 -27
  154. package/src/memory/admin.ts +33 -32
  155. package/src/memory/app-store.ts +69 -0
  156. package/src/memory/conversation-bootstrap.ts +1 -1
  157. package/src/memory/conversation-crud.ts +136 -107
  158. package/src/memory/conversation-group-migration.ts +1 -1
  159. package/src/memory/conversation-queries.ts +58 -12
  160. package/src/memory/conversation-title-service.ts +1 -0
  161. package/src/memory/db-init.ts +182 -376
  162. package/src/memory/graph/bootstrap.ts +75 -66
  163. package/src/memory/graph/capability-seed.ts +167 -15
  164. package/src/memory/graph/consolidation.ts +38 -4
  165. package/src/memory/graph/conversation-graph-memory.ts +133 -104
  166. package/src/memory/graph/extraction-job.ts +9 -4
  167. package/src/memory/graph/extraction.ts +66 -23
  168. package/src/memory/graph/graph-memory-state-store.ts +37 -0
  169. package/src/memory/graph/graph-search.ts +29 -15
  170. package/src/memory/graph/injection.ts +38 -8
  171. package/src/memory/graph/inspect.ts +12 -3
  172. package/src/memory/graph/retriever.ts +365 -262
  173. package/src/memory/graph/store.test.ts +48 -0
  174. package/src/memory/graph/store.ts +150 -11
  175. package/src/memory/graph/tool-handlers.ts +84 -209
  176. package/src/memory/graph/tools.ts +8 -52
  177. package/src/memory/graph/types.ts +24 -0
  178. package/src/memory/job-handlers/cleanup.ts +44 -1
  179. package/src/memory/jobs-store.ts +70 -60
  180. package/src/memory/jobs-worker.ts +44 -28
  181. package/src/memory/llm-request-log-store.ts +96 -12
  182. package/src/memory/memory-recall-log-store.ts +49 -5
  183. package/src/memory/migrations/203-drop-memory-items-tables.ts +33 -1
  184. package/src/memory/migrations/206-memory-graph-node-edits.ts +19 -0
  185. package/src/memory/migrations/206-scrub-corrupted-image-attachments.ts +131 -0
  186. package/src/memory/migrations/207-conversation-graph-memory-state.ts +20 -0
  187. package/src/memory/migrations/208-conversations-last-message-at.ts +35 -0
  188. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +85 -0
  189. package/src/memory/migrations/210-schedule-reuse-conversation.ts +13 -0
  190. package/src/memory/migrations/211-memory-recall-logs-query-context.ts +21 -0
  191. package/src/memory/migrations/212-llm-request-logs-created-at-index.ts +19 -0
  192. package/src/memory/migrations/index.ts +8 -0
  193. package/src/memory/migrations/registry.ts +8 -0
  194. package/src/memory/schema/conversations.ts +14 -0
  195. package/src/memory/schema/infrastructure.ts +8 -1
  196. package/src/memory/schema/memory-core.ts +0 -51
  197. package/src/memory/schema/memory-graph.ts +15 -0
  198. package/src/memory/task-memory-cleanup.ts +30 -11
  199. package/src/notifications/copy-composer.ts +86 -0
  200. package/src/notifications/decision-engine.ts +35 -0
  201. package/src/permissions/checker.ts +12 -1
  202. package/src/permissions/permission-mode-store.ts +180 -0
  203. package/src/permissions/permission-mode.ts +31 -0
  204. package/src/permissions/workspace-policy.ts +9 -0
  205. package/src/prompts/system-prompt.ts +59 -7
  206. package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +100 -0
  207. package/src/prompts/templates/BOOTSTRAP.md +70 -165
  208. package/src/prompts/templates/HEARTBEAT.md +3 -1
  209. package/src/prompts/templates/SOUL.md +25 -4
  210. package/src/prompts/templates/UPDATES.md +8 -0
  211. package/src/providers/anthropic/client.ts +107 -219
  212. package/src/runtime/auth/route-policy.ts +23 -0
  213. package/src/runtime/http-server.ts +32 -2
  214. package/src/runtime/http-types.ts +12 -1
  215. package/src/runtime/migrations/vbundle-builder.ts +389 -3
  216. package/src/runtime/migrations/vbundle-importer.ts +8 -6
  217. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +378 -0
  218. package/src/runtime/routes/app-management-routes.ts +1 -11
  219. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +26 -0
  220. package/src/runtime/routes/archive-utils.ts +29 -0
  221. package/src/runtime/routes/avatar-routes.ts +2 -9
  222. package/src/runtime/routes/btw-routes.ts +14 -1
  223. package/src/runtime/routes/conversation-analysis-routes.ts +173 -0
  224. package/src/runtime/routes/conversation-management-routes.ts +1 -14
  225. package/src/runtime/routes/conversation-query-routes.ts +49 -3
  226. package/src/runtime/routes/conversation-routes.ts +264 -44
  227. package/src/runtime/routes/heartbeat-routes.ts +4 -10
  228. package/src/runtime/routes/identity-routes.ts +53 -18
  229. package/src/runtime/routes/llm-context-normalization.ts +14 -10
  230. package/src/runtime/routes/log-export-routes.ts +23 -275
  231. package/src/runtime/routes/memory-item-routes.test.ts +168 -233
  232. package/src/runtime/routes/migration-routes.ts +18 -7
  233. package/src/runtime/routes/profiler-routes.ts +350 -0
  234. package/src/runtime/routes/schedule-routes.ts +27 -12
  235. package/src/runtime/routes/settings-routes.ts +95 -8
  236. package/src/runtime/routes/subagents-routes.ts +28 -7
  237. package/src/runtime/routes/user-route-dispatcher.ts +223 -0
  238. package/src/runtime/routes/user-routes.ts +41 -0
  239. package/src/runtime/routes/workspace-routes.ts +0 -1
  240. package/src/schedule/schedule-store.ts +30 -0
  241. package/src/schedule/scheduler.ts +45 -18
  242. package/src/skills/catalog-install.ts +10 -2
  243. package/src/skills/managed-store.ts +2 -2
  244. package/src/skills/skill-memory.ts +1 -293
  245. package/src/subagent/index.ts +13 -3
  246. package/src/subagent/manager.ts +308 -29
  247. package/src/subagent/types.ts +68 -0
  248. package/src/tasks/task-runner.ts +4 -4
  249. package/src/tools/apps/executors.ts +29 -4
  250. package/src/tools/filesystem/list.ts +93 -0
  251. package/src/tools/permission-checker.ts +78 -0
  252. package/src/tools/registry.ts +4 -0
  253. package/src/tools/schedule/create.ts +3 -0
  254. package/src/tools/schedule/list.ts +1 -0
  255. package/src/tools/schedule/update.ts +6 -0
  256. package/src/tools/shared/filesystem/errors.ts +5 -0
  257. package/src/tools/shared/filesystem/file-ops-service.ts +90 -2
  258. package/src/tools/shared/filesystem/types.ts +17 -0
  259. package/src/tools/shared/shell-output.ts +31 -2
  260. package/src/tools/subagent/abort.ts +12 -2
  261. package/src/tools/subagent/message.ts +9 -2
  262. package/src/tools/subagent/notify-parent.ts +79 -0
  263. package/src/tools/subagent/read.ts +29 -8
  264. package/src/tools/subagent/resolve.ts +21 -0
  265. package/src/tools/subagent/spawn.ts +2 -0
  266. package/src/tools/subagent/status.ts +11 -1
  267. package/src/tools/system/avatar-generator.ts +3 -3
  268. package/src/tools/system/register.ts +23 -0
  269. package/src/tools/system/set-permission-mode.ts +103 -0
  270. package/src/tools/terminal/parser.ts +30 -5
  271. package/src/tools/terminal/safe-env.ts +16 -1
  272. package/src/tools/tool-manifest.ts +6 -0
  273. package/src/tools/types.ts +2 -0
  274. package/src/util/logger.ts +1 -1
  275. package/src/util/platform.ts +50 -17
  276. package/src/workspace/migrations/023-move-config-files-to-workspace.ts +2 -2
  277. package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +2 -2
  278. package/src/workspace/migrations/028-recover-conversations-from-disk-view.ts +270 -0
  279. package/src/workspace/migrations/029-seed-pkb.ts +84 -0
  280. package/src/workspace/migrations/registry.ts +4 -0
  281. package/src/workspace/top-level-renderer.ts +5 -9
  282. package/src/__tests__/cli-memory.test.ts +0 -377
  283. package/src/__tests__/clipboard.test.ts +0 -88
  284. package/src/cli/cli-memory.ts +0 -179
  285. package/src/util/clipboard.ts +0 -34
@@ -1,377 +0,0 @@
1
- import { rmSync } from "node:fs";
2
- import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
3
-
4
- import { Command } from "commander";
5
- import { eq, like } from "drizzle-orm";
6
-
7
- mock.module("../util/logger.js", () => ({
8
- getLogger: () =>
9
- new Proxy({} as Record<string, unknown>, {
10
- get: () => () => {},
11
- }),
12
- }));
13
-
14
- mock.module("../memory/qdrant-client.js", () => ({
15
- getQdrantClient: () => ({
16
- searchWithFilter: async () => [],
17
- hybridSearch: async () => [],
18
- upsertPoints: async () => {},
19
- deletePoints: async () => {},
20
- }),
21
- initQdrantClient: () => {},
22
- }));
23
-
24
- // Controllable mock for buildCliProgram
25
- let mockCommands: { name: string; description: string }[] = [];
26
-
27
- function makeMockProgram(): Command {
28
- const program = new Command();
29
- for (const cmd of mockCommands) {
30
- program.command(cmd.name).description(cmd.description);
31
- }
32
- return program;
33
- }
34
-
35
- mock.module("../cli/program.js", () => ({
36
- buildCliProgram: () => makeMockProgram(),
37
- }));
38
-
39
- import { DEFAULT_CONFIG } from "../config/defaults.js";
40
-
41
- const TEST_CONFIG = {
42
- ...DEFAULT_CONFIG,
43
- memory: {
44
- ...DEFAULT_CONFIG.memory,
45
- enabled: true,
46
- extraction: {
47
- ...DEFAULT_CONFIG.memory.extraction,
48
- useLLM: false,
49
- },
50
- },
51
- };
52
-
53
- mock.module("../config/loader.js", () => ({
54
- loadConfig: () => TEST_CONFIG,
55
- getConfig: () => TEST_CONFIG,
56
- loadRawConfig: () => ({}),
57
- saveRawConfig: () => {},
58
- invalidateConfigCache: () => {},
59
- }));
60
-
61
- import {
62
- buildCliCapabilityStatement,
63
- seedCliCommandMemories,
64
- upsertCliCapabilityMemory,
65
- } from "../cli/cli-memory.js";
66
- import { getDb, initializeDb, resetDb } from "../memory/db.js";
67
- import { memoryGraphNodes, memoryJobs } from "../memory/schema.js";
68
- import { ensureDataDir, getDbPath } from "../util/platform.js";
69
-
70
- ensureDataDir();
71
- initializeDb();
72
-
73
- afterAll(() => {
74
- resetDb();
75
- });
76
-
77
- function resetTables() {
78
- const db = getDb();
79
- db.run("DELETE FROM memory_embeddings");
80
- db.run("DELETE FROM memory_graph_nodes");
81
- db.run("DELETE FROM memory_jobs");
82
- }
83
-
84
- // ─── buildCliCapabilityStatement ────────────────────────────────────────────
85
-
86
- describe("buildCliCapabilityStatement", () => {
87
- test("includes 'assistant' prefix, name, and description", () => {
88
- const result = buildCliCapabilityStatement("doctor", "Run diagnostic checks");
89
- expect(result).toContain('"assistant doctor"');
90
- expect(result).toContain("Run diagnostic checks");
91
- });
92
-
93
- test("truncates long statements to 500 chars", () => {
94
- const longDesc = "x".repeat(600);
95
- const result = buildCliCapabilityStatement("test", longDesc);
96
- expect(result.length).toBe(500);
97
- });
98
- });
99
-
100
- // ─── upsertCliCapabilityMemory ──────────────────────────────────────────────
101
-
102
- describe("upsertCliCapabilityMemory", () => {
103
- beforeEach(resetTables);
104
-
105
- test("inserts with correct type, content, confidence, significance", () => {
106
- upsertCliCapabilityMemory("doctor", "Run diagnostic checks");
107
-
108
- const db = getDb();
109
- const items = db.select().from(memoryGraphNodes).all();
110
- expect(items).toHaveLength(1);
111
- expect(items[0].type).toBe("procedural");
112
- expect(items[0].content).toMatch(/^cli:doctor\n/);
113
- expect(items[0].confidence).toBe(1.0);
114
- expect(items[0].significance).toBe(0.7);
115
- expect(items[0].fidelity).toBe("vivid");
116
- expect(items[0].scopeId).toBe("default");
117
-
118
- // Should also enqueue an embed_graph_node job
119
- const jobs = db.select().from(memoryJobs).all();
120
- expect(jobs).toHaveLength(1);
121
- expect(jobs[0].type).toBe("embed_graph_node");
122
- });
123
-
124
- test("is idempotent (same entry only touches lastAccessed)", () => {
125
- upsertCliCapabilityMemory("doctor", "Run diagnostic checks");
126
-
127
- const db = getDb();
128
- const before = db.select().from(memoryGraphNodes).all();
129
- expect(before).toHaveLength(1);
130
- const originalLastAccessed = before[0].lastAccessed;
131
-
132
- // Upsert again
133
- upsertCliCapabilityMemory("doctor", "Run diagnostic checks");
134
-
135
- const after = db.select().from(memoryGraphNodes).all();
136
- expect(after).toHaveLength(1);
137
- // Same content, so only lastAccessed changes
138
- expect(after[0].content).toBe(before[0].content);
139
- expect(after[0].lastAccessed).toBeGreaterThanOrEqual(originalLastAccessed);
140
-
141
- // Should NOT enqueue a second embed job (only 1 from initial insert)
142
- const jobs = db.select().from(memoryJobs).all();
143
- expect(jobs).toHaveLength(1);
144
- });
145
-
146
- test("updates content when description changes", () => {
147
- upsertCliCapabilityMemory("doctor", "Original description");
148
-
149
- const db = getDb();
150
- const before = db.select().from(memoryGraphNodes).all();
151
- expect(before).toHaveLength(1);
152
- expect(before[0].content).toContain("Original description");
153
-
154
- // Change description
155
- upsertCliCapabilityMemory("doctor", "Updated description");
156
-
157
- const after = db.select().from(memoryGraphNodes).all();
158
- expect(after).toHaveLength(1);
159
- expect(after[0].content).toContain("Updated description");
160
- expect(after[0].content).not.toBe(before[0].content);
161
-
162
- // Should enqueue a second embed job
163
- const jobs = db.select().from(memoryJobs).all();
164
- expect(jobs).toHaveLength(2);
165
- });
166
-
167
- test("reactivates soft-deleted items", () => {
168
- upsertCliCapabilityMemory("doctor", "Run diagnostic checks");
169
-
170
- const db = getDb();
171
- // Soft-delete the item
172
- db.update(memoryGraphNodes)
173
- .set({ fidelity: "gone" })
174
- .where(like(memoryGraphNodes.content, "cli:doctor\n%"))
175
- .run();
176
-
177
- const deleted = db.select().from(memoryGraphNodes).all();
178
- expect(deleted[0].fidelity).toBe("gone");
179
-
180
- // Clear jobs from initial insert
181
- db.run("DELETE FROM memory_jobs");
182
-
183
- // Upsert again — should reactivate
184
- upsertCliCapabilityMemory("doctor", "Run diagnostic checks");
185
-
186
- const reactivated = db.select().from(memoryGraphNodes).all();
187
- expect(reactivated).toHaveLength(1);
188
- expect(reactivated[0].fidelity).toBe("vivid");
189
-
190
- // Should enqueue embed job for reactivated item
191
- const jobs = db.select().from(memoryJobs).all();
192
- expect(jobs).toHaveLength(1);
193
- expect(jobs[0].type).toBe("embed_graph_node");
194
- });
195
-
196
- test("does not throw on DB error", () => {
197
- resetDb();
198
- const db = getDb();
199
- db.run("DROP TABLE IF EXISTS memory_graph_nodes");
200
-
201
- expect(() => {
202
- upsertCliCapabilityMemory("doctor", "Run diagnostic checks");
203
- }).not.toThrow();
204
-
205
- // Restore DB state for subsequent tests.
206
- resetDb();
207
- const dbPath = getDbPath();
208
- for (const ext of ["", "-wal", "-shm"]) {
209
- rmSync(`${dbPath}${ext}`, { force: true });
210
- }
211
- initializeDb();
212
- });
213
- });
214
-
215
- // ─── seedCliCommandMemories ─────────────────────────────────────────────────
216
-
217
- describe("seedCliCommandMemories", () => {
218
- beforeEach(() => {
219
- resetTables();
220
- // Reset mock commands
221
- mockCommands = [];
222
- });
223
-
224
- test("upserts capability memories for all commands", () => {
225
- mockCommands = [
226
- { name: "doctor", description: "Run diagnostic checks" },
227
- { name: "config", description: "Manage configuration" },
228
- { name: "keys", description: "Manage API keys" },
229
- ];
230
-
231
- seedCliCommandMemories();
232
-
233
- const db = getDb();
234
- const items = db
235
- .select()
236
- .from(memoryGraphNodes)
237
- .where(eq(memoryGraphNodes.type, "procedural"))
238
- .all();
239
- expect(items).toHaveLength(3);
240
-
241
- const contentPrefixes = items.map((i) => i.content.split("\n")[0]).sort();
242
- expect(contentPrefixes).toEqual([
243
- "cli:config",
244
- "cli:doctor",
245
- "cli:keys",
246
- ]);
247
-
248
- // All should be vivid
249
- for (const item of items) {
250
- expect(item.fidelity).toBe("vivid");
251
- }
252
- });
253
-
254
- test("prunes stale capabilities for commands no longer registered", () => {
255
- // First seed with three commands
256
- mockCommands = [
257
- { name: "doctor", description: "Run diagnostic checks" },
258
- { name: "config", description: "Manage configuration" },
259
- { name: "keys", description: "Manage API keys" },
260
- ];
261
- seedCliCommandMemories();
262
-
263
- const db = getDb();
264
- const beforeItems = db
265
- .select()
266
- .from(memoryGraphNodes)
267
- .where(eq(memoryGraphNodes.type, "procedural"))
268
- .all();
269
- expect(beforeItems).toHaveLength(3);
270
- expect(beforeItems.every((i) => i.fidelity === "vivid")).toBe(true);
271
-
272
- // Now seed with only doctor — config and keys should be pruned
273
- mockCommands = [
274
- { name: "doctor", description: "Run diagnostic checks" },
275
- ];
276
- seedCliCommandMemories();
277
-
278
- const afterItems = db
279
- .select()
280
- .from(memoryGraphNodes)
281
- .where(eq(memoryGraphNodes.type, "procedural"))
282
- .all();
283
- expect(afterItems).toHaveLength(3); // still 3 rows, but 2 are soft-deleted
284
-
285
- const active = afterItems.filter((i) => i.fidelity === "vivid");
286
- const deleted = afterItems.filter((i) => i.fidelity === "gone");
287
-
288
- expect(active).toHaveLength(1);
289
- expect(active[0].content).toMatch(/^cli:doctor\n/);
290
-
291
- expect(deleted).toHaveLength(2);
292
- const deletedPrefixes = deleted.map((i) => i.content.split("\n")[0]).sort();
293
- expect(deletedPrefixes).toEqual(["cli:config", "cli:keys"]);
294
- });
295
-
296
- test("handles empty command list without errors", () => {
297
- // Pre-populate a CLI command so we can verify it gets pruned
298
- upsertCliCapabilityMemory("old-command", "An old command");
299
-
300
- const db = getDb();
301
- const beforeItems = db.select().from(memoryGraphNodes).all();
302
- expect(beforeItems).toHaveLength(1);
303
- expect(beforeItems[0].fidelity).toBe("vivid");
304
-
305
- // Seed with empty commands
306
- mockCommands = [];
307
- seedCliCommandMemories();
308
-
309
- // The existing command should be pruned (soft-deleted)
310
- const afterItems = db.select().from(memoryGraphNodes).all();
311
- expect(afterItems).toHaveLength(1);
312
- expect(afterItems[0].fidelity).toBe("gone");
313
- });
314
-
315
- test("does not prune non-cli capability memories", () => {
316
- // Pre-insert a skill capability memory directly into the DB
317
- const db = getDb();
318
- const now = Date.now();
319
- db.insert(memoryGraphNodes)
320
- .values({
321
- id: "skill-test-item",
322
- type: "procedural",
323
- content: "skill:test-skill\nThe test skill does things.",
324
- fidelity: "vivid",
325
- confidence: 1.0,
326
- significance: 0.7,
327
- sourceType: "inferred",
328
- scopeId: "default",
329
- created: now,
330
- lastAccessed: now,
331
- lastConsolidated: now,
332
- emotionalCharge: '{"valence":0,"intensity":0.1,"decayCurve":"linear","decayRate":0.05,"originalIntensity":0.1}',
333
- stability: 14,
334
- reinforcementCount: 0,
335
- lastReinforced: now,
336
- sourceConversations: "[]",
337
- narrativeRole: null,
338
- partOfStory: null,
339
- })
340
- .run();
341
-
342
- // Seed with empty commands — CLI pruner runs but should skip skill:* items
343
- mockCommands = [];
344
- seedCliCommandMemories();
345
-
346
- const item = db
347
- .select()
348
- .from(memoryGraphNodes)
349
- .where(like(memoryGraphNodes.content, "skill:test-skill\n%"))
350
- .get();
351
- expect(item).toBeDefined();
352
- expect(item!.fidelity).toBe("vivid");
353
- });
354
-
355
- test("does not throw on error", () => {
356
- mockCommands = [
357
- { name: "doctor", description: "Run diagnostic checks" },
358
- ];
359
-
360
- // Drop memory_graph_nodes to force a DB error during the prune phase
361
- resetDb();
362
- const db = getDb();
363
- db.run("DROP TABLE IF EXISTS memory_graph_nodes");
364
-
365
- expect(() => {
366
- seedCliCommandMemories();
367
- }).not.toThrow();
368
-
369
- // Restore DB state for subsequent tests.
370
- resetDb();
371
- const dbPath = getDbPath();
372
- for (const ext of ["", "-wal", "-shm"]) {
373
- rmSync(`${dbPath}${ext}`, { force: true });
374
- }
375
- initializeDb();
376
- });
377
- });
@@ -1,88 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
-
3
- import {
4
- extractLastCodeBlock,
5
- formatConversationForExport,
6
- } from "../util/clipboard.js";
7
-
8
- describe("formatConversationForExport", () => {
9
- test("formats user and assistant messages", () => {
10
- const messages = [
11
- { role: "user", text: "Hello" },
12
- { role: "assistant", text: "Hi there!" },
13
- ];
14
- expect(formatConversationForExport(messages)).toBe(
15
- "you> Hello\n\nassistant> Hi there!",
16
- );
17
- });
18
-
19
- test("handles a single message", () => {
20
- const messages = [{ role: "user", text: "Just me" }];
21
- expect(formatConversationForExport(messages)).toBe("you> Just me");
22
- });
23
-
24
- test("handles empty messages array", () => {
25
- expect(formatConversationForExport([])).toBe("");
26
- });
27
-
28
- test("preserves multiline message text", () => {
29
- const messages = [{ role: "assistant", text: "Line 1\nLine 2\nLine 3" }];
30
- expect(formatConversationForExport(messages)).toBe(
31
- "assistant> Line 1\nLine 2\nLine 3",
32
- );
33
- });
34
- });
35
-
36
- describe("extractLastCodeBlock", () => {
37
- test("extracts a simple code block", () => {
38
- const text = "```\nhello world\n```";
39
- expect(extractLastCodeBlock(text)).toBe("hello world");
40
- });
41
-
42
- test("extracts code block with language tag", () => {
43
- const text = "```typescript\nconst x = 1;\n```";
44
- expect(extractLastCodeBlock(text)).toBe("const x = 1;");
45
- });
46
-
47
- test("returns the last code block when multiple exist", () => {
48
- const text = "```\nfirst\n```\nsome text\n```\nsecond\n```";
49
- expect(extractLastCodeBlock(text)).toBe("second");
50
- });
51
-
52
- test("handles empty code blocks", () => {
53
- const text = "```\n```";
54
- expect(extractLastCodeBlock(text)).toBe("");
55
- });
56
-
57
- test("handles empty code blocks with language tag", () => {
58
- const text = "```python\n```";
59
- expect(extractLastCodeBlock(text)).toBe("");
60
- });
61
-
62
- test("does not match inline backticks as closing fence", () => {
63
- const text = '```\nconst s = "```"\n```';
64
- expect(extractLastCodeBlock(text)).toBe('const s = "```"');
65
- });
66
-
67
- test("handles multi-line code blocks", () => {
68
- const text = "```js\nfunction foo() {\n return 42;\n}\n```";
69
- expect(extractLastCodeBlock(text)).toBe(
70
- "function foo() {\n return 42;\n}",
71
- );
72
- });
73
-
74
- test("returns null when no code blocks exist", () => {
75
- expect(extractLastCodeBlock("no code here")).toBeNull();
76
- expect(extractLastCodeBlock("`inline code`")).toBeNull();
77
- });
78
-
79
- test("extracts last block when separated by text", () => {
80
- const text = "```\nfirst\n```\nSome explanation\n```\nsecond\n```";
81
- expect(extractLastCodeBlock(text)).toBe("second");
82
- });
83
-
84
- test("handles non-empty block followed by empty block with text between", () => {
85
- const text = "```\nreal code\n```\nSome text\n```\n```";
86
- expect(extractLastCodeBlock(text)).toBe("");
87
- });
88
- });
@@ -1,179 +0,0 @@
1
- import { and, eq, like, sql } from "drizzle-orm";
2
- import { v4 as uuid } from "uuid";
3
-
4
- import { getDb } from "../memory/db.js";
5
- import { enqueueMemoryJob } from "../memory/jobs-store.js";
6
- import { memoryGraphNodes } from "../memory/schema.js";
7
- import { getLogger } from "../util/logger.js";
8
- import { buildCliProgram } from "./program.js";
9
-
10
- const log = getLogger("cli-memory");
11
-
12
- /**
13
- * Build a capability statement for a CLI command.
14
- * Truncated to 500 chars max (matching the limit used by memory item extraction).
15
- */
16
- export function buildCliCapabilityStatement(
17
- name: string,
18
- description: string,
19
- ): string {
20
- let statement = `The "assistant ${name}" CLI command is available. ${description}.`;
21
-
22
- // Truncate to 500 chars max
23
- if (statement.length > 500) {
24
- statement = statement.slice(0, 500);
25
- }
26
-
27
- return statement;
28
- }
29
-
30
- /** Default emotional charge for capability graph nodes. */
31
- const DEFAULT_EMOTIONAL_CHARGE = JSON.stringify({
32
- valence: 0,
33
- intensity: 0.1,
34
- decayCurve: "linear",
35
- decayRate: 0.05,
36
- originalIntensity: 0.1,
37
- });
38
-
39
- /**
40
- * Upsert a capability memory graph node for a CLI command.
41
- * Best-effort: errors are logged but never thrown.
42
- */
43
- export function upsertCliCapabilityMemory(
44
- commandName: string,
45
- description: string,
46
- ): void {
47
- try {
48
- const db = getDb();
49
- const statement = buildCliCapabilityStatement(commandName, description);
50
- const content = `cli:${commandName}\n${statement}`;
51
- const scopeId = "default";
52
- const now = Date.now();
53
-
54
- const existing = db
55
- .select()
56
- .from(memoryGraphNodes)
57
- .where(
58
- and(
59
- eq(memoryGraphNodes.type, "procedural"),
60
- like(memoryGraphNodes.content, `cli:${commandName}\n%`),
61
- eq(memoryGraphNodes.scopeId, scopeId),
62
- ),
63
- )
64
- .get();
65
-
66
- if (existing) {
67
- if (
68
- existing.content === content &&
69
- existing.fidelity !== "gone"
70
- ) {
71
- // Same content — just touch lastAccessed
72
- db.update(memoryGraphNodes)
73
- .set({ lastAccessed: now })
74
- .where(eq(memoryGraphNodes.id, existing.id))
75
- .run();
76
- return;
77
- }
78
-
79
- if (existing.fidelity !== "gone") {
80
- // Content changed — update content
81
- db.update(memoryGraphNodes)
82
- .set({
83
- content,
84
- lastAccessed: now,
85
- })
86
- .where(eq(memoryGraphNodes.id, existing.id))
87
- .run();
88
- enqueueMemoryJob("embed_graph_node", { nodeId: existing.id });
89
- return;
90
- }
91
-
92
- // fidelity === "gone" — reactivate
93
- db.update(memoryGraphNodes)
94
- .set({
95
- fidelity: "vivid",
96
- content,
97
- created: now,
98
- lastAccessed: now,
99
- })
100
- .where(eq(memoryGraphNodes.id, existing.id))
101
- .run();
102
- enqueueMemoryJob("embed_graph_node", { nodeId: existing.id });
103
- return;
104
- }
105
-
106
- // No existing — insert new graph node
107
- const id = uuid();
108
- db.insert(memoryGraphNodes)
109
- .values({
110
- id,
111
- content,
112
- type: "procedural",
113
- created: now,
114
- lastAccessed: now,
115
- lastConsolidated: now,
116
- emotionalCharge: DEFAULT_EMOTIONAL_CHARGE,
117
- fidelity: "vivid",
118
- confidence: 1.0,
119
- significance: 0.7,
120
- stability: 14,
121
- reinforcementCount: 0,
122
- lastReinforced: now,
123
- sourceConversations: JSON.stringify([]),
124
- sourceType: "inferred",
125
- narrativeRole: null,
126
- partOfStory: null,
127
- scopeId,
128
- })
129
- .run();
130
- enqueueMemoryJob("embed_graph_node", { nodeId: id });
131
- } catch (err) {
132
- log.warn({ err, commandName }, "Failed to upsert CLI capability memory");
133
- }
134
- }
135
-
136
- /**
137
- * Seed capability memory graph nodes for all CLI commands.
138
- * Prunes stale entries whose commands are no longer registered.
139
- * Best-effort: errors are logged but never thrown.
140
- */
141
- export function seedCliCommandMemories(): void {
142
- try {
143
- const program = buildCliProgram();
144
- const commandNames = new Set<string>();
145
-
146
- for (const cmd of program.commands) {
147
- commandNames.add(cmd.name());
148
- upsertCliCapabilityMemory(cmd.name(), cmd.description());
149
- }
150
-
151
- // Prune stale capability memories for commands no longer registered
152
- const db = getDb();
153
- const allCapabilities = db
154
- .select()
155
- .from(memoryGraphNodes)
156
- .where(
157
- and(
158
- eq(memoryGraphNodes.type, "procedural"),
159
- eq(memoryGraphNodes.scopeId, "default"),
160
- sql`${memoryGraphNodes.fidelity} != 'gone'`,
161
- ),
162
- )
163
- .all();
164
-
165
- const now = Date.now();
166
- for (const item of allCapabilities) {
167
- if (!item.content.startsWith("cli:")) continue;
168
- const itemCommandName = item.content.split("\n")[0].replace("cli:", "");
169
- if (!commandNames.has(itemCommandName)) {
170
- db.update(memoryGraphNodes)
171
- .set({ fidelity: "gone", lastAccessed: now })
172
- .where(eq(memoryGraphNodes.id, item.id))
173
- .run();
174
- }
175
- }
176
- } catch (err) {
177
- log.warn({ err }, "Failed to seed CLI command memories");
178
- }
179
- }
@@ -1,34 +0,0 @@
1
- import { execSync } from "node:child_process";
2
-
3
- import { PlatformError } from "./errors.js";
4
- import { getClipboardCommand } from "./platform.js";
5
-
6
- export function copyToClipboard(text: string): void {
7
- const cmd = getClipboardCommand();
8
- if (!cmd) {
9
- throw new PlatformError("Clipboard not supported on this platform");
10
- }
11
- execSync(cmd, { input: text, stdio: ["pipe", "ignore", "ignore"] });
12
- }
13
-
14
- export function formatConversationForExport(
15
- messages: Array<{ role: string; text: string }>,
16
- ): string {
17
- return messages
18
- .map((m) => {
19
- const label = m.role === "user" ? "you" : "assistant";
20
- return `${label}> ${m.text}`;
21
- })
22
- .join("\n\n");
23
- }
24
-
25
- export function extractLastCodeBlock(text: string): string | null {
26
- const re = /```[^\n]*\n((?:[\s\S]*?\n)?)```/g;
27
- let last: RegExpExecArray | null = null;
28
- let m: RegExpExecArray | null;
29
- while ((m = re.exec(text)) != null) {
30
- last = m;
31
- }
32
- if (!last) return null;
33
- return last[1].trimEnd();
34
- }