@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
@@ -0,0 +1,562 @@
1
+ import { mkdirSync, rmSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
4
+
5
+ import { Command } from "commander";
6
+
7
+ import { getWorkspaceRoutesDir } from "../../../util/platform.js";
8
+
9
+ // ---------------------------------------------------------------------------
10
+ // Mock state
11
+ // ---------------------------------------------------------------------------
12
+
13
+ let mockPublicBaseUrl: string | null = null;
14
+
15
+ // ---------------------------------------------------------------------------
16
+ // Mocks
17
+ // ---------------------------------------------------------------------------
18
+
19
+ mock.module("../../../config/loader.js", () => ({
20
+ getConfig: () => ({
21
+ ingress: mockPublicBaseUrl
22
+ ? { publicBaseUrl: mockPublicBaseUrl }
23
+ : undefined,
24
+ }),
25
+ }));
26
+
27
+ mock.module("../../../inbound/public-ingress-urls.js", () => ({
28
+ getPublicBaseUrl: (config: { ingress?: { publicBaseUrl?: string } }) => {
29
+ const url = config.ingress?.publicBaseUrl;
30
+ if (!url) throw new Error("No public base URL configured");
31
+ return url;
32
+ },
33
+ }));
34
+
35
+ mock.module("../../../util/logger.js", () => ({
36
+ getLogger: () => ({
37
+ info: () => {},
38
+ warn: () => {},
39
+ error: () => {},
40
+ debug: () => {},
41
+ }),
42
+ getCliLogger: () => ({
43
+ info: () => {},
44
+ warn: () => {},
45
+ error: () => {},
46
+ debug: () => {},
47
+ }),
48
+ }));
49
+
50
+ // ---------------------------------------------------------------------------
51
+ // Import module under test (after mocks are registered)
52
+ // ---------------------------------------------------------------------------
53
+
54
+ const { registerRoutesCommand } = await import("../routes.js");
55
+
56
+ // ---------------------------------------------------------------------------
57
+ // Test helper
58
+ // ---------------------------------------------------------------------------
59
+
60
+ async function runCommand(
61
+ args: string[],
62
+ ): Promise<{ stdout: string; exitCode: number }> {
63
+ const originalStdoutWrite = process.stdout.write.bind(process.stdout);
64
+ const originalStderrWrite = process.stderr.write.bind(process.stderr);
65
+ const originalConsoleLog = console.log.bind(console);
66
+ const stdoutChunks: string[] = [];
67
+
68
+ process.stdout.write = ((chunk: unknown) => {
69
+ stdoutChunks.push(typeof chunk === "string" ? chunk : String(chunk));
70
+ return true;
71
+ }) as typeof process.stdout.write;
72
+
73
+ process.stderr.write = (() => true) as typeof process.stderr.write;
74
+
75
+ console.log = (...logArgs: unknown[]) => {
76
+ stdoutChunks.push(
77
+ logArgs.map((a) => (typeof a === "string" ? a : String(a))).join(" ") +
78
+ "\n",
79
+ );
80
+ };
81
+
82
+ process.exitCode = 0;
83
+
84
+ try {
85
+ const program = new Command();
86
+ program.exitOverride();
87
+ program.configureOutput({
88
+ writeErr: () => {},
89
+ writeOut: (str: string) => stdoutChunks.push(str),
90
+ });
91
+ registerRoutesCommand(program);
92
+ await program.parseAsync(["node", "assistant", ...args]);
93
+ } catch {
94
+ if (process.exitCode === 0) process.exitCode = 1;
95
+ } finally {
96
+ process.stdout.write = originalStdoutWrite;
97
+ process.stderr.write = originalStderrWrite;
98
+ console.log = originalConsoleLog;
99
+ }
100
+
101
+ const exitCode = process.exitCode ?? 0;
102
+ process.exitCode = 0;
103
+
104
+ return { exitCode, stdout: stdoutChunks.join("") };
105
+ }
106
+
107
+ // ---------------------------------------------------------------------------
108
+ // Helpers for writing handler files into the workspace routes dir
109
+ // ---------------------------------------------------------------------------
110
+
111
+ let routesDir: string;
112
+
113
+ function writeHandler(relativePath: string, content: string): void {
114
+ const fullPath = join(routesDir, relativePath);
115
+ const dir = fullPath.substring(0, fullPath.lastIndexOf("/"));
116
+ mkdirSync(dir, { recursive: true });
117
+ writeFileSync(fullPath, content, "utf-8");
118
+ }
119
+
120
+ // ---------------------------------------------------------------------------
121
+ // Setup / teardown
122
+ // ---------------------------------------------------------------------------
123
+
124
+ beforeEach(() => {
125
+ routesDir = getWorkspaceRoutesDir();
126
+ mkdirSync(routesDir, { recursive: true });
127
+ mockPublicBaseUrl = null;
128
+ process.exitCode = 0;
129
+ });
130
+
131
+ afterEach(() => {
132
+ try {
133
+ rmSync(routesDir, { recursive: true, force: true });
134
+ } catch {
135
+ /* best-effort cleanup */
136
+ }
137
+ });
138
+
139
+ // ---------------------------------------------------------------------------
140
+ // routes list
141
+ // ---------------------------------------------------------------------------
142
+
143
+ describe("assistant routes list", () => {
144
+ test("empty routes dir returns zero routes in JSON", async () => {
145
+ const { exitCode, stdout } = await runCommand(["routes", "list", "--json"]);
146
+ expect(exitCode).toBe(0);
147
+ const parsed = JSON.parse(stdout);
148
+ expect(parsed.ok).toBe(true);
149
+ expect(parsed.routes).toEqual([]);
150
+ });
151
+
152
+ test("empty routes dir shows guidance in human output", async () => {
153
+ const { exitCode } = await runCommand(["routes", "list"]);
154
+ expect(exitCode).toBe(0);
155
+ });
156
+
157
+ test("discovers a single GET handler", async () => {
158
+ writeHandler(
159
+ "status.ts",
160
+ `export async function GET(req: Request) { return new Response("ok"); }`,
161
+ );
162
+
163
+ const { exitCode, stdout } = await runCommand(["routes", "list", "--json"]);
164
+ expect(exitCode).toBe(0);
165
+ const parsed = JSON.parse(stdout);
166
+ expect(parsed.ok).toBe(true);
167
+ expect(parsed.routes).toHaveLength(1);
168
+ expect(parsed.routes[0].routePath).toBe("/x/status");
169
+ expect(parsed.routes[0].methods).toEqual(["GET"]);
170
+ });
171
+
172
+ test("discovers multiple routes sorted alphabetically", async () => {
173
+ writeHandler(
174
+ "zebra.ts",
175
+ `export function GET() { return new Response("z"); }`,
176
+ );
177
+ writeHandler(
178
+ "alpha.ts",
179
+ `export function POST() { return new Response("a"); }`,
180
+ );
181
+
182
+ const { exitCode, stdout } = await runCommand(["routes", "list", "--json"]);
183
+ expect(exitCode).toBe(0);
184
+ const parsed = JSON.parse(stdout);
185
+ expect(parsed.routes).toHaveLength(2);
186
+ expect(parsed.routes[0].routePath).toBe("/x/alpha");
187
+ expect(parsed.routes[1].routePath).toBe("/x/zebra");
188
+ });
189
+
190
+ test("discovers multi-method handler", async () => {
191
+ writeHandler(
192
+ "items.ts",
193
+ [
194
+ `export function GET() { return new Response("list"); }`,
195
+ `export function POST() { return new Response("create"); }`,
196
+ `export function DELETE() { return new Response("remove"); }`,
197
+ ].join("\n"),
198
+ );
199
+
200
+ const { exitCode, stdout } = await runCommand(["routes", "list", "--json"]);
201
+ expect(exitCode).toBe(0);
202
+ const parsed = JSON.parse(stdout);
203
+ expect(parsed.routes[0].methods).toEqual(["GET", "POST", "DELETE"]);
204
+ });
205
+
206
+ test("discovers index file as directory route", async () => {
207
+ writeHandler(
208
+ "my-app/index.ts",
209
+ `export function GET() { return new Response("app"); }`,
210
+ );
211
+
212
+ const { exitCode, stdout } = await runCommand(["routes", "list", "--json"]);
213
+ expect(exitCode).toBe(0);
214
+ const parsed = JSON.parse(stdout);
215
+ expect(parsed.routes).toHaveLength(1);
216
+ expect(parsed.routes[0].routePath).toBe("/x/my-app");
217
+ });
218
+
219
+ test("discovers subdirectory routes", async () => {
220
+ writeHandler(
221
+ "api/v1/users.ts",
222
+ `export function GET() { return new Response("users"); }`,
223
+ );
224
+
225
+ const { exitCode, stdout } = await runCommand(["routes", "list", "--json"]);
226
+ expect(exitCode).toBe(0);
227
+ const parsed = JSON.parse(stdout);
228
+ expect(parsed.routes[0].routePath).toBe("/x/api/v1/users");
229
+ });
230
+
231
+ test("discovers .js handlers", async () => {
232
+ writeHandler(
233
+ "health.js",
234
+ `export function GET() { return new Response("ok"); }`,
235
+ );
236
+
237
+ const { exitCode, stdout } = await runCommand(["routes", "list", "--json"]);
238
+ expect(exitCode).toBe(0);
239
+ const parsed = JSON.parse(stdout);
240
+ expect(parsed.routes).toHaveLength(1);
241
+ expect(parsed.routes[0].routePath).toBe("/x/health");
242
+ });
243
+
244
+ test("extracts description export", async () => {
245
+ writeHandler(
246
+ "submit.ts",
247
+ [
248
+ `export const description = "Form submission handler";`,
249
+ `export function POST() { return new Response("ok"); }`,
250
+ ].join("\n"),
251
+ );
252
+
253
+ const { exitCode, stdout } = await runCommand(["routes", "list", "--json"]);
254
+ expect(exitCode).toBe(0);
255
+ const parsed = JSON.parse(stdout);
256
+ expect(parsed.routes[0].description).toBe("Form submission handler");
257
+ });
258
+
259
+ test("null description when not exported", async () => {
260
+ writeHandler(
261
+ "simple.ts",
262
+ `export function GET() { return new Response("ok"); }`,
263
+ );
264
+
265
+ const { exitCode, stdout } = await runCommand(["routes", "list", "--json"]);
266
+ expect(exitCode).toBe(0);
267
+ const parsed = JSON.parse(stdout);
268
+ expect(parsed.routes[0].description).toBeNull();
269
+ });
270
+
271
+ test("includes publicUrl when public base URL is configured", async () => {
272
+ mockPublicBaseUrl = "https://example.ngrok-free.app/v1/assistants/asst_xyz";
273
+ writeHandler(
274
+ "status.ts",
275
+ `export function GET() { return new Response("ok"); }`,
276
+ );
277
+
278
+ const { exitCode, stdout } = await runCommand(["routes", "list", "--json"]);
279
+ expect(exitCode).toBe(0);
280
+ const parsed = JSON.parse(stdout);
281
+ expect(parsed.routes[0].publicUrl).toBe(
282
+ "https://example.ngrok-free.app/v1/assistants/asst_xyz/x/status",
283
+ );
284
+ });
285
+
286
+ test("publicUrl is null when no public base URL configured", async () => {
287
+ mockPublicBaseUrl = null;
288
+ writeHandler(
289
+ "status.ts",
290
+ `export function GET() { return new Response("ok"); }`,
291
+ );
292
+
293
+ const { exitCode, stdout } = await runCommand(["routes", "list", "--json"]);
294
+ expect(exitCode).toBe(0);
295
+ const parsed = JSON.parse(stdout);
296
+ expect(parsed.routes[0].publicUrl).toBeNull();
297
+ });
298
+
299
+ test("ignores non-handler files", async () => {
300
+ writeHandler("readme.md", "# Routes\nDocumentation file");
301
+ writeHandler(
302
+ "handler.ts",
303
+ `export function GET() { return new Response("ok"); }`,
304
+ );
305
+
306
+ const { exitCode, stdout } = await runCommand(["routes", "list", "--json"]);
307
+ expect(exitCode).toBe(0);
308
+ const parsed = JSON.parse(stdout);
309
+ expect(parsed.routes).toHaveLength(1);
310
+ expect(parsed.routes[0].routePath).toBe("/x/handler");
311
+ });
312
+
313
+ test("human output runs without error for populated routes", async () => {
314
+ writeHandler(
315
+ "status.ts",
316
+ `export function GET() { return new Response("ok"); }`,
317
+ );
318
+
319
+ const { exitCode } = await runCommand(["routes", "list"]);
320
+ expect(exitCode).toBe(0);
321
+ });
322
+
323
+ test("root index file maps to /x/", async () => {
324
+ writeHandler(
325
+ "index.ts",
326
+ `export function GET() { return new Response("root"); }`,
327
+ );
328
+
329
+ const { exitCode, stdout } = await runCommand(["routes", "list", "--json"]);
330
+ expect(exitCode).toBe(0);
331
+ const parsed = JSON.parse(stdout);
332
+ expect(parsed.routes).toHaveLength(1);
333
+ expect(parsed.routes[0].routePath).toBe("/x/");
334
+ });
335
+
336
+ test("JSON output includes filePath relative to routes dir", async () => {
337
+ writeHandler(
338
+ "api/submit.ts",
339
+ `export function POST() { return new Response("ok"); }`,
340
+ );
341
+
342
+ const { exitCode, stdout } = await runCommand(["routes", "list", "--json"]);
343
+ expect(exitCode).toBe(0);
344
+ const parsed = JSON.parse(stdout);
345
+ expect(parsed.routes[0].filePath).toBe("api/submit.ts");
346
+ });
347
+ });
348
+
349
+ // ---------------------------------------------------------------------------
350
+ // routes inspect
351
+ // ---------------------------------------------------------------------------
352
+
353
+ describe("assistant routes inspect", () => {
354
+ test("inspects a handler by route path (JSON)", async () => {
355
+ writeHandler(
356
+ "status.ts",
357
+ [
358
+ `export const description = "Health check endpoint";`,
359
+ `export function GET() { return new Response("ok"); }`,
360
+ `export function POST() { return new Response("created"); }`,
361
+ ].join("\n"),
362
+ );
363
+
364
+ const { exitCode, stdout } = await runCommand([
365
+ "routes",
366
+ "inspect",
367
+ "status",
368
+ "--json",
369
+ ]);
370
+ expect(exitCode).toBe(0);
371
+ const parsed = JSON.parse(stdout);
372
+ expect(parsed.ok).toBe(true);
373
+ expect(parsed.route.routePath).toBe("/x/status");
374
+ expect(parsed.route.methods).toEqual(["GET", "POST"]);
375
+ expect(parsed.route.description).toBe("Health check endpoint");
376
+ expect(parsed.route.filePath).toContain("status.ts");
377
+ expect(parsed.route.fileSize).toBeGreaterThan(0);
378
+ expect(parsed.route.modifiedAt).toBeTruthy();
379
+ });
380
+
381
+ test("inspect resolves index file convention", async () => {
382
+ writeHandler(
383
+ "dashboard/index.ts",
384
+ `export function GET() { return new Response("dashboard"); }`,
385
+ );
386
+
387
+ const { exitCode, stdout } = await runCommand([
388
+ "routes",
389
+ "inspect",
390
+ "dashboard",
391
+ "--json",
392
+ ]);
393
+ expect(exitCode).toBe(0);
394
+ const parsed = JSON.parse(stdout);
395
+ expect(parsed.ok).toBe(true);
396
+ expect(parsed.route.routePath).toBe("/x/dashboard");
397
+ expect(parsed.route.methods).toEqual(["GET"]);
398
+ expect(parsed.route.filePath).toContain("index.ts");
399
+ });
400
+
401
+ test("inspect resolves .js files", async () => {
402
+ writeHandler(
403
+ "legacy.js",
404
+ `export function POST() { return new Response("ok"); }`,
405
+ );
406
+
407
+ const { exitCode, stdout } = await runCommand([
408
+ "routes",
409
+ "inspect",
410
+ "legacy",
411
+ "--json",
412
+ ]);
413
+ expect(exitCode).toBe(0);
414
+ const parsed = JSON.parse(stdout);
415
+ expect(parsed.ok).toBe(true);
416
+ expect(parsed.route.filePath).toContain("legacy.js");
417
+ });
418
+
419
+ test("inspect includes publicUrl when configured", async () => {
420
+ mockPublicBaseUrl = "https://example.com/v1/assistants/asst_1";
421
+ writeHandler(
422
+ "submit.ts",
423
+ `export function POST() { return new Response("ok"); }`,
424
+ );
425
+
426
+ const { exitCode, stdout } = await runCommand([
427
+ "routes",
428
+ "inspect",
429
+ "submit",
430
+ "--json",
431
+ ]);
432
+ expect(exitCode).toBe(0);
433
+ const parsed = JSON.parse(stdout);
434
+ expect(parsed.route.publicUrl).toBe(
435
+ "https://example.com/v1/assistants/asst_1/x/submit",
436
+ );
437
+ });
438
+
439
+ test("inspect publicUrl is null when not configured", async () => {
440
+ mockPublicBaseUrl = null;
441
+ writeHandler(
442
+ "submit.ts",
443
+ `export function POST() { return new Response("ok"); }`,
444
+ );
445
+
446
+ const { exitCode, stdout } = await runCommand([
447
+ "routes",
448
+ "inspect",
449
+ "submit",
450
+ "--json",
451
+ ]);
452
+ expect(exitCode).toBe(0);
453
+ const parsed = JSON.parse(stdout);
454
+ expect(parsed.route.publicUrl).toBeNull();
455
+ });
456
+
457
+ test("inspect returns error for missing handler (JSON)", async () => {
458
+ const { exitCode, stdout } = await runCommand([
459
+ "routes",
460
+ "inspect",
461
+ "nonexistent",
462
+ "--json",
463
+ ]);
464
+ expect(exitCode).toBe(1);
465
+ const parsed = JSON.parse(stdout);
466
+ expect(parsed.ok).toBe(false);
467
+ expect(parsed.error).toContain("No handler file found");
468
+ expect(parsed.error).toContain("nonexistent");
469
+ });
470
+
471
+ test("inspect returns error for missing handler (human output)", async () => {
472
+ const { exitCode } = await runCommand(["routes", "inspect", "nonexistent"]);
473
+ expect(exitCode).toBe(1);
474
+ });
475
+
476
+ test("inspect handles subdirectory routes", async () => {
477
+ writeHandler(
478
+ "api/v2/users.ts",
479
+ `export function GET() { return new Response("users"); }`,
480
+ );
481
+
482
+ const { exitCode, stdout } = await runCommand([
483
+ "routes",
484
+ "inspect",
485
+ "api/v2/users",
486
+ "--json",
487
+ ]);
488
+ expect(exitCode).toBe(0);
489
+ const parsed = JSON.parse(stdout);
490
+ expect(parsed.ok).toBe(true);
491
+ expect(parsed.route.routePath).toBe("/x/api/v2/users");
492
+ });
493
+
494
+ test("inspect human output runs without error", async () => {
495
+ writeHandler(
496
+ "check.ts",
497
+ `export function GET() { return new Response("ok"); }`,
498
+ );
499
+
500
+ const { exitCode } = await runCommand(["routes", "inspect", "check"]);
501
+ expect(exitCode).toBe(0);
502
+ });
503
+
504
+ test("inspect shows handler with no exported methods", async () => {
505
+ writeHandler("empty.ts", `export const description = "Placeholder";`);
506
+
507
+ const { exitCode, stdout } = await runCommand([
508
+ "routes",
509
+ "inspect",
510
+ "empty",
511
+ "--json",
512
+ ]);
513
+ expect(exitCode).toBe(0);
514
+ const parsed = JSON.parse(stdout);
515
+ expect(parsed.route.methods).toEqual([]);
516
+ expect(parsed.route.description).toBe("Placeholder");
517
+ });
518
+
519
+ test("inspect prefers direct file over index file", async () => {
520
+ writeHandler(
521
+ "ambiguous.ts",
522
+ `export function GET() { return new Response("direct"); }`,
523
+ );
524
+ writeHandler(
525
+ "ambiguous/index.ts",
526
+ `export function POST() { return new Response("index"); }`,
527
+ );
528
+
529
+ const { exitCode, stdout } = await runCommand([
530
+ "routes",
531
+ "inspect",
532
+ "ambiguous",
533
+ "--json",
534
+ ]);
535
+ expect(exitCode).toBe(0);
536
+ const parsed = JSON.parse(stdout);
537
+ // Direct file should be preferred over index
538
+ expect(parsed.route.methods).toEqual(["GET"]);
539
+ });
540
+
541
+ test("inspect prefers .ts over .js", async () => {
542
+ writeHandler(
543
+ "both.ts",
544
+ `export function GET() { return new Response("ts"); }`,
545
+ );
546
+ writeHandler(
547
+ "both.js",
548
+ `export function POST() { return new Response("js"); }`,
549
+ );
550
+
551
+ const { exitCode, stdout } = await runCommand([
552
+ "routes",
553
+ "inspect",
554
+ "both",
555
+ "--json",
556
+ ]);
557
+ expect(exitCode).toBe(0);
558
+ const parsed = JSON.parse(stdout);
559
+ // .ts is checked first in HANDLER_EXTENSIONS
560
+ expect(parsed.route.methods).toEqual(["GET"]);
561
+ });
562
+ });
@@ -375,12 +375,6 @@ Examples:
375
375
  targetId: segId,
376
376
  });
377
377
  }
378
- for (const itemId of result.orphanedItemIds) {
379
- enqueueMemoryJob("delete_qdrant_vectors", {
380
- targetType: "item",
381
- targetId: itemId,
382
- });
383
- }
384
378
  for (const summaryId of result.deletedSummaryIds) {
385
379
  enqueueMemoryJob("delete_qdrant_vectors", {
386
380
  targetType: "summary",
@@ -390,8 +384,7 @@ Examples:
390
384
 
391
385
  log.info(
392
386
  `Wiped conversation "${conversation.title ?? "Untitled"}". ` +
393
- `Restored ${result.unsupersededItemIds.length} memory items, ` +
394
- `deleted ${result.deletedSummaryIds.length} summaries, ` +
387
+ `Deleted ${result.deletedSummaryIds.length} summaries, ` +
395
388
  `cancelled ${result.cancelledJobCount} jobs.`,
396
389
  );
397
390
  });