@vellumai/assistant 0.12.1 → 0.12.2-staging.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 +20 -21
- package/docs/architecture/turn-actor.md +15 -8
- package/docs/guardian-request-flow.md +16 -0
- package/node_modules/@vellumai/slack-text/src/index.ts +13 -8
- package/node_modules/@vellumai/slack-text/src/label-resolution-entities.test.ts +95 -0
- package/openapi.yaml +360 -51
- package/package.json +3 -1
- package/scripts/smoke-desktop-window-theme.ts +186 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +18 -10
- package/src/__tests__/anthropic-provider.test.ts +0 -63
- package/src/__tests__/app-compiler.test.ts +6 -0
- package/src/__tests__/app-control-tool-schemas.test.ts +5 -2
- package/src/__tests__/app-dir-path-guard.test.ts +1 -3
- package/src/__tests__/assistant-event-hub-machine-name.test.ts +6 -7
- package/src/__tests__/assistant-event-hub.test.ts +44 -0
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
- package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +5 -2
- package/src/__tests__/computer-use-tools.test.ts +19 -7
- package/src/__tests__/config-schema-cmd.test.ts +2 -2
- package/src/__tests__/config-schema.test.ts +28 -29
- package/src/__tests__/config-watcher.test.ts +23 -8
- package/src/__tests__/connection-model-compat.test.ts +2 -2
- package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
- package/src/__tests__/conversation-pairing.test.ts +26 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -10
- package/src/__tests__/conversation-queue.test.ts +48 -207
- package/src/__tests__/conversation-retry-discard.test.ts +54 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +28 -0
- package/src/__tests__/conversation-skill-tools.test.ts +27 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +8 -3
- package/src/__tests__/conversation-surfaces-point-at-budget.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-point-at-capability.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
- package/src/__tests__/conversation-title-service.test.ts +14 -10
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +3 -5
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +51 -0
- package/src/__tests__/credential-record-retire.test.ts +171 -1
- package/src/__tests__/credential-routes.test.ts +250 -3
- package/src/__tests__/credential-security-invariants.test.ts +5 -3
- package/src/__tests__/cu-unified-flow.test.ts +14 -4
- package/src/__tests__/document-sync-tags.test.ts +25 -0
- package/src/__tests__/drain-kick-guard.test.ts +0 -2
- package/src/__tests__/drain-requeue-on-contention.test.ts +0 -6
- package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
- package/src/__tests__/git-porcelain.test.ts +47 -0
- package/src/__tests__/heartbeat-service.test.ts +56 -40
- package/src/__tests__/helpers/set-workspace-mcp.ts +11 -0
- package/src/__tests__/host-bash-proxy.test.ts +26 -45
- package/src/__tests__/host-browser-proxy.test.ts +6 -17
- package/src/__tests__/host-cu-proxy.test.ts +596 -0
- package/src/__tests__/host-file-proxy-targeted.test.ts +23 -0
- package/src/__tests__/host-file-proxy.test.ts +11 -23
- package/src/__tests__/host-transfer-proxy-targeted.test.ts +30 -9
- package/src/__tests__/list-messages-camera-frame.test.ts +121 -0
- package/src/__tests__/llm-catalog-parity.test.ts +25 -0
- package/src/__tests__/mcp-auth-routes.test.ts +6 -9
- package/src/__tests__/mcp-config-secret-boundary.test.ts +78 -136
- package/src/__tests__/mcp-health-check.test.ts +8 -11
- package/src/__tests__/mcp-list-plugin-servers.test.ts +8 -10
- package/src/__tests__/message-deleted-hook-dispatch.test.ts +119 -0
- package/src/__tests__/messages-after-tiebreaker.test.ts +91 -0
- package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
- package/src/__tests__/messaging-send-tool.test.ts +355 -548
- package/src/__tests__/model-intents.test.ts +3 -0
- package/src/__tests__/notification-vellum-adapter.test.ts +85 -2
- package/src/__tests__/oauth-commands-routes.test.ts +168 -0
- package/src/__tests__/oauth-provider-profiles.test.ts +18 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +22 -0
- package/src/__tests__/oauth-providers-routes.test.ts +1 -0
- package/src/__tests__/openai-provider.test.ts +1 -0
- package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
- package/src/__tests__/platform-callback-registration.test.ts +8 -16
- package/src/__tests__/platform-client-verify-credential.test.ts +2 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +14 -0
- package/src/__tests__/qdrant-manager.test.ts +132 -0
- package/src/__tests__/resolve-app.test.ts +135 -0
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +229 -6
- package/src/__tests__/script-proxy-certs.test.ts +35 -0
- package/src/__tests__/secret-routes-acp-guard.test.ts +59 -1
- package/src/__tests__/service-account-token.test.ts +247 -0
- package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
- package/src/__tests__/skill-feature-flags.test.ts +2 -2
- package/src/__tests__/skills.test.ts +9 -4
- package/src/__tests__/source-fingerprint.test.ts +184 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +70 -3
- package/src/__tests__/subagent-tools.test.ts +3 -2
- package/src/__tests__/telephony-tts-guard.test.ts +38 -3
- package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
- package/src/__tests__/ui-channel-variants.test.ts +1 -56
- package/src/__tests__/unicode.test.ts +13 -0
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +4 -0
- package/src/__tests__/watch-retro-tool-availability.test.ts +8 -17
- package/src/__tests__/workspace-git-service.test.ts +26 -0
- package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +336 -0
- package/src/acp/__tests__/acp-claude-oauth.test.ts +257 -6
- package/src/acp/__tests__/acp-credentials.test.ts +13 -0
- package/src/acp/__tests__/agent-process.test.ts +258 -4
- package/src/acp/__tests__/auth-required.test.ts +96 -0
- package/src/acp/__tests__/claude-token-refresh.test.ts +257 -0
- package/src/acp/__tests__/client-handler.test.ts +90 -2
- package/src/acp/__tests__/helpers/acp-model-option.ts +51 -0
- package/src/acp/__tests__/prepare-agent-env.test.ts +60 -1
- package/src/acp/__tests__/session-manager-persistence.test.ts +5 -1
- package/src/acp/__tests__/session-manager-resume.test.ts +270 -6
- package/src/acp/__tests__/session-manager-usage.test.ts +4 -2
- package/src/acp/__tests__/session-manager.test.ts +895 -4
- package/src/acp/acp-claude-oauth.ts +328 -14
- package/src/acp/acp-credentials.ts +19 -0
- package/src/acp/agent-process.test.ts +4 -1
- package/src/acp/agent-process.ts +96 -10
- package/src/acp/auth-required.ts +50 -1
- package/src/acp/auto-install.test.ts +9 -7
- package/src/acp/claude-token-refresh.ts +150 -0
- package/src/acp/client-handler.ts +16 -3
- package/src/acp/model-config.test.ts +166 -0
- package/src/acp/model-config.ts +138 -0
- package/src/acp/prepare-agent-env.ts +21 -6
- package/src/acp/resolve-agent.test.ts +220 -6
- package/src/acp/resolve-agent.ts +88 -23
- package/src/acp/session-manager.test.ts +196 -3
- package/src/acp/session-manager.ts +441 -18
- package/src/acp/types.ts +50 -5
- package/src/agent/loop.ts +1 -0
- package/src/api/events/acp-session-model-update.test.ts +88 -0
- package/src/api/events/acp-session-model-update.ts +46 -0
- package/src/api/events/app-files-changed.ts +4 -3
- package/src/api/events/notification-intent.ts +2 -0
- package/src/api/events/user-message-echo.ts +7 -1
- package/src/api/index.ts +6 -0
- package/src/api/responses/conversation-message.ts +5 -0
- package/src/apps/app-store.ts +1 -1
- package/src/apps/resolve-app.ts +81 -0
- package/src/apps/source-fingerprint.ts +266 -0
- package/src/avatar/identity-avatar.ts +7 -2
- package/src/bundler/app-compiler.ts +15 -0
- package/src/calls/__tests__/barge-in-guard.test.ts +81 -0
- package/src/{__tests__ → calls/__tests__}/call-controller.test.ts +503 -82
- package/src/{__tests__ → calls/__tests__}/media-stream-audio-transcode.test.ts +1 -1
- package/src/{__tests__ → calls/__tests__}/media-stream-output.test.ts +37 -8
- package/src/{__tests__ → calls/__tests__}/media-stream-parser.test.ts +1 -1
- package/src/{__tests__ → calls/__tests__}/media-stream-server-integration.test.ts +150 -57
- package/src/{__tests__ → calls/__tests__}/media-stream-stt-session.test.ts +5 -5
- package/src/{__tests__ → calls/__tests__}/media-turn-detector.test.ts +1 -1
- package/src/{live-voice → calls}/__tests__/progress-narration.test.ts +4 -4
- package/src/{live-voice → calls}/__tests__/progress-phrases.test.ts +1 -1
- package/src/calls/__tests__/voice-control-protocol.test.ts +120 -25
- package/src/calls/__tests__/voice-leg-coordinator.test.ts +229 -0
- package/src/calls/__tests__/voice-progress-cadence.test.ts +274 -0
- package/src/{__tests__/voice-session-bridge.test.ts → calls/__tests__/voice-session-bridge-turn-flow.test.ts} +20 -12
- package/src/calls/__tests__/voice-session-bridge.test.ts +56 -1
- package/src/calls/__tests__/voice-triage-escalate.test.ts +105 -0
- package/src/calls/barge-in-guard.ts +118 -0
- package/src/calls/call-constants.ts +24 -4
- package/src/calls/call-controller.ts +499 -199
- package/src/calls/call-transport.ts +16 -0
- package/src/calls/media-stream-output.ts +32 -0
- package/src/calls/media-stream-server.ts +82 -30
- package/src/calls/media-stream-stt-session.ts +15 -0
- package/src/{live-voice → calls}/progress-narration.ts +2 -2
- package/src/calls/resolve-call-tts-provider.ts +33 -2
- package/src/calls/tts-call-strategy.ts +9 -5
- package/src/calls/types.ts +2 -0
- package/src/calls/voice-control-protocol.ts +155 -21
- package/src/calls/voice-leg-coordinator.ts +189 -0
- package/src/calls/voice-progress-cadence.ts +423 -0
- package/src/calls/voice-session-bridge.ts +92 -72
- package/src/calls/voice-triage-escalate.ts +178 -59
- package/src/cli/commands/__tests__/apps.test.ts +245 -0
- package/src/cli/commands/__tests__/bash.test.ts +53 -0
- package/src/cli/commands/__tests__/roadmap.help.test.ts +19 -0
- package/src/cli/commands/apps.help.ts +67 -13
- package/src/cli/commands/apps.ts +183 -0
- package/src/cli/commands/bash.help.ts +8 -13
- package/src/cli/commands/bash.ts +7 -25
- package/src/cli/commands/channels/__tests__/send.test.ts +218 -0
- package/src/cli/commands/channels/index.help.ts +70 -2
- package/src/cli/commands/channels/index.ts +16 -3
- package/src/cli/commands/channels/send.ts +128 -0
- package/src/cli/commands/credentials.help.ts +6 -2
- package/src/cli/commands/credentials.ts +2 -2
- package/src/cli/commands/mcp.help.ts +8 -8
- package/src/cli/commands/mcp.ts +1 -1
- package/src/cli/commands/oauth/status.ts +5 -0
- package/src/cli/commands/roadmap.help.ts +5 -1
- package/src/cli/commands/roadmap.ts +1 -1
- package/src/cli/lib/bundled-marketplace.json +103 -90
- package/src/config/__tests__/default-profile-catalog.test.ts +22 -0
- package/src/config/__tests__/memory-retrospective-schema.test.ts +15 -1
- package/src/config/__tests__/platform-identity.test.ts +255 -0
- package/src/config/__tests__/platform-rehydration.test.ts +99 -9
- package/src/config/acp-agent-write.ts +43 -0
- package/src/config/acp-defaults.test.ts +13 -3
- package/src/config/acp-defaults.ts +23 -6
- package/src/config/acp-schema.test.ts +81 -0
- package/src/config/acp-schema.ts +30 -2
- package/src/config/bundled-skills/acp/SKILL.md +22 -14
- package/src/config/bundled-skills/acp/TOOLS.json +5 -1
- package/src/config/bundled-skills/app-builder/SKILL.md +12 -9
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
- package/src/config/bundled-skills/computer-use/SKILL.md +29 -2
- package/src/config/bundled-skills/computer-use/TOOLS.json +99 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-sequence.ts +12 -0
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +2 -1
- package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +2 -1
- package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -1
- package/src/config/bundled-skills/messaging/SKILL.md +3 -3
- package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +67 -139
- package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -1
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -1
- package/src/config/bundled-skills/schedule/SKILL.md +3 -4
- package/src/config/bundled-skills/screen-annotation/SKILL.md +8 -1
- package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +4 -1
- package/src/config/bundled-skills/skill-management/TOOLS.json +6 -2
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +5 -2
- package/src/config/default-profile-catalog.ts +1 -1
- package/src/config/env.ts +2 -2
- package/src/config/feature-flag-registry.json +33 -13
- package/src/config/loader.ts +2 -0
- package/src/config/memory-v3-gate.test.ts +31 -0
- package/src/config/memory-v3-gate.ts +20 -16
- package/src/config/platform-identity.ts +201 -0
- package/src/config/platform-rehydration.ts +11 -27
- package/src/config/schema.ts +2 -2
- package/src/config/schemas/__tests__/live-voice.test.ts +8 -158
- package/src/config/schemas/__tests__/voice.test.ts +148 -0
- package/src/config/schemas/calls.ts +1 -28
- package/src/config/schemas/live-voice.ts +1 -173
- package/src/config/schemas/mcp.ts +1 -1
- package/src/config/schemas/memory-retrospective.ts +10 -0
- package/src/config/schemas/services.ts +18 -0
- package/src/config/schemas/skills.ts +0 -14
- package/src/config/schemas/voice.ts +150 -0
- package/src/credential-execution/process-manager.test.ts +18 -19
- package/src/credential-health/credential-health-service.ts +11 -17
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -121
- package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +4 -1
- package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +133 -0
- package/src/daemon/app-change-notify.ts +45 -0
- package/src/daemon/config-watcher.ts +13 -194
- package/src/daemon/conversation-error.ts +3 -2
- package/src/daemon/conversation-media-retry.ts +2 -1
- package/src/daemon/conversation-process.ts +35 -64
- package/src/daemon/conversation-runtime-assembly.ts +20 -7
- package/src/daemon/conversation-skill-tools.ts +13 -31
- package/src/daemon/conversation-store.ts +1 -1
- package/src/daemon/conversation-surfaces.ts +21 -10
- package/src/daemon/conversation-tool-setup.ts +45 -59
- package/src/daemon/conversation.ts +0 -3
- package/src/daemon/dictation-profile-store.ts +2 -1
- package/src/daemon/handlers/shared.ts +2 -1
- package/src/daemon/handlers/skills.ts +6 -3
- package/src/daemon/host-bash-proxy.ts +8 -12
- package/src/daemon/host-browser-proxy.ts +15 -1
- package/src/daemon/host-cu-proxy.ts +224 -19
- package/src/daemon/host-file-proxy.ts +8 -10
- package/src/daemon/host-proxy-preactivation.ts +2 -1
- package/src/daemon/host-transfer-proxy.ts +14 -18
- package/src/daemon/lifecycle.ts +0 -5
- package/src/daemon/mcp-reload-service.ts +5 -5
- package/src/daemon/message-types/sync.ts +1 -0
- package/src/daemon/orphan-reaper.ts +1 -1
- package/src/daemon/providers-setup.ts +1 -1
- package/src/daemon/reaction-history-render.ts +2 -1
- package/src/daemon/shutdown-handlers.ts +1 -9
- package/src/daemon/skill-memory-refresh.ts +64 -0
- package/src/daemon/tool-side-effects.ts +7 -18
- package/src/desktop/__tests__/fake-desktop.ts +122 -0
- package/src/desktop/desktop-chrome-policy.test.ts +79 -0
- package/src/desktop/desktop-chrome-policy.ts +47 -0
- package/src/desktop/desktop-chrome-session.ts +37 -0
- package/src/desktop/desktop-dependencies.test.ts +102 -0
- package/src/desktop/desktop-dependencies.ts +308 -0
- package/src/desktop/desktop-feature.test.ts +18 -0
- package/src/desktop/desktop-feature.ts +19 -0
- package/src/desktop/desktop-panel-config.test.ts +135 -0
- package/src/desktop/desktop-panel-config.ts +149 -0
- package/src/desktop/desktop-session-manager.test.ts +730 -0
- package/src/desktop/desktop-session-manager.ts +850 -0
- package/src/desktop/desktop-stream-bridge.test.ts +395 -0
- package/src/desktop/desktop-stream-bridge.ts +214 -0
- package/src/desktop/desktop-wallpaper.test.ts +191 -0
- package/src/desktop/desktop-wallpaper.ts +113 -0
- package/src/desktop/desktop-window-drag.ts +177 -0
- package/src/desktop/desktop-window-manager-config.test.ts +230 -0
- package/src/desktop/desktop-window-manager-config.ts +238 -0
- package/src/desktop/desktop-window-theme.ts +170 -0
- package/src/heartbeat/heartbeat-service.ts +21 -3
- package/src/home/__tests__/feed-writer.test.ts +22 -0
- package/src/home/feed-writer.ts +11 -2
- package/src/hooks/types.ts +33 -0
- package/src/i18n/AGENTS.md +22 -0
- package/src/i18n/__tests__/i18n.test.ts +108 -0
- package/src/i18n/index.ts +16 -0
- package/src/i18n/locales.ts +93 -0
- package/src/i18n/messages.ts +61 -0
- package/src/i18n/title.ts +25 -0
- package/src/inbound/platform-callback-registration.ts +4 -5
- package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +121 -0
- package/src/live-voice/__tests__/live-voice-events.test.ts +8 -7
- package/src/live-voice/__tests__/live-voice-metrics.test.ts +0 -3
- package/src/live-voice/__tests__/live-voice-progress.test.ts +94 -15
- package/src/live-voice/__tests__/live-voice-sight-frame-echo.test.ts +238 -0
- package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
- package/src/live-voice/__tests__/protocol.test.ts +39 -0
- package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +13 -69
- package/src/live-voice/__tests__/session-controls.test.ts +79 -0
- package/src/live-voice/live-voice-photo.ts +25 -6
- package/src/live-voice/live-voice-session.ts +297 -699
- package/src/live-voice/protocol.ts +60 -2
- package/src/live-voice/session-controls.ts +111 -0
- package/src/mcp/__tests__/effective-config.test.ts +2 -2
- package/src/mcp/__tests__/workspace-mcp-config.test.ts +135 -0
- package/src/mcp/effective-config.ts +3 -3
- package/src/mcp/mcp-header-store.ts +29 -45
- package/src/mcp/spec-schema.ts +111 -0
- package/src/mcp/startup.ts +8 -4
- package/src/mcp/workspace-mcp-config.ts +188 -0
- package/src/messaging/provider.ts +10 -3
- package/src/messaging/providers/__tests__/proactive-address.test.ts +95 -0
- package/src/messaging/providers/callback-routing.ts +18 -0
- package/src/messaging/providers/channel-transport.ts +56 -0
- package/src/messaging/providers/discord/transport.ts +17 -0
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +6 -17
- package/src/messaging/providers/slack/adapter.ts +7 -44
- package/src/messaging/providers/slack/transport.ts +16 -0
- package/src/messaging/providers/telegram-bot/adapter.ts +1 -48
- package/src/messaging/providers/telegram-bot/client.ts +0 -26
- package/src/messaging/providers/telegram-bot/transport.ts +20 -0
- package/src/messaging/providers/whatsapp/adapter.ts +1 -48
- package/src/messaging/providers/whatsapp/transport.ts +18 -0
- package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +8 -0
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +55 -0
- package/src/notifications/__tests__/guardian-feed-projection.test.ts +158 -2
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +153 -3
- package/src/notifications/__tests__/schedule-result-producer.test.ts +99 -3
- package/src/notifications/adapters/macos.ts +3 -0
- package/src/notifications/assistant-reply-producer.ts +8 -3
- package/src/notifications/conversation-pairing.ts +36 -16
- package/src/notifications/delivered-post-record.ts +11 -1
- package/src/notifications/guardian-feed-projection.ts +125 -5
- package/src/notifications/home-feed-side-effect.ts +55 -7
- package/src/notifications/schedule-result-producer.ts +66 -22
- package/src/oauth/AGENTS.md +2 -0
- package/src/oauth/__tests__/seed-providers-managed.test.ts +149 -0
- package/src/oauth/byo-connection.test.ts +105 -0
- package/src/oauth/byo-connection.ts +36 -6
- package/src/oauth/connection-resolver.test.ts +100 -0
- package/src/oauth/connection-resolver.ts +120 -3
- package/src/oauth/provider-serializer.ts +9 -0
- package/src/oauth/scope-utils.ts +24 -0
- package/src/oauth/seed-providers.ts +314 -27
- package/src/outbound-proxy/certs.ts +3 -5
- package/src/permissions/checker.test.ts +11 -6
- package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
- package/src/persistence/conversation-crud.ts +71 -63
- package/src/persistence/conversation-lineage.ts +12 -4
- package/src/persistence/conversation-queries.ts +2 -2
- package/src/persistence/conversation-title-placeholders.ts +23 -0
- package/src/persistence/conversation-title-service.ts +9 -26
- package/src/persistence/embeddings/qdrant-manager.ts +163 -18
- package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
- package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
- package/src/persistence/message-cursor.ts +78 -0
- package/src/persistence/message-reads.ts +30 -0
- package/src/persistence/migrations/157-invite-contact-id.ts +18 -6
- package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +149 -0
- package/src/persistence/schema/memory-core.ts +5 -0
- package/src/platform/client.test.ts +4 -2
- package/src/platform/client.ts +2 -10
- package/src/plugin-api/constants.ts +2 -0
- package/src/plugin-api/index.ts +1 -0
- package/src/plugin-api/types.ts +2 -0
- package/src/plugins/defaults/index.ts +2 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +46 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +118 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +83 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +9 -5
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +20 -10
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +5 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +219 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +31 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +63 -0
- package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +4 -0
- package/src/plugins/defaults/memory/context-search/agent-runner.ts +3 -2
- package/src/plugins/defaults/memory/context-search/format.ts +3 -2
- package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +6 -3
- package/src/plugins/defaults/memory/context-search/sources/workspace.ts +3 -2
- package/src/plugins/defaults/memory/graph/capability-seed.ts +6 -7
- package/src/plugins/defaults/memory/hooks/init.ts +10 -0
- package/src/plugins/defaults/memory/hooks/message-deleted.ts +21 -0
- package/src/plugins/defaults/memory/hooks/shutdown.ts +2 -0
- package/src/plugins/defaults/memory/host-utils.ts +10 -0
- package/src/plugins/defaults/memory/jobs-worker.ts +13 -0
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +34 -60
- package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +126 -0
- package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +79 -0
- package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +28 -0
- package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +3 -5
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +12 -3
- package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
- package/src/plugins/defaults/memory/memory-retrospective-state.ts +312 -71
- package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +2 -1
- package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +5 -3
- package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +9 -0
- package/src/plugins/defaults/memory/startup.ts +28 -2
- package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +7 -9
- package/src/plugins/defaults/memory/substrate/page-index.ts +6 -1
- package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
- package/src/plugins/defaults/memory/v1/graph/consolidation.ts +4 -2
- package/src/plugins/defaults/memory/v1/graph/extraction.ts +4 -2
- package/src/plugins/defaults/memory/v1/graph/retriever.ts +4 -1
- package/src/plugins/defaults/memory/v2/reranker.ts +6 -1
- package/src/plugins/defaults/memory/v3/__tests__/card.test.ts +24 -0
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +125 -0
- package/src/plugins/defaults/memory/v3/__tests__/sections.test.ts +26 -0
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +30 -0
- package/src/plugins/defaults/memory/v3/card.ts +3 -2
- package/src/plugins/defaults/memory/v3/pool-select.ts +5 -3
- package/src/plugins/defaults/memory/v3/sections.ts +9 -3
- package/src/plugins/defaults/memory/v3/shadow-plugin.ts +5 -2
- package/src/plugins/defaults/memory/worker.ts +6 -0
- package/src/plugins/mcp-servers.ts +15 -75
- package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
- package/src/providers/__tests__/retry-callsite.test.ts +41 -0
- package/src/providers/__tests__/retry-outbound-sanitize.test.ts +176 -0
- package/src/providers/anthropic/client.ts +0 -47
- package/src/providers/gemini/client.ts +9 -23
- package/src/providers/gemini-thought-signature.test.ts +63 -0
- package/src/providers/gemini-thought-signature.ts +44 -0
- package/src/providers/inference/__tests__/endpoint-probe.test.ts +42 -3
- package/src/providers/inference/auth.ts +2 -3
- package/src/providers/inference/connection-availability.ts +27 -8
- package/src/providers/inference/endpoint-probe.ts +10 -1
- package/src/providers/inference/resolve-auth.ts +22 -4
- package/src/providers/inference/service-account-token.ts +248 -0
- package/src/providers/model-catalog.ts +34 -0
- package/src/providers/model-intents.ts +10 -5
- package/src/providers/openai/__tests__/chat-completions-output-token-limit.test.ts +141 -0
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +146 -18
- package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +613 -0
- package/src/providers/openai/__tests__/google-thought-signature.test.ts +235 -0
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +83 -1
- package/src/providers/openai/chat-completions-provider.ts +212 -79
- package/src/providers/openai/codex-models.ts +0 -4
- package/src/providers/openai/google-thought-signature.ts +320 -0
- package/src/providers/opencode/client.test.ts +73 -4
- package/src/providers/opencode/client.ts +60 -3
- package/src/providers/openrouter/client.ts +20 -0
- package/src/providers/outbound-request-sanitize.ts +96 -0
- package/src/providers/retry.ts +30 -19
- package/src/providers/unparseable-tool-args.ts +5 -1
- package/src/runtime/AGENTS.md +3 -1
- package/src/runtime/__tests__/channel-send.test.ts +443 -0
- package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +89 -0
- package/src/runtime/__tests__/host-observe.test.ts +40 -1
- package/src/runtime/__tests__/live-voice-upgrade.test.ts +58 -0
- package/src/runtime/__tests__/runtime-ws-test-utils.ts +72 -0
- package/src/runtime/__tests__/watch-stream-upgrade.test.ts +59 -0
- package/src/runtime/agent-wake.ts +2 -1
- package/src/runtime/assistant-event-hub.ts +21 -3
- package/src/runtime/auth/same-actor.ts +38 -3
- package/src/runtime/channel-send.ts +316 -0
- package/src/runtime/host-observe.ts +11 -1
- package/src/runtime/http-server.ts +160 -151
- package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +16 -5
- package/src/runtime/routes/__tests__/acp-routes.test.ts +159 -4
- package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +275 -0
- package/src/runtime/routes/__tests__/channel-send-routes.test.ts +127 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +108 -1
- package/src/runtime/routes/__tests__/default-provider-routes.test.ts +2 -2
- package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +107 -0
- package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +16 -12
- package/src/runtime/routes/acp-claude-auth-routes.ts +11 -3
- package/src/runtime/routes/acp-routes.test.ts +15 -0
- package/src/runtime/routes/acp-routes.ts +72 -9
- package/src/runtime/routes/app-management-routes.ts +90 -2
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/channel-send-routes.ts +85 -0
- package/src/runtime/routes/conversation-list-routes.ts +14 -3
- package/src/runtime/routes/conversation-query-routes.ts +40 -0
- package/src/runtime/routes/conversation-routes.ts +16 -3
- package/src/runtime/routes/credential-routes.ts +20 -14
- package/src/runtime/routes/desktop-setup-routes.test.ts +74 -0
- package/src/runtime/routes/desktop-setup-routes.ts +35 -0
- package/src/runtime/routes/diagnostics-routes.ts +61 -4
- package/src/runtime/routes/events-routes.ts +4 -0
- package/src/runtime/routes/host-cu-routes.ts +6 -0
- package/src/runtime/routes/index.ts +4 -2
- package/src/runtime/routes/log-export-routes.ts +2 -0
- package/src/runtime/routes/mcp-auth-routes.ts +39 -47
- package/src/runtime/routes/migration-routes.ts +6 -20
- package/src/runtime/routes/oauth-commands-routes.ts +50 -0
- package/src/runtime/routes/oauth-providers.ts +7 -0
- package/src/runtime/routes/platform-routes.ts +18 -15
- package/src/runtime/routes/workspace-routes.test.ts +55 -26
- package/src/runtime/routes/workspace-routes.ts +46 -63
- package/src/runtime/routes/workspace-tree-walk.test.ts +244 -0
- package/src/runtime/routes/workspace-tree-walk.ts +240 -0
- package/src/runtime/routes/workspace-utils.ts +17 -0
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +28 -0
- package/src/runtime/services/conversation-serializer.ts +16 -3
- package/src/runtime/sync/resource-sync-events.ts +2 -1
- package/src/schedule/run-script.ts +2 -1
- package/src/schedule/schedule-store.ts +9 -2
- package/src/schedule/worker.ts +1 -1
- package/src/security/__tests__/ces-http-record-backend.test.ts +137 -0
- package/src/security/ces-http-record-backend.ts +120 -0
- package/src/security/ces-rpc-record-backend.ts +7 -0
- package/src/security/untrusted-content.ts +3 -1
- package/src/skills/__tests__/workspace-skill-md-signature.test.ts +60 -0
- package/src/skills/workspace-skill-md-signature.ts +50 -0
- package/src/subagent/consult-context.ts +12 -25
- package/src/tools/__tests__/side-effect-abort-guard.test.ts +1 -0
- package/src/tools/acp/list-agents.test.ts +3 -1
- package/src/tools/acp/spawn.test.ts +146 -11
- package/src/tools/acp/spawn.ts +21 -3
- package/src/tools/acp/status.test.ts +94 -0
- package/src/tools/acp/status.ts +40 -3
- package/src/tools/app-control/skill-proxy-bridge.ts +2 -1
- package/src/tools/browser/browser-execution.ts +1 -1
- package/src/tools/browser/cdp-client/accessibility-snapshot.ts +9 -3
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +2 -1
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +3 -2
- package/src/tools/browser/network-recorder.ts +2 -1
- package/src/tools/capability-offer.test.ts +22 -2
- package/src/tools/capability-offer.ts +16 -1
- package/src/tools/computer-use/definitions.ts +121 -2
- package/src/tools/computer-use/skill-proxy-bridge.ts +2 -1
- package/src/tools/credentials/metadata-store.ts +100 -0
- package/src/tools/filesystem/search.ts +2 -1
- package/src/tools/host-filesystem/edit.test.ts +2 -1
- package/src/tools/host-filesystem/edit.ts +2 -2
- package/src/tools/host-filesystem/read.test.ts +2 -1
- package/src/tools/host-filesystem/read.ts +2 -2
- package/src/tools/host-filesystem/transfer.test.ts +2 -1
- package/src/tools/host-filesystem/transfer.ts +2 -2
- package/src/tools/host-filesystem/write.test.ts +2 -1
- package/src/tools/host-filesystem/write.ts +2 -2
- package/src/tools/host-terminal/host-shell.test.ts +16 -0
- package/src/tools/host-terminal/host-shell.ts +4 -20
- package/src/tools/network/web-search.ts +6 -1
- package/src/tools/policy-context.ts +6 -7
- package/src/tools/skills/scaffold-managed.ts +61 -3
- package/src/tools/subagent/spawn.ts +5 -8
- package/src/tools/terminal/__tests__/sanitized-bash.test.ts +57 -0
- package/src/tools/terminal/sanitized-bash.ts +105 -0
- package/src/tools/ui-surface/channel-variants.ts +10 -59
- package/src/tools/watch/watch-retro-report.ts +0 -8
- package/src/types/host-capabilities.ts +5 -3
- package/src/util/__tests__/language-subtag.test.ts +23 -1
- package/src/util/ca-bundle.ts +13 -0
- package/src/util/language-subtag.ts +16 -0
- package/src/util/platform.ts +8 -0
- package/src/util/unicode.ts +15 -1
- package/src/watch/watch-retro.ts +6 -8
- package/src/watch/watch-timeline.ts +2 -2
- package/src/workflows/run-manager.ts +2 -1
- package/src/workspace/git-porcelain.ts +66 -0
- package/src/workspace/git-service.ts +6 -55
- package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +258 -0
- package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +78 -0
- package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +214 -0
- package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +102 -0
- package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +199 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/__tests__/app-source-watcher.test.ts +0 -218
- package/src/__tests__/config-watcher-skill-reseed.test.ts +0 -359
- package/src/__tests__/speaker-identification.test.ts +0 -56
- package/src/calls/speaker-identification.ts +0 -233
- package/src/daemon/app-source-watcher.ts +0 -218
- package/src/messaging/providers/whatsapp/client.ts +0 -29
- package/src/runtime/routes/debug-bash-routes.ts +0 -173
- package/src/util/fs-watcher-error.ts +0 -36
- /package/src/{live-voice → calls}/progress-phrases.ts +0 -0
|
@@ -21,7 +21,9 @@ import {
|
|
|
21
21
|
const testDataDir = process.env.VELLUM_WORKSPACE_DIR!;
|
|
22
22
|
|
|
23
23
|
import {
|
|
24
|
+
describeQdrantStartFailure,
|
|
24
25
|
QdrantManager,
|
|
26
|
+
QdrantStartError,
|
|
25
27
|
resolveQdrantReleaseAsset,
|
|
26
28
|
} from "../persistence/embeddings/qdrant-manager.js";
|
|
27
29
|
|
|
@@ -395,6 +397,136 @@ describe("QdrantManager", () => {
|
|
|
395
397
|
// THEN the error carries the stdout explanation, not just the noise
|
|
396
398
|
await expect(mgr.start()).rejects.toThrow("Failed to load local shard");
|
|
397
399
|
}, 10_000);
|
|
400
|
+
|
|
401
|
+
test("classifies a crash as an exit with its code and captured output", async () => {
|
|
402
|
+
// The reporter must not have to parse the message: the step, the exit
|
|
403
|
+
// code and the raw streams travel as fields.
|
|
404
|
+
placeFakeBinary(
|
|
405
|
+
'#!/bin/sh\necho "Panic occurred: no such shard"\nexit 101',
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
const mgr = new QdrantManager({
|
|
409
|
+
url: `http://127.0.0.1:${getTestPort()}`,
|
|
410
|
+
...FAST_TIMEOUTS,
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
let caught: unknown;
|
|
414
|
+
await mgr.start().catch((err) => {
|
|
415
|
+
caught = err;
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
expect(caught).toBeInstanceOf(QdrantStartError);
|
|
419
|
+
const err = caught as QdrantStartError;
|
|
420
|
+
expect(err.kind).toBe("exited");
|
|
421
|
+
expect(err.exitCode).toBe(101);
|
|
422
|
+
expect(err.stdout).toContain("no such shard");
|
|
423
|
+
}, 10_000);
|
|
424
|
+
|
|
425
|
+
test("classifies a readiness timeout as not_ready with no exit code", async () => {
|
|
426
|
+
placeFakeBinary("#!/bin/sh\nexec sleep 300");
|
|
427
|
+
|
|
428
|
+
const mgr = new QdrantManager({
|
|
429
|
+
url: `http://127.0.0.1:${getTestPort()}`,
|
|
430
|
+
...FAST_TIMEOUTS,
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
let caught: unknown;
|
|
434
|
+
await mgr.start().catch((err) => {
|
|
435
|
+
caught = err;
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
expect(caught).toBeInstanceOf(QdrantStartError);
|
|
439
|
+
expect((caught as QdrantStartError).kind).toBe("not_ready");
|
|
440
|
+
expect((caught as QdrantStartError).exitCode).toBeNull();
|
|
441
|
+
}, 10_000);
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
describe("describeQdrantStartFailure", () => {
|
|
445
|
+
const dataDir = "/home/example/workspace-data";
|
|
446
|
+
const failure = (stdout: string, kind: "exited" | "not_ready" = "exited") =>
|
|
447
|
+
new QdrantStartError(
|
|
448
|
+
"boom",
|
|
449
|
+
kind,
|
|
450
|
+
kind === "exited" ? 101 : null,
|
|
451
|
+
stdout,
|
|
452
|
+
"",
|
|
453
|
+
);
|
|
454
|
+
|
|
455
|
+
test("takes the reason from the last panic marker, after the backtrace", () => {
|
|
456
|
+
const stdout =
|
|
457
|
+
" 0: backtrace frame\n 1: another frame\n" +
|
|
458
|
+
"ERROR qdrant::startup: Panic occurred in file mod.rs at line 301: " +
|
|
459
|
+
"Failed to load local shard";
|
|
460
|
+
|
|
461
|
+
expect(describeQdrantStartFailure(failure(stdout), dataDir)).toEqual({
|
|
462
|
+
kind: "exited",
|
|
463
|
+
exit_code: 101,
|
|
464
|
+
panic:
|
|
465
|
+
"Panic occurred in file mod.rs at line 301: Failed to load local shard",
|
|
466
|
+
});
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
test("replaces the data directory wherever it appears", () => {
|
|
470
|
+
const stdout = `Panic occurred: cannot open ${dataDir}/qdrant/collections/x and ${dataDir}/qdrant/wal`;
|
|
471
|
+
|
|
472
|
+
expect(describeQdrantStartFailure(failure(stdout), dataDir).panic).toBe(
|
|
473
|
+
"Panic occurred: cannot open <data>/qdrant/collections/x and <data>/qdrant/wal",
|
|
474
|
+
);
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
test("falls back to the last stdout line when Qdrant died without panicking", () => {
|
|
478
|
+
const stdout =
|
|
479
|
+
"INFO starting\nERROR Address already in use (os error 48)\n";
|
|
480
|
+
|
|
481
|
+
expect(describeQdrantStartFailure(failure(stdout), dataDir).panic).toBe(
|
|
482
|
+
"ERROR Address already in use (os error 48)",
|
|
483
|
+
);
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
test("reports no reason for a silent timeout", () => {
|
|
487
|
+
expect(
|
|
488
|
+
describeQdrantStartFailure(failure("", "not_ready"), dataDir),
|
|
489
|
+
).toEqual({ kind: "not_ready", exit_code: null, panic: null });
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* The telemetry server's measure: JSON with every code unit above 0x7E
|
|
494
|
+
* escaped to six ASCII bytes (`jsonByteLength` in
|
|
495
|
+
* `telemetry-wire.generated.ts`), minus the enclosing quotes.
|
|
496
|
+
*/
|
|
497
|
+
const serverBytes = (s: string) =>
|
|
498
|
+
JSON.stringify(s).replace(/[^\x00-\x7e]/g, "\\uxxxx").length - 2;
|
|
499
|
+
|
|
500
|
+
test("caps an ASCII reason at the byte budget", () => {
|
|
501
|
+
const { panic } = describeQdrantStartFailure(
|
|
502
|
+
failure("Panic occurred: " + "x".repeat(5_000)),
|
|
503
|
+
dataDir,
|
|
504
|
+
);
|
|
505
|
+
|
|
506
|
+
expect(panic?.length).toBe(1_024);
|
|
507
|
+
expect(serverBytes(panic!)).toBe(1_024);
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
test("caps a non-ASCII reason by the server's escaped size, not by characters", () => {
|
|
511
|
+
// Each CJK character serializes to six bytes on the server, so a
|
|
512
|
+
// character cap would overshoot the budget six-fold and the event would
|
|
513
|
+
// be dropped at ingest.
|
|
514
|
+
const { panic } = describeQdrantStartFailure(
|
|
515
|
+
failure("Panic occurred: " + "測".repeat(2_000)),
|
|
516
|
+
dataDir,
|
|
517
|
+
);
|
|
518
|
+
|
|
519
|
+
expect(serverBytes(panic!)).toBeLessThanOrEqual(1_024);
|
|
520
|
+
expect(serverBytes(panic! + "測")).toBeGreaterThan(1_024);
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
test("never drops an unclassified failure", () => {
|
|
524
|
+
expect(describeQdrantStartFailure(new Error("nope"), dataDir)).toEqual({
|
|
525
|
+
kind: "unknown",
|
|
526
|
+
exit_code: null,
|
|
527
|
+
panic: null,
|
|
528
|
+
});
|
|
529
|
+
});
|
|
398
530
|
});
|
|
399
531
|
|
|
400
532
|
// ── Binary Detection ─────────────────────────────────────────
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for resolving an app query by name, slug, or id.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
9
|
+
|
|
10
|
+
import { createApp } from "../apps/app-store.js";
|
|
11
|
+
import { resolveAppQuery } from "../apps/resolve-app.js";
|
|
12
|
+
import { getWorkspacePluginsDir } from "../util/platform.js";
|
|
13
|
+
|
|
14
|
+
let workspaceDir: string;
|
|
15
|
+
let previousWorkspaceDir: string | undefined;
|
|
16
|
+
|
|
17
|
+
function freshWorkspace(): string {
|
|
18
|
+
return join(
|
|
19
|
+
tmpdir(),
|
|
20
|
+
`vellum-resolve-app-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function installPlugin(name: string): string {
|
|
25
|
+
const pluginDir = join(getWorkspacePluginsDir(), name);
|
|
26
|
+
mkdirSync(pluginDir, { recursive: true });
|
|
27
|
+
writeFileSync(
|
|
28
|
+
join(pluginDir, "package.json"),
|
|
29
|
+
JSON.stringify({ name, version: "1.0.0" }),
|
|
30
|
+
);
|
|
31
|
+
return pluginDir;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function bundleApp(pluginDir: string, app: string): void {
|
|
35
|
+
const appDir = join(pluginDir, "apps", app);
|
|
36
|
+
mkdirSync(appDir, { recursive: true });
|
|
37
|
+
writeFileSync(join(appDir, "index.html"), "<h1>Plugin app</h1>");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
previousWorkspaceDir = process.env.VELLUM_WORKSPACE_DIR;
|
|
42
|
+
workspaceDir = freshWorkspace();
|
|
43
|
+
process.env.VELLUM_WORKSPACE_DIR = workspaceDir;
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
afterEach(() => {
|
|
47
|
+
rmSync(workspaceDir, { recursive: true, force: true });
|
|
48
|
+
if (previousWorkspaceDir === undefined) {
|
|
49
|
+
delete process.env.VELLUM_WORKSPACE_DIR;
|
|
50
|
+
} else {
|
|
51
|
+
process.env.VELLUM_WORKSPACE_DIR = previousWorkspaceDir;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe("resolveAppQuery", () => {
|
|
56
|
+
test("resolves a workspace app by display name, slug, and id", () => {
|
|
57
|
+
const created = createApp({
|
|
58
|
+
name: "Budget",
|
|
59
|
+
schemaJson: "{}",
|
|
60
|
+
htmlDefinition: "<h1>Budget</h1>",
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const byName = resolveAppQuery("Budget");
|
|
64
|
+
expect(byName.ok).toBe(true);
|
|
65
|
+
if (byName.ok) {
|
|
66
|
+
expect(byName.app.id).toBe(created.id);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const byNameCase = resolveAppQuery("budget");
|
|
70
|
+
expect(byNameCase.ok).toBe(true);
|
|
71
|
+
if (byNameCase.ok) {
|
|
72
|
+
expect(byNameCase.app.id).toBe(created.id);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const slug = created.dirName ?? "";
|
|
76
|
+
expect(slug.length).toBeGreaterThan(0);
|
|
77
|
+
const bySlug = resolveAppQuery(slug);
|
|
78
|
+
expect(bySlug.ok).toBe(true);
|
|
79
|
+
if (bySlug.ok) {
|
|
80
|
+
expect(bySlug.app.id).toBe(created.id);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const byId = resolveAppQuery(created.id);
|
|
84
|
+
expect(byId.ok).toBe(true);
|
|
85
|
+
if (byId.ok) {
|
|
86
|
+
expect(byId.app.id).toBe(created.id);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("returns not_found for an unknown name", () => {
|
|
91
|
+
const result = resolveAppQuery("missing");
|
|
92
|
+
expect(result).toEqual({
|
|
93
|
+
ok: false,
|
|
94
|
+
reason: "not_found",
|
|
95
|
+
query: "missing",
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("returns ambiguous when two apps share a display name", () => {
|
|
100
|
+
const first = createApp({
|
|
101
|
+
name: "Notes",
|
|
102
|
+
schemaJson: "{}",
|
|
103
|
+
htmlDefinition: "<h1>One</h1>",
|
|
104
|
+
});
|
|
105
|
+
const second = createApp({
|
|
106
|
+
name: "Notes",
|
|
107
|
+
schemaJson: "{}",
|
|
108
|
+
htmlDefinition: "<h1>Two</h1>",
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const result = resolveAppQuery("Notes");
|
|
112
|
+
expect(result.ok).toBe(false);
|
|
113
|
+
if (!result.ok && result.reason === "ambiguous") {
|
|
114
|
+
const ids = result.matches.map((m) => m.id).sort();
|
|
115
|
+
expect(ids).toEqual([first.id, second.id].sort());
|
|
116
|
+
} else {
|
|
117
|
+
throw new Error("expected ambiguous resolution");
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("resolves a plugin app by directory name", () => {
|
|
122
|
+
const pluginDir = installPlugin("charts");
|
|
123
|
+
bundleApp(pluginDir, "viewer");
|
|
124
|
+
|
|
125
|
+
const result = resolveAppQuery("viewer");
|
|
126
|
+
expect(result.ok).toBe(true);
|
|
127
|
+
if (result.ok) {
|
|
128
|
+
expect(result.app.origin).toEqual({
|
|
129
|
+
kind: "plugin",
|
|
130
|
+
pluginName: "charts",
|
|
131
|
+
});
|
|
132
|
+
expect(result.app.id).toBe("plugins~charts~viewer");
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
});
|
|
@@ -1103,6 +1103,7 @@ describe("scaffold_managed_skill tool", () => {
|
|
|
1103
1103
|
activation_hints: HINTS,
|
|
1104
1104
|
overwrite: true,
|
|
1105
1105
|
files: [{ path: "references/failure-modes.md", content: "gotchas" }],
|
|
1106
|
+
change_summary: "Added the failure modes reference.",
|
|
1106
1107
|
},
|
|
1107
1108
|
makeRetrospectiveContext(),
|
|
1108
1109
|
);
|
|
@@ -1550,6 +1551,7 @@ describe("scaffold_managed_skill tool", () => {
|
|
|
1550
1551
|
body_markdown: "V2 procedure.",
|
|
1551
1552
|
activation_hints: HINTS,
|
|
1552
1553
|
overwrite: true,
|
|
1554
|
+
change_summary: "Refined the procedure.",
|
|
1553
1555
|
},
|
|
1554
1556
|
makeRetrospectiveContext({ conversationId: "retro-run-conv" }),
|
|
1555
1557
|
lineageSeam(),
|
|
@@ -1702,6 +1704,7 @@ describe("background skill update notification", () => {
|
|
|
1702
1704
|
body_markdown: "1. Refined steps.",
|
|
1703
1705
|
activation_hints: HINTS,
|
|
1704
1706
|
overwrite: true,
|
|
1707
|
+
change_summary: "Refined the steps.",
|
|
1705
1708
|
},
|
|
1706
1709
|
makeRetrospectiveContext({ conversationId: "retro-run-conv" }),
|
|
1707
1710
|
lineage(),
|
|
@@ -1715,9 +1718,7 @@ describe("background skill update notification", () => {
|
|
|
1715
1718
|
// must be the conversation the work came from, not the hidden fork.
|
|
1716
1719
|
expect(signal.sourceContextId).toBe("source-conv");
|
|
1717
1720
|
expect(signal.contextPayload?.skillId).toBe("weekly-export");
|
|
1718
|
-
expect(
|
|
1719
|
-
"Weekly Report Export",
|
|
1720
|
-
);
|
|
1721
|
+
expect(signal.contextPayload?.summary).toBe("Refined the steps.");
|
|
1721
1722
|
// The home feed falls back to `title`/`body` when no channel copy was
|
|
1722
1723
|
// rendered, which is the intended quiet shape for this signal. Without
|
|
1723
1724
|
// them a suppressed delivery would leave the feed item unwritten.
|
|
@@ -1725,15 +1726,236 @@ describe("background skill update notification", () => {
|
|
|
1725
1726
|
expect(signal.contextPayload?.title).toBe(
|
|
1726
1727
|
"Skill updated: Weekly Report Export",
|
|
1727
1728
|
);
|
|
1728
|
-
expect(
|
|
1729
|
-
"Weekly Report Export",
|
|
1730
|
-
);
|
|
1729
|
+
expect(signal.contextPayload?.body).toBe("Refined the steps.");
|
|
1731
1730
|
// Deduped per skill per day so repeated refinements cannot flood the feed.
|
|
1732
1731
|
expect(signal.dedupeKey).toBe(
|
|
1733
1732
|
`skill-updated:weekly-export:${new Date().toISOString().slice(0, 10)}`,
|
|
1734
1733
|
);
|
|
1735
1734
|
});
|
|
1736
1735
|
|
|
1736
|
+
test("the notice body is the pass's change_summary, so the feed says what changed", async () => {
|
|
1737
|
+
await seedAssistantSkill("weekly-export", "Old body.");
|
|
1738
|
+
|
|
1739
|
+
const result = await executeScaffoldManagedSkill(
|
|
1740
|
+
{
|
|
1741
|
+
skill_id: "weekly-export",
|
|
1742
|
+
name: "Weekly Report Export",
|
|
1743
|
+
description: "export the weekly usage report",
|
|
1744
|
+
body_markdown: "1. Refined steps.",
|
|
1745
|
+
activation_hints: HINTS,
|
|
1746
|
+
overwrite: true,
|
|
1747
|
+
change_summary:
|
|
1748
|
+
"Added the retry after an expired session and the export endpoint that held steady.",
|
|
1749
|
+
},
|
|
1750
|
+
makeRetrospectiveContext({ conversationId: "retro-run-conv" }),
|
|
1751
|
+
lineage(),
|
|
1752
|
+
);
|
|
1753
|
+
|
|
1754
|
+
expect(result.isError).toBe(false);
|
|
1755
|
+
expect(emittedSignals).toHaveLength(1);
|
|
1756
|
+
const payload = emittedSignals[0]!.contextPayload;
|
|
1757
|
+
// The title still names the skill; the body is the change itself.
|
|
1758
|
+
expect(payload?.title).toBe("Skill updated: Weekly Report Export");
|
|
1759
|
+
expect(payload?.body).toBe(
|
|
1760
|
+
"Added the retry after an expired session and the export endpoint that held steady.",
|
|
1761
|
+
);
|
|
1762
|
+
expect(payload?.summary).toBe(payload?.body);
|
|
1763
|
+
});
|
|
1764
|
+
|
|
1765
|
+
test("a background overwrite without a change_summary is refused before the write", async () => {
|
|
1766
|
+
await seedAssistantSkill("weekly-export", "Old body.");
|
|
1767
|
+
const skillFile = join(TEST_DIR, "skills", "weekly-export", "SKILL.md");
|
|
1768
|
+
const before = readFileSync(skillFile, "utf-8");
|
|
1769
|
+
|
|
1770
|
+
for (const input of [
|
|
1771
|
+
{},
|
|
1772
|
+
// Whitespace-only reads as missing, not as an empty notice.
|
|
1773
|
+
{ change_summary: " \n " },
|
|
1774
|
+
]) {
|
|
1775
|
+
const result = await executeScaffoldManagedSkill(
|
|
1776
|
+
{
|
|
1777
|
+
skill_id: "weekly-export",
|
|
1778
|
+
name: "Weekly Report Export",
|
|
1779
|
+
description: "export the weekly usage report",
|
|
1780
|
+
body_markdown: "1. Refined steps.",
|
|
1781
|
+
activation_hints: HINTS,
|
|
1782
|
+
overwrite: true,
|
|
1783
|
+
...input,
|
|
1784
|
+
},
|
|
1785
|
+
makeRetrospectiveContext({ conversationId: "retro-run-conv" }),
|
|
1786
|
+
lineage(),
|
|
1787
|
+
);
|
|
1788
|
+
|
|
1789
|
+
// Self-correcting: the error returns to the pass in the same turn, so
|
|
1790
|
+
// the retry carries the field and nothing was lost meanwhile.
|
|
1791
|
+
expect(result.isError).toBe(true);
|
|
1792
|
+
expect(result.content).toBe(
|
|
1793
|
+
'Error: change_summary is required when updating an existing skill: pass one or two short sentences (under 200 characters) for the person who reads the "Skill updated" notice, naming what you changed and what in the trace prompted it (for example "Added the retry after an expired session and the export endpoint that held steady.").',
|
|
1794
|
+
);
|
|
1795
|
+
expect(readFileSync(skillFile, "utf-8")).toBe(before);
|
|
1796
|
+
expect(emittedSignals).toHaveLength(0);
|
|
1797
|
+
}
|
|
1798
|
+
});
|
|
1799
|
+
|
|
1800
|
+
test("a background call on an existing skill without overwrite hears the overwrite error, not the summary requirement", async () => {
|
|
1801
|
+
await seedAssistantSkill("weekly-export", "Old body.");
|
|
1802
|
+
|
|
1803
|
+
const result = await executeScaffoldManagedSkill(
|
|
1804
|
+
{
|
|
1805
|
+
skill_id: "weekly-export",
|
|
1806
|
+
name: "Weekly Report Export",
|
|
1807
|
+
description: "export the weekly usage report",
|
|
1808
|
+
body_markdown: "1. Refined steps.",
|
|
1809
|
+
activation_hints: HINTS,
|
|
1810
|
+
},
|
|
1811
|
+
makeRetrospectiveContext({ conversationId: "retro-run-conv" }),
|
|
1812
|
+
lineage(),
|
|
1813
|
+
);
|
|
1814
|
+
|
|
1815
|
+
// The flag is what actually blocks this call, so that is the error it
|
|
1816
|
+
// gets: a retry that only adds change_summary would still fail.
|
|
1817
|
+
expect(result.isError).toBe(true);
|
|
1818
|
+
expect(result.content).toContain("Set overwrite=true to replace it.");
|
|
1819
|
+
expect(result.content).not.toContain("change_summary");
|
|
1820
|
+
expect(emittedSignals).toHaveLength(0);
|
|
1821
|
+
});
|
|
1822
|
+
|
|
1823
|
+
test("the ownership backstop speaks before the change_summary requirement", async () => {
|
|
1824
|
+
await executeScaffoldManagedSkill(
|
|
1825
|
+
{
|
|
1826
|
+
skill_id: "user-owned",
|
|
1827
|
+
name: "User Owned",
|
|
1828
|
+
description: "a person wrote this",
|
|
1829
|
+
body_markdown: "V1.",
|
|
1830
|
+
activation_hints: HINTS,
|
|
1831
|
+
},
|
|
1832
|
+
makeContext(),
|
|
1833
|
+
);
|
|
1834
|
+
|
|
1835
|
+
const result = await executeScaffoldManagedSkill(
|
|
1836
|
+
{
|
|
1837
|
+
skill_id: "user-owned",
|
|
1838
|
+
name: "User Owned",
|
|
1839
|
+
description: "a person wrote this",
|
|
1840
|
+
body_markdown: "V2.",
|
|
1841
|
+
activation_hints: HINTS,
|
|
1842
|
+
overwrite: true,
|
|
1843
|
+
},
|
|
1844
|
+
makeRetrospectiveContext({ conversationId: "retro-run-conv" }),
|
|
1845
|
+
lineage(),
|
|
1846
|
+
);
|
|
1847
|
+
|
|
1848
|
+
expect(result.isError).toBe(true);
|
|
1849
|
+
expect(result.content).toContain("not verifiably assistant-authored");
|
|
1850
|
+
});
|
|
1851
|
+
|
|
1852
|
+
test("a user-directed overwrite needs no change_summary: it never notifies", async () => {
|
|
1853
|
+
await executeScaffoldManagedSkill(
|
|
1854
|
+
{
|
|
1855
|
+
skill_id: "user-skill",
|
|
1856
|
+
name: "User Skill",
|
|
1857
|
+
description: "asked for",
|
|
1858
|
+
body_markdown: "V1.",
|
|
1859
|
+
activation_hints: HINTS,
|
|
1860
|
+
},
|
|
1861
|
+
makeContext(),
|
|
1862
|
+
);
|
|
1863
|
+
|
|
1864
|
+
const result = await executeScaffoldManagedSkill(
|
|
1865
|
+
{
|
|
1866
|
+
skill_id: "user-skill",
|
|
1867
|
+
name: "User Skill",
|
|
1868
|
+
description: "asked for",
|
|
1869
|
+
body_markdown: "V2.",
|
|
1870
|
+
activation_hints: HINTS,
|
|
1871
|
+
overwrite: true,
|
|
1872
|
+
},
|
|
1873
|
+
makeContext(),
|
|
1874
|
+
);
|
|
1875
|
+
|
|
1876
|
+
expect(result.isError).toBe(false);
|
|
1877
|
+
});
|
|
1878
|
+
|
|
1879
|
+
test("change_summary is sanitized like any other notification body", async () => {
|
|
1880
|
+
await seedAssistantSkill("weekly-export", "Old body.");
|
|
1881
|
+
|
|
1882
|
+
await executeScaffoldManagedSkill(
|
|
1883
|
+
{
|
|
1884
|
+
skill_id: "weekly-export",
|
|
1885
|
+
name: "Weekly Report Export",
|
|
1886
|
+
description: "export the weekly usage report",
|
|
1887
|
+
body_markdown: "1. Refined steps.",
|
|
1888
|
+
activation_hints: HINTS,
|
|
1889
|
+
overwrite: true,
|
|
1890
|
+
change_summary: `Added\u0007 the retry.\r\n\n\n\nDropped the login step. ${"x".repeat(300)}`,
|
|
1891
|
+
},
|
|
1892
|
+
makeRetrospectiveContext({ conversationId: "retro-run-conv" }),
|
|
1893
|
+
lineage(),
|
|
1894
|
+
);
|
|
1895
|
+
|
|
1896
|
+
const body = String(emittedSignals[0]!.contextPayload?.body);
|
|
1897
|
+
// Control characters go, horizontal whitespace collapses, blank-line runs
|
|
1898
|
+
// collapse to one paragraph break, and the whole thing is clamped to the
|
|
1899
|
+
// shared notification preview budget.
|
|
1900
|
+
expect(
|
|
1901
|
+
body.startsWith("Added the retry.\n\nDropped the login step. xxx"),
|
|
1902
|
+
).toBe(true);
|
|
1903
|
+
expect(body).not.toMatch(/[\u0000-\u0008\u000b\u000c\u000e-\u001f]/);
|
|
1904
|
+
expect(body.length).toBe(200);
|
|
1905
|
+
expect(body.endsWith("\u2026")).toBe(true);
|
|
1906
|
+
});
|
|
1907
|
+
|
|
1908
|
+
test("a non-string change_summary is a self-correcting error", async () => {
|
|
1909
|
+
await seedAssistantSkill("weekly-export", "Old body.");
|
|
1910
|
+
|
|
1911
|
+
const result = await executeScaffoldManagedSkill(
|
|
1912
|
+
{
|
|
1913
|
+
skill_id: "weekly-export",
|
|
1914
|
+
name: "Weekly Report Export",
|
|
1915
|
+
description: "export the weekly usage report",
|
|
1916
|
+
body_markdown: "1. Refined steps.",
|
|
1917
|
+
activation_hints: HINTS,
|
|
1918
|
+
overwrite: true,
|
|
1919
|
+
change_summary: ["not", "a", "string"],
|
|
1920
|
+
},
|
|
1921
|
+
makeRetrospectiveContext({ conversationId: "retro-run-conv" }),
|
|
1922
|
+
lineage(),
|
|
1923
|
+
);
|
|
1924
|
+
|
|
1925
|
+
expect(result.isError).toBe(true);
|
|
1926
|
+
expect(result.content).toBe("Error: change_summary must be a string");
|
|
1927
|
+
expect(emittedSignals).toHaveLength(0);
|
|
1928
|
+
});
|
|
1929
|
+
|
|
1930
|
+
test("the registered tool's schema declares change_summary as an optional string", () => {
|
|
1931
|
+
// A call through the registered tool is validated against TOOLS.json
|
|
1932
|
+
// first, and the validator rejects undeclared keys, so the field has to
|
|
1933
|
+
// be in the schema for the executor to ever see it. It stays optional
|
|
1934
|
+
// there: the requirement holds only for a background overwrite of an
|
|
1935
|
+
// existing skill, which the schema cannot express, so the executor
|
|
1936
|
+
// enforces it.
|
|
1937
|
+
const scaffoldTool = readScaffoldToolEntry();
|
|
1938
|
+
expect(scaffoldTool.input_schema.properties.change_summary).toMatchObject({
|
|
1939
|
+
type: "string",
|
|
1940
|
+
});
|
|
1941
|
+
expect(scaffoldTool.input_schema.required).not.toContain("change_summary");
|
|
1942
|
+
|
|
1943
|
+
const result = validateInputAgainstSchema(
|
|
1944
|
+
"scaffold_managed_skill",
|
|
1945
|
+
{
|
|
1946
|
+
skill_id: "s",
|
|
1947
|
+
name: "N",
|
|
1948
|
+
description: "D",
|
|
1949
|
+
body_markdown: "B",
|
|
1950
|
+
activation_hints: HINTS,
|
|
1951
|
+
overwrite: true,
|
|
1952
|
+
change_summary: "Added the retry step.",
|
|
1953
|
+
},
|
|
1954
|
+
scaffoldTool.input_schema,
|
|
1955
|
+
);
|
|
1956
|
+
expect(result.ok).toBe(true);
|
|
1957
|
+
});
|
|
1958
|
+
|
|
1737
1959
|
test("it falls back to the run conversation when fork lineage does not resolve", async () => {
|
|
1738
1960
|
await seedAssistantSkill("weekly-export", "Old body.");
|
|
1739
1961
|
|
|
@@ -1745,6 +1967,7 @@ describe("background skill update notification", () => {
|
|
|
1745
1967
|
body_markdown: "1. Refined steps.",
|
|
1746
1968
|
activation_hints: HINTS,
|
|
1747
1969
|
overwrite: true,
|
|
1970
|
+
change_summary: "Refined the steps.",
|
|
1748
1971
|
},
|
|
1749
1972
|
makeRetrospectiveContext({ conversationId: "retro-run-conv" }),
|
|
1750
1973
|
{ getConversation: () => null },
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
getCAPath,
|
|
9
9
|
issueLeafCert,
|
|
10
10
|
} from "../outbound-proxy/index.js";
|
|
11
|
+
import { writeCombinedCABundle } from "../util/ca-bundle.js";
|
|
11
12
|
|
|
12
13
|
let dataDir: string;
|
|
13
14
|
|
|
@@ -19,6 +20,40 @@ afterAll(async () => {
|
|
|
19
20
|
await rm(dataDir, { recursive: true, force: true });
|
|
20
21
|
});
|
|
21
22
|
|
|
23
|
+
test("combined trust verifies certificates from both CA sources", async () => {
|
|
24
|
+
const roots = [join(dataDir, "system"), join(dataDir, "extra")];
|
|
25
|
+
const leaves: string[] = [];
|
|
26
|
+
for (const root of roots) {
|
|
27
|
+
await ensureLocalCA(root);
|
|
28
|
+
const caDir = join(root, "proxy-ca");
|
|
29
|
+
await issueLeafCert(caDir, "example.com");
|
|
30
|
+
leaves.push(join(caDir, "issued", "example.com.pem"));
|
|
31
|
+
}
|
|
32
|
+
const bundle = join(dataDir, "combined.pem");
|
|
33
|
+
await writeCombinedCABundle(
|
|
34
|
+
getCAPath(roots[0]!),
|
|
35
|
+
getCAPath(roots[1]!),
|
|
36
|
+
bundle,
|
|
37
|
+
);
|
|
38
|
+
const verify = Bun.spawn(
|
|
39
|
+
["openssl", "verify", "-CAfile", bundle, ...leaves],
|
|
40
|
+
{
|
|
41
|
+
stdout: "pipe",
|
|
42
|
+
stderr: "pipe",
|
|
43
|
+
windowsHide: true,
|
|
44
|
+
},
|
|
45
|
+
);
|
|
46
|
+
const [code, output, error] = await Promise.all([
|
|
47
|
+
verify.exited,
|
|
48
|
+
new Response(verify.stdout).text(),
|
|
49
|
+
new Response(verify.stderr).text(),
|
|
50
|
+
]);
|
|
51
|
+
expect({ code, error }).toEqual({ code: 0, error: "" });
|
|
52
|
+
for (const leaf of leaves) {
|
|
53
|
+
expect(output).toContain(`${leaf}: OK`);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
22
57
|
describe("ensureLocalCA", () => {
|
|
23
58
|
test("creates CA cert and key with correct permissions", async () => {
|
|
24
59
|
await ensureLocalCA(dataDir);
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
ACP_OAUTH_EXPIRES_AT_FIELD,
|
|
5
|
+
ACP_OAUTH_REFRESH_TOKEN_FIELD,
|
|
6
|
+
ACP_OAUTH_TOKEN_FIELD,
|
|
7
|
+
ACP_SERVICE,
|
|
8
|
+
} from "../acp/acp-credentials.js";
|
|
4
9
|
import { credentialKey } from "../security/credential-key.js";
|
|
5
10
|
|
|
6
11
|
let secureKeyStore: Record<string, string | undefined> = {};
|
|
@@ -32,6 +37,11 @@ mock.module("../oauth/manual-token-connection.js", () => ({
|
|
|
32
37
|
syncManualTokenConnection: async () => {},
|
|
33
38
|
}));
|
|
34
39
|
|
|
40
|
+
mock.module("../runtime/routes/credential-in-use.js", () => ({
|
|
41
|
+
assertCredentialNotInUse: () => [],
|
|
42
|
+
invalidateConnectionsAfterCredentialDelete: () => {},
|
|
43
|
+
}));
|
|
44
|
+
|
|
35
45
|
afterAll(() => {
|
|
36
46
|
mock.restore();
|
|
37
47
|
});
|
|
@@ -42,6 +52,9 @@ import { ROUTES } from "../runtime/routes/secret-routes.js";
|
|
|
42
52
|
const addRoute = ROUTES.find(
|
|
43
53
|
(r) => r.method === "POST" && r.endpoint === "secrets",
|
|
44
54
|
)!;
|
|
55
|
+
const deleteRoute = ROUTES.find(
|
|
56
|
+
(r) => r.method === "DELETE" && r.endpoint === "secrets",
|
|
57
|
+
)!;
|
|
45
58
|
|
|
46
59
|
function addAcpOauthToken(value: string) {
|
|
47
60
|
return addRoute.handler({
|
|
@@ -74,4 +87,49 @@ describe("secret routes ACP OAuth-token format guard", () => {
|
|
|
74
87
|
secureKeyStore[credentialKey(ACP_SERVICE, ACP_OAUTH_TOKEN_FIELD)],
|
|
75
88
|
).toBe("sk-ant-oat01-valid-oauth-token");
|
|
76
89
|
});
|
|
90
|
+
|
|
91
|
+
test("stores a pasted Claude token without touching leftover refresh material", async () => {
|
|
92
|
+
secureKeyStore[credentialKey(ACP_SERVICE, ACP_OAUTH_REFRESH_TOKEN_FIELD)] =
|
|
93
|
+
"stale-refresh";
|
|
94
|
+
secureKeyStore[credentialKey(ACP_SERVICE, ACP_OAUTH_EXPIRES_AT_FIELD)] =
|
|
95
|
+
"111";
|
|
96
|
+
|
|
97
|
+
await addAcpOauthToken("sk-ant-oat01-pasted-token");
|
|
98
|
+
|
|
99
|
+
expect(
|
|
100
|
+
secureKeyStore[credentialKey(ACP_SERVICE, ACP_OAUTH_TOKEN_FIELD)],
|
|
101
|
+
).toBe("sk-ant-oat01-pasted-token");
|
|
102
|
+
expect(
|
|
103
|
+
secureKeyStore[credentialKey(ACP_SERVICE, ACP_OAUTH_REFRESH_TOKEN_FIELD)],
|
|
104
|
+
).toBe("stale-refresh");
|
|
105
|
+
expect(
|
|
106
|
+
secureKeyStore[credentialKey(ACP_SERVICE, ACP_OAUTH_EXPIRES_AT_FIELD)],
|
|
107
|
+
).toBe("111");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("deletes only the requested Claude access-token field", async () => {
|
|
111
|
+
secureKeyStore[credentialKey(ACP_SERVICE, ACP_OAUTH_TOKEN_FIELD)] =
|
|
112
|
+
"sk-ant-oat01-connected";
|
|
113
|
+
secureKeyStore[credentialKey(ACP_SERVICE, ACP_OAUTH_REFRESH_TOKEN_FIELD)] =
|
|
114
|
+
"refresh-leftover";
|
|
115
|
+
secureKeyStore[credentialKey(ACP_SERVICE, ACP_OAUTH_EXPIRES_AT_FIELD)] =
|
|
116
|
+
"111";
|
|
117
|
+
|
|
118
|
+
await deleteRoute.handler({
|
|
119
|
+
body: {
|
|
120
|
+
type: "credential",
|
|
121
|
+
name: `${ACP_SERVICE}:${ACP_OAUTH_TOKEN_FIELD}`,
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
expect(
|
|
126
|
+
secureKeyStore[credentialKey(ACP_SERVICE, ACP_OAUTH_TOKEN_FIELD)],
|
|
127
|
+
).toBeUndefined();
|
|
128
|
+
expect(
|
|
129
|
+
secureKeyStore[credentialKey(ACP_SERVICE, ACP_OAUTH_REFRESH_TOKEN_FIELD)],
|
|
130
|
+
).toBe("refresh-leftover");
|
|
131
|
+
expect(
|
|
132
|
+
secureKeyStore[credentialKey(ACP_SERVICE, ACP_OAUTH_EXPIRES_AT_FIELD)],
|
|
133
|
+
).toBe("111");
|
|
134
|
+
});
|
|
77
135
|
});
|