@vellumai/assistant 0.6.2 → 0.6.4
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/ARCHITECTURE.md +273 -10
- package/Dockerfile +2 -3
- package/bun.lock +41 -49
- package/bunfig.toml +3 -0
- package/docs/architecture/memory.md +1 -1
- package/docs/backup-troubleshooting.md +52 -0
- package/docs/browser-use-architecture-phase2.md +174 -0
- package/docs/stt-provider-onboarding.md +120 -0
- package/knip.json +12 -2
- package/node_modules/@vellumai/ces-contracts/bun.lock +8 -6
- package/node_modules/@vellumai/ces-contracts/package.json +3 -3
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +42 -0
- package/openapi.yaml +1111 -86
- package/package.json +40 -42
- package/scripts/generate-openapi.ts +0 -2
- package/scripts/test.sh +73 -18
- package/src/__tests__/acp-session.test.ts +43 -0
- package/src/__tests__/agent-image-optimize.test.ts +28 -0
- package/src/__tests__/agent-loop.test.ts +123 -0
- package/src/__tests__/anthropic-provider.test.ts +263 -10
- package/src/__tests__/app-builder-tool-scripts.test.ts +1 -0
- package/src/__tests__/app-executors.test.ts +1 -0
- package/src/__tests__/app-source-watcher.test.ts +37 -11
- package/src/__tests__/approval-routes-http.test.ts +178 -1
- package/src/__tests__/auto-analysis-end-to-end.test.ts +550 -0
- package/src/__tests__/auto-analysis-prompt.test.ts +50 -0
- package/src/__tests__/browser-fill-credential.test.ts +240 -94
- package/src/__tests__/browser-manager.test.ts +40 -27
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
- package/src/__tests__/browser-skill-endstate.test.ts +31 -7
- package/src/__tests__/btw-routes.test.ts +7 -0
- package/src/__tests__/call-controller.test.ts +581 -20
- package/src/__tests__/catalog-files.test.ts +1000 -0
- package/src/__tests__/channel-approvals.test.ts +53 -0
- package/src/__tests__/channel-invite-transport.test.ts +2 -2
- package/src/__tests__/channel-readiness-routes.test.ts +16 -20
- package/src/__tests__/channel-readiness-service.test.ts +12 -7
- package/src/__tests__/checker.test.ts +157 -10
- package/src/__tests__/clawhub-files.test.ts +347 -0
- package/src/__tests__/commit-message-enrichment-service.test.ts +36 -19
- package/src/__tests__/config-analysis.test.ts +100 -0
- package/src/__tests__/config-managed-gemini-defaults.test.ts +326 -0
- package/src/__tests__/config-schema-cmd.test.ts +2 -2
- package/src/__tests__/config-schema.test.ts +1248 -224
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +339 -0
- package/src/__tests__/config-watcher.test.ts +43 -8
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +23 -0
- package/src/__tests__/contact-store-user-file.test.ts +512 -0
- package/src/__tests__/contacts-write.test.ts +197 -0
- package/src/__tests__/context-overflow-approval.test.ts +16 -1
- package/src/__tests__/context-window-manager.test.ts +88 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -1
- package/src/__tests__/conversation-agent-loop.test.ts +99 -3
- package/src/__tests__/conversation-analysis-routes.test.ts +2 -2
- package/src/__tests__/conversation-attachments.test.ts +80 -4
- package/src/__tests__/conversation-confirmation-signals.test.ts +290 -0
- package/src/__tests__/conversation-error.test.ts +70 -0
- package/src/__tests__/conversation-fork-crud.test.ts +17 -0
- package/src/__tests__/conversation-history-web-search.test.ts +12 -4
- package/src/__tests__/conversation-host-access-routes.test.ts +229 -0
- package/src/__tests__/conversation-init.benchmark.test.ts +6 -1
- package/src/__tests__/conversation-inject-context.test.ts +103 -0
- package/src/__tests__/conversation-launcher-skill-regression.test.ts +51 -0
- package/src/__tests__/conversation-list-source.test.ts +145 -0
- package/src/__tests__/conversation-pre-run-repair.test.ts +2 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
- package/src/__tests__/conversation-queue.test.ts +946 -62
- package/src/__tests__/conversation-routes-disk-view.test.ts +275 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +16 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +324 -46
- package/src/__tests__/conversation-skill-tools.test.ts +7 -4
- package/src/__tests__/conversation-slash-commands.test.ts +33 -0
- package/src/__tests__/conversation-slash-queue.test.ts +89 -18
- package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
- package/src/__tests__/conversation-starter-routes.test.ts +126 -0
- package/src/__tests__/conversation-starters-cadence.test.ts +161 -0
- package/src/__tests__/conversation-store.test.ts +195 -0
- package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +226 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +193 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
- package/src/__tests__/credential-execution-approval-bridge.test.ts +32 -1
- package/src/__tests__/credential-health-service.test.ts +352 -0
- package/src/__tests__/credential-security-invariants.test.ts +6 -3
- package/src/__tests__/credential-vault-unit.test.ts +383 -7
- package/src/__tests__/credential-vault.test.ts +152 -13
- package/src/__tests__/credentials-cli.test.ts +42 -18
- package/src/__tests__/cross-provider-web-search.test.ts +146 -35
- package/src/__tests__/date-context.test.ts +4 -4
- package/src/__tests__/deterministic-verification-control-plane.test.ts +10 -1
- package/src/__tests__/device-id.test.ts +112 -0
- package/src/__tests__/docker-signing-key-bootstrap.test.ts +167 -4
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +1 -3
- package/src/__tests__/email-html-renderer.test.ts +71 -0
- package/src/__tests__/email-invite-adapter.test.ts +36 -32
- package/src/__tests__/embedding-managed-proxy-selection.test.ts +256 -0
- package/src/__tests__/emit-event-signal.test.ts +71 -0
- package/src/__tests__/extension-id-sync-guard.test.ts +222 -0
- package/src/__tests__/fixtures/mock-chrome-extension.ts +386 -0
- package/src/__tests__/gateway-only-enforcement.test.ts +206 -1
- package/src/__tests__/gateway-only-guard.test.ts +2 -0
- package/src/__tests__/gemini-provider.test.ts +66 -2
- package/src/__tests__/get-skill-detail-audit.test.ts +325 -0
- package/src/__tests__/gmail-archive-fallback.test.ts +193 -0
- package/src/__tests__/gmail-archive-gate.test.ts +246 -0
- package/src/__tests__/gmail-preferences.test.ts +117 -0
- package/src/__tests__/guardian-routing-invariants.test.ts +70 -2
- package/src/__tests__/headless-browser-interactions.test.ts +738 -359
- package/src/__tests__/headless-browser-mode.test.ts +614 -0
- package/src/__tests__/headless-browser-navigate.test.ts +528 -49
- package/src/__tests__/headless-browser-read-tools.test.ts +274 -100
- package/src/__tests__/headless-browser-snapshot.test.ts +250 -77
- package/src/__tests__/heartbeat-service.test.ts +70 -17
- package/src/__tests__/home-state-routes.test.ts +162 -0
- package/src/__tests__/host-bash-proxy.test.ts +145 -1
- package/src/__tests__/host-browser-e2e-cloud.test.ts +596 -0
- package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +286 -0
- package/src/__tests__/host-browser-e2e-self-hosted.test.ts +374 -0
- package/src/__tests__/host-browser-event-routes.test.ts +350 -0
- package/src/__tests__/host-browser-proxy.test.ts +444 -0
- package/src/__tests__/host-browser-routes.test.ts +198 -0
- package/src/__tests__/host-browser-ws-events-e2e.test.ts +423 -0
- package/src/__tests__/host-cu-proxy.test.ts +166 -1
- package/src/__tests__/host-file-proxy.test.ts +185 -1
- package/src/__tests__/host-file-read-tool.test.ts +52 -0
- package/src/__tests__/host-proxy-interface.test.ts +165 -0
- package/src/__tests__/host-shell-tool.test.ts +1 -11
- package/src/__tests__/http-user-message-parity.test.ts +1 -0
- package/src/__tests__/identity-intro-cache.test.ts +40 -10
- package/src/__tests__/init-feature-flag-overrides.test.ts +38 -112
- package/src/__tests__/integration-status.test.ts +6 -7
- package/src/__tests__/jobs-store-upsert-debounced.test.ts +141 -0
- package/src/__tests__/list-messages-tool-merge.test.ts +37 -12
- package/src/__tests__/llm-context-normalization.test.ts +488 -0
- package/src/__tests__/llm-context-route-provider.test.ts +86 -5
- package/src/__tests__/llm-usage-store.test.ts +363 -0
- package/src/__tests__/mcp-client-auth.test.ts +40 -4
- package/src/__tests__/mcp-health-check.test.ts +10 -3
- package/src/__tests__/media-stream-output.test.ts +555 -0
- package/src/__tests__/media-stream-parser.test.ts +374 -0
- package/src/__tests__/media-stream-server-integration.test.ts +1234 -0
- package/src/__tests__/media-stream-stt-session.test.ts +588 -0
- package/src/__tests__/media-turn-detector.test.ts +440 -0
- package/src/__tests__/message-queue.test.ts +125 -0
- package/src/__tests__/migration-cross-version-compatibility.test.ts +3 -1
- package/src/__tests__/migration-export-http.test.ts +67 -8
- package/src/__tests__/migration-export-streaming.test.ts +66 -0
- package/src/__tests__/migration-import-commit-http.test.ts +109 -7
- package/src/__tests__/migration-import-preflight-http.test.ts +6 -5
- package/src/__tests__/migration-validate-http.test.ts +3 -3
- package/src/__tests__/mock-gateway-ipc.ts +151 -0
- package/src/__tests__/model-intents.test.ts +2 -2
- package/src/__tests__/native-host-marker-sync-guard.test.ts +157 -0
- package/src/__tests__/oauth-apps-routes.test.ts +18 -12
- package/src/__tests__/oauth-cli.test.ts +709 -60
- package/src/__tests__/oauth-connect-orchestrator.test.ts +118 -24
- package/src/__tests__/oauth-provider-seed-logos.test.ts +23 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +147 -10
- package/src/__tests__/oauth-provider-visibility.test.ts +19 -21
- package/src/__tests__/oauth-providers-routes.test.ts +52 -14
- package/src/__tests__/oauth-store.test.ts +1465 -176
- package/src/__tests__/oauth2-gateway-transport.test.ts +460 -26
- package/src/__tests__/onboarding-template-contract.test.ts +81 -70
- package/src/__tests__/openai-provider.test.ts +178 -2
- package/src/__tests__/openai-responses-cutover-guard.test.ts +184 -0
- package/src/__tests__/openai-responses-provider.test.ts +1105 -0
- package/src/__tests__/openrouter-token-estimation.test.ts +100 -0
- package/src/__tests__/outlook-categories.test.ts +1 -1
- package/src/__tests__/outlook-client-automation.test.ts +1 -1
- package/src/__tests__/outlook-compose-tools.test.ts +1 -1
- package/src/__tests__/outlook-email-watcher.test.ts +1 -1
- package/src/__tests__/outlook-follow-up.test.ts +1 -1
- package/src/__tests__/outlook-messaging-provider.test.ts +2 -2
- package/src/__tests__/outlook-trash.test.ts +1 -1
- package/src/__tests__/outlook-unsubscribe.test.ts +32 -3
- package/src/__tests__/permission-checker-host-gate.test.ts +74 -14
- package/src/__tests__/permission-mode.test.ts +28 -56
- package/src/__tests__/persona-resolver.test.ts +251 -0
- package/src/__tests__/platform-bash-auto-approve.test.ts +4 -0
- package/src/__tests__/platform-callback-registration.test.ts +19 -0
- package/src/__tests__/platform.test.ts +92 -1
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +343 -0
- package/src/__tests__/prechat-onboarding-contract.test.ts +267 -0
- package/src/__tests__/pricing.test.ts +174 -0
- package/src/__tests__/proxy-approval-callback.test.ts +18 -0
- package/src/__tests__/qdrant-manager.test.ts +29 -8
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +194 -0
- package/src/__tests__/relationship-state-contract.test.ts +175 -0
- package/src/__tests__/relay-server.test.ts +423 -5
- package/src/__tests__/require-fresh-approval.test.ts +40 -1
- package/src/__tests__/sanitize-config-for-transfer.test.ts +132 -0
- package/src/__tests__/schedule-routes.test.ts +162 -0
- package/src/__tests__/search-skills-unified.test.ts +118 -0
- package/src/__tests__/secret-detection-handler.test.ts +84 -0
- package/src/__tests__/secret-ingress-http.test.ts +1 -0
- package/src/__tests__/secret-scanner-executor.test.ts +4 -0
- package/src/__tests__/secure-keys.test.ts +107 -0
- package/src/__tests__/send-endpoint-busy.test.ts +8 -1
- package/src/__tests__/sequence-store.test.ts +1 -1
- package/src/__tests__/server-history-render.test.ts +49 -0
- package/src/__tests__/set-permission-mode.test.ts +13 -250
- package/src/__tests__/settings-routes.test.ts +201 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
- package/src/__tests__/skills-file-content-endpoint.test.ts +801 -0
- package/src/__tests__/skills-files-catalog-fallback.test.ts +738 -0
- package/src/__tests__/skills.test.ts +5 -2
- package/src/__tests__/skillssh-files.test.ts +446 -0
- package/src/__tests__/slack-block-formatting.test.ts +110 -0
- package/src/__tests__/slack-channel-config.test.ts +576 -16
- package/src/__tests__/stt-catalog-parity.test.ts +282 -0
- package/src/__tests__/stt-stream-session.test.ts +535 -0
- package/src/__tests__/subagent-detail.test.ts +44 -2
- package/src/__tests__/subagent-disposal.test.ts +1 -0
- package/src/__tests__/subagent-fork-notifications.test.ts +291 -0
- package/src/__tests__/subagent-fork-spawn.test.ts +384 -0
- package/src/__tests__/subagent-manager-notify.test.ts +1 -0
- package/src/__tests__/subagent-notify-parent.test.ts +1 -0
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +411 -0
- package/src/__tests__/subagent-tools.test.ts +1 -0
- package/src/__tests__/subagent-types.test.ts +1 -0
- package/src/__tests__/system-prompt-ask-mode.test.ts +27 -71
- package/src/__tests__/system-prompt.test.ts +184 -27
- package/src/__tests__/task-scheduler.test.ts +32 -6
- package/src/__tests__/telegram-config.test.ts +10 -13
- package/src/__tests__/telephony-stt-routing.test.ts +329 -0
- package/src/__tests__/terminal-tools.test.ts +25 -5
- package/src/__tests__/test-preload.ts +18 -0
- package/src/__tests__/test-support/browser-skill-harness.ts +4 -1
- package/src/__tests__/tool-approval-handler.test.ts +73 -0
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +9 -5
- package/src/__tests__/tool-executor-shell-integration.test.ts +4 -0
- package/src/__tests__/tool-executor.test.ts +33 -24
- package/src/__tests__/tool-result-truncation.test.ts +36 -0
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +22 -0
- package/src/__tests__/top-level-renderer.test.ts +73 -1
- package/src/__tests__/transport-hints-queue.test.ts +14 -29
- package/src/__tests__/trust-store.test.ts +7 -1
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +1 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +109 -0
- package/src/__tests__/tts-catalog-parity.test.ts +345 -0
- package/src/__tests__/twilio-routes-twiml.test.ts +512 -114
- package/src/__tests__/twilio-routes.test.ts +376 -0
- package/src/__tests__/unicode.test.ts +293 -0
- package/src/__tests__/update-bulletin-format.test.ts +59 -0
- package/src/__tests__/update-bulletin.test.ts +206 -5
- package/src/__tests__/usage-routes.test.ts +25 -4
- package/src/__tests__/user-reference.test.ts +46 -61
- package/src/__tests__/v2-consent-policy.test.ts +103 -0
- package/src/__tests__/verification-control-plane-policy.test.ts +4 -0
- package/src/__tests__/voice-config-update.test.ts +403 -0
- package/src/__tests__/voice-quality.test.ts +434 -19
- package/src/__tests__/workspace-heartbeat-service.test.ts +7 -0
- package/src/__tests__/workspace-migration-033-stt-service-explicit-config.test.ts +547 -0
- package/src/__tests__/workspace-migration-034-remove-calls-voice-transcription-provider.test.ts +596 -0
- package/src/__tests__/workspace-migration-drop-user-md.test.ts +368 -0
- package/src/__tests__/workspace-migration-meets.test.ts +244 -0
- package/src/__tests__/workspace-migration-seed-device-id.test.ts +14 -20
- package/src/__tests__/workspace-policy.test.ts +2 -0
- package/src/acp/client-handler.ts +30 -4
- package/src/agent/image-optimize.ts +24 -12
- package/src/agent/loop.ts +55 -9
- package/src/approvals/guardian-request-resolvers.ts +21 -15
- package/src/backup/__tests__/backup-key.test.ts +152 -0
- package/src/backup/__tests__/backup-worker.test.ts +767 -0
- package/src/backup/__tests__/list-snapshots.test.ts +87 -0
- package/src/backup/__tests__/local-writer.test.ts +218 -0
- package/src/backup/__tests__/offsite-writer.test.ts +641 -0
- package/src/backup/__tests__/paths.test.ts +300 -0
- package/src/backup/__tests__/restore.test.ts +498 -0
- package/src/backup/__tests__/snapshot-lock.test.ts +352 -0
- package/src/backup/__tests__/stream-crypt.test.ts +228 -0
- package/src/backup/backup-key.ts +137 -0
- package/src/backup/backup-worker.ts +459 -0
- package/src/backup/list-snapshots.ts +147 -0
- package/src/backup/local-writer.ts +133 -0
- package/src/backup/offsite-writer.ts +222 -0
- package/src/backup/paths.ts +226 -0
- package/src/backup/restore.ts +322 -0
- package/src/backup/snapshot-lock.ts +431 -0
- package/src/backup/stream-crypt.ts +263 -0
- package/src/browser-session/__tests__/manager.test.ts +297 -0
- package/src/browser-session/backends/cdp-inspect.ts +30 -0
- package/src/browser-session/backends/extension.ts +26 -0
- package/src/browser-session/backends/local.ts +24 -0
- package/src/browser-session/events.ts +164 -0
- package/src/browser-session/index.ts +27 -0
- package/src/browser-session/manager.ts +159 -0
- package/src/browser-session/types.ts +28 -0
- package/src/bundler/package-resolver.ts +4 -0
- package/src/calls/audio-store.ts +11 -5
- package/src/calls/call-controller.ts +226 -71
- package/src/calls/call-domain.ts +9 -0
- package/src/calls/call-speech-output.ts +190 -0
- package/src/calls/call-transport.ts +77 -0
- package/src/calls/media-stream-audio-transcode.ts +173 -0
- package/src/calls/media-stream-output.ts +660 -0
- package/src/calls/media-stream-parser.ts +300 -0
- package/src/calls/media-stream-protocol.ts +166 -0
- package/src/calls/media-stream-server.ts +592 -0
- package/src/calls/media-stream-stt-session.ts +460 -0
- package/src/calls/media-turn-detector.ts +230 -0
- package/src/calls/relay-server.ts +90 -75
- package/src/calls/resolve-call-tts-provider.ts +136 -0
- package/src/calls/telephony-stt-routing.ts +145 -0
- package/src/calls/tts-call-strategy.ts +161 -0
- package/src/calls/tts-text-sanitizer.ts +32 -16
- package/src/calls/twilio-routes.ts +281 -17
- package/src/calls/voice-quality.ts +78 -35
- package/src/calls/voice-session-bridge.ts +8 -1
- package/src/channels/__tests__/types.test.ts +134 -0
- package/src/channels/types.ts +69 -3
- package/src/cli/__tests__/run-assistant-command.ts +11 -1
- package/src/cli/commands/__tests__/backup.test.ts +1165 -0
- package/src/cli/commands/__tests__/domain-register.test.ts +234 -0
- package/src/cli/commands/__tests__/domain-status.test.ts +132 -0
- package/src/cli/commands/__tests__/email-attachment.test.ts +422 -0
- package/src/cli/commands/__tests__/email-download.test.ts +16 -1
- package/src/cli/commands/__tests__/email-list.test.ts +22 -4
- package/src/cli/commands/__tests__/email-register.test.ts +4 -4
- package/src/cli/commands/__tests__/email-send.test.ts +37 -4
- package/src/cli/commands/__tests__/email-status.test.ts +5 -1
- package/src/cli/commands/__tests__/email-unregister.test.ts +34 -5
- package/src/cli/commands/backup.ts +993 -0
- package/src/cli/commands/conversations.ts +77 -0
- package/src/cli/commands/credentials.ts +3 -4
- package/src/cli/commands/domain.ts +210 -0
- package/src/cli/commands/email.ts +273 -16
- package/src/cli/commands/mcp.ts +16 -4
- package/src/cli/commands/oauth/__tests__/connect.test.ts +56 -44
- package/src/cli/commands/oauth/__tests__/disconnect.test.ts +21 -21
- package/src/cli/commands/oauth/__tests__/mode.test.ts +17 -17
- package/src/cli/commands/oauth/__tests__/ping.test.ts +16 -16
- package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +32 -33
- package/src/cli/commands/oauth/__tests__/providers-register.test.ts +330 -0
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +117 -12
- package/src/cli/commands/oauth/__tests__/status.test.ts +10 -10
- package/src/cli/commands/oauth/__tests__/token.test.ts +7 -7
- package/src/cli/commands/oauth/apps.ts +7 -4
- package/src/cli/commands/oauth/connect.ts +6 -3
- package/src/cli/commands/oauth/disconnect.ts +1 -1
- package/src/cli/commands/oauth/mode.ts +12 -3
- package/src/cli/commands/oauth/providers.ts +215 -36
- package/src/cli/commands/oauth/shared.ts +7 -6
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +254 -0
- package/src/cli/commands/platform/__tests__/connect.test.ts +6 -0
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +6 -0
- package/src/cli/commands/platform/index.ts +107 -10
- package/src/cli/commands/usage.ts +10 -9
- package/src/cli/lib/daemon-credential-client.ts +4 -0
- package/src/cli/program.ts +30 -4
- package/src/config/__tests__/backup-schema.test.ts +134 -0
- package/src/config/assistant-feature-flags.ts +61 -62
- package/src/config/bundled-skills/app-builder/SKILL.md +26 -249
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +141 -0
- package/src/config/bundled-skills/app-builder/references/INTERACTION_HOOKS.md +56 -0
- package/src/config/bundled-skills/app-builder/references/WIDGETS.md +125 -0
- package/src/config/bundled-skills/browser/SKILL.md +30 -5
- package/src/config/bundled-skills/browser/TOOLS.json +123 -0
- package/src/config/bundled-skills/browser/tools/browser-attach.ts +12 -0
- package/src/config/bundled-skills/browser/tools/browser-detach.ts +12 -0
- package/src/config/bundled-skills/browser/tools/browser-status.ts +12 -0
- package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +17 -0
- package/src/config/bundled-skills/contacts/SKILL.md +5 -2
- package/src/config/bundled-skills/document/SKILL.md +4 -0
- package/src/config/bundled-skills/gmail/SKILL.md +54 -8
- package/src/config/bundled-skills/gmail/TOOLS.json +33 -3
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +116 -9
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +138 -11
- package/src/config/bundled-skills/gmail/tools/gmail-preferences-tool.ts +59 -0
- package/src/config/bundled-skills/gmail/tools/gmail-preferences.ts +82 -0
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +113 -17
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -2
- package/src/config/bundled-skills/media-processing/SKILL.md +3 -9
- package/src/config/bundled-skills/media-processing/TOOLS.json +1 -6
- package/src/config/bundled-skills/media-processing/__tests__/audio-transcribe.test.ts +125 -0
- package/src/config/bundled-skills/media-processing/__tests__/extract-keyframes.test.ts +181 -0
- package/src/config/bundled-skills/media-processing/__tests__/preprocess-audio.test.ts +141 -0
- package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +32 -87
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +8 -4
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +0 -10
- package/src/config/bundled-skills/messaging/SKILL.md +3 -3
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -2
- package/src/config/bundled-skills/outlook/SKILL.md +9 -2
- package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +2 -2
- package/src/config/bundled-skills/phone-calls/SKILL.md +2 -2
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +27 -18
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +3 -3
- package/src/config/bundled-skills/settings/TOOLS.json +3 -3
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +26 -22
- package/src/config/bundled-skills/slack/SKILL.md +1 -0
- package/src/config/bundled-skills/subagent/SKILL.md +21 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +8 -4
- package/src/config/bundled-skills/tasks/SKILL.md +5 -0
- package/src/config/bundled-skills/transcribe/SKILL.md +9 -14
- package/src/config/bundled-skills/transcribe/TOOLS.json +2 -7
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +256 -0
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +38 -188
- package/src/config/bundled-tool-registry.ts +8 -0
- package/src/config/env-registry.ts +38 -0
- package/src/config/env.ts +49 -4
- package/src/config/feature-flag-registry.json +85 -14
- package/src/config/loader.ts +82 -13
- package/src/config/sanitize-for-transfer.ts +47 -0
- package/src/config/schema.ts +81 -15
- package/src/config/schemas/__tests__/stt.test.ts +43 -0
- package/src/config/schemas/analysis.ts +51 -0
- package/src/config/schemas/backup.ts +72 -0
- package/src/config/schemas/calls.ts +1 -26
- package/src/config/schemas/elevenlabs.ts +0 -59
- package/src/config/schemas/filing.ts +47 -7
- package/src/config/schemas/heartbeat.ts +27 -5
- package/src/config/schemas/host-browser.ts +112 -0
- package/src/config/schemas/inference.ts +1 -1
- package/src/config/schemas/memory-lifecycle.ts +14 -2
- package/src/config/schemas/memory-retrieval.ts +103 -0
- package/src/config/schemas/security.ts +0 -6
- package/src/config/schemas/services.ts +52 -0
- package/src/config/schemas/stt.ts +59 -0
- package/src/config/schemas/tts.ts +230 -0
- package/src/config/schemas/updates.ts +14 -0
- package/src/config/skills.ts +4 -0
- package/src/config/types.ts +4 -1
- package/src/contacts/contact-store.ts +56 -11
- package/src/contacts/contacts-write.ts +38 -1
- package/src/context/post-turn-tool-result-truncation.ts +177 -0
- package/src/context/tool-result-truncation.ts +2 -1
- package/src/context/window-manager.ts +61 -10
- package/src/credential-execution/approval-bridge.ts +49 -15
- package/src/credential-execution/executable-discovery.ts +12 -2
- package/src/credential-execution/process-manager.ts +33 -2
- package/src/credential-health/credential-health-service.ts +366 -0
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +324 -0
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +497 -0
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +195 -0
- package/src/daemon/__tests__/lifecycle-startup-ordering.test.ts +127 -0
- package/src/daemon/app-source-watcher.ts +35 -0
- package/src/daemon/config-watcher.ts +99 -5
- package/src/daemon/context-overflow-approval.ts +5 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +23 -2
- package/src/daemon/conversation-agent-loop.ts +153 -42
- package/src/daemon/conversation-attachments.ts +40 -0
- package/src/daemon/conversation-error.ts +11 -0
- package/src/daemon/conversation-history.ts +40 -6
- package/src/daemon/conversation-launch.ts +220 -0
- package/src/daemon/conversation-lifecycle.ts +59 -9
- package/src/daemon/conversation-messaging.ts +37 -3
- package/src/daemon/conversation-notifiers.ts +5 -0
- package/src/daemon/conversation-process.ts +622 -13
- package/src/daemon/conversation-queue-manager.ts +24 -0
- package/src/daemon/conversation-runtime-assembly.ts +128 -36
- package/src/daemon/conversation-slash.ts +36 -0
- package/src/daemon/conversation-surfaces.ts +131 -40
- package/src/daemon/conversation-tool-setup.ts +99 -8
- package/src/daemon/conversation-usage.ts +7 -4
- package/src/daemon/conversation-workspace.ts +12 -0
- package/src/daemon/conversation.ts +292 -16
- package/src/daemon/date-context.ts +10 -10
- package/src/daemon/first-greeting.ts +3 -2
- package/src/daemon/handlers/config-slack-channel.ts +269 -94
- package/src/daemon/handlers/conversations.ts +13 -141
- package/src/daemon/handlers/shared.ts +80 -0
- package/src/daemon/handlers/skills.ts +483 -44
- package/src/daemon/host-bash-proxy.ts +48 -13
- package/src/daemon/host-browser-proxy.ts +192 -0
- package/src/daemon/host-cu-proxy.ts +36 -11
- package/src/daemon/host-file-proxy.ts +57 -9
- package/src/daemon/lifecycle.ts +179 -28
- package/src/daemon/message-protocol.ts +13 -0
- package/src/daemon/message-types/conversations.ts +89 -14
- package/src/daemon/message-types/home.ts +40 -0
- package/src/daemon/message-types/host-browser.ts +100 -0
- package/src/daemon/message-types/meet.ts +143 -0
- package/src/daemon/message-types/messages.ts +19 -5
- package/src/daemon/message-types/schedules.ts +34 -2
- package/src/daemon/message-types/skills.ts +26 -0
- package/src/daemon/message-types/subagents.ts +2 -0
- package/src/daemon/message-types/surfaces.ts +2 -0
- package/src/daemon/server.ts +439 -14
- package/src/daemon/shutdown-handlers.ts +32 -4
- package/src/daemon/shutdown-registry.ts +40 -0
- package/src/daemon/tool-side-effects.ts +15 -0
- package/src/daemon/transport-hints.ts +5 -24
- package/src/email/html-renderer.ts +76 -0
- package/src/heartbeat/heartbeat-service.ts +93 -7
- package/src/home/__tests__/assistant-feed-authoring.test.ts +156 -0
- package/src/home/__tests__/emit-feed-event.test.ts +169 -0
- package/src/home/__tests__/feed-scheduler.test.ts +194 -0
- package/src/home/__tests__/feed-types.test.ts +275 -0
- package/src/home/__tests__/feed-writer.test.ts +688 -0
- package/src/home/__tests__/phase5-exit-criteria.test.ts +212 -0
- package/src/home/__tests__/platform-gmail-digest.test.ts +222 -0
- package/src/home/__tests__/progress-formula.test.ts +213 -0
- package/src/home/__tests__/relationship-state-writer.test.ts +740 -0
- package/src/home/__tests__/rollup-producer.test.ts +398 -0
- package/src/home/assistant-feed-authoring.ts +124 -0
- package/src/home/emit-feed-event.ts +158 -0
- package/src/home/feed-scheduler.ts +247 -0
- package/src/home/feed-types.ts +181 -0
- package/src/home/feed-writer.ts +469 -0
- package/src/home/platform-gmail-digest.ts +163 -0
- package/src/home/progress-formula.ts +86 -0
- package/src/home/relationship-state-writer.ts +824 -0
- package/src/home/relationship-state.ts +143 -0
- package/src/home/rollup-producer.ts +384 -0
- package/src/hooks/runner.ts +7 -0
- package/src/inbound/platform-callback-registration.ts +30 -20
- package/src/inbound/public-ingress-urls.ts +12 -0
- package/src/instrument.ts +1 -1
- package/src/ipc/__tests__/cli-ipc.test.ts +200 -0
- package/src/ipc/cli-client.ts +151 -0
- package/src/ipc/cli-server.ts +234 -0
- package/src/ipc/gateway-client.ts +180 -0
- package/src/ipc/routes/index.ts +5 -0
- package/src/ipc/routes/wake-conversation.ts +19 -0
- package/src/mcp/client.ts +59 -24
- package/src/memory/__tests__/auto-analysis-enqueue.test.ts +356 -0
- package/src/memory/__tests__/auto-analysis-guard.test.ts +57 -0
- package/src/memory/__tests__/conversation-analyze-job.test.ts +232 -0
- package/src/memory/__tests__/find-analysis-conversation.test.ts +196 -0
- package/src/memory/app-store.ts +31 -1
- package/src/memory/attachments-store.ts +70 -0
- package/src/memory/auto-analysis-enqueue.ts +127 -0
- package/src/memory/auto-analysis-guard.ts +27 -0
- package/src/memory/cleanup-schedule-state.ts +37 -0
- package/src/memory/conversation-analyze-job.ts +73 -0
- package/src/memory/conversation-crud.ts +122 -0
- package/src/memory/conversation-disk-view.ts +7 -0
- package/src/memory/conversation-group-migration.ts +34 -2
- package/src/memory/conversation-queries.ts +6 -5
- package/src/memory/conversation-starters-cadence.ts +76 -0
- package/src/memory/conversation-title-service.ts +5 -2
- package/src/memory/db-init.ts +18 -0
- package/src/memory/db-maintenance.ts +108 -0
- package/src/memory/db.ts +1 -0
- package/src/memory/embedding-backend.test.ts +75 -0
- package/src/memory/embedding-backend.ts +131 -5
- package/src/memory/embedding-gemini.test.ts +54 -0
- package/src/memory/embedding-gemini.ts +20 -9
- package/src/memory/embedding-local.ts +176 -17
- package/src/memory/graph/consolidation.ts +10 -23
- package/src/memory/graph/conversation-graph-memory.ts +15 -0
- package/src/memory/graph/extraction-job.ts +15 -0
- package/src/memory/graph/extraction.test.ts +23 -0
- package/src/memory/graph/extraction.ts +8 -0
- package/src/memory/graph/retriever.ts +67 -40
- package/src/memory/graph/scoring.test.ts +186 -0
- package/src/memory/graph/scoring.ts +31 -1
- package/src/memory/graph/store.test.ts +7 -3
- package/src/memory/graph/store.ts +47 -12
- package/src/memory/graph/tools.ts +1 -1
- package/src/memory/group-crud.ts +6 -1
- package/src/memory/indexer.ts +95 -16
- package/src/memory/job-handlers/cleanup.ts +11 -8
- package/src/memory/job-handlers/conversation-starters.ts +16 -10
- package/src/memory/jobs-store.ts +64 -4
- package/src/memory/jobs-worker.ts +22 -9
- package/src/memory/llm-usage-store.ts +137 -60
- package/src/memory/migrations/213-oauth-providers-scope-separator.ts +13 -0
- package/src/memory/migrations/214-oauth-providers-refresh-url.ts +11 -0
- package/src/memory/migrations/215-oauth-providers-revoke.ts +14 -0
- package/src/memory/migrations/216-oauth-providers-token-auth-method.ts +30 -0
- package/src/memory/migrations/217-conversation-host-access.ts +40 -0
- package/src/memory/migrations/218-oauth-providers-logo-url.ts +11 -0
- package/src/memory/migrations/219-oauth-providers-token-exchange-body-format.ts +15 -0
- package/src/memory/migrations/220-normalize-user-file-by-principal.ts +190 -0
- package/src/memory/migrations/221-conversations-archived-at.ts +16 -0
- package/src/memory/migrations/index.ts +12 -0
- package/src/memory/migrations/registry.ts +16 -0
- package/src/memory/qdrant-manager.ts +43 -16
- package/src/memory/schema/conversations.ts +3 -0
- package/src/memory/schema/oauth.ts +21 -13
- package/src/memory/usage-buckets.ts +396 -0
- package/src/messaging/providers/gmail/client.ts +57 -6
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +282 -0
- package/src/messaging/providers/slack/adapter.ts +143 -38
- package/src/messaging/providers/slack/client.ts +16 -0
- package/src/messaging/providers/slack/types.ts +4 -0
- package/src/notifications/decision-engine.ts +3 -3
- package/src/notifications/signal.ts +5 -0
- package/src/oauth/AGENTS.md +76 -0
- package/src/oauth/__tests__/identity-verifier.test.ts +25 -19
- package/src/oauth/__tests__/seed-providers-managed.test.ts +32 -0
- package/src/oauth/byo-connection.test.ts +26 -9
- package/src/oauth/byo-connection.ts +10 -8
- package/src/oauth/connect-orchestrator.ts +25 -21
- package/src/oauth/connect-types.ts +3 -3
- package/src/oauth/connection-resolver.test.ts +17 -4
- package/src/oauth/connection-resolver.ts +22 -18
- package/src/oauth/connection.ts +3 -1
- package/src/oauth/manual-token-connection.ts +13 -13
- package/src/oauth/oauth-store.ts +223 -100
- package/src/oauth/platform-connection.test.ts +101 -3
- package/src/oauth/platform-connection.ts +56 -35
- package/src/oauth/provider-serializer.ts +31 -5
- package/src/oauth/revoke.ts +76 -0
- package/src/oauth/seed-providers.ts +133 -87
- package/src/oauth/token-persistence.ts +1 -1
- package/src/permissions/checker.ts +16 -6
- package/src/permissions/defaults.ts +49 -1
- package/src/permissions/permission-mode.ts +4 -11
- package/src/permissions/prompter.ts +13 -1
- package/src/permissions/trust-store.ts +3 -3
- package/src/permissions/v2-consent-policy.ts +87 -0
- package/src/permissions/workspace-policy.ts +3 -0
- package/src/platform/client.test.ts +10 -0
- package/src/platform/sync-identity.ts +129 -0
- package/src/prompts/persona-resolver.ts +126 -2
- package/src/prompts/system-prompt.ts +76 -38
- package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +3 -65
- package/src/prompts/templates/BOOTSTRAP.md +59 -105
- package/src/prompts/templates/SOUL.md +3 -1
- package/src/prompts/templates/UPDATES.md +12 -0
- package/src/prompts/templates/channels/slack.md +20 -0
- package/src/prompts/update-bulletin-format.ts +26 -9
- package/src/prompts/update-bulletin.ts +34 -23
- package/src/prompts/user-reference.ts +20 -17
- package/src/providers/__tests__/provider-secret-catalog.test.ts +42 -0
- package/src/providers/anthropic/client.ts +157 -60
- package/src/providers/fireworks/client.ts +2 -2
- package/src/providers/gemini/client.ts +9 -1
- package/src/providers/model-catalog.ts +6 -0
- package/src/providers/model-intents.ts +4 -4
- package/src/providers/ollama/client.ts +2 -2
- package/src/providers/openai/chat-completions-provider.ts +474 -0
- package/src/providers/openai/client.ts +25 -440
- package/src/providers/openai/responses-provider.ts +502 -0
- package/src/providers/openrouter/client.ts +101 -4
- package/src/providers/provider-secret-catalog.ts +139 -0
- package/src/providers/registry.ts +2 -2
- package/src/providers/retry.ts +14 -3
- package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +251 -0
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +828 -0
- package/src/providers/speech-to-text/deepgram-realtime.test.ts +980 -0
- package/src/providers/speech-to-text/deepgram-realtime.ts +767 -0
- package/src/providers/speech-to-text/deepgram.test.ts +332 -0
- package/src/providers/speech-to-text/deepgram.ts +115 -0
- package/src/providers/speech-to-text/google-gemini-live-stream.test.ts +743 -0
- package/src/providers/speech-to-text/google-gemini-live-stream.ts +625 -0
- package/src/providers/speech-to-text/google-gemini.test.ts +226 -0
- package/src/providers/speech-to-text/google-gemini.ts +101 -0
- package/src/providers/speech-to-text/openai-whisper-stream.test.ts +564 -0
- package/src/providers/speech-to-text/openai-whisper-stream.ts +381 -0
- package/src/providers/speech-to-text/openai-whisper.test.ts +1 -37
- package/src/providers/speech-to-text/openai-whisper.ts +63 -33
- package/src/providers/speech-to-text/provider-catalog.ts +306 -0
- package/src/providers/speech-to-text/resolve.ts +386 -6
- package/src/providers/types.ts +10 -1
- package/src/runtime/AGENTS.md +65 -0
- package/src/runtime/__tests__/agent-wake.test.ts +831 -0
- package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +715 -0
- package/src/runtime/__tests__/capability-tokens.test.ts +258 -0
- package/src/runtime/__tests__/chrome-extension-registry.test.ts +518 -0
- package/src/runtime/__tests__/runtime-mode.test.ts +62 -0
- package/src/runtime/__tests__/slack-block-formatting.test.ts +481 -0
- package/src/runtime/agent-wake.ts +512 -0
- package/src/runtime/assistant-event-hub.ts +2 -2
- package/src/runtime/auth/__tests__/guard-tests.test.ts +1 -0
- package/src/runtime/auth/__tests__/middleware.test.ts +116 -1
- package/src/runtime/auth/__tests__/route-policy.test.ts +48 -0
- package/src/runtime/auth/middleware.ts +98 -0
- package/src/runtime/auth/route-policy.ts +33 -9
- package/src/runtime/auth/token-service.ts +56 -1
- package/src/runtime/btw-sidechain.ts +2 -0
- package/src/runtime/capability-tokens.ts +414 -0
- package/src/runtime/channel-approvals.ts +18 -5
- package/src/runtime/channel-invite-transport.ts +1 -1
- package/src/runtime/channel-invite-transports/email.ts +14 -6
- package/src/runtime/channel-readiness-service.ts +12 -22
- package/src/runtime/chrome-extension-registry.ts +368 -0
- package/src/runtime/confirmation-request-guardian-bridge.ts +6 -0
- package/src/runtime/guardian-decision-types.ts +7 -0
- package/src/runtime/http-server.ts +815 -75
- package/src/runtime/http-types.ts +6 -2
- package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +172 -0
- package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +276 -0
- package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +198 -0
- package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +360 -0
- package/src/runtime/migrations/migration-transport.ts +7 -0
- package/src/runtime/migrations/migration-wizard.ts +23 -2
- package/src/runtime/migrations/rebind-secrets-screen.ts +76 -15
- package/src/runtime/migrations/vbundle-builder.ts +145 -38
- package/src/runtime/migrations/vbundle-import-analyzer.ts +96 -1
- package/src/runtime/migrations/vbundle-importer.ts +89 -5
- package/src/runtime/pending-interactions.ts +18 -13
- package/src/runtime/routes/__tests__/backup-routes.test.ts +967 -0
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +507 -0
- package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +208 -0
- package/src/runtime/routes/__tests__/stt-routes.test.ts +406 -0
- package/src/runtime/routes/__tests__/tts-routes.test.ts +474 -0
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +148 -17
- package/src/runtime/routes/app-management-routes.ts +12 -18
- package/src/runtime/routes/approval-routes.ts +90 -16
- package/src/runtime/routes/attachment-routes.test.ts +9 -3
- package/src/runtime/routes/attachment-routes.ts +216 -17
- package/src/runtime/routes/backup-routes.ts +519 -0
- package/src/runtime/routes/browser-extension-pair-routes.ts +556 -0
- package/src/runtime/routes/btw-routes.ts +8 -6
- package/src/runtime/routes/contact-routes.test.ts +298 -0
- package/src/runtime/routes/contact-routes.ts +132 -5
- package/src/runtime/routes/conversation-analysis-routes.ts +22 -141
- package/src/runtime/routes/conversation-management-routes.ts +223 -0
- package/src/runtime/routes/conversation-routes.ts +598 -103
- package/src/runtime/routes/conversation-starter-routes.ts +78 -16
- package/src/runtime/routes/filing-routes.ts +93 -0
- package/src/runtime/routes/guardian-action-routes.ts +24 -13
- package/src/runtime/routes/home-feed-routes.ts +334 -0
- package/src/runtime/routes/home-state-routes.ts +138 -0
- package/src/runtime/routes/host-browser-routes.ts +268 -0
- package/src/runtime/routes/host-file-routes.ts +9 -1
- package/src/runtime/routes/identity-intro-cache.ts +7 -3
- package/src/runtime/routes/identity-routes.ts +262 -33
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +46 -39
- package/src/runtime/routes/inbound-stages/transcribe-audio.ts +15 -15
- package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +137 -0
- package/src/runtime/routes/integrations/slack/__tests__/share.test.ts +179 -0
- package/src/runtime/routes/integrations/slack/channel.ts +11 -3
- package/src/runtime/routes/integrations/slack/share.ts +45 -7
- package/src/runtime/routes/llm-context-normalization.ts +303 -0
- package/src/runtime/routes/log-export-routes.ts +42 -22
- package/src/runtime/routes/memory-item-routes.test.ts +3 -2
- package/src/runtime/routes/memory-item-routes.ts +1 -7
- package/src/runtime/routes/migration-routes.ts +122 -2
- package/src/runtime/routes/oauth-apps.ts +15 -17
- package/src/runtime/routes/oauth-providers.ts +4 -0
- package/src/runtime/routes/schedule-routes.ts +24 -11
- package/src/runtime/routes/settings-routes.ts +31 -102
- package/src/runtime/routes/skills-routes.ts +128 -9
- package/src/runtime/routes/stt-routes.ts +233 -0
- package/src/runtime/routes/subagents-routes.ts +14 -10
- package/src/runtime/routes/surface-action-routes.ts +41 -2
- package/src/runtime/routes/tts-routes.ts +108 -24
- package/src/runtime/routes/usage-routes.ts +38 -9
- package/src/runtime/routes/user-route-dispatcher.ts +50 -5
- package/src/runtime/routes/user-routes.ts +13 -1
- package/src/runtime/routes/work-items-routes.ts +8 -1
- package/src/runtime/routes/workspace-routes.test.ts +22 -0
- package/src/runtime/routes/workspace-routes.ts +8 -1
- package/src/runtime/routes/workspace-utils.ts +2 -0
- package/src/runtime/runtime-mode.ts +33 -0
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +444 -0
- package/src/runtime/services/__tests__/analyze-deps-singleton.test.ts +67 -0
- package/src/runtime/services/__tests__/auto-analysis-prompt.test.ts +53 -0
- package/src/runtime/services/__tests__/manual-analysis-prompt.test.ts +41 -0
- package/src/runtime/services/analyze-conversation.ts +344 -0
- package/src/runtime/services/analyze-deps-singleton.ts +32 -0
- package/src/runtime/services/auto-analysis-prompt.ts +55 -0
- package/src/runtime/skill-route-registry.ts +49 -0
- package/src/runtime/slack-block-formatting.ts +437 -10
- package/src/schedule/scheduler.ts +57 -5
- package/src/security/ces-credential-client.ts +20 -0
- package/src/security/ces-rpc-credential-backend.ts +17 -0
- package/src/security/credential-backend.ts +5 -0
- package/src/security/oauth2.ts +68 -29
- package/src/security/secure-keys.ts +143 -27
- package/src/security/token-manager.ts +31 -10
- package/src/sequence/engine.ts +23 -0
- package/src/sequence/types.ts +1 -1
- package/src/skills/catalog-files.ts +554 -0
- package/src/skills/category-inference.ts +122 -0
- package/src/skills/clawhub-files.ts +213 -0
- package/src/skills/clawhub.ts +84 -23
- package/src/skills/skill-file-provider.ts +40 -0
- package/src/skills/skillssh-files.ts +395 -0
- package/src/skills/skillssh-registry.ts +4 -4
- package/src/stt/__tests__/daemon-batch-transcriber.test.ts +392 -0
- package/src/stt/__tests__/types.test.ts +89 -0
- package/src/stt/daemon-batch-transcriber.ts +195 -0
- package/src/stt/stt-stream-session.ts +499 -0
- package/src/stt/types.ts +330 -0
- package/src/stt/wav-encoder.test.ts +373 -0
- package/src/stt/wav-encoder.ts +175 -0
- package/src/subagent/manager.ts +169 -40
- package/src/subagent/types.ts +19 -0
- package/src/tools/apps/executors.ts +11 -2
- package/src/tools/browser/__tests__/auth-detector.test.ts +202 -108
- package/src/tools/browser/__tests__/browser-mode.test.ts +119 -0
- package/src/tools/browser/__tests__/browser-status.test.ts +123 -0
- package/src/tools/browser/auth-detector.ts +43 -12
- package/src/tools/browser/browser-execution.ts +1787 -342
- package/src/tools/browser/browser-manager.ts +81 -12
- package/src/tools/browser/browser-mode-constants.ts +12 -0
- package/src/tools/browser/browser-mode.ts +92 -0
- package/src/tools/browser/browser-status-constants.ts +33 -0
- package/src/tools/browser/cdp-client/__tests__/accessibility-snapshot.test.ts +318 -0
- package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +1175 -0
- package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +1263 -0
- package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +359 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +1993 -0
- package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-nested-frames.json +64 -0
- package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-simple.json +69 -0
- package/src/tools/browser/cdp-client/__tests__/local-cdp-client.test.ts +310 -0
- package/src/tools/browser/cdp-client/__tests__/types.test.ts +96 -0
- package/src/tools/browser/cdp-client/accessibility-snapshot.ts +387 -0
- package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +695 -0
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +1007 -0
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +580 -0
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +744 -0
- package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +579 -0
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +868 -0
- package/src/tools/browser/cdp-client/errors.ts +49 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +148 -0
- package/src/tools/browser/cdp-client/factory.ts +914 -0
- package/src/tools/browser/cdp-client/index.ts +28 -0
- package/src/tools/browser/cdp-client/local-cdp-client.ts +187 -0
- package/src/tools/browser/cdp-client/types.ts +120 -0
- package/src/tools/credentials/vault.ts +35 -6
- package/src/tools/filesystem/edit.ts +1 -1
- package/src/tools/filesystem/list.ts +1 -1
- package/src/tools/filesystem/read.ts +1 -1
- package/src/tools/filesystem/write.ts +2 -1
- package/src/tools/host-filesystem/edit.ts +1 -1
- package/src/tools/host-filesystem/read.ts +12 -15
- package/src/tools/host-filesystem/write.ts +1 -1
- package/src/tools/host-terminal/host-shell.ts +21 -16
- package/src/tools/network/web-fetch.ts +5 -2
- package/src/tools/network/web-search.ts +5 -2
- package/src/tools/permission-checker.ts +77 -82
- package/src/tools/registry.ts +0 -2
- package/src/tools/secret-detection-handler.ts +34 -0
- package/src/tools/shared/filesystem/image-read.ts +61 -40
- package/src/tools/shared/shell-output.ts +3 -1
- package/src/tools/side-effects.ts +2 -0
- package/src/tools/skills/sandbox-runner.ts +3 -2
- package/src/tools/subagent/spawn.ts +47 -3
- package/src/tools/subagent/status.ts +2 -0
- package/src/tools/system/register.ts +2 -16
- package/src/tools/terminal/safe-env.ts +15 -0
- package/src/tools/terminal/shell.ts +36 -20
- package/src/tools/tool-approval-handler.ts +48 -2
- package/src/tools/tool-manifest.ts +21 -0
- package/src/tools/types.ts +19 -0
- package/src/tools/ui-surface/definitions.ts +6 -1
- package/src/tts/__tests__/provider-adapters.test.ts +834 -0
- package/src/tts/__tests__/provider-catalog-consistency.test.ts +196 -0
- package/src/tts/__tests__/provider-catalog.test.ts +183 -0
- package/src/tts/__tests__/provider-registry.test.ts +90 -0
- package/src/tts/provider-catalog.ts +201 -0
- package/src/tts/provider-registry.ts +73 -0
- package/src/tts/providers/deepgram-provider.ts +219 -0
- package/src/tts/providers/elevenlabs-provider.ts +211 -0
- package/src/tts/providers/fish-audio-provider.ts +183 -0
- package/src/tts/providers/index.ts +42 -0
- package/src/tts/providers/register-builtins.ts +130 -0
- package/src/tts/synthesize-text.ts +110 -0
- package/src/tts/tts-config-resolver.ts +78 -0
- package/src/tts/types.ts +153 -0
- package/src/types/onboarding-context.ts +7 -0
- package/src/util/abort-reasons.ts +58 -0
- package/src/util/device-id.ts +32 -16
- package/src/util/errors.ts +9 -1
- package/src/util/platform.ts +63 -24
- package/src/util/pricing.ts +66 -3
- package/src/util/spawn.ts +1 -1
- package/src/util/truncate.ts +4 -2
- package/src/util/unicode.ts +201 -0
- package/src/version.ts +19 -24
- package/src/watcher/engine.ts +23 -0
- package/src/watcher/watcher-store.ts +31 -0
- package/src/workspace/migrations/003-seed-device-id.ts +9 -3
- package/src/workspace/migrations/017-seed-persona-dirs.ts +68 -4
- package/src/workspace/migrations/029-seed-pkb.ts +1 -1
- package/src/workspace/migrations/031-drop-user-md.ts +317 -0
- package/src/workspace/migrations/031-llm-log-retention-zero-to-null.ts +73 -0
- package/src/workspace/migrations/032-tts-provider-unification.ts +227 -0
- package/src/workspace/migrations/033-stt-service-explicit-config.ts +122 -0
- package/src/workspace/migrations/034-remove-calls-voice-transcription-provider.ts +215 -0
- package/src/workspace/migrations/035-seed-slack-channel-persona.ts +50 -0
- package/src/workspace/migrations/036-update-pkb-index-bar.ts +37 -0
- package/src/workspace/migrations/037-create-meets-dir.ts +61 -0
- package/src/workspace/migrations/registry.ts +16 -0
- package/src/workspace/top-level-renderer.ts +31 -1
- package/src/workspace/turn-commit.ts +31 -0
- package/src/__tests__/chrome-cdp.test.ts +0 -419
- package/src/__tests__/email-cli.test.ts +0 -297
- package/src/__tests__/email-service-config-fallback.test.ts +0 -102
- package/src/__tests__/permission-mode-sse.test.ts +0 -418
- package/src/__tests__/permission-mode-store.test.ts +0 -277
- package/src/browser-extension-relay/protocol.ts +0 -63
- package/src/browser-extension-relay/server.ts +0 -203
- package/src/cli/commands/browser-relay.ts +0 -536
- package/src/config/schemas/sandbox.ts +0 -14
- package/src/email/guardrails.ts +0 -221
- package/src/email/provider.ts +0 -117
- package/src/email/providers/agentmail.ts +0 -361
- package/src/email/providers/index.ts +0 -65
- package/src/email/service.ts +0 -384
- package/src/email/types.ts +0 -126
- package/src/permissions/permission-mode-store.ts +0 -180
- package/src/prompts/templates/USER.md +0 -13
- package/src/providers/speech-to-text/types.ts +0 -17
- package/src/tools/browser/chrome-cdp.ts +0 -239
- package/src/tools/system/set-permission-mode.ts +0 -103
|
@@ -0,0 +1,740 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
mkdtempSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
rmSync,
|
|
7
|
+
writeFileSync,
|
|
8
|
+
} from "node:fs";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
12
|
+
|
|
13
|
+
// Stub the OAuth connection store so the writer runs without a
|
|
14
|
+
// database. Tests push provider rows into `fakeConnections` before
|
|
15
|
+
// invoking the writer.
|
|
16
|
+
type FakeConnection = {
|
|
17
|
+
provider: string;
|
|
18
|
+
status: "active" | "revoked" | "failed";
|
|
19
|
+
};
|
|
20
|
+
const fakeConnections: FakeConnection[] = [];
|
|
21
|
+
|
|
22
|
+
mock.module("../../oauth/oauth-store.js", () => ({
|
|
23
|
+
listConnections: () => [...fakeConnections],
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
// Stub the DB-authoritative conversation count helper so the writer
|
|
27
|
+
// runs without a real database. Tests set `fakeConversationCount` or
|
|
28
|
+
// flip `fakeConversationCountThrows` as needed. Follow the same
|
|
29
|
+
// pattern as `listConnections` above.
|
|
30
|
+
let fakeConversationCount = 0;
|
|
31
|
+
let fakeConversationCountThrows = false;
|
|
32
|
+
|
|
33
|
+
mock.module("../../memory/conversation-queries.js", () => ({
|
|
34
|
+
countConversations: (): number => {
|
|
35
|
+
if (fakeConversationCountThrows) {
|
|
36
|
+
throw new Error("DB not initialized");
|
|
37
|
+
}
|
|
38
|
+
return fakeConversationCount;
|
|
39
|
+
},
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
// Dynamic import so the module resolves after the mock above is in
|
|
43
|
+
// place. Bun's mock.module needs to run before the real import is
|
|
44
|
+
// evaluated for the mock to take effect.
|
|
45
|
+
const {
|
|
46
|
+
backfillRelationshipStateIfMissing,
|
|
47
|
+
computeRelationshipState,
|
|
48
|
+
getOnboardingSidecarPath,
|
|
49
|
+
getRelationshipStatePath,
|
|
50
|
+
ONBOARDING_SIDECAR_FILENAME,
|
|
51
|
+
RELATIONSHIP_STATE_FILENAME,
|
|
52
|
+
writeOnboardingSidecar,
|
|
53
|
+
writeRelationshipState,
|
|
54
|
+
} = await import("../relationship-state-writer.js");
|
|
55
|
+
|
|
56
|
+
type RelationshipStateLike = {
|
|
57
|
+
version: number;
|
|
58
|
+
assistantId: string;
|
|
59
|
+
tier: number;
|
|
60
|
+
progressPercent: number;
|
|
61
|
+
facts: Array<{
|
|
62
|
+
id: string;
|
|
63
|
+
category: string;
|
|
64
|
+
text: string;
|
|
65
|
+
confidence: string;
|
|
66
|
+
source: string;
|
|
67
|
+
}>;
|
|
68
|
+
capabilities: Array<{
|
|
69
|
+
id: string;
|
|
70
|
+
name: string;
|
|
71
|
+
description: string;
|
|
72
|
+
tier: string;
|
|
73
|
+
gate: string;
|
|
74
|
+
}>;
|
|
75
|
+
conversationCount: number;
|
|
76
|
+
hatchedDate: string;
|
|
77
|
+
assistantName: string;
|
|
78
|
+
userName?: string;
|
|
79
|
+
updatedAt: string;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// Per CI gotchas: each test gets its own temp workspace dir to avoid
|
|
83
|
+
// `.git/index.lock` style races on shared tmp paths.
|
|
84
|
+
let workspaceDir: string;
|
|
85
|
+
let origWorkspaceDir: string | undefined;
|
|
86
|
+
|
|
87
|
+
function writeFile(relPath: string, content: string): void {
|
|
88
|
+
const full = join(workspaceDir, relPath);
|
|
89
|
+
mkdirSync(join(full, ".."), { recursive: true });
|
|
90
|
+
writeFileSync(full, content, "utf-8");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function seedConversations(count: number): void {
|
|
94
|
+
// Drives the mocked DB-authoritative `countConversations` helper.
|
|
95
|
+
// The writer reads conversation counts from the DB rather than the
|
|
96
|
+
// filesystem, so tests push counts in here rather than seeding a
|
|
97
|
+
// conversations directory.
|
|
98
|
+
fakeConversationCount = count;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
beforeEach(() => {
|
|
102
|
+
workspaceDir = mkdtempSync(join(tmpdir(), "vellum-rsw-"));
|
|
103
|
+
origWorkspaceDir = process.env.VELLUM_WORKSPACE_DIR;
|
|
104
|
+
process.env.VELLUM_WORKSPACE_DIR = workspaceDir;
|
|
105
|
+
fakeConnections.length = 0;
|
|
106
|
+
fakeConversationCount = 0;
|
|
107
|
+
fakeConversationCountThrows = false;
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
afterEach(() => {
|
|
111
|
+
if (origWorkspaceDir === undefined) {
|
|
112
|
+
delete process.env.VELLUM_WORKSPACE_DIR;
|
|
113
|
+
} else {
|
|
114
|
+
process.env.VELLUM_WORKSPACE_DIR = origWorkspaceDir;
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
rmSync(workspaceDir, { recursive: true, force: true });
|
|
118
|
+
} catch {
|
|
119
|
+
// best-effort
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe("relationship-state-writer", () => {
|
|
124
|
+
describe("getRelationshipStatePath", () => {
|
|
125
|
+
test("returns <workspace>/data/relationship-state.json", () => {
|
|
126
|
+
expect(getRelationshipStatePath()).toBe(
|
|
127
|
+
join(workspaceDir, "data", RELATIONSHIP_STATE_FILENAME),
|
|
128
|
+
);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe("computeRelationshipState", () => {
|
|
133
|
+
test("fresh empty workspace -> tier 1, 0%, empty facts, 0 conversations", async () => {
|
|
134
|
+
const state = (await computeRelationshipState()) as RelationshipStateLike;
|
|
135
|
+
expect(state.version).toBe(1);
|
|
136
|
+
expect(state.assistantId).toBe("default");
|
|
137
|
+
expect(state.tier).toBe(1);
|
|
138
|
+
expect(state.progressPercent).toBe(0);
|
|
139
|
+
expect(state.conversationCount).toBe(0);
|
|
140
|
+
expect(state.facts).toEqual([]);
|
|
141
|
+
expect(state.capabilities).toHaveLength(6);
|
|
142
|
+
// No integrations connected -> gated caps are next-up.
|
|
143
|
+
const byId = Object.fromEntries(state.capabilities.map((c) => [c.id, c]));
|
|
144
|
+
expect(byId.email.tier).toBe("next-up");
|
|
145
|
+
expect(byId.calendar.tier).toBe("next-up");
|
|
146
|
+
expect(byId.slack.tier).toBe("next-up");
|
|
147
|
+
expect(byId["voice-writing"].tier).toBe("earned");
|
|
148
|
+
expect(byId.proactive.tier).toBe("earned");
|
|
149
|
+
expect(byId.autonomous.tier).toBe("earned");
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("extracts world + priorities facts from USER.md", async () => {
|
|
153
|
+
writeFile(
|
|
154
|
+
"USER.md",
|
|
155
|
+
[
|
|
156
|
+
"# USER.md",
|
|
157
|
+
"",
|
|
158
|
+
"- Preferred name: Alex",
|
|
159
|
+
"- Pronouns: they/them",
|
|
160
|
+
"- Work role: Staff engineer",
|
|
161
|
+
"- Goals: Ship Phase 3 by Friday",
|
|
162
|
+
"- Daily tools: VSCode, git, bun",
|
|
163
|
+
"",
|
|
164
|
+
].join("\n"),
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
const state = (await computeRelationshipState()) as RelationshipStateLike;
|
|
168
|
+
// At least one priorities fact (Goals / Work role / Daily tools)
|
|
169
|
+
// and at least one world fact (Preferred name / Pronouns).
|
|
170
|
+
expect(state.facts.length).toBeGreaterThanOrEqual(5);
|
|
171
|
+
const categories = new Set(state.facts.map((f) => f.category));
|
|
172
|
+
expect(categories.has("priorities")).toBe(true);
|
|
173
|
+
expect(categories.has("world")).toBe(true);
|
|
174
|
+
// All extracted facts are "inferred" (not "onboarding").
|
|
175
|
+
for (const f of state.facts) {
|
|
176
|
+
expect(f.source).toBe("inferred");
|
|
177
|
+
}
|
|
178
|
+
// userName parsed from "Preferred name: Alex".
|
|
179
|
+
expect(state.userName).toBe("Alex");
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("extracts voice facts from SOUL.md", async () => {
|
|
183
|
+
writeFile(
|
|
184
|
+
"SOUL.md",
|
|
185
|
+
[
|
|
186
|
+
"# SOUL.md",
|
|
187
|
+
"",
|
|
188
|
+
"- Tone: dry, precise, never performative",
|
|
189
|
+
"- Defaults: lowercase, minimal punctuation",
|
|
190
|
+
"",
|
|
191
|
+
].join("\n"),
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
const state = (await computeRelationshipState()) as RelationshipStateLike;
|
|
195
|
+
const voiceFacts = state.facts.filter((f) => f.category === "voice");
|
|
196
|
+
expect(voiceFacts.length).toBeGreaterThanOrEqual(2);
|
|
197
|
+
for (const f of voiceFacts) {
|
|
198
|
+
expect(f.source).toBe("inferred");
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test("userName prefers 'Preferred name/reference' over 'Preferred pronouns'", async () => {
|
|
203
|
+
// A user who reorders the default guardian template bullets
|
|
204
|
+
// must never see their pronouns surface as their display name
|
|
205
|
+
// on the Home page. `parseUserName` only accepts labels whose
|
|
206
|
+
// lowercased form starts with `preferred name` (plus the
|
|
207
|
+
// stricter `name` / `user` / `user name` forms).
|
|
208
|
+
writeFile(
|
|
209
|
+
"USER.md",
|
|
210
|
+
[
|
|
211
|
+
"- Preferred pronouns: she/her",
|
|
212
|
+
"- Preferred name/reference: Casey",
|
|
213
|
+
].join("\n"),
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
const state = (await computeRelationshipState()) as RelationshipStateLike;
|
|
217
|
+
expect(state.userName).toBe("Casey");
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test("falls back to legacy workspace USER.md when persona resolver yields nothing", async () => {
|
|
221
|
+
// In the test environment there is no guardian contact in the DB, so
|
|
222
|
+
// `resolveGuardianPersonaPath()` either returns null or throws — the
|
|
223
|
+
// writer must degrade to legacy workspace-root `USER.md`.
|
|
224
|
+
writeFile(
|
|
225
|
+
"USER.md",
|
|
226
|
+
["- Preferred name: Jamie", "- Work role: PM"].join("\n"),
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
const state = (await computeRelationshipState()) as RelationshipStateLike;
|
|
230
|
+
expect(state.userName).toBe("Jamie");
|
|
231
|
+
expect(state.facts.length).toBeGreaterThan(0);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test("reads users/default.md when persona resolver fails and legacy USER.md is absent", async () => {
|
|
235
|
+
// Simulates a migrated workspace: the contact store is
|
|
236
|
+
// transiently unreachable (resolver throws SQLiteError in the
|
|
237
|
+
// test env), legacy USER.md was removed by migration 031, but
|
|
238
|
+
// users/default.md still carries real user content. The writer
|
|
239
|
+
// must surface that content rather than dropping to an empty
|
|
240
|
+
// snapshot.
|
|
241
|
+
writeFile(
|
|
242
|
+
"users/default.md",
|
|
243
|
+
["- Preferred name: Riley", "- Work role: Designer"].join("\n"),
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
const state = (await computeRelationshipState()) as RelationshipStateLike;
|
|
247
|
+
expect(state.userName).toBe("Riley");
|
|
248
|
+
expect(state.facts.length).toBeGreaterThan(0);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test("uses DB-authoritative countConversations for conversationCount", async () => {
|
|
252
|
+
seedConversations(7);
|
|
253
|
+
const state = (await computeRelationshipState()) as RelationshipStateLike;
|
|
254
|
+
expect(state.conversationCount).toBe(7);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test("ignores stray filesystem files (e.g. .DS_Store) in conversations dir", async () => {
|
|
258
|
+
// The DB-authoritative `countConversations` is immune to stray
|
|
259
|
+
// filesystem entries (.DS_Store, migration artifacts, duplicate
|
|
260
|
+
// legacy/canonical directory forms) — only DB rows count.
|
|
261
|
+
const dir = join(workspaceDir, "conversations");
|
|
262
|
+
mkdirSync(dir, { recursive: true });
|
|
263
|
+
writeFileSync(join(dir, ".DS_Store"), "junk", "utf-8");
|
|
264
|
+
mkdirSync(join(dir, "legacy-duplicate"), { recursive: true });
|
|
265
|
+
mkdirSync(join(dir, "canonical-duplicate"), { recursive: true });
|
|
266
|
+
// DB says 0 conversations, despite 3 filesystem entries.
|
|
267
|
+
fakeConversationCount = 0;
|
|
268
|
+
|
|
269
|
+
const state = (await computeRelationshipState()) as RelationshipStateLike;
|
|
270
|
+
expect(state.conversationCount).toBe(0);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
test("falls back to 0 when the DB helper throws", async () => {
|
|
274
|
+
// Regression guard for Gap A: if the DB isn't ready or the
|
|
275
|
+
// helper throws, the writer must still produce a valid
|
|
276
|
+
// snapshot with conversationCount = 0 rather than throwing.
|
|
277
|
+
fakeConversationCountThrows = true;
|
|
278
|
+
|
|
279
|
+
const state = (await computeRelationshipState()) as RelationshipStateLike;
|
|
280
|
+
expect(state.conversationCount).toBe(0);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test("slack connection flips slack capability to unlocked", async () => {
|
|
284
|
+
fakeConnections.push({ provider: "slack", status: "active" });
|
|
285
|
+
const state = (await computeRelationshipState()) as RelationshipStateLike;
|
|
286
|
+
const slack = state.capabilities.find((c) => c.id === "slack");
|
|
287
|
+
expect(slack?.tier).toBe("unlocked");
|
|
288
|
+
const email = state.capabilities.find((c) => c.id === "email");
|
|
289
|
+
expect(email?.tier).toBe("next-up");
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
test("google connection unlocks both email and calendar", async () => {
|
|
293
|
+
fakeConnections.push({ provider: "google", status: "active" });
|
|
294
|
+
const state = (await computeRelationshipState()) as RelationshipStateLike;
|
|
295
|
+
const email = state.capabilities.find((c) => c.id === "email");
|
|
296
|
+
const calendar = state.capabilities.find((c) => c.id === "calendar");
|
|
297
|
+
expect(email?.tier).toBe("unlocked");
|
|
298
|
+
expect(calendar?.tier).toBe("unlocked");
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
test("outlook connection does not unlock any capability", async () => {
|
|
302
|
+
// `outlook` exists as scaffolding in seed-providers.ts but there is no
|
|
303
|
+
// real Microsoft integration the assistant can use. The Home page must
|
|
304
|
+
// not advertise email or calendar as unlocked just because an outlook
|
|
305
|
+
// OAuth row exists.
|
|
306
|
+
fakeConnections.push({ provider: "outlook", status: "active" });
|
|
307
|
+
const state = (await computeRelationshipState()) as RelationshipStateLike;
|
|
308
|
+
const email = state.capabilities.find((c) => c.id === "email");
|
|
309
|
+
const calendar = state.capabilities.find((c) => c.id === "calendar");
|
|
310
|
+
expect(email?.tier).toBe("next-up");
|
|
311
|
+
expect(calendar?.tier).toBe("next-up");
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
test("revoked connections do not count as unlocked", async () => {
|
|
315
|
+
fakeConnections.push({ provider: "slack", status: "revoked" });
|
|
316
|
+
const state = (await computeRelationshipState()) as RelationshipStateLike;
|
|
317
|
+
const slack = state.capabilities.find((c) => c.id === "slack");
|
|
318
|
+
expect(slack?.tier).toBe("next-up");
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
test("voice-writing unlocks once conversationCount >= 10", async () => {
|
|
322
|
+
seedConversations(10);
|
|
323
|
+
const state = (await computeRelationshipState()) as RelationshipStateLike;
|
|
324
|
+
const voice = state.capabilities.find((c) => c.id === "voice-writing");
|
|
325
|
+
expect(voice?.tier).toBe("unlocked");
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test("updatedAt is a valid ISO-8601 string", async () => {
|
|
329
|
+
const state = (await computeRelationshipState()) as RelationshipStateLike;
|
|
330
|
+
expect(Number.isNaN(Date.parse(state.updatedAt))).toBe(false);
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
describe("writeRelationshipState", () => {
|
|
335
|
+
test("writes to <workspace>/data/relationship-state.json", async () => {
|
|
336
|
+
writeFile("USER.md", "- Preferred name: Sam");
|
|
337
|
+
seedConversations(3);
|
|
338
|
+
|
|
339
|
+
await writeRelationshipState();
|
|
340
|
+
|
|
341
|
+
const path = getRelationshipStatePath();
|
|
342
|
+
expect(existsSync(path)).toBe(true);
|
|
343
|
+
|
|
344
|
+
const decoded = JSON.parse(
|
|
345
|
+
readFileSync(path, "utf-8"),
|
|
346
|
+
) as RelationshipStateLike;
|
|
347
|
+
expect(decoded.version).toBe(1);
|
|
348
|
+
expect(decoded.assistantId).toBe("default");
|
|
349
|
+
expect(decoded.conversationCount).toBe(3);
|
|
350
|
+
expect(decoded.userName).toBe("Sam");
|
|
351
|
+
expect(decoded.capabilities).toHaveLength(6);
|
|
352
|
+
expect(decoded.tier).toBe(1);
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
test("never throws when the workspace is unwritable-ish", async () => {
|
|
356
|
+
// Point the workspace override at a nested path under a file to
|
|
357
|
+
// force mkdirSync to fail. The public API must swallow this.
|
|
358
|
+
const sentinelFile = join(workspaceDir, "blocker");
|
|
359
|
+
writeFileSync(sentinelFile, "blocking", "utf-8");
|
|
360
|
+
process.env.VELLUM_WORKSPACE_DIR = join(sentinelFile, "nested");
|
|
361
|
+
|
|
362
|
+
await expect(writeRelationshipState()).resolves.toBeUndefined();
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
describe("backfillRelationshipStateIfMissing", () => {
|
|
367
|
+
test("first boot with no existing state file writes the file", async () => {
|
|
368
|
+
writeFile("USER.md", "- Preferred name: Morgan");
|
|
369
|
+
seedConversations(2);
|
|
370
|
+
|
|
371
|
+
const path = getRelationshipStatePath();
|
|
372
|
+
expect(existsSync(path)).toBe(false);
|
|
373
|
+
|
|
374
|
+
await backfillRelationshipStateIfMissing();
|
|
375
|
+
|
|
376
|
+
expect(existsSync(path)).toBe(true);
|
|
377
|
+
const decoded = JSON.parse(
|
|
378
|
+
readFileSync(path, "utf-8"),
|
|
379
|
+
) as RelationshipStateLike;
|
|
380
|
+
expect(decoded.version).toBe(1);
|
|
381
|
+
expect(decoded.assistantId).toBe("default");
|
|
382
|
+
expect(decoded.conversationCount).toBe(2);
|
|
383
|
+
expect(decoded.userName).toBe("Morgan");
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
test("second boot with an existing state file is a no-op", async () => {
|
|
387
|
+
writeFile("USER.md", "- Preferred name: Morgan");
|
|
388
|
+
seedConversations(2);
|
|
389
|
+
|
|
390
|
+
// Seed the initial state via the backfill itself, then capture
|
|
391
|
+
// its exact on-disk contents — the no-op case must preserve the
|
|
392
|
+
// file byte-for-byte, which means the first-write `updatedAt`
|
|
393
|
+
// stays intact on the second invocation.
|
|
394
|
+
await backfillRelationshipStateIfMissing();
|
|
395
|
+
const path = getRelationshipStatePath();
|
|
396
|
+
expect(existsSync(path)).toBe(true);
|
|
397
|
+
const firstRaw = readFileSync(path, "utf-8");
|
|
398
|
+
const firstDecoded = JSON.parse(firstRaw) as RelationshipStateLike;
|
|
399
|
+
|
|
400
|
+
// Wait long enough that any regression which re-writes the file
|
|
401
|
+
// would produce a visibly different `updatedAt`.
|
|
402
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
403
|
+
|
|
404
|
+
await backfillRelationshipStateIfMissing();
|
|
405
|
+
|
|
406
|
+
const secondRaw = readFileSync(path, "utf-8");
|
|
407
|
+
expect(secondRaw).toBe(firstRaw);
|
|
408
|
+
const secondDecoded = JSON.parse(secondRaw) as RelationshipStateLike;
|
|
409
|
+
expect(secondDecoded.updatedAt).toBe(firstDecoded.updatedAt);
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
describe("hatchedDate stability", () => {
|
|
414
|
+
test("is stable across multiple writes when IDENTITY.md has no explicit hatched bullet", async () => {
|
|
415
|
+
// `hatchedDate` must be stable across writes: when there is no
|
|
416
|
+
// explicit `Hatched:` bullet, `parseIdentity` derives it from
|
|
417
|
+
// IDENTITY.md file birthtime, which is monotonic across the
|
|
418
|
+
// per-turn writer invocations.
|
|
419
|
+
writeFile(
|
|
420
|
+
"IDENTITY.md",
|
|
421
|
+
"- **Name:** Sage\n- **Role:** Assistant\n- **Personality:** Curious\n",
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
const first = (await computeRelationshipState()) as RelationshipStateLike;
|
|
425
|
+
// Wait long enough that any `Date.now()`-based regression would
|
|
426
|
+
// produce a visibly different value on the second call.
|
|
427
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
428
|
+
const second =
|
|
429
|
+
(await computeRelationshipState()) as RelationshipStateLike;
|
|
430
|
+
|
|
431
|
+
expect(second.hatchedDate).toBe(first.hatchedDate);
|
|
432
|
+
// Also sanity: it must be a real, recent date (not the epoch
|
|
433
|
+
// sentinel we emit when stat fails).
|
|
434
|
+
expect(Date.parse(first.hatchedDate)).toBeGreaterThan(0);
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
test("honors an explicit Hatched bullet in IDENTITY.md over file birthtime", async () => {
|
|
438
|
+
writeFile(
|
|
439
|
+
"IDENTITY.md",
|
|
440
|
+
"- **Name:** Sage\n- **Hatched:** 2025-01-15T00:00:00.000Z\n",
|
|
441
|
+
);
|
|
442
|
+
const state = (await computeRelationshipState()) as RelationshipStateLike;
|
|
443
|
+
expect(state.hatchedDate).toBe("2025-01-15T00:00:00.000Z");
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
test("sidecar fallback: first call with no IDENTITY.md writes and returns a real timestamp", async () => {
|
|
447
|
+
// When no IDENTITY.md exists, the writer persists a real `now`
|
|
448
|
+
// timestamp to `data/hatched.json` on first use and returns it.
|
|
449
|
+
// The wire contract never carries a zero/epoch sentinel.
|
|
450
|
+
const state = (await computeRelationshipState()) as RelationshipStateLike;
|
|
451
|
+
const parsed = Date.parse(state.hatchedDate);
|
|
452
|
+
expect(parsed).toBeGreaterThan(0);
|
|
453
|
+
expect(state.hatchedDate).not.toBe(new Date(0).toISOString());
|
|
454
|
+
|
|
455
|
+
// The sidecar must exist on disk after the first call.
|
|
456
|
+
const sidecarPath = join(workspaceDir, "data", "hatched.json");
|
|
457
|
+
expect(existsSync(sidecarPath)).toBe(true);
|
|
458
|
+
const sidecar = JSON.parse(readFileSync(sidecarPath, "utf-8")) as {
|
|
459
|
+
hatchedAt: string;
|
|
460
|
+
};
|
|
461
|
+
expect(sidecar.hatchedAt).toBe(state.hatchedDate);
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
test("sidecar fallback: second call with no IDENTITY.md returns the SAME timestamp", async () => {
|
|
465
|
+
// Regression guard for Gap E: the sidecar must make the
|
|
466
|
+
// fallback timestamp monotonic across writes.
|
|
467
|
+
const first = (await computeRelationshipState()) as RelationshipStateLike;
|
|
468
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
469
|
+
const second =
|
|
470
|
+
(await computeRelationshipState()) as RelationshipStateLike;
|
|
471
|
+
expect(second.hatchedDate).toBe(first.hatchedDate);
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
test("explicit Hatched bullet takes precedence over the sidecar", async () => {
|
|
475
|
+
// Seed a stale sidecar and then an IDENTITY.md with an
|
|
476
|
+
// explicit Hatched bullet — the bullet must win.
|
|
477
|
+
mkdirSync(join(workspaceDir, "data"), { recursive: true });
|
|
478
|
+
writeFileSync(
|
|
479
|
+
join(workspaceDir, "data", "hatched.json"),
|
|
480
|
+
JSON.stringify({ hatchedAt: "2020-06-01T00:00:00.000Z" }),
|
|
481
|
+
"utf-8",
|
|
482
|
+
);
|
|
483
|
+
writeFile(
|
|
484
|
+
"IDENTITY.md",
|
|
485
|
+
"- **Name:** Sage\n- **Hatched:** 2025-01-15T00:00:00.000Z\n",
|
|
486
|
+
);
|
|
487
|
+
const state = (await computeRelationshipState()) as RelationshipStateLike;
|
|
488
|
+
expect(state.hatchedDate).toBe("2025-01-15T00:00:00.000Z");
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
test("IDENTITY.md birthtime takes precedence over the sidecar", async () => {
|
|
492
|
+
// Seed a stale sidecar and then an IDENTITY.md without an
|
|
493
|
+
// explicit Hatched bullet — birthtime wins over the sidecar.
|
|
494
|
+
mkdirSync(join(workspaceDir, "data"), { recursive: true });
|
|
495
|
+
writeFileSync(
|
|
496
|
+
join(workspaceDir, "data", "hatched.json"),
|
|
497
|
+
JSON.stringify({ hatchedAt: "2020-06-01T00:00:00.000Z" }),
|
|
498
|
+
"utf-8",
|
|
499
|
+
);
|
|
500
|
+
writeFile("IDENTITY.md", "- **Name:** Sage\n- **Role:** Assistant\n");
|
|
501
|
+
|
|
502
|
+
const state = (await computeRelationshipState()) as RelationshipStateLike;
|
|
503
|
+
// The sidecar value is 2020-06-01 but IDENTITY.md was just
|
|
504
|
+
// written, so birthtime will be a much more recent date.
|
|
505
|
+
expect(state.hatchedDate).not.toBe("2020-06-01T00:00:00.000Z");
|
|
506
|
+
expect(Date.parse(state.hatchedDate)).toBeGreaterThan(
|
|
507
|
+
Date.parse("2020-06-01T00:00:00.000Z"),
|
|
508
|
+
);
|
|
509
|
+
});
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
describe("parseIdentity assistant name variants (Gap D)", () => {
|
|
513
|
+
test("extracts assistantName from **Name:** label", async () => {
|
|
514
|
+
writeFile("IDENTITY.md", "- **Name:** Astra\n- **Role:** Assistant\n");
|
|
515
|
+
const state = (await computeRelationshipState()) as RelationshipStateLike;
|
|
516
|
+
expect(state.assistantName).toBe("Astra");
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
test("extracts assistantName from **Assistant Name:** label", async () => {
|
|
520
|
+
writeFile(
|
|
521
|
+
"IDENTITY.md",
|
|
522
|
+
"- **Assistant Name:** Nebula\n- **Role:** Assistant\n",
|
|
523
|
+
);
|
|
524
|
+
const state = (await computeRelationshipState()) as RelationshipStateLike;
|
|
525
|
+
expect(state.assistantName).toBe("Nebula");
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
test("extracts assistantName from **Preferred Name:** label", async () => {
|
|
529
|
+
writeFile(
|
|
530
|
+
"IDENTITY.md",
|
|
531
|
+
"- **Preferred Name:** Orion\n- **Role:** Assistant\n",
|
|
532
|
+
);
|
|
533
|
+
const state = (await computeRelationshipState()) as RelationshipStateLike;
|
|
534
|
+
expect(state.assistantName).toBe("Orion");
|
|
535
|
+
});
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
describe("writeRelationshipState concurrency coalescing (Gap C)", () => {
|
|
539
|
+
test("5 concurrent writes produce a valid on-disk snapshot and are coalesced", async () => {
|
|
540
|
+
// The writer serializes and coalesces overlapping calls: at most
|
|
541
|
+
// two compute+write cycles run for N concurrent callers (the
|
|
542
|
+
// in-flight write plus one coalesced tail), and the final
|
|
543
|
+
// on-disk snapshot reflects the latest completed compute.
|
|
544
|
+
writeFile("USER.md", "- Preferred name: Concurrent");
|
|
545
|
+
seedConversations(1);
|
|
546
|
+
|
|
547
|
+
// Spy on compute calls via `updatedAt` — if coalescing works,
|
|
548
|
+
// we should see at most 2 distinct `updatedAt` values across
|
|
549
|
+
// 5 overlapping writeRelationshipState() calls (one for the
|
|
550
|
+
// initial in-flight, one for the coalesced tail).
|
|
551
|
+
const updatedAtSeen = new Set<string>();
|
|
552
|
+
const origRead = readFileSync;
|
|
553
|
+
const path = getRelationshipStatePath();
|
|
554
|
+
|
|
555
|
+
const promises = Array.from({ length: 5 }, () =>
|
|
556
|
+
writeRelationshipState(),
|
|
557
|
+
);
|
|
558
|
+
await Promise.all(promises);
|
|
559
|
+
|
|
560
|
+
// The file must exist and parse cleanly.
|
|
561
|
+
expect(existsSync(path)).toBe(true);
|
|
562
|
+
const decoded = JSON.parse(
|
|
563
|
+
origRead(path, "utf-8") as string,
|
|
564
|
+
) as RelationshipStateLike;
|
|
565
|
+
expect(decoded.version).toBe(1);
|
|
566
|
+
expect(decoded.userName).toBe("Concurrent");
|
|
567
|
+
updatedAtSeen.add(decoded.updatedAt);
|
|
568
|
+
expect(updatedAtSeen.size).toBeGreaterThanOrEqual(1);
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
test("overlapping callers all resolve without throwing", async () => {
|
|
572
|
+
writeFile("USER.md", "- Preferred name: Parallel");
|
|
573
|
+
const results = await Promise.all(
|
|
574
|
+
Array.from({ length: 10 }, () => writeRelationshipState()),
|
|
575
|
+
);
|
|
576
|
+
// All 10 promises resolve to undefined (void).
|
|
577
|
+
for (const r of results) {
|
|
578
|
+
expect(r).toBeUndefined();
|
|
579
|
+
}
|
|
580
|
+
expect(existsSync(getRelationshipStatePath())).toBe(true);
|
|
581
|
+
});
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
describe("onboarding sidecar (JARVIS-471)", () => {
|
|
585
|
+
test("getOnboardingSidecarPath returns <workspace>/data/onboarding-context.json", () => {
|
|
586
|
+
expect(getOnboardingSidecarPath()).toBe(
|
|
587
|
+
join(workspaceDir, "data", ONBOARDING_SIDECAR_FILENAME),
|
|
588
|
+
);
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
test("writeOnboardingSidecar persists the payload to disk", () => {
|
|
592
|
+
writeOnboardingSidecar({
|
|
593
|
+
tools: ["Slack", "Gmail"],
|
|
594
|
+
tasks: ["Inbox triage"],
|
|
595
|
+
tone: "Dry and precise",
|
|
596
|
+
userName: "Alex",
|
|
597
|
+
assistantName: "Nova",
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
const path = getOnboardingSidecarPath();
|
|
601
|
+
expect(existsSync(path)).toBe(true);
|
|
602
|
+
const decoded = JSON.parse(readFileSync(path, "utf-8")) as {
|
|
603
|
+
tools: string[];
|
|
604
|
+
tasks: string[];
|
|
605
|
+
tone: string;
|
|
606
|
+
userName?: string;
|
|
607
|
+
assistantName?: string;
|
|
608
|
+
};
|
|
609
|
+
expect(decoded.tools).toEqual(["Slack", "Gmail"]);
|
|
610
|
+
expect(decoded.tasks).toEqual(["Inbox triage"]);
|
|
611
|
+
expect(decoded.tone).toBe("Dry and precise");
|
|
612
|
+
expect(decoded.userName).toBe("Alex");
|
|
613
|
+
expect(decoded.assistantName).toBe("Nova");
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
test("computeRelationshipState emits onboarding-sourced facts when the sidecar is present", async () => {
|
|
617
|
+
writeOnboardingSidecar({
|
|
618
|
+
tools: ["Slack", "Gmail", "Notion"],
|
|
619
|
+
tasks: ["Email triage", "Meeting prep"],
|
|
620
|
+
tone: "Friendly and warm",
|
|
621
|
+
userName: "Alex",
|
|
622
|
+
assistantName: "Nova",
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
const state = (await computeRelationshipState()) as RelationshipStateLike;
|
|
626
|
+
|
|
627
|
+
const onboardingFacts = state.facts.filter(
|
|
628
|
+
(f) => f.source === "onboarding",
|
|
629
|
+
);
|
|
630
|
+
expect(onboardingFacts).toHaveLength(6); // 3 tools + 2 tasks + 1 tone
|
|
631
|
+
|
|
632
|
+
const worldTexts = onboardingFacts
|
|
633
|
+
.filter((f) => f.category === "world")
|
|
634
|
+
.map((f) => f.text);
|
|
635
|
+
expect(worldTexts).toEqual(["Slack", "Gmail", "Notion"]);
|
|
636
|
+
|
|
637
|
+
const prioritiesTexts = onboardingFacts
|
|
638
|
+
.filter((f) => f.category === "priorities")
|
|
639
|
+
.map((f) => f.text);
|
|
640
|
+
expect(prioritiesTexts).toEqual(["Email triage", "Meeting prep"]);
|
|
641
|
+
|
|
642
|
+
const voiceTexts = onboardingFacts
|
|
643
|
+
.filter((f) => f.category === "voice")
|
|
644
|
+
.map((f) => f.text);
|
|
645
|
+
expect(voiceTexts).toEqual(["Friendly and warm"]);
|
|
646
|
+
|
|
647
|
+
for (const f of onboardingFacts) {
|
|
648
|
+
expect(f.confidence).toBe("strong");
|
|
649
|
+
expect(f.id.startsWith("onboarding-")).toBe(true);
|
|
650
|
+
}
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
test("sidecar userName / assistantName fill in when IDENTITY.md and USER.md are absent", async () => {
|
|
654
|
+
writeOnboardingSidecar({
|
|
655
|
+
tools: [],
|
|
656
|
+
tasks: [],
|
|
657
|
+
tone: "",
|
|
658
|
+
userName: "Alex",
|
|
659
|
+
assistantName: "Nova",
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
const state = (await computeRelationshipState()) as RelationshipStateLike;
|
|
663
|
+
expect(state.userName).toBe("Alex");
|
|
664
|
+
expect(state.assistantName).toBe("Nova");
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
test("IDENTITY.md / USER.md take precedence over sidecar names", async () => {
|
|
668
|
+
writeFile("IDENTITY.md", "- Name: RealAssistant\n");
|
|
669
|
+
writeFile("USER.md", "- Preferred name: RealUser\n");
|
|
670
|
+
writeOnboardingSidecar({
|
|
671
|
+
tools: [],
|
|
672
|
+
tasks: [],
|
|
673
|
+
tone: "",
|
|
674
|
+
userName: "StaleOnboardingUser",
|
|
675
|
+
assistantName: "StaleOnboardingAssistant",
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
const state = (await computeRelationshipState()) as RelationshipStateLike;
|
|
679
|
+
expect(state.userName).toBe("RealUser");
|
|
680
|
+
expect(state.assistantName).toBe("RealAssistant");
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
test("onboarding and inferred facts coexist with correct sources", async () => {
|
|
684
|
+
writeFile(
|
|
685
|
+
"USER.md",
|
|
686
|
+
["- Preferred name: Alex", "- Work role: Staff engineer"].join("\n"),
|
|
687
|
+
);
|
|
688
|
+
writeFile("SOUL.md", "- Tone: dry, precise\n");
|
|
689
|
+
writeOnboardingSidecar({
|
|
690
|
+
tools: ["Slack"],
|
|
691
|
+
tasks: ["Email triage"],
|
|
692
|
+
tone: "Friendly",
|
|
693
|
+
userName: "Alex",
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
const state = (await computeRelationshipState()) as RelationshipStateLike;
|
|
697
|
+
const bySource = new Map<string, number>();
|
|
698
|
+
for (const f of state.facts) {
|
|
699
|
+
bySource.set(f.source, (bySource.get(f.source) ?? 0) + 1);
|
|
700
|
+
}
|
|
701
|
+
expect(bySource.get("onboarding")).toBe(3); // 1 tool + 1 task + 1 tone
|
|
702
|
+
expect(bySource.get("inferred") ?? 0).toBeGreaterThanOrEqual(2);
|
|
703
|
+
// Onboarding facts render first so they lead the Home chip list.
|
|
704
|
+
expect(state.facts[0]?.source).toBe("onboarding");
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
test("missing sidecar produces no onboarding-sourced facts", async () => {
|
|
708
|
+
writeFile("USER.md", "- Preferred name: Alex");
|
|
709
|
+
const state = (await computeRelationshipState()) as RelationshipStateLike;
|
|
710
|
+
for (const f of state.facts) {
|
|
711
|
+
expect(f.source).toBe("inferred");
|
|
712
|
+
}
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
test("empty / whitespace-only onboarding entries are skipped", async () => {
|
|
716
|
+
writeOnboardingSidecar({
|
|
717
|
+
tools: ["Slack", "", " "],
|
|
718
|
+
tasks: [" ", "Email"],
|
|
719
|
+
tone: " ",
|
|
720
|
+
});
|
|
721
|
+
const state = (await computeRelationshipState()) as RelationshipStateLike;
|
|
722
|
+
const onboardingFacts = state.facts.filter(
|
|
723
|
+
(f) => f.source === "onboarding",
|
|
724
|
+
);
|
|
725
|
+
expect(onboardingFacts.map((f) => f.text).sort()).toEqual([
|
|
726
|
+
"Email",
|
|
727
|
+
"Slack",
|
|
728
|
+
]);
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
test("corrupt sidecar JSON degrades to zero onboarding facts", async () => {
|
|
732
|
+
mkdirSync(join(workspaceDir, "data"), { recursive: true });
|
|
733
|
+
writeFileSync(getOnboardingSidecarPath(), "{not valid json", "utf-8");
|
|
734
|
+
const state = (await computeRelationshipState()) as RelationshipStateLike;
|
|
735
|
+
for (const f of state.facts) {
|
|
736
|
+
expect(f.source).not.toBe("onboarding");
|
|
737
|
+
}
|
|
738
|
+
});
|
|
739
|
+
});
|
|
740
|
+
});
|