@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
|
@@ -3,6 +3,9 @@ import { join } from "node:path";
|
|
|
3
3
|
|
|
4
4
|
import type { Command } from "commander";
|
|
5
5
|
|
|
6
|
+
import { getConfig } from "../../config/loader.js";
|
|
7
|
+
import { resolveSkillStates } from "../../config/skill-state.js";
|
|
8
|
+
import { loadSkillCatalog } from "../../config/skills.js";
|
|
6
9
|
import type { CatalogSkill } from "../../skills/catalog-install.js";
|
|
7
10
|
import {
|
|
8
11
|
fetchCatalog,
|
|
@@ -56,13 +59,13 @@ Examples:
|
|
|
56
59
|
|
|
57
60
|
skills
|
|
58
61
|
.command("list")
|
|
59
|
-
.description("List
|
|
62
|
+
.description("List all skills (bundled, installed, and catalog)")
|
|
60
63
|
.option("--json", "Machine-readable JSON output")
|
|
61
64
|
.addHelpText(
|
|
62
65
|
"after",
|
|
63
66
|
`
|
|
64
|
-
Lists all skills
|
|
65
|
-
|
|
67
|
+
Lists all skills: bundled (compiled-in), installed (user-added), and
|
|
68
|
+
available catalog skills with their ID, name, and description.
|
|
66
69
|
|
|
67
70
|
Examples:
|
|
68
71
|
$ assistant skills list
|
|
@@ -70,34 +73,103 @@ Examples:
|
|
|
70
73
|
)
|
|
71
74
|
.action(async (opts: { json?: boolean }) => {
|
|
72
75
|
try {
|
|
73
|
-
//
|
|
74
|
-
|
|
76
|
+
// ── Bundled + installed skills (from loadSkillCatalog) ────────
|
|
77
|
+
const localCatalog = loadSkillCatalog();
|
|
78
|
+
const config = getConfig();
|
|
79
|
+
const resolved = resolveSkillStates(localCatalog, config);
|
|
80
|
+
const bundled = resolved.filter(
|
|
81
|
+
(r) => r.summary.source === "bundled",
|
|
82
|
+
);
|
|
83
|
+
const installed = resolved.filter(
|
|
84
|
+
(r) =>
|
|
85
|
+
r.summary.source === "managed" ||
|
|
86
|
+
r.summary.source === "workspace" ||
|
|
87
|
+
r.summary.source === "extra",
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
// ── Remote catalog skills ────────────────────────────────────
|
|
75
91
|
const repoSkillsDir = getRepoSkillsDir();
|
|
76
|
-
let
|
|
92
|
+
let remoteCatalog: CatalogSkill[];
|
|
77
93
|
if (repoSkillsDir) {
|
|
78
|
-
|
|
94
|
+
remoteCatalog = readLocalCatalog(repoSkillsDir);
|
|
79
95
|
} else {
|
|
80
|
-
|
|
96
|
+
remoteCatalog = await fetchCatalog();
|
|
81
97
|
}
|
|
98
|
+
// Exclude catalog skills that are already installed/bundled
|
|
99
|
+
const localIds = new Set(localCatalog.map((s) => s.id));
|
|
100
|
+
const availableCatalog = remoteCatalog.filter(
|
|
101
|
+
(s) => !localIds.has(s.id),
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const totalCount =
|
|
105
|
+
bundled.length + installed.length + availableCatalog.length;
|
|
82
106
|
|
|
83
107
|
if (opts.json) {
|
|
84
|
-
|
|
108
|
+
const bundledJson = bundled.map((r) => ({
|
|
109
|
+
id: r.summary.id,
|
|
110
|
+
name: r.summary.displayName,
|
|
111
|
+
description: r.summary.description,
|
|
112
|
+
emoji: r.summary.emoji,
|
|
113
|
+
state: r.state,
|
|
114
|
+
}));
|
|
115
|
+
const installedJson = installed.map((r) => ({
|
|
116
|
+
id: r.summary.id,
|
|
117
|
+
name: r.summary.displayName,
|
|
118
|
+
description: r.summary.description,
|
|
119
|
+
emoji: r.summary.emoji,
|
|
120
|
+
state: r.state,
|
|
121
|
+
}));
|
|
122
|
+
console.log(
|
|
123
|
+
JSON.stringify({
|
|
124
|
+
ok: true,
|
|
125
|
+
skills: [...bundledJson, ...installedJson, ...availableCatalog],
|
|
126
|
+
bundled: bundledJson,
|
|
127
|
+
installed: installedJson,
|
|
128
|
+
catalog: availableCatalog,
|
|
129
|
+
}),
|
|
130
|
+
);
|
|
85
131
|
return;
|
|
86
132
|
}
|
|
87
133
|
|
|
88
|
-
if (
|
|
89
|
-
log.info("No skills available
|
|
134
|
+
if (totalCount === 0) {
|
|
135
|
+
log.info("No skills available.");
|
|
90
136
|
return;
|
|
91
137
|
}
|
|
92
138
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
: "";
|
|
99
|
-
|
|
100
|
-
|
|
139
|
+
if (bundled.length > 0) {
|
|
140
|
+
log.info(`Bundled skills (${bundled.length}):\n`);
|
|
141
|
+
for (const r of bundled) {
|
|
142
|
+
const s = r.summary;
|
|
143
|
+
const emoji = s.emoji ? `${s.emoji} ` : "";
|
|
144
|
+
const state = r.state === "disabled" ? " [disabled]" : "";
|
|
145
|
+
log.info(` ${emoji}${s.id}${state}`);
|
|
146
|
+
log.info(` ${s.displayName} — ${s.description}`);
|
|
147
|
+
}
|
|
148
|
+
log.info("");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (installed.length > 0) {
|
|
152
|
+
log.info(`Installed skills (${installed.length}):\n`);
|
|
153
|
+
for (const r of installed) {
|
|
154
|
+
const s = r.summary;
|
|
155
|
+
const emoji = s.emoji ? `${s.emoji} ` : "";
|
|
156
|
+
const state = r.state === "disabled" ? " [disabled]" : "";
|
|
157
|
+
log.info(` ${emoji}${s.id}${state}`);
|
|
158
|
+
log.info(` ${s.displayName} — ${s.description}`);
|
|
159
|
+
}
|
|
160
|
+
log.info("");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (availableCatalog.length > 0) {
|
|
164
|
+
log.info(`Available catalog skills (${availableCatalog.length}):\n`);
|
|
165
|
+
for (const s of availableCatalog) {
|
|
166
|
+
const emoji = s.emoji ? `${s.emoji} ` : "";
|
|
167
|
+
const deps = s.includes?.length
|
|
168
|
+
? ` (requires: ${s.includes.join(", ")})`
|
|
169
|
+
: "";
|
|
170
|
+
log.info(` ${emoji}${s.id}`);
|
|
171
|
+
log.info(` ${s.name} — ${s.description}${deps}`);
|
|
172
|
+
}
|
|
101
173
|
}
|
|
102
174
|
} catch (err) {
|
|
103
175
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -139,6 +211,14 @@ Examples:
|
|
|
139
211
|
const limit = parseInt(opts.limit, 10) || 10;
|
|
140
212
|
|
|
141
213
|
try {
|
|
214
|
+
// ── Bundled + installed skill search ─────────────────────────
|
|
215
|
+
const localCatalog = loadSkillCatalog();
|
|
216
|
+
const bundledMatches = filterByQuery(localCatalog, query, [
|
|
217
|
+
(s) => s.id,
|
|
218
|
+
(s) => s.displayName,
|
|
219
|
+
(s) => s.description,
|
|
220
|
+
]);
|
|
221
|
+
|
|
142
222
|
// ── Vellum catalog search ────────────────────────────────────
|
|
143
223
|
const repoSkillsDir = getRepoSkillsDir();
|
|
144
224
|
let catalog: CatalogSkill[];
|
|
@@ -151,8 +231,11 @@ Examples:
|
|
|
151
231
|
catalog = [];
|
|
152
232
|
}
|
|
153
233
|
}
|
|
234
|
+
// Exclude catalog entries that match a bundled/installed skill
|
|
235
|
+
const localIds = new Set(localCatalog.map((s) => s.id));
|
|
236
|
+
const filteredCatalog = catalog.filter((s) => !localIds.has(s.id));
|
|
154
237
|
|
|
155
|
-
const catalogMatches = filterByQuery(
|
|
238
|
+
const catalogMatches = filterByQuery(filteredCatalog, query, [
|
|
156
239
|
(s) => s.id,
|
|
157
240
|
(s) => s.name,
|
|
158
241
|
(s) => s.description,
|
|
@@ -202,6 +285,7 @@ Examples:
|
|
|
202
285
|
]);
|
|
203
286
|
|
|
204
287
|
if (
|
|
288
|
+
bundledMatches.length === 0 &&
|
|
205
289
|
catalogMatches.length === 0 &&
|
|
206
290
|
registryResults.length === 0 &&
|
|
207
291
|
clawhubResults.length === 0
|
|
@@ -210,6 +294,7 @@ Examples:
|
|
|
210
294
|
console.log(
|
|
211
295
|
JSON.stringify({
|
|
212
296
|
ok: true,
|
|
297
|
+
bundled: [],
|
|
213
298
|
catalog: [],
|
|
214
299
|
community: [],
|
|
215
300
|
clawhub: [],
|
|
@@ -255,6 +340,13 @@ Examples:
|
|
|
255
340
|
console.log(
|
|
256
341
|
JSON.stringify({
|
|
257
342
|
ok: true,
|
|
343
|
+
bundled: bundledMatches.map((s) => ({
|
|
344
|
+
id: s.id,
|
|
345
|
+
name: s.displayName,
|
|
346
|
+
description: s.description,
|
|
347
|
+
emoji: s.emoji,
|
|
348
|
+
source: s.source,
|
|
349
|
+
})),
|
|
258
350
|
catalog: catalogMatches,
|
|
259
351
|
community: registryResults,
|
|
260
352
|
clawhub: clawhubResults,
|
|
@@ -271,6 +363,24 @@ Examples:
|
|
|
271
363
|
const isInstalled = (id: string) =>
|
|
272
364
|
existsSync(join(skillsDir, id, "SKILL.md"));
|
|
273
365
|
|
|
366
|
+
// ── Display bundled/installed results ─────────────────────────
|
|
367
|
+
if (bundledMatches.length > 0) {
|
|
368
|
+
log.info(
|
|
369
|
+
`Bundled & installed skills (${bundledMatches.length}):\n`,
|
|
370
|
+
);
|
|
371
|
+
for (const s of bundledMatches) {
|
|
372
|
+
const emoji = s.emoji ? `${s.emoji} ` : "";
|
|
373
|
+
const tag = s.source === "bundled" ? " [bundled]" : " [installed]";
|
|
374
|
+
log.info(` ${emoji}${s.displayName}${tag}`);
|
|
375
|
+
if (s.displayName !== s.id) {
|
|
376
|
+
log.info(` ID: ${s.id}`);
|
|
377
|
+
}
|
|
378
|
+
log.info(` ${s.description}`);
|
|
379
|
+
log.info(` Load: skill_load skill=${s.id}`);
|
|
380
|
+
log.info("");
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
274
384
|
// ── Display catalog results ──────────────────────────────────
|
|
275
385
|
if (catalogMatches.length > 0) {
|
|
276
386
|
log.info(`Vellum catalog (${catalogMatches.length}):\n`);
|
package/src/cli/program.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
|
|
3
|
+
import { initFeatureFlagOverrides } from "../config/assistant-feature-flags.js";
|
|
3
4
|
import { getConfig } from "../config/loader.js";
|
|
4
5
|
import { isEmailEnabled } from "../email/feature-gate.js";
|
|
5
6
|
import { registerHooksCommand } from "../hooks/cli.js";
|
|
@@ -26,19 +27,26 @@ import { registerMemoryCommand } from "./commands/memory.js";
|
|
|
26
27
|
import { registerNotificationsCommand } from "./commands/notifications.js";
|
|
27
28
|
import { registerOAuthCommand } from "./commands/oauth/index.js";
|
|
28
29
|
import { registerPlatformCommand } from "./commands/platform/index.js";
|
|
30
|
+
import { registerRoutesCommand } from "./commands/routes.js";
|
|
29
31
|
import { registerSequenceCommand } from "./commands/sequence.js";
|
|
30
32
|
import { registerShotgunCommand } from "./commands/shotgun.js";
|
|
31
33
|
import { registerSkillsCommand } from "./commands/skills.js";
|
|
32
34
|
import { registerTrustCommand } from "./commands/trust.js";
|
|
33
35
|
import { registerUsageCommand } from "./commands/usage.js";
|
|
34
36
|
|
|
35
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Build the CLI program tree. Pre-populates the feature flag cache from
|
|
39
|
+
* the gateway so flag-gated commands are registered correctly.
|
|
40
|
+
*/
|
|
41
|
+
export async function buildCliProgram(): Promise<Command> {
|
|
42
|
+
await initFeatureFlagOverrides();
|
|
36
43
|
const program = new Command();
|
|
37
44
|
|
|
38
45
|
program
|
|
39
46
|
.name("assistant")
|
|
40
47
|
.description("Local AI assistant")
|
|
41
|
-
.version(APP_VERSION)
|
|
48
|
+
.version(APP_VERSION)
|
|
49
|
+
.allowExcessArguments(true);
|
|
42
50
|
|
|
43
51
|
program.addHelpText(
|
|
44
52
|
"after",
|
|
@@ -75,6 +83,7 @@ Examples:
|
|
|
75
83
|
registerNotificationsCommand(program);
|
|
76
84
|
registerPlatformCommand(program);
|
|
77
85
|
registerOAuthCommand(program);
|
|
86
|
+
registerRoutesCommand(program);
|
|
78
87
|
registerSkillsCommand(program);
|
|
79
88
|
registerBrowserRelayCommand(program);
|
|
80
89
|
registerUsageCommand(program);
|
package/src/cli.ts
CHANGED
|
@@ -33,11 +33,6 @@ import {
|
|
|
33
33
|
type EventStreamWatcher,
|
|
34
34
|
watchEventStream,
|
|
35
35
|
} from "./signals/event-stream.js";
|
|
36
|
-
import {
|
|
37
|
-
copyToClipboard,
|
|
38
|
-
extractLastCodeBlock,
|
|
39
|
-
formatConversationForExport,
|
|
40
|
-
} from "./util/clipboard.js";
|
|
41
36
|
import { formatDiff, formatNewFileDiff } from "./util/diff.js";
|
|
42
37
|
import { getHistoryPath, getSignalsDir } from "./util/platform.js";
|
|
43
38
|
import { Spinner } from "./util/spinner.js";
|
|
@@ -143,7 +138,6 @@ export async function startCli(): Promise<void> {
|
|
|
143
138
|
let conversationId = "";
|
|
144
139
|
let pendingUserContent: string | null = null;
|
|
145
140
|
let generating = false;
|
|
146
|
-
let lastResponse = "";
|
|
147
141
|
let lastUsage: {
|
|
148
142
|
inputTokens: number;
|
|
149
143
|
outputTokens: number;
|
|
@@ -153,7 +147,6 @@ export async function startCli(): Promise<void> {
|
|
|
153
147
|
model: string;
|
|
154
148
|
} | null = null;
|
|
155
149
|
let pendingSessionPick = false;
|
|
156
|
-
let pendingCopySession = false;
|
|
157
150
|
let toolStreaming = false;
|
|
158
151
|
let lastDisplayedError: string | null = null;
|
|
159
152
|
let eventSubscription: EventStreamWatcher | null = null;
|
|
@@ -398,7 +391,6 @@ export async function startCli(): Promise<void> {
|
|
|
398
391
|
if (pendingUserContent) {
|
|
399
392
|
const content = pendingUserContent;
|
|
400
393
|
pendingUserContent = null;
|
|
401
|
-
lastResponse = "";
|
|
402
394
|
sendUserMessage(content).then((result) => {
|
|
403
395
|
if (result.ok) {
|
|
404
396
|
generating = true;
|
|
@@ -408,7 +400,6 @@ export async function startCli(): Promise<void> {
|
|
|
408
400
|
process.stdout.write(`${result.message}\n`);
|
|
409
401
|
rl.question("Send anyway? (y/N): ", (answer) => {
|
|
410
402
|
if (answer.trim().toLowerCase() === "y") {
|
|
411
|
-
lastResponse = "";
|
|
412
403
|
sendUserMessage(content, {
|
|
413
404
|
bypassSecretCheck: true,
|
|
414
405
|
}).then((retryResult) => {
|
|
@@ -439,7 +430,6 @@ export async function startCli(): Promise<void> {
|
|
|
439
430
|
|
|
440
431
|
case "assistant_text_delta":
|
|
441
432
|
spinner.stop();
|
|
442
|
-
lastResponse += msg.text;
|
|
443
433
|
process.stdout.write(msg.text);
|
|
444
434
|
break;
|
|
445
435
|
|
|
@@ -595,9 +585,8 @@ export async function startCli(): Promise<void> {
|
|
|
595
585
|
case "error":
|
|
596
586
|
spinner.stop();
|
|
597
587
|
generating = false;
|
|
598
|
-
if (pendingSessionPick
|
|
588
|
+
if (pendingSessionPick) {
|
|
599
589
|
pendingSessionPick = false;
|
|
600
|
-
pendingCopySession = false;
|
|
601
590
|
rl.removeAllListeners("line");
|
|
602
591
|
rl.on("line", handleLine);
|
|
603
592
|
}
|
|
@@ -646,26 +635,6 @@ export async function startCli(): Promise<void> {
|
|
|
646
635
|
break;
|
|
647
636
|
|
|
648
637
|
case "history_response":
|
|
649
|
-
if (pendingCopySession) {
|
|
650
|
-
pendingCopySession = false;
|
|
651
|
-
if (msg.messages.length === 0) {
|
|
652
|
-
process.stdout.write("\n No messages to copy.\n\n");
|
|
653
|
-
} else {
|
|
654
|
-
try {
|
|
655
|
-
const formatted = formatConversationForExport(msg.messages);
|
|
656
|
-
copyToClipboard(formatted);
|
|
657
|
-
process.stdout.write(
|
|
658
|
-
`\n Copied conversation (${msg.messages.length} messages) to clipboard.\n\n`,
|
|
659
|
-
);
|
|
660
|
-
} catch (err) {
|
|
661
|
-
process.stdout.write(
|
|
662
|
-
`\n Clipboard error: ${(err as Error).message}\n\n`,
|
|
663
|
-
);
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
prompt();
|
|
667
|
-
break;
|
|
668
|
-
}
|
|
669
638
|
process.stdout.write("\n");
|
|
670
639
|
if (msg.messages.length === 0) {
|
|
671
640
|
process.stdout.write(" No messages in this conversation.\n");
|
|
@@ -686,7 +655,6 @@ export async function startCli(): Promise<void> {
|
|
|
686
655
|
if (msg.removedCount === 0) {
|
|
687
656
|
process.stdout.write("\n Nothing to undo.\n\n");
|
|
688
657
|
} else {
|
|
689
|
-
lastResponse = "";
|
|
690
658
|
process.stdout.write(
|
|
691
659
|
`\n Removed last exchange (${msg.removedCount} messages).\n\n`,
|
|
692
660
|
);
|
|
@@ -754,21 +722,6 @@ export async function startCli(): Promise<void> {
|
|
|
754
722
|
/* ignore */
|
|
755
723
|
}
|
|
756
724
|
|
|
757
|
-
if (content === "/copy") {
|
|
758
|
-
if (!lastResponse) {
|
|
759
|
-
process.stdout.write("No response to copy.\n");
|
|
760
|
-
} else {
|
|
761
|
-
try {
|
|
762
|
-
copyToClipboard(lastResponse);
|
|
763
|
-
process.stdout.write("Copied to clipboard.\n");
|
|
764
|
-
} catch (err) {
|
|
765
|
-
process.stdout.write(`Clipboard error: ${(err as Error).message}\n`);
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
prompt();
|
|
769
|
-
return;
|
|
770
|
-
}
|
|
771
|
-
|
|
772
725
|
if (content === "/conversations") {
|
|
773
726
|
pendingSessionPick = true;
|
|
774
727
|
try {
|
|
@@ -787,65 +740,6 @@ export async function startCli(): Promise<void> {
|
|
|
787
740
|
return;
|
|
788
741
|
}
|
|
789
742
|
|
|
790
|
-
if (content === "/copy-code") {
|
|
791
|
-
const code = extractLastCodeBlock(lastResponse);
|
|
792
|
-
if (code == null) {
|
|
793
|
-
process.stdout.write("No code block found.\n");
|
|
794
|
-
} else {
|
|
795
|
-
try {
|
|
796
|
-
copyToClipboard(code);
|
|
797
|
-
process.stdout.write("Copied code block to clipboard.\n");
|
|
798
|
-
} catch (err) {
|
|
799
|
-
process.stdout.write(`Clipboard error: ${(err as Error).message}\n`);
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
prompt();
|
|
803
|
-
return;
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
if (content === "/copy-conversation") {
|
|
807
|
-
try {
|
|
808
|
-
const mapping = getConversationByKey(conversationKey);
|
|
809
|
-
if (!mapping) {
|
|
810
|
-
process.stdout.write("\n No messages to copy.\n\n");
|
|
811
|
-
prompt();
|
|
812
|
-
return;
|
|
813
|
-
}
|
|
814
|
-
const rawMessages = getMessages(mapping.conversationId);
|
|
815
|
-
if (rawMessages.length === 0) {
|
|
816
|
-
process.stdout.write("\n No messages to copy.\n\n");
|
|
817
|
-
} else {
|
|
818
|
-
const rendered = rawMessages.map((msg) => {
|
|
819
|
-
let parsedContent: unknown;
|
|
820
|
-
try {
|
|
821
|
-
parsedContent = JSON.parse(msg.content);
|
|
822
|
-
} catch {
|
|
823
|
-
parsedContent = msg.content;
|
|
824
|
-
}
|
|
825
|
-
return {
|
|
826
|
-
role: msg.role as "user" | "assistant",
|
|
827
|
-
text: renderHistoryContent(parsedContent).text,
|
|
828
|
-
};
|
|
829
|
-
});
|
|
830
|
-
try {
|
|
831
|
-
const formatted = formatConversationForExport(rendered);
|
|
832
|
-
copyToClipboard(formatted);
|
|
833
|
-
process.stdout.write(
|
|
834
|
-
`\n Copied conversation (${rawMessages.length} messages) to clipboard.\n\n`,
|
|
835
|
-
);
|
|
836
|
-
} catch (err) {
|
|
837
|
-
process.stdout.write(
|
|
838
|
-
`\n Clipboard error: ${(err as Error).message}\n\n`,
|
|
839
|
-
);
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
} catch {
|
|
843
|
-
process.stdout.write("[Failed to fetch history]\n");
|
|
844
|
-
}
|
|
845
|
-
prompt();
|
|
846
|
-
return;
|
|
847
|
-
}
|
|
848
|
-
|
|
849
743
|
if (content === "/new") {
|
|
850
744
|
// Create a new conversation by using a unique key
|
|
851
745
|
conversationKey = `builtin-cli:${randomUUID()}`;
|
|
@@ -859,7 +753,6 @@ export async function startCli(): Promise<void> {
|
|
|
859
753
|
}
|
|
860
754
|
|
|
861
755
|
if (content === "/clear") {
|
|
862
|
-
lastResponse = "";
|
|
863
756
|
process.stdout.write("\x1b[r");
|
|
864
757
|
process.stdout.write("\x1b[2J\x1b[H");
|
|
865
758
|
mainScreenLayout = renderMainScreen();
|
|
@@ -955,7 +848,6 @@ export async function startCli(): Promise<void> {
|
|
|
955
848
|
if (result.removedCount === 0) {
|
|
956
849
|
process.stdout.write("\n Nothing to undo.\n\n");
|
|
957
850
|
} else {
|
|
958
|
-
lastResponse = "";
|
|
959
851
|
process.stdout.write(
|
|
960
852
|
`\n Removed last exchange (${result.removedCount} messages).\n\n`,
|
|
961
853
|
);
|
|
@@ -1016,15 +908,6 @@ export async function startCli(): Promise<void> {
|
|
|
1016
908
|
" /undo Remove last message exchange\n",
|
|
1017
909
|
);
|
|
1018
910
|
process.stdout.write(" /usage Show token usage and cost\n");
|
|
1019
|
-
process.stdout.write(
|
|
1020
|
-
" /copy Copy last response to clipboard\n",
|
|
1021
|
-
);
|
|
1022
|
-
process.stdout.write(
|
|
1023
|
-
" /copy-code Copy last code block to clipboard\n",
|
|
1024
|
-
);
|
|
1025
|
-
process.stdout.write(
|
|
1026
|
-
" /copy-conversation Copy entire conversation to clipboard\n",
|
|
1027
|
-
);
|
|
1028
911
|
process.stdout.write(" /help Show this help\n");
|
|
1029
912
|
process.stdout.write("\n");
|
|
1030
913
|
prompt();
|
|
@@ -1032,14 +915,12 @@ export async function startCli(): Promise<void> {
|
|
|
1032
915
|
}
|
|
1033
916
|
|
|
1034
917
|
// Regular user message
|
|
1035
|
-
lastResponse = "";
|
|
1036
918
|
sendUserMessage(content).then((result) => {
|
|
1037
919
|
if (!result.ok) {
|
|
1038
920
|
if (result.error === "secret_blocked" && result.message) {
|
|
1039
921
|
process.stdout.write(`${result.message}\n`);
|
|
1040
922
|
rl.question("Send anyway? (y/N): ", (answer) => {
|
|
1041
923
|
if (answer.trim().toLowerCase() === "y") {
|
|
1042
|
-
lastResponse = "";
|
|
1043
924
|
sendUserMessage(content, { bypassSecretCheck: true }).then(
|
|
1044
925
|
(retryResult) => {
|
|
1045
926
|
if (retryResult.ok) {
|
|
@@ -20,7 +20,6 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
20
20
|
import { homedir } from "node:os";
|
|
21
21
|
import { dirname, join } from "node:path";
|
|
22
22
|
|
|
23
|
-
import { getIsContainerized } from "./env-registry.js";
|
|
24
23
|
import type { AssistantConfig } from "./schema.js";
|
|
25
24
|
|
|
26
25
|
// ---------------------------------------------------------------------------
|
|
@@ -173,61 +172,49 @@ function loadOverridesFromFile(): Record<string, boolean> {
|
|
|
173
172
|
}
|
|
174
173
|
|
|
175
174
|
/**
|
|
176
|
-
*
|
|
175
|
+
* Fetch override values from the gateway via async HTTP.
|
|
177
176
|
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
* gateway returns `{ flags: Array<{ key, enabled, ... }> }` and we extract
|
|
181
|
-
* just the key → enabled map.
|
|
177
|
+
* Returns the gateway's merged feature flag map (persisted > remote >
|
|
178
|
+
* registry), or an empty record on any failure (network, auth, parse).
|
|
182
179
|
*/
|
|
183
|
-
function
|
|
180
|
+
async function fetchOverridesFromGateway(): Promise<Record<string, boolean>> {
|
|
184
181
|
try {
|
|
185
182
|
// Lazy-import to avoid circular dependency and keep this module
|
|
186
183
|
// importable from bootstrap code when not in containerized mode.
|
|
187
184
|
const { getGatewayInternalBaseUrl } =
|
|
188
185
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
189
186
|
require("./env.js") as typeof import("./env.js");
|
|
190
|
-
const {
|
|
187
|
+
const {
|
|
188
|
+
mintEdgeRelayToken,
|
|
189
|
+
isSigningKeyInitialized,
|
|
190
|
+
initAuthSigningKey,
|
|
191
|
+
resolveSigningKey,
|
|
192
|
+
} =
|
|
191
193
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
192
194
|
require("../runtime/auth/token-service.js") as typeof import("../runtime/auth/token-service.js");
|
|
193
195
|
|
|
196
|
+
// CLI subprocesses don't run daemon startup, so the signing key
|
|
197
|
+
// may not be initialized yet. Initialize it now so mintEdgeRelayToken
|
|
198
|
+
// can produce a valid JWT for the gateway request.
|
|
199
|
+
if (!isSigningKeyInitialized()) {
|
|
200
|
+
initAuthSigningKey(resolveSigningKey());
|
|
201
|
+
}
|
|
202
|
+
|
|
194
203
|
const url = `${getGatewayInternalBaseUrl()}/v1/feature-flags`;
|
|
195
204
|
const token = mintEdgeRelayToken();
|
|
196
205
|
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
"
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
"Accept: application/json",
|
|
210
|
-
"-w",
|
|
211
|
-
"\n%{http_code}",
|
|
212
|
-
url,
|
|
213
|
-
],
|
|
214
|
-
{ stdout: "pipe", stderr: "pipe" },
|
|
215
|
-
);
|
|
216
|
-
|
|
217
|
-
if (proc.exitCode !== 0) return {};
|
|
218
|
-
|
|
219
|
-
const output = proc.stdout.toString().trim();
|
|
220
|
-
const lastNewline = output.lastIndexOf("\n");
|
|
221
|
-
const responseBody = lastNewline >= 0 ? output.slice(0, lastNewline) : "";
|
|
222
|
-
const statusCode = parseInt(
|
|
223
|
-
lastNewline >= 0 ? output.slice(lastNewline + 1) : output,
|
|
224
|
-
10,
|
|
225
|
-
);
|
|
226
|
-
|
|
227
|
-
if (statusCode < 200 || statusCode >= 300) return {};
|
|
228
|
-
if (!responseBody) return {};
|
|
229
|
-
|
|
230
|
-
const parsed = JSON.parse(responseBody) as {
|
|
206
|
+
const response = await fetch(url, {
|
|
207
|
+
method: "GET",
|
|
208
|
+
headers: {
|
|
209
|
+
Authorization: `Bearer ${token}`,
|
|
210
|
+
Accept: "application/json",
|
|
211
|
+
},
|
|
212
|
+
signal: AbortSignal.timeout(10_000),
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
if (!response.ok) return {};
|
|
216
|
+
|
|
217
|
+
const parsed = (await response.json()) as {
|
|
231
218
|
flags?: Array<{ key: string; enabled: boolean }>;
|
|
232
219
|
};
|
|
233
220
|
if (!Array.isArray(parsed.flags)) return {};
|
|
@@ -245,25 +232,42 @@ function loadOverridesFromGateway(): Record<string, boolean> {
|
|
|
245
232
|
}
|
|
246
233
|
|
|
247
234
|
/**
|
|
248
|
-
*
|
|
235
|
+
* Pre-populate the override cache from the gateway (async).
|
|
249
236
|
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
* the gateway
|
|
237
|
+
* Call this once during startup (daemon or CLI entry) before any sync
|
|
238
|
+
* `isAssistantFeatureFlagEnabled` calls. In containerized mode, always
|
|
239
|
+
* uses the gateway. In local mode, falls back to the local file when
|
|
240
|
+
* the gateway is unreachable.
|
|
253
241
|
*
|
|
254
|
-
*
|
|
242
|
+
* On failure, the cache is left unset so subsequent sync calls fall
|
|
243
|
+
* through to the file-based fallback rather than caching an empty map
|
|
244
|
+
* that masks all overrides for the process lifetime.
|
|
255
245
|
*/
|
|
256
|
-
function
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
const gatewayOverrides = loadOverridesFromGateway();
|
|
260
|
-
if (Object.keys(gatewayOverrides).length > 0 || getIsContainerized()) {
|
|
246
|
+
export async function initFeatureFlagOverrides(): Promise<void> {
|
|
247
|
+
const gatewayOverrides = await fetchOverridesFromGateway();
|
|
248
|
+
if (Object.keys(gatewayOverrides).length > 0) {
|
|
261
249
|
cachedOverrides = gatewayOverrides;
|
|
262
|
-
return
|
|
250
|
+
return;
|
|
263
251
|
}
|
|
264
252
|
|
|
265
|
-
//
|
|
266
|
-
// (
|
|
253
|
+
// Gateway returned empty or failed. Leave the cache unset so
|
|
254
|
+
// loadOverrides() falls through to file on the next sync read,
|
|
255
|
+
// regardless of containerized vs local mode.
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Read cached overrides synchronously.
|
|
260
|
+
*
|
|
261
|
+
* If `initFeatureFlagOverrides()` was called at startup, this returns the
|
|
262
|
+
* pre-populated cache. Otherwise falls back to the local file — this
|
|
263
|
+
* ensures the resolver never blocks on a network call.
|
|
264
|
+
*/
|
|
265
|
+
function loadOverrides(): Record<string, boolean> {
|
|
266
|
+
if (cachedOverrides != null) return cachedOverrides;
|
|
267
|
+
|
|
268
|
+
// Cache not yet populated (initFeatureFlagOverrides wasn't called or
|
|
269
|
+
// hasn't finished). Fall back to the local file so the resolver still
|
|
270
|
+
// works, just without gateway data.
|
|
267
271
|
cachedOverrides = loadOverridesFromFile();
|
|
268
272
|
return cachedOverrides;
|
|
269
273
|
}
|