@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.
- package/AGENTS.md +4 -0
- package/ARCHITECTURE.md +68 -15
- package/Dockerfile +2 -2
- package/bun.lock +6 -2
- package/docker-entrypoint.sh +32 -1
- package/docs/architecture/integrations.md +1 -1
- package/docs/architecture/memory.md +21 -24
- package/openapi.yaml +538 -3
- package/package.json +5 -1
- package/src/__tests__/anthropic-provider.test.ts +160 -95
- package/src/__tests__/app-dir-path-guard.test.ts +1 -0
- package/src/__tests__/app-executors.test.ts +47 -1
- package/src/__tests__/app-source-watcher.test.ts +159 -0
- package/src/__tests__/checker.test.ts +38 -6
- package/src/__tests__/config-schema.test.ts +5 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +4 -6
- package/src/__tests__/conversation-agent-loop.test.ts +4 -51
- package/src/__tests__/conversation-history-web-search.test.ts +1 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +653 -832
- package/src/__tests__/conversation-runtime-workspace.test.ts +1 -93
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +17 -4
- package/src/__tests__/conversation-wipe.test.ts +2 -6
- package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -12
- package/src/__tests__/conversation-workspace-injection.test.ts +25 -26
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
- package/src/__tests__/copy-composer-tc-templates.test.ts +335 -0
- package/src/__tests__/date-context.test.ts +76 -210
- package/src/__tests__/db-schedule-syntax-migration.test.ts +16 -1
- package/src/__tests__/file-list-tool.test.ts +219 -0
- package/src/__tests__/first-greeting.test.ts +1 -1
- package/src/__tests__/heartbeat-service.test.ts +180 -3
- package/src/__tests__/identity-routes.test.ts +328 -0
- package/src/__tests__/injection-block.test.ts +24 -0
- package/src/__tests__/install-skill-routing.test.ts +7 -6
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +15 -14
- package/src/__tests__/list-messages-tool-merge.test.ts +300 -0
- package/src/__tests__/llm-context-normalization.test.ts +18 -18
- package/src/__tests__/llm-context-route-provider.test.ts +101 -0
- package/src/__tests__/llm-request-log-turn-query.test.ts +162 -0
- package/src/__tests__/log-export-workspace.test.ts +72 -105
- package/src/__tests__/mcp-abort-signal.test.ts +5 -0
- package/src/__tests__/mcp-client-auth.test.ts +5 -0
- package/src/__tests__/memory-recall-log-store.test.ts +132 -0
- package/src/__tests__/migration-export-streaming.test.ts +304 -0
- package/src/__tests__/migration-import-commit-http.test.ts +11 -10
- package/src/__tests__/mock-fetch.ts +87 -0
- package/src/__tests__/notification-decision-recipient-context.test.ts +282 -0
- package/src/__tests__/onboarding-template-contract.test.ts +62 -14
- package/src/__tests__/parser.test.ts +32 -0
- package/src/__tests__/permission-checker-host-gate.test.ts +452 -0
- package/src/__tests__/permission-controls-v2-flag.test.ts +55 -0
- package/src/__tests__/permission-mode-sse.test.ts +418 -0
- package/src/__tests__/permission-mode-store.test.ts +277 -0
- package/src/__tests__/permission-mode.test.ts +101 -0
- package/src/__tests__/platform-bash-auto-approve.test.ts +359 -0
- package/src/__tests__/profiler-routes.test.ts +502 -0
- package/src/__tests__/profiler-run-store.test.ts +441 -0
- package/src/__tests__/proxy-approval-callback.test.ts +4 -75
- package/src/__tests__/registry.test.ts +1 -1
- package/src/__tests__/sandbox-host-parity.test.ts +5 -4
- package/src/__tests__/scheduler-reuse-conversation.test.ts +368 -0
- package/src/__tests__/scrub-corrupted-image-attachments.test.ts +278 -0
- package/src/__tests__/search-skills-unified.test.ts +4 -3
- package/src/__tests__/send-endpoint-busy.test.ts +42 -3
- package/src/__tests__/set-permission-mode.test.ts +274 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +12 -0
- package/src/__tests__/skill-memory.test.ts +2 -783
- package/src/__tests__/strip-memory-injections.test.ts +187 -0
- package/src/__tests__/subagent-detail.test.ts +84 -0
- package/src/__tests__/subagent-disposal.test.ts +308 -0
- package/src/__tests__/subagent-manager-notify.test.ts +19 -10
- package/src/__tests__/subagent-notify-parent.test.ts +390 -0
- package/src/__tests__/subagent-role-registry.test.ts +108 -0
- package/src/__tests__/subagent-tool-filtering.test.ts +71 -0
- package/src/__tests__/subagent-tools.test.ts +464 -4
- package/src/__tests__/system-prompt-ask-mode.test.ts +139 -0
- package/src/__tests__/task-memory-cleanup.test.ts +12 -12
- package/src/__tests__/terminal-tools.test.ts +17 -27
- package/src/__tests__/test-preload.ts +4 -0
- package/src/__tests__/tool-executor.test.ts +4 -26
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +1 -0
- package/src/__tests__/top-level-renderer.test.ts +10 -13
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +116 -2
- package/src/__tests__/workspace-migration-028-recover-conversations-from-disk-view.test.ts +387 -0
- package/src/agent/loop.ts +6 -0
- package/src/approvals/guardian-request-resolvers.ts +24 -0
- package/src/avatar/traits-png-sync.ts +3 -3
- package/src/cli/__tests__/run-assistant-command.ts +29 -0
- package/src/cli/commands/__tests__/email-download.test.ts +245 -0
- package/src/cli/commands/__tests__/email-list.test.ts +192 -0
- package/src/cli/commands/__tests__/email-register.test.ts +186 -0
- package/src/cli/commands/__tests__/email-send.test.ts +291 -0
- package/src/cli/commands/__tests__/email-status.test.ts +181 -0
- package/src/cli/commands/__tests__/email-unregister.test.ts +139 -0
- package/src/cli/commands/__tests__/routes.test.ts +562 -0
- package/src/cli/commands/conversations.ts +1 -8
- package/src/cli/commands/email.ts +584 -835
- package/src/cli/commands/memory.ts +1 -34
- package/src/cli/commands/notifications.ts +7 -2
- package/src/cli/commands/oauth/connect.ts +14 -5
- package/src/cli/commands/routes.ts +396 -0
- package/src/cli/commands/skills.ts +130 -20
- package/src/cli/program.ts +2 -0
- package/src/cli.ts +1 -120
- package/src/config/bundled-skills/app-builder/SKILL.md +4 -1
- package/src/config/bundled-skills/gmail/SKILL.md +2 -2
- package/src/config/bundled-skills/messaging/SKILL.md +7 -0
- package/src/config/bundled-skills/schedule/SKILL.md +22 -2
- package/src/config/bundled-skills/schedule/TOOLS.json +8 -0
- package/src/config/bundled-skills/settings/tools/avatar-get.ts +3 -13
- package/src/config/bundled-skills/settings/tools/avatar-remove.ts +2 -4
- package/src/config/bundled-skills/settings/tools/avatar-update.ts +5 -2
- package/src/config/bundled-skills/slack/SKILL.md +2 -0
- package/src/config/bundled-skills/subagent/SKILL.md +43 -3
- package/src/config/bundled-skills/subagent/TOOLS.json +29 -4
- package/src/config/env-registry.ts +63 -0
- package/src/config/feature-flag-registry.json +17 -1
- package/src/config/schema.ts +8 -0
- package/src/config/schemas/filing.ts +51 -0
- package/src/config/schemas/heartbeat.ts +15 -12
- package/src/config/schemas/memory-lifecycle.ts +12 -0
- package/src/config/schemas/security.ts +14 -0
- package/src/daemon/app-source-watcher.ts +93 -0
- package/src/daemon/config-watcher.ts +79 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +20 -0
- package/src/daemon/conversation-agent-loop.ts +158 -65
- package/src/daemon/conversation-history.ts +4 -19
- package/src/daemon/conversation-lifecycle.ts +8 -14
- package/src/daemon/conversation-process.ts +13 -7
- package/src/daemon/conversation-runtime-assembly.ts +300 -306
- package/src/daemon/conversation-tool-setup.ts +44 -14
- package/src/daemon/conversation-workspace.ts +1 -2
- package/src/daemon/conversation.ts +18 -0
- package/src/daemon/date-context.ts +26 -53
- package/src/daemon/first-greeting.ts +1 -1
- package/src/daemon/handlers/conversations.ts +4 -7
- package/src/daemon/handlers/shared.test.ts +143 -0
- package/src/daemon/handlers/shared.ts +63 -5
- package/src/daemon/handlers/skills.ts +11 -18
- package/src/daemon/lifecycle.ts +199 -157
- package/src/daemon/message-types/conversations.ts +25 -6
- package/src/daemon/message-types/messages.ts +9 -1
- package/src/daemon/message-types/schedules.ts +1 -0
- package/src/daemon/message-types/settings.ts +6 -0
- package/src/daemon/profiler-run-store.ts +557 -0
- package/src/daemon/server.ts +89 -9
- package/src/daemon/shutdown-handlers.ts +5 -0
- package/src/daemon/tool-side-effects.ts +23 -3
- package/src/export/transcript-formatter.ts +148 -0
- package/src/filing/filing-service.ts +228 -0
- package/src/heartbeat/heartbeat-service.ts +96 -7
- package/src/mcp/client.ts +6 -0
- package/src/mcp/mcp-oauth-provider.ts +149 -27
- package/src/memory/admin.ts +33 -32
- package/src/memory/app-store.ts +69 -0
- package/src/memory/conversation-bootstrap.ts +1 -1
- package/src/memory/conversation-crud.ts +136 -107
- package/src/memory/conversation-group-migration.ts +1 -1
- package/src/memory/conversation-queries.ts +58 -12
- package/src/memory/conversation-title-service.ts +1 -0
- package/src/memory/db-init.ts +182 -376
- package/src/memory/graph/bootstrap.ts +75 -66
- package/src/memory/graph/capability-seed.ts +167 -15
- package/src/memory/graph/consolidation.ts +38 -4
- package/src/memory/graph/conversation-graph-memory.ts +133 -104
- package/src/memory/graph/extraction-job.ts +9 -4
- package/src/memory/graph/extraction.ts +66 -23
- package/src/memory/graph/graph-memory-state-store.ts +37 -0
- package/src/memory/graph/graph-search.ts +29 -15
- package/src/memory/graph/injection.ts +38 -8
- package/src/memory/graph/inspect.ts +12 -3
- package/src/memory/graph/retriever.ts +365 -262
- package/src/memory/graph/store.test.ts +48 -0
- package/src/memory/graph/store.ts +150 -11
- package/src/memory/graph/tool-handlers.ts +84 -209
- package/src/memory/graph/tools.ts +8 -52
- package/src/memory/graph/types.ts +24 -0
- package/src/memory/job-handlers/cleanup.ts +44 -1
- package/src/memory/jobs-store.ts +70 -60
- package/src/memory/jobs-worker.ts +44 -28
- package/src/memory/llm-request-log-store.ts +96 -12
- package/src/memory/memory-recall-log-store.ts +49 -5
- package/src/memory/migrations/203-drop-memory-items-tables.ts +33 -1
- package/src/memory/migrations/206-memory-graph-node-edits.ts +19 -0
- package/src/memory/migrations/206-scrub-corrupted-image-attachments.ts +131 -0
- package/src/memory/migrations/207-conversation-graph-memory-state.ts +20 -0
- package/src/memory/migrations/208-conversations-last-message-at.ts +35 -0
- package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +85 -0
- package/src/memory/migrations/210-schedule-reuse-conversation.ts +13 -0
- package/src/memory/migrations/211-memory-recall-logs-query-context.ts +21 -0
- package/src/memory/migrations/212-llm-request-logs-created-at-index.ts +19 -0
- package/src/memory/migrations/index.ts +8 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/schema/conversations.ts +14 -0
- package/src/memory/schema/infrastructure.ts +8 -1
- package/src/memory/schema/memory-core.ts +0 -51
- package/src/memory/schema/memory-graph.ts +15 -0
- package/src/memory/task-memory-cleanup.ts +30 -11
- package/src/notifications/copy-composer.ts +86 -0
- package/src/notifications/decision-engine.ts +35 -0
- package/src/permissions/checker.ts +12 -1
- package/src/permissions/permission-mode-store.ts +180 -0
- package/src/permissions/permission-mode.ts +31 -0
- package/src/permissions/workspace-policy.ts +9 -0
- package/src/prompts/system-prompt.ts +59 -7
- package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +100 -0
- package/src/prompts/templates/BOOTSTRAP.md +70 -165
- package/src/prompts/templates/HEARTBEAT.md +3 -1
- package/src/prompts/templates/SOUL.md +25 -4
- package/src/prompts/templates/UPDATES.md +8 -0
- package/src/providers/anthropic/client.ts +107 -219
- package/src/runtime/auth/route-policy.ts +23 -0
- package/src/runtime/http-server.ts +32 -2
- package/src/runtime/http-types.ts +12 -1
- package/src/runtime/migrations/vbundle-builder.ts +389 -3
- package/src/runtime/migrations/vbundle-importer.ts +8 -6
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +378 -0
- package/src/runtime/routes/app-management-routes.ts +1 -11
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +26 -0
- package/src/runtime/routes/archive-utils.ts +29 -0
- package/src/runtime/routes/avatar-routes.ts +2 -9
- package/src/runtime/routes/btw-routes.ts +14 -1
- package/src/runtime/routes/conversation-analysis-routes.ts +173 -0
- package/src/runtime/routes/conversation-management-routes.ts +1 -14
- package/src/runtime/routes/conversation-query-routes.ts +49 -3
- package/src/runtime/routes/conversation-routes.ts +264 -44
- package/src/runtime/routes/heartbeat-routes.ts +4 -10
- package/src/runtime/routes/identity-routes.ts +53 -18
- package/src/runtime/routes/llm-context-normalization.ts +14 -10
- package/src/runtime/routes/log-export-routes.ts +23 -275
- package/src/runtime/routes/memory-item-routes.test.ts +168 -233
- package/src/runtime/routes/migration-routes.ts +18 -7
- package/src/runtime/routes/profiler-routes.ts +350 -0
- package/src/runtime/routes/schedule-routes.ts +27 -12
- package/src/runtime/routes/settings-routes.ts +95 -8
- package/src/runtime/routes/subagents-routes.ts +28 -7
- package/src/runtime/routes/user-route-dispatcher.ts +223 -0
- package/src/runtime/routes/user-routes.ts +41 -0
- package/src/runtime/routes/workspace-routes.ts +0 -1
- package/src/schedule/schedule-store.ts +30 -0
- package/src/schedule/scheduler.ts +45 -18
- package/src/skills/catalog-install.ts +10 -2
- package/src/skills/managed-store.ts +2 -2
- package/src/skills/skill-memory.ts +1 -293
- package/src/subagent/index.ts +13 -3
- package/src/subagent/manager.ts +308 -29
- package/src/subagent/types.ts +68 -0
- package/src/tasks/task-runner.ts +4 -4
- package/src/tools/apps/executors.ts +29 -4
- package/src/tools/filesystem/list.ts +93 -0
- package/src/tools/permission-checker.ts +78 -0
- package/src/tools/registry.ts +4 -0
- package/src/tools/schedule/create.ts +3 -0
- package/src/tools/schedule/list.ts +1 -0
- package/src/tools/schedule/update.ts +6 -0
- package/src/tools/shared/filesystem/errors.ts +5 -0
- package/src/tools/shared/filesystem/file-ops-service.ts +90 -2
- package/src/tools/shared/filesystem/types.ts +17 -0
- package/src/tools/shared/shell-output.ts +31 -2
- package/src/tools/subagent/abort.ts +12 -2
- package/src/tools/subagent/message.ts +9 -2
- package/src/tools/subagent/notify-parent.ts +79 -0
- package/src/tools/subagent/read.ts +29 -8
- package/src/tools/subagent/resolve.ts +21 -0
- package/src/tools/subagent/spawn.ts +2 -0
- package/src/tools/subagent/status.ts +11 -1
- package/src/tools/system/avatar-generator.ts +3 -3
- package/src/tools/system/register.ts +23 -0
- package/src/tools/system/set-permission-mode.ts +103 -0
- package/src/tools/terminal/parser.ts +30 -5
- package/src/tools/terminal/safe-env.ts +16 -1
- package/src/tools/tool-manifest.ts +6 -0
- package/src/tools/types.ts +2 -0
- package/src/util/logger.ts +1 -1
- package/src/util/platform.ts +50 -17
- package/src/workspace/migrations/023-move-config-files-to-workspace.ts +2 -2
- package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +2 -2
- package/src/workspace/migrations/028-recover-conversations-from-disk-view.ts +270 -0
- package/src/workspace/migrations/029-seed-pkb.ts +84 -0
- package/src/workspace/migrations/registry.ts +4 -0
- package/src/workspace/top-level-renderer.ts +5 -9
- package/src/__tests__/cli-memory.test.ts +0 -377
- package/src/__tests__/clipboard.test.ts +0 -88
- package/src/cli/cli-memory.ts +0 -179
- 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
|
-
`
|
|
394
|
-
`deleted ${result.deletedSummaryIds.length} summaries, ` +
|
|
387
|
+
`Deleted ${result.deletedSummaryIds.length} summaries, ` +
|
|
395
388
|
`cancelled ${result.cancelledJobCount} jobs.`,
|
|
396
389
|
);
|
|
397
390
|
});
|