@vellumai/assistant 0.6.0 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +4 -0
- package/ARCHITECTURE.md +68 -15
- package/Dockerfile +2 -2
- package/bun.lock +6 -2
- package/docker-entrypoint.sh +42 -1
- package/docs/architecture/integrations.md +1 -1
- package/docs/architecture/memory.md +21 -24
- package/node_modules/@vellumai/ces-contracts/src/handles.ts +7 -9
- package/openapi.yaml +539 -4
- 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__/assistant-event-hub.test.ts +30 -0
- package/src/__tests__/checker.test.ts +138 -172
- package/src/__tests__/cli-command-risk-guard.test.ts +1 -1
- package/src/__tests__/config-schema.test.ts +5 -0
- package/src/__tests__/context-overflow-approval.test.ts +5 -5
- 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-analysis-routes.test.ts +169 -0
- package/src/__tests__/conversation-directories-parse.test.ts +105 -0
- 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__/credential-execution-approval-bridge.test.ts +0 -2
- 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__/init-feature-flag-overrides.test.ts +167 -0
- package/src/__tests__/injection-block.test.ts +24 -0
- package/src/__tests__/inline-command-runner.test.ts +7 -5
- 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 +257 -100
- package/src/__tests__/managed-credential-catalog-cli.test.ts +12 -14
- 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__/navigate-settings-tab.test.ts +14 -1
- package/src/__tests__/notification-broadcaster.test.ts +65 -0
- package/src/__tests__/notification-decision-recipient-context.test.ts +282 -0
- package/src/__tests__/onboarding-template-contract.test.ts +63 -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__/pkb-autoinject.test.ts +96 -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__/require-fresh-approval.test.ts +0 -2
- package/src/__tests__/sandbox-diagnostics.test.ts +1 -32
- 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-sandbox.test.ts +1 -1
- package/src/__tests__/terminal-tools.test.ts +16 -29
- package/src/__tests__/test-preload.ts +18 -0
- package/src/__tests__/tool-domain-event-publisher.test.ts +0 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -8
- package/src/__tests__/tool-executor.test.ts +4 -27
- 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__/transport-hints-queue.test.ts +77 -0
- package/src/__tests__/trust-store.test.ts +4 -4
- 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/__tests__/workspace-migration-030-seed-pkb-autoinject.test.ts +168 -0
- package/src/__tests__/workspace-policy.test.ts +2 -7
- package/src/agent/loop.ts +6 -29
- package/src/approvals/guardian-request-resolvers.ts +24 -0
- package/src/avatar/traits-png-sync.ts +3 -3
- package/src/channels/types.ts +5 -0
- package/src/cli/__tests__/run-assistant-command.ts +56 -0
- package/src/cli/__tests__/unknown-command.test.ts +33 -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/default-action.ts +68 -1
- 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/__tests__/connect.test.ts +27 -0
- package/src/cli/commands/oauth/connect.ts +25 -5
- package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +1 -1
- package/src/cli/commands/routes.ts +396 -0
- package/src/cli/commands/skills.ts +130 -20
- package/src/cli/program.ts +11 -2
- package/src/cli.ts +1 -120
- package/src/config/assistant-feature-flags.ts +59 -55
- package/src/config/bundled-skills/app-builder/SKILL.md +91 -5
- package/src/config/bundled-skills/gmail/SKILL.md +13 -8
- package/src/config/bundled-skills/gmail/TOOLS.json +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -1
- 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.json +1 -1
- 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/settings/tools/navigate-settings-tab.ts +8 -3
- 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/config/schemas/services.ts +8 -0
- package/src/credential-execution/approval-bridge.ts +0 -1
- package/src/credential-execution/managed-catalog.ts +3 -7
- package/src/daemon/app-source-watcher.ts +93 -0
- package/src/daemon/config-watcher.ts +85 -3
- package/src/daemon/context-overflow-approval.ts +0 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +20 -0
- package/src/daemon/conversation-agent-loop.ts +179 -65
- package/src/daemon/conversation-attachments.ts +0 -1
- package/src/daemon/conversation-history.ts +4 -19
- package/src/daemon/conversation-lifecycle.ts +8 -14
- package/src/daemon/conversation-messaging.ts +3 -0
- package/src/daemon/conversation-process.ts +30 -8
- package/src/daemon/conversation-queue-manager.ts +8 -0
- package/src/daemon/conversation-runtime-assembly.ts +359 -308
- package/src/daemon/conversation-surfaces.ts +65 -0
- package/src/daemon/conversation-tool-setup.ts +44 -17
- package/src/daemon/conversation-workspace.ts +1 -2
- package/src/daemon/conversation.ts +19 -3
- package/src/daemon/date-context.ts +26 -53
- package/src/daemon/first-greeting.ts +1 -1
- package/src/daemon/handlers/conversations.ts +5 -7
- package/src/daemon/handlers/shared.test.ts +143 -0
- package/src/daemon/handlers/shared.ts +70 -5
- package/src/daemon/handlers/skills.ts +11 -18
- package/src/daemon/lifecycle.ts +220 -158
- package/src/daemon/message-types/conversations.ts +29 -6
- package/src/daemon/message-types/messages.ts +9 -2
- package/src/daemon/message-types/notifications.ts +12 -0
- package/src/daemon/message-types/schedules.ts +1 -0
- package/src/daemon/message-types/settings.ts +18 -0
- package/src/daemon/profiler-run-store.ts +557 -0
- package/src/daemon/server.ts +87 -10
- package/src/daemon/shutdown-handlers.ts +5 -0
- package/src/daemon/tool-side-effects.ts +23 -3
- package/src/daemon/transport-hints.ts +33 -0
- 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/index.ts +1 -1
- 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 +151 -117
- package/src/memory/conversation-directories.ts +39 -0
- package/src/memory/conversation-group-migration.ts +66 -6
- 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/embedding-local.ts +1 -1
- package/src/memory/graph/bootstrap.ts +75 -66
- package/src/memory/graph/capability-seed.ts +167 -17
- 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/group-crud.ts +25 -9
- 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/messaging/provider.ts +1 -1
- package/src/notifications/broadcaster.ts +6 -0
- package/src/notifications/conversation-pairing.ts +12 -4
- package/src/notifications/copy-composer.ts +86 -0
- package/src/notifications/decision-engine.ts +35 -0
- package/src/notifications/emit-signal.ts +14 -0
- package/src/notifications/signal.ts +11 -0
- package/src/oauth/platform-connection.test.ts +2 -2
- package/src/oauth/seed-providers.ts +1 -0
- package/src/permissions/checker.ts +15 -4
- package/src/permissions/defaults.ts +7 -8
- package/src/permissions/permission-mode-store.ts +180 -0
- package/src/permissions/permission-mode.ts +31 -0
- package/src/permissions/prompter.ts +0 -2
- package/src/permissions/workspace-policy.ts +9 -0
- package/src/platform/client.ts +1 -1
- package/src/prompts/system-prompt.ts +59 -7
- package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +100 -0
- package/src/prompts/templates/BOOTSTRAP.md +76 -162
- package/src/prompts/templates/HEARTBEAT.md +3 -1
- package/src/prompts/templates/SOUL.md +30 -9
- package/src/prompts/templates/UPDATES.md +8 -0
- package/src/providers/anthropic/client.ts +107 -219
- package/src/runtime/assistant-event-hub.ts +22 -0
- package/src/runtime/auth/route-policy.ts +23 -0
- package/src/runtime/auth/token-service.ts +8 -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 +185 -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 +270 -44
- package/src/runtime/routes/group-routes.ts +22 -8
- 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/AGENTS.md +104 -0
- package/src/runtime/routes/log-export/__tests__/workspace-allowlist-error-contract.test.ts +103 -0
- package/src/runtime/routes/log-export/__tests__/workspace-allowlist.test.ts +716 -0
- package/src/runtime/routes/log-export/workspace-allowlist.ts +458 -0
- package/src/runtime/routes/log-export-routes.ts +41 -278
- 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/inline-command-runner.ts +12 -14
- 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 -18
- 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/secret-detection-handler.ts +0 -1
- 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/skills/sandbox-runner.ts +3 -6
- 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/terminal/sandbox-diagnostics.ts +4 -4
- package/src/tools/terminal/sandbox.ts +4 -1
- package/src/tools/terminal/shell.ts +3 -5
- package/src/tools/tool-manifest.ts +6 -0
- package/src/tools/types.ts +2 -3
- package/src/util/logger.ts +1 -1
- package/src/util/platform.ts +50 -17
- package/src/watcher/provider-types.ts +1 -1
- 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 +85 -0
- package/src/workspace/migrations/030-seed-pkb-autoinject.ts +73 -0
- package/src/workspace/migrations/registry.ts +6 -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
|
+
});
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for initFeatureFlagOverrides() — the async gateway fetch that
|
|
3
|
+
* pre-populates the feature flag cache before CLI program construction.
|
|
4
|
+
*/
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
clearFeatureFlagOverridesCache,
|
|
9
|
+
initFeatureFlagOverrides,
|
|
10
|
+
isAssistantFeatureFlagEnabled,
|
|
11
|
+
} from "../config/assistant-feature-flags.js";
|
|
12
|
+
import * as tokenService from "../runtime/auth/token-service.js";
|
|
13
|
+
import { getMockFetchCalls, mockFetch, resetMockFetch } from "./mock-fetch.js";
|
|
14
|
+
|
|
15
|
+
const VALID_HEX_KEY = "ab".repeat(32);
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
clearFeatureFlagOverridesCache();
|
|
19
|
+
tokenService._resetSigningKeyForTesting();
|
|
20
|
+
|
|
21
|
+
// Set up a signing key so mintEdgeRelayToken() works
|
|
22
|
+
process.env.ACTOR_TOKEN_SIGNING_KEY = VALID_HEX_KEY;
|
|
23
|
+
tokenService.initAuthSigningKey(tokenService.resolveSigningKey());
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
resetMockFetch();
|
|
28
|
+
clearFeatureFlagOverridesCache();
|
|
29
|
+
tokenService._resetSigningKeyForTesting();
|
|
30
|
+
delete process.env.ACTOR_TOKEN_SIGNING_KEY;
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe("initFeatureFlagOverrides", () => {
|
|
34
|
+
it("populates cache from gateway fetch response", async () => {
|
|
35
|
+
mockFetch(
|
|
36
|
+
"/v1/feature-flags",
|
|
37
|
+
{ method: "GET" },
|
|
38
|
+
{
|
|
39
|
+
body: {
|
|
40
|
+
flags: [
|
|
41
|
+
{
|
|
42
|
+
key: "foo-enabled",
|
|
43
|
+
enabled: true,
|
|
44
|
+
label: "Foo",
|
|
45
|
+
defaultEnabled: false,
|
|
46
|
+
description: "",
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
key: "bar-enabled",
|
|
50
|
+
enabled: true,
|
|
51
|
+
label: "Bar",
|
|
52
|
+
defaultEnabled: true,
|
|
53
|
+
description: "",
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
status: 200,
|
|
58
|
+
},
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
await initFeatureFlagOverrides();
|
|
62
|
+
|
|
63
|
+
const config = {} as any;
|
|
64
|
+
expect(isAssistantFeatureFlagEnabled("foo-enabled", config)).toBe(true);
|
|
65
|
+
expect(isAssistantFeatureFlagEnabled("bar-enabled", config)).toBe(true);
|
|
66
|
+
|
|
67
|
+
// Verify fetch was called with correct URL and auth header
|
|
68
|
+
const calls = getMockFetchCalls();
|
|
69
|
+
expect(calls.length).toBe(1);
|
|
70
|
+
expect(calls[0].path).toContain("/v1/feature-flags");
|
|
71
|
+
const headers = calls[0].init.headers as Record<string, string> | undefined;
|
|
72
|
+
expect(headers).toHaveProperty("Authorization");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("sends a valid Bearer JWT in the Authorization header", async () => {
|
|
76
|
+
mockFetch(
|
|
77
|
+
"/v1/feature-flags",
|
|
78
|
+
{ method: "GET" },
|
|
79
|
+
{ body: { flags: [] }, status: 200 },
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
await initFeatureFlagOverrides();
|
|
83
|
+
|
|
84
|
+
const calls = getMockFetchCalls();
|
|
85
|
+
expect(calls.length).toBe(1);
|
|
86
|
+
const headers = calls[0].init.headers as Record<string, string> | undefined;
|
|
87
|
+
const authHeader = headers?.Authorization;
|
|
88
|
+
|
|
89
|
+
expect(authHeader).toBeDefined();
|
|
90
|
+
expect(authHeader).toMatch(/^Bearer /);
|
|
91
|
+
|
|
92
|
+
// Verify it's a valid JWT (three dot-separated base64url segments)
|
|
93
|
+
const token = authHeader!.replace("Bearer ", "");
|
|
94
|
+
const parts = token.split(".");
|
|
95
|
+
expect(parts.length).toBe(3);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("falls back gracefully when gateway is unreachable", async () => {
|
|
99
|
+
mockFetch("/v1/feature-flags", { method: "GET" }, { status: 500 });
|
|
100
|
+
|
|
101
|
+
// Should not throw
|
|
102
|
+
await initFeatureFlagOverrides();
|
|
103
|
+
|
|
104
|
+
// Without gateway data or file, undeclared flags default to true
|
|
105
|
+
const config = {} as any;
|
|
106
|
+
expect(isAssistantFeatureFlagEnabled("foo-enabled", config)).toBe(true);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("falls back gracefully on non-OK HTTP status", async () => {
|
|
110
|
+
mockFetch(
|
|
111
|
+
"/v1/feature-flags",
|
|
112
|
+
{ method: "GET" },
|
|
113
|
+
{ body: "Unauthorized", status: 401 },
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
await initFeatureFlagOverrides();
|
|
117
|
+
|
|
118
|
+
// Undeclared flags default to true without overrides
|
|
119
|
+
const config = {} as any;
|
|
120
|
+
expect(isAssistantFeatureFlagEnabled("foo-enabled", config)).toBe(true);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("initializes signing key lazily when not yet set", async () => {
|
|
124
|
+
// Reset signing key to simulate fresh CLI subprocess
|
|
125
|
+
tokenService._resetSigningKeyForTesting();
|
|
126
|
+
delete process.env.ACTOR_TOKEN_SIGNING_KEY;
|
|
127
|
+
|
|
128
|
+
expect(tokenService.isSigningKeyInitialized()).toBe(false);
|
|
129
|
+
|
|
130
|
+
mockFetch(
|
|
131
|
+
"/v1/feature-flags",
|
|
132
|
+
{ method: "GET" },
|
|
133
|
+
{
|
|
134
|
+
body: {
|
|
135
|
+
flags: [{ key: "expected-enabled", enabled: true }],
|
|
136
|
+
},
|
|
137
|
+
status: 200,
|
|
138
|
+
},
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
await initFeatureFlagOverrides();
|
|
142
|
+
|
|
143
|
+
// Signing key should have been initialized during the fetch
|
|
144
|
+
expect(tokenService.isSigningKeyInitialized()).toBe(true);
|
|
145
|
+
|
|
146
|
+
// And the flag should be resolved correctly
|
|
147
|
+
const config = {} as any;
|
|
148
|
+
expect(isAssistantFeatureFlagEnabled("expected-enabled", config)).toBe(
|
|
149
|
+
true,
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("does not cache empty gateway response", async () => {
|
|
154
|
+
mockFetch(
|
|
155
|
+
"/v1/feature-flags",
|
|
156
|
+
{ method: "GET" },
|
|
157
|
+
{ body: { flags: [] }, status: 200 },
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
await initFeatureFlagOverrides();
|
|
161
|
+
|
|
162
|
+
// Undeclared flags without overrides default to true (not false from
|
|
163
|
+
// a cached empty map)
|
|
164
|
+
const config = {} as any;
|
|
165
|
+
expect(isAssistantFeatureFlagEnabled("foo-enabled", config)).toBe(true);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
@@ -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",
|
|
@@ -11,7 +11,6 @@ const mockConfig = {
|
|
|
11
11
|
model: "test",
|
|
12
12
|
maxTokens: 4096,
|
|
13
13
|
dataDir: "/tmp",
|
|
14
|
-
sandbox: { enabled: true },
|
|
15
14
|
timeouts: {
|
|
16
15
|
shellDefaultTimeoutSec: 120,
|
|
17
16
|
shellMaxTimeoutSec: 600,
|
|
@@ -104,20 +103,23 @@ describe("runInlineCommand", () => {
|
|
|
104
103
|
// ── Sandbox enforcement ──────────────────────────────────────────────────
|
|
105
104
|
|
|
106
105
|
describe("sandbox enforcement", () => {
|
|
107
|
-
test("always passes sandbox config with enabled=
|
|
106
|
+
test("always passes sandbox config with enabled=false", async () => {
|
|
108
107
|
lastWrapCall = null;
|
|
109
108
|
await runInlineCommand("echo sandbox-check", CWD);
|
|
110
109
|
|
|
111
110
|
expect(lastWrapCall).not.toBeNull();
|
|
112
|
-
expect(lastWrapCall!.config.enabled).toBe(
|
|
111
|
+
expect(lastWrapCall!.config.enabled).toBe(false);
|
|
113
112
|
});
|
|
114
113
|
|
|
115
|
-
test("
|
|
114
|
+
test("does not pass networkMode when sandbox is disabled", async () => {
|
|
116
115
|
lastWrapCall = null;
|
|
117
116
|
await runInlineCommand("echo network-check", CWD);
|
|
118
117
|
|
|
119
118
|
expect(lastWrapCall).not.toBeNull();
|
|
120
|
-
|
|
119
|
+
// networkMode is a no-op when sandbox is disabled (wrapCommand returns
|
|
120
|
+
// a plain bash invocation), so it is not passed. Network isolation is
|
|
121
|
+
// provided by the Docker/platform-managed container.
|
|
122
|
+
expect(lastWrapCall!.options).toBeUndefined();
|
|
121
123
|
});
|
|
122
124
|
|
|
123
125
|
test("uses the provided workingDir as cwd", async () => {
|
|
@@ -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
|
|