@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,328 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for identity/health route handlers, focusing on profiler metadata
|
|
3
|
+
* in /v1/health and /v1/healthz responses.
|
|
4
|
+
*
|
|
5
|
+
* Proves:
|
|
6
|
+
* - Backward compatibility: health endpoints return expected shape when
|
|
7
|
+
* profiler mode is off (no env vars).
|
|
8
|
+
* - Profiler payload: when profiler env vars are set, the response includes
|
|
9
|
+
* a `profiler` object with the expected structure and budget state.
|
|
10
|
+
* - Artifact detection: when run manifests and Bun summary files exist,
|
|
11
|
+
* the response correctly reports artifact counts and lastCompletedRun.
|
|
12
|
+
*/
|
|
13
|
+
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
16
|
+
|
|
17
|
+
// Silence logger before any imports that use it
|
|
18
|
+
mock.module("../util/logger.js", () => ({
|
|
19
|
+
getLogger: () =>
|
|
20
|
+
new Proxy({} as Record<string, unknown>, {
|
|
21
|
+
get: () => () => {},
|
|
22
|
+
}),
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
import { handleDetailedHealth } from "../runtime/routes/identity-routes.js";
|
|
26
|
+
import { getWorkspaceDir } from "../util/platform.js";
|
|
27
|
+
|
|
28
|
+
// ── Env helpers ─────────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
let savedEnv: Record<string, string | undefined>;
|
|
31
|
+
|
|
32
|
+
const PROFILER_ENV_KEYS = [
|
|
33
|
+
"VELLUM_PROFILER_RUN_ID",
|
|
34
|
+
"VELLUM_PROFILER_MODE",
|
|
35
|
+
"VELLUM_PROFILER_MAX_BYTES",
|
|
36
|
+
"VELLUM_PROFILER_MAX_RUNS",
|
|
37
|
+
"VELLUM_PROFILER_MIN_FREE_MB",
|
|
38
|
+
] as const;
|
|
39
|
+
|
|
40
|
+
function clearProfilerEnv(): void {
|
|
41
|
+
for (const key of PROFILER_ENV_KEYS) {
|
|
42
|
+
delete process.env[key];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function setProfilerEnv(
|
|
47
|
+
mode: string,
|
|
48
|
+
runId: string,
|
|
49
|
+
opts?: { maxBytes?: number; maxRuns?: number; minFreeMb?: number },
|
|
50
|
+
): void {
|
|
51
|
+
process.env.VELLUM_PROFILER_RUN_ID = runId;
|
|
52
|
+
process.env.VELLUM_PROFILER_MODE = mode;
|
|
53
|
+
if (opts?.maxBytes !== undefined) {
|
|
54
|
+
process.env.VELLUM_PROFILER_MAX_BYTES = String(opts.maxBytes);
|
|
55
|
+
}
|
|
56
|
+
if (opts?.maxRuns !== undefined) {
|
|
57
|
+
process.env.VELLUM_PROFILER_MAX_RUNS = String(opts.maxRuns);
|
|
58
|
+
}
|
|
59
|
+
if (opts?.minFreeMb !== undefined) {
|
|
60
|
+
process.env.VELLUM_PROFILER_MIN_FREE_MB = String(opts.minFreeMb);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ── Filesystem helpers ──────────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
function ensureProfilerRunDir(runId: string): string {
|
|
67
|
+
const wsDir = getWorkspaceDir();
|
|
68
|
+
const runDir = join(wsDir, "data", "profiler", "runs", runId);
|
|
69
|
+
mkdirSync(runDir, { recursive: true });
|
|
70
|
+
return runDir;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function writeRunManifest(
|
|
74
|
+
runId: string,
|
|
75
|
+
manifest: {
|
|
76
|
+
status: "active" | "completed";
|
|
77
|
+
createdAt?: string;
|
|
78
|
+
updatedAt?: string;
|
|
79
|
+
completedAt?: string;
|
|
80
|
+
totalBytes?: number;
|
|
81
|
+
},
|
|
82
|
+
): void {
|
|
83
|
+
const runDir = ensureProfilerRunDir(runId);
|
|
84
|
+
const m: Record<string, unknown> = {
|
|
85
|
+
runId,
|
|
86
|
+
status: manifest.status,
|
|
87
|
+
createdAt: manifest.createdAt ?? new Date().toISOString(),
|
|
88
|
+
updatedAt: manifest.updatedAt ?? new Date().toISOString(),
|
|
89
|
+
totalBytes: manifest.totalBytes ?? 0,
|
|
90
|
+
};
|
|
91
|
+
if (manifest.completedAt) {
|
|
92
|
+
m.completedAt = manifest.completedAt;
|
|
93
|
+
}
|
|
94
|
+
writeFileSync(join(runDir, "manifest.json"), JSON.stringify(m, null, 2));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function writeArtifactFile(
|
|
98
|
+
runId: string,
|
|
99
|
+
filename: string,
|
|
100
|
+
sizeBytes: number,
|
|
101
|
+
): void {
|
|
102
|
+
const runDir = ensureProfilerRunDir(runId);
|
|
103
|
+
writeFileSync(join(runDir, filename), Buffer.alloc(sizeBytes));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ── Setup / teardown ────────────────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
beforeEach(() => {
|
|
109
|
+
savedEnv = {};
|
|
110
|
+
for (const key of PROFILER_ENV_KEYS) {
|
|
111
|
+
savedEnv[key] = process.env[key];
|
|
112
|
+
}
|
|
113
|
+
clearProfilerEnv();
|
|
114
|
+
|
|
115
|
+
// Clean up any profiler run directories from previous tests so
|
|
116
|
+
// rescanRuns() doesn't pick up stale state in the shared workspace.
|
|
117
|
+
const profilerRunsDir = join(getWorkspaceDir(), "data", "profiler", "runs");
|
|
118
|
+
if (existsSync(profilerRunsDir)) {
|
|
119
|
+
rmSync(profilerRunsDir, { recursive: true, force: true });
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
afterEach(() => {
|
|
124
|
+
for (const [key, value] of Object.entries(savedEnv)) {
|
|
125
|
+
if (value === undefined) {
|
|
126
|
+
delete process.env[key];
|
|
127
|
+
} else {
|
|
128
|
+
process.env[key] = value;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// ── Tests ───────────────────────────────────────────────────────────────
|
|
134
|
+
|
|
135
|
+
describe("identity routes — health endpoint", () => {
|
|
136
|
+
describe("backward compatibility (profiler disabled)", () => {
|
|
137
|
+
test("/v1/health returns expected shape without profiler key when env vars are absent", async () => {
|
|
138
|
+
const res = handleDetailedHealth();
|
|
139
|
+
expect(res.status).toBe(200);
|
|
140
|
+
|
|
141
|
+
const body = (await res.json()) as Record<string, unknown>;
|
|
142
|
+
expect(body.status).toBe("healthy");
|
|
143
|
+
expect(body.timestamp).toBeDefined();
|
|
144
|
+
expect(body.version).toBeDefined();
|
|
145
|
+
expect(body.disk).toBeDefined();
|
|
146
|
+
expect(body.memory).toBeDefined();
|
|
147
|
+
expect(body.cpu).toBeDefined();
|
|
148
|
+
expect(body.migrations).toBeDefined();
|
|
149
|
+
|
|
150
|
+
// Profiler should either be absent or show enabled: false
|
|
151
|
+
if ("profiler" in body) {
|
|
152
|
+
const profiler = body.profiler as Record<string, unknown>;
|
|
153
|
+
expect(profiler.enabled).toBe(false);
|
|
154
|
+
expect(profiler.mode).toBeNull();
|
|
155
|
+
expect(profiler.runId).toBeNull();
|
|
156
|
+
expect(profiler.budget).toBeNull();
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("/v1/healthz returns the same shape as /v1/health", async () => {
|
|
161
|
+
// Both endpoints call handleDetailedHealth, so the shape must match
|
|
162
|
+
const res = handleDetailedHealth();
|
|
163
|
+
const body = (await res.json()) as Record<string, unknown>;
|
|
164
|
+
|
|
165
|
+
expect(body.status).toBe("healthy");
|
|
166
|
+
expect(body.timestamp).toBeDefined();
|
|
167
|
+
expect(body.migrations).toBeDefined();
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
describe("profiler payload (profiler enabled)", () => {
|
|
172
|
+
test("returns profiler object with enabled=true when env vars are set", async () => {
|
|
173
|
+
setProfilerEnv("cpu", "run-health-test-1", {
|
|
174
|
+
maxBytes: 10_000_000,
|
|
175
|
+
minFreeMb: 10,
|
|
176
|
+
});
|
|
177
|
+
ensureProfilerRunDir("run-health-test-1");
|
|
178
|
+
|
|
179
|
+
const res = handleDetailedHealth();
|
|
180
|
+
const body = (await res.json()) as Record<string, unknown>;
|
|
181
|
+
|
|
182
|
+
expect(body.profiler).toBeDefined();
|
|
183
|
+
const profiler = body.profiler as Record<string, unknown>;
|
|
184
|
+
expect(profiler.enabled).toBe(true);
|
|
185
|
+
expect(profiler.mode).toBe("cpu");
|
|
186
|
+
expect(profiler.runId).toBe("run-health-test-1");
|
|
187
|
+
expect(profiler.runDir).toContain("run-health-test-1");
|
|
188
|
+
expect(typeof profiler.totalBytes).toBe("number");
|
|
189
|
+
expect(typeof profiler.artifactCount).toBe("number");
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("includes budget block with expected fields", async () => {
|
|
193
|
+
setProfilerEnv("heap", "run-budget-test", {
|
|
194
|
+
maxBytes: 50_000_000,
|
|
195
|
+
minFreeMb: 100,
|
|
196
|
+
});
|
|
197
|
+
ensureProfilerRunDir("run-budget-test");
|
|
198
|
+
|
|
199
|
+
const res = handleDetailedHealth();
|
|
200
|
+
const body = (await res.json()) as Record<string, unknown>;
|
|
201
|
+
const profiler = body.profiler as Record<string, unknown>;
|
|
202
|
+
const budget = profiler.budget as Record<string, unknown>;
|
|
203
|
+
|
|
204
|
+
expect(budget).toBeDefined();
|
|
205
|
+
expect(budget.maxBytes).toBe(50_000_000);
|
|
206
|
+
expect(typeof budget.remainingBytes).toBe("number");
|
|
207
|
+
expect(budget.minFreeMb).toBe(100);
|
|
208
|
+
expect(typeof budget.freeMb).toBe("number");
|
|
209
|
+
expect(typeof budget.overBudget).toBe("boolean");
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test("reports artifact count from .cpuprofile files", async () => {
|
|
213
|
+
setProfilerEnv("cpu", "run-artifact-count", {
|
|
214
|
+
maxBytes: 100_000_000,
|
|
215
|
+
minFreeMb: 0,
|
|
216
|
+
});
|
|
217
|
+
writeArtifactFile("run-artifact-count", "profile-1.cpuprofile", 1024);
|
|
218
|
+
writeArtifactFile("run-artifact-count", "profile-2.cpuprofile", 2048);
|
|
219
|
+
// Non-artifact file should not count
|
|
220
|
+
writeArtifactFile("run-artifact-count", "log.txt", 512);
|
|
221
|
+
|
|
222
|
+
const res = handleDetailedHealth();
|
|
223
|
+
const body = (await res.json()) as Record<string, unknown>;
|
|
224
|
+
const profiler = body.profiler as Record<string, unknown>;
|
|
225
|
+
|
|
226
|
+
expect(profiler.artifactCount).toBe(2);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test("detects over-budget state when total bytes exceed maxBytes", async () => {
|
|
230
|
+
setProfilerEnv("cpu+heap", "run-over-budget", {
|
|
231
|
+
maxBytes: 100, // Very small budget
|
|
232
|
+
minFreeMb: 0,
|
|
233
|
+
});
|
|
234
|
+
// Write a file larger than the budget
|
|
235
|
+
writeArtifactFile("run-over-budget", "big.cpuprofile", 5000);
|
|
236
|
+
|
|
237
|
+
const res = handleDetailedHealth();
|
|
238
|
+
const body = (await res.json()) as Record<string, unknown>;
|
|
239
|
+
const profiler = body.profiler as Record<string, unknown>;
|
|
240
|
+
const budget = profiler.budget as Record<string, unknown>;
|
|
241
|
+
|
|
242
|
+
expect(budget.overBudget).toBe(true);
|
|
243
|
+
expect(budget.remainingBytes).toBe(0);
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
describe("lastCompletedRun", () => {
|
|
248
|
+
test("returns null when no completed runs exist", async () => {
|
|
249
|
+
setProfilerEnv("cpu", "run-no-completed", {
|
|
250
|
+
maxBytes: 100_000_000,
|
|
251
|
+
minFreeMb: 0,
|
|
252
|
+
});
|
|
253
|
+
ensureProfilerRunDir("run-no-completed");
|
|
254
|
+
|
|
255
|
+
const res = handleDetailedHealth();
|
|
256
|
+
const body = (await res.json()) as Record<string, unknown>;
|
|
257
|
+
const profiler = body.profiler as Record<string, unknown>;
|
|
258
|
+
|
|
259
|
+
expect(profiler.lastCompletedRun).toBeNull();
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test("returns completed run summary with artifact count and hasSummaries", async () => {
|
|
263
|
+
setProfilerEnv("cpu", "active-run-xyz", {
|
|
264
|
+
maxBytes: 100_000_000,
|
|
265
|
+
minFreeMb: 0,
|
|
266
|
+
});
|
|
267
|
+
ensureProfilerRunDir("active-run-xyz");
|
|
268
|
+
|
|
269
|
+
// Create a completed run with artifacts and a summary file
|
|
270
|
+
const completedId = "completed-run-abc";
|
|
271
|
+
const expectedCompletedAt = "2025-06-01T00:30:00Z";
|
|
272
|
+
writeRunManifest(completedId, {
|
|
273
|
+
status: "completed",
|
|
274
|
+
createdAt: "2025-06-01T00:00:00Z",
|
|
275
|
+
updatedAt: "2025-06-01T01:00:00Z",
|
|
276
|
+
completedAt: expectedCompletedAt,
|
|
277
|
+
totalBytes: 4096,
|
|
278
|
+
});
|
|
279
|
+
writeArtifactFile(completedId, "profile.cpuprofile", 3072);
|
|
280
|
+
writeArtifactFile(completedId, "summary.md", 256);
|
|
281
|
+
|
|
282
|
+
const res = handleDetailedHealth();
|
|
283
|
+
const body = (await res.json()) as Record<string, unknown>;
|
|
284
|
+
const profiler = body.profiler as Record<string, unknown>;
|
|
285
|
+
const last = profiler.lastCompletedRun as Record<string, unknown>;
|
|
286
|
+
|
|
287
|
+
expect(last).toBeDefined();
|
|
288
|
+
expect(last.runId).toBe(completedId);
|
|
289
|
+
expect(last.artifactCount).toBe(1); // Only .cpuprofile counts
|
|
290
|
+
expect(last.hasSummaries).toBe(true);
|
|
291
|
+
expect(typeof last.totalBytes).toBe("number");
|
|
292
|
+
// completedAt should reflect the manifest's completedAt value,
|
|
293
|
+
// not the current time or updatedAt.
|
|
294
|
+
expect(last.completedAt).toBe(expectedCompletedAt);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
test("selects the most recent completed run when multiple exist", async () => {
|
|
298
|
+
setProfilerEnv("heap", "active-multi", {
|
|
299
|
+
maxBytes: 100_000_000,
|
|
300
|
+
maxRuns: 100,
|
|
301
|
+
minFreeMb: 0,
|
|
302
|
+
});
|
|
303
|
+
ensureProfilerRunDir("active-multi");
|
|
304
|
+
|
|
305
|
+
writeRunManifest("older-completed", {
|
|
306
|
+
status: "completed",
|
|
307
|
+
createdAt: "2025-01-01T00:00:00Z",
|
|
308
|
+
updatedAt: "2025-01-01T01:00:00Z",
|
|
309
|
+
});
|
|
310
|
+
writeArtifactFile("older-completed", "old.heapsnapshot", 512);
|
|
311
|
+
|
|
312
|
+
writeRunManifest("newer-completed", {
|
|
313
|
+
status: "completed",
|
|
314
|
+
createdAt: "2025-06-15T00:00:00Z",
|
|
315
|
+
updatedAt: "2025-06-15T01:00:00Z",
|
|
316
|
+
});
|
|
317
|
+
writeArtifactFile("newer-completed", "new.heapsnapshot", 1024);
|
|
318
|
+
|
|
319
|
+
const res = handleDetailedHealth();
|
|
320
|
+
const body = (await res.json()) as Record<string, unknown>;
|
|
321
|
+
const profiler = body.profiler as Record<string, unknown>;
|
|
322
|
+
const last = profiler.lastCompletedRun as Record<string, unknown>;
|
|
323
|
+
|
|
324
|
+
expect(last).toBeDefined();
|
|
325
|
+
expect(last.runId).toBe("newer-completed");
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
});
|
|
@@ -125,6 +125,18 @@ describe("assembleInjectionBlock", () => {
|
|
|
125
125
|
expect(result).toContain("[skill]");
|
|
126
126
|
expect(result).toContain("→ use skill_load to activate");
|
|
127
127
|
});
|
|
128
|
+
|
|
129
|
+
test("assembleInjectionBlock omits skill_load suffix for CLI commands", () => {
|
|
130
|
+
const node = makeScoredNode({
|
|
131
|
+
type: "procedural",
|
|
132
|
+
content:
|
|
133
|
+
'cli:bash\nThe "assistant bash" CLI command is available. Execute a shell command.',
|
|
134
|
+
});
|
|
135
|
+
const result = assembleInjectionBlock([node]);
|
|
136
|
+
expect(result).not.toContain("[skill]");
|
|
137
|
+
expect(result).not.toContain("skill_load to activate");
|
|
138
|
+
expect(result).toContain("CLI command is available");
|
|
139
|
+
});
|
|
128
140
|
});
|
|
129
141
|
|
|
130
142
|
describe("assembleContextBlock — procedural nodes", () => {
|
|
@@ -139,6 +151,18 @@ describe("assembleContextBlock — procedural nodes", () => {
|
|
|
139
151
|
expect(result).toContain("use skill_load to activate");
|
|
140
152
|
});
|
|
141
153
|
|
|
154
|
+
test("omits skill_load suffix for CLI commands", () => {
|
|
155
|
+
const node = makeScoredNode({
|
|
156
|
+
type: "procedural",
|
|
157
|
+
content:
|
|
158
|
+
'cli:bash\nThe "assistant bash" CLI command is available. Execute a shell command.',
|
|
159
|
+
});
|
|
160
|
+
const result = assembleContextBlock([node]);
|
|
161
|
+
expect(result).toContain("### Skills You Can Use");
|
|
162
|
+
expect(result).not.toContain("skill_load to activate");
|
|
163
|
+
expect(result).toContain("CLI command is available");
|
|
164
|
+
});
|
|
165
|
+
|
|
142
166
|
test("strips skill: prefix from old-format content", () => {
|
|
143
167
|
const node = makeScoredNode({
|
|
144
168
|
type: "procedural",
|
|
@@ -32,7 +32,7 @@ const mockInstallExternalSkill = mock(
|
|
|
32
32
|
);
|
|
33
33
|
const mockGetCatalog = mock(async () => []);
|
|
34
34
|
const mockInstallSkillLocally = mock(async () => {});
|
|
35
|
-
const
|
|
35
|
+
const mockSeedSkillGraphNodes = mock(() => {});
|
|
36
36
|
const mockEnsureSkillEntry = mock(
|
|
37
37
|
(_raw: Record<string, unknown>, _id: string) => ({
|
|
38
38
|
enabled: false,
|
|
@@ -115,9 +115,10 @@ mock.module("../skills/managed-store.js", () => ({
|
|
|
115
115
|
removeSkillsIndexEntry: () => {},
|
|
116
116
|
validateManagedSkillId: () => null,
|
|
117
117
|
}));
|
|
118
|
-
mock.module("../
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
mock.module("../memory/graph/capability-seed.js", () => ({
|
|
119
|
+
deleteSkillCapabilityNode: () => {},
|
|
120
|
+
seedSkillGraphNodes: mockSeedSkillGraphNodes,
|
|
121
|
+
seedUninstalledCatalogSkillMemories: async () => {},
|
|
121
122
|
}));
|
|
122
123
|
mock.module("../util/platform.js", () => ({
|
|
123
124
|
getWorkspaceSkillsDir: () => "/tmp/test-skills",
|
|
@@ -158,7 +159,7 @@ describe("installSkill routing", () => {
|
|
|
158
159
|
mockInstallExternalSkill.mockReset();
|
|
159
160
|
mockGetCatalog.mockReset();
|
|
160
161
|
mockInstallSkillLocally.mockReset();
|
|
161
|
-
|
|
162
|
+
mockSeedSkillGraphNodes.mockReset();
|
|
162
163
|
mockEnsureSkillEntry.mockReset();
|
|
163
164
|
|
|
164
165
|
// Defaults
|
|
@@ -167,7 +168,7 @@ describe("installSkill routing", () => {
|
|
|
167
168
|
mockInstallExternalSkill.mockResolvedValue(undefined);
|
|
168
169
|
mockGetCatalog.mockResolvedValue([]);
|
|
169
170
|
mockInstallSkillLocally.mockResolvedValue(undefined);
|
|
170
|
-
|
|
171
|
+
mockSeedSkillGraphNodes.mockReturnValue(undefined);
|
|
171
172
|
mockEnsureSkillEntry.mockReturnValue({ enabled: false });
|
|
172
173
|
});
|
|
173
174
|
|
|
@@ -37,15 +37,15 @@ describe("claimMemoryJobs with Qdrant circuit breaker", () => {
|
|
|
37
37
|
|
|
38
38
|
test("claims embed jobs when circuit breaker is closed (healthy)", () => {
|
|
39
39
|
enqueueMemoryJob("embed_segment", { segmentId: "seg-1" });
|
|
40
|
-
enqueueMemoryJob("
|
|
41
|
-
enqueueMemoryJob("
|
|
40
|
+
enqueueMemoryJob("embed_graph_node", { nodeId: "node-1" });
|
|
41
|
+
enqueueMemoryJob("graph_extract", { conversationId: "conv-1" });
|
|
42
42
|
|
|
43
43
|
const claimed = claimMemoryJobs(10);
|
|
44
44
|
const types = claimed.map((j) => j.type);
|
|
45
45
|
|
|
46
46
|
expect(types).toContain("embed_segment");
|
|
47
|
-
expect(types).toContain("
|
|
48
|
-
expect(types).toContain("
|
|
47
|
+
expect(types).toContain("embed_graph_node");
|
|
48
|
+
expect(types).toContain("graph_extract");
|
|
49
49
|
expect(claimed).toHaveLength(3);
|
|
50
50
|
});
|
|
51
51
|
|
|
@@ -62,9 +62,9 @@ describe("claimMemoryJobs with Qdrant circuit breaker", () => {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
enqueueMemoryJob("embed_segment", { segmentId: "seg-1" });
|
|
65
|
-
enqueueMemoryJob("
|
|
65
|
+
enqueueMemoryJob("embed_graph_node", { nodeId: "node-1" });
|
|
66
66
|
enqueueMemoryJob("embed_summary", { summaryId: "sum-1" });
|
|
67
|
-
enqueueMemoryJob("
|
|
67
|
+
enqueueMemoryJob("graph_extract", { conversationId: "conv-1" });
|
|
68
68
|
enqueueMemoryJob("build_conversation_summary", {
|
|
69
69
|
conversationId: "conv-1",
|
|
70
70
|
});
|
|
@@ -73,10 +73,10 @@ describe("claimMemoryJobs with Qdrant circuit breaker", () => {
|
|
|
73
73
|
const types = claimed.map((j) => j.type);
|
|
74
74
|
|
|
75
75
|
// Only non-embed jobs should be claimed
|
|
76
|
-
expect(types).toContain("
|
|
76
|
+
expect(types).toContain("graph_extract");
|
|
77
77
|
expect(types).toContain("build_conversation_summary");
|
|
78
78
|
expect(types).not.toContain("embed_segment");
|
|
79
|
-
expect(types).not.toContain("
|
|
79
|
+
expect(types).not.toContain("embed_graph_node");
|
|
80
80
|
expect(types).not.toContain("embed_summary");
|
|
81
81
|
expect(claimed).toHaveLength(2);
|
|
82
82
|
});
|
|
@@ -95,7 +95,7 @@ describe("claimMemoryJobs with Qdrant circuit breaker", () => {
|
|
|
95
95
|
|
|
96
96
|
// Verify embed jobs are skipped while open
|
|
97
97
|
enqueueMemoryJob("embed_segment", { segmentId: "seg-1" });
|
|
98
|
-
enqueueMemoryJob("
|
|
98
|
+
enqueueMemoryJob("graph_extract", { conversationId: "conv-1" });
|
|
99
99
|
|
|
100
100
|
const claimedWhileOpen = claimMemoryJobs(10);
|
|
101
101
|
expect(claimedWhileOpen.map((j) => j.type)).not.toContain("embed_segment");
|
|
@@ -104,21 +104,22 @@ describe("claimMemoryJobs with Qdrant circuit breaker", () => {
|
|
|
104
104
|
_resetQdrantBreaker();
|
|
105
105
|
|
|
106
106
|
// Re-enqueue an embed job (the previous one is now "running")
|
|
107
|
-
enqueueMemoryJob("
|
|
107
|
+
enqueueMemoryJob("embed_graph_node", { nodeId: "node-2" });
|
|
108
108
|
|
|
109
109
|
const claimedAfterClose = claimMemoryJobs(10);
|
|
110
110
|
const types = claimedAfterClose.map((j) => j.type);
|
|
111
111
|
|
|
112
|
-
expect(types).toContain("
|
|
112
|
+
expect(types).toContain("embed_graph_node");
|
|
113
113
|
});
|
|
114
114
|
|
|
115
115
|
test("all embed job types are skipped when breaker is open", async () => {
|
|
116
116
|
const embedTypes: MemoryJobType[] = [
|
|
117
117
|
"embed_segment",
|
|
118
|
-
"embed_item",
|
|
119
118
|
"embed_summary",
|
|
120
119
|
"embed_media",
|
|
121
120
|
"embed_attachment",
|
|
121
|
+
"embed_graph_node",
|
|
122
|
+
"graph_trigger_embed",
|
|
122
123
|
];
|
|
123
124
|
|
|
124
125
|
// Trip the circuit breaker
|
|
@@ -137,13 +138,13 @@ describe("claimMemoryJobs with Qdrant circuit breaker", () => {
|
|
|
137
138
|
enqueueMemoryJob(type, { id: `test-${type}` });
|
|
138
139
|
}
|
|
139
140
|
// Also enqueue a non-embed job
|
|
140
|
-
enqueueMemoryJob("
|
|
141
|
+
enqueueMemoryJob("graph_consolidate", { conversationId: "conv-1" });
|
|
141
142
|
|
|
142
143
|
const claimed = claimMemoryJobs(20);
|
|
143
144
|
const types = claimed.map((j) => j.type);
|
|
144
145
|
|
|
145
146
|
// Only the non-embed job should be claimed
|
|
146
147
|
expect(claimed).toHaveLength(1);
|
|
147
|
-
expect(types).toEqual(["
|
|
148
|
+
expect(types).toEqual(["graph_consolidate"]);
|
|
148
149
|
});
|
|
149
150
|
});
|