@vellumai/assistant 0.5.6 → 0.5.8
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/.env.example +16 -2
- package/ARCHITECTURE.md +6 -75
- package/Dockerfile +3 -2
- package/README.md +0 -2
- package/bun.lock +0 -414
- package/docker-entrypoint.sh +9 -0
- package/docs/architecture/keychain-broker.md +45 -240
- package/docs/architecture/memory.md +13 -11
- package/docs/architecture/security.md +0 -17
- package/docs/credential-execution-service.md +2 -2
- package/node_modules/@vellumai/ces-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-contracts/src/error.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/grants.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/handles.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/index.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +120 -1
- package/node_modules/@vellumai/credential-storage/package.json +1 -0
- package/node_modules/@vellumai/egress-proxy/package.json +1 -0
- package/package.json +2 -3
- package/src/__tests__/actor-token-service.test.ts +0 -114
- package/src/__tests__/approval-cascade.test.ts +0 -1
- package/src/__tests__/assistant-feature-flags-integration.test.ts +30 -29
- package/src/__tests__/browser-fill-credential.test.ts +1 -1
- package/src/__tests__/browser-skill-endstate.test.ts +6 -5
- package/src/__tests__/btw-routes.test.ts +0 -39
- package/src/__tests__/call-controller.test.ts +0 -1
- package/src/__tests__/call-domain.test.ts +0 -128
- package/src/__tests__/ces-rpc-credential-backend.test.ts +199 -0
- package/src/__tests__/ces-startup-timeout.test.ts +40 -0
- package/src/__tests__/channel-approval-routes.test.ts +0 -5
- package/src/__tests__/channel-readiness-service.test.ts +1 -60
- package/src/__tests__/checker.test.ts +4 -2
- package/src/__tests__/cli-command-risk-guard.test.ts +112 -0
- package/src/__tests__/config-schema-cmd.test.ts +0 -2
- package/src/__tests__/config-schema.test.ts +3 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +0 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -2
- package/src/__tests__/conversation-agent-loop.test.ts +2 -4
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -5
- package/src/__tests__/conversation-confirmation-signals.test.ts +0 -1
- package/src/__tests__/conversation-error.test.ts +15 -1
- package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
- package/src/__tests__/conversation-messaging-secret-redirect.test.ts +1 -1
- package/src/__tests__/conversation-pre-run-repair.test.ts +0 -1
- package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -1
- package/src/__tests__/conversation-queue.test.ts +0 -1
- package/src/__tests__/conversation-skill-tools.test.ts +0 -54
- package/src/__tests__/conversation-slash-queue.test.ts +0 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +0 -1
- package/src/__tests__/conversation-title-service.test.ts +87 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +0 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -1
- package/src/__tests__/credential-execution-client.test.ts +5 -2
- package/src/__tests__/credential-execution-feature-gates.test.ts +59 -30
- package/src/__tests__/credential-execution-managed-contract.test.ts +35 -20
- package/src/__tests__/credential-security-e2e.test.ts +1 -67
- package/src/__tests__/credential-security-invariants.test.ts +6 -50
- package/src/__tests__/credentials-cli.test.ts +82 -3
- package/src/__tests__/daemon-credential-client.test.ts +123 -0
- package/src/__tests__/db-migration-rollback.test.ts +2015 -1
- package/src/__tests__/deterministic-verification-control-plane.test.ts +1 -0
- package/src/__tests__/docker-signing-key-bootstrap.test.ts +34 -143
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +6 -4
- package/src/__tests__/gateway-client-managed-outbound.test.ts +79 -1
- package/src/__tests__/guardian-routing-state.test.ts +0 -5
- package/src/__tests__/host-shell-tool.test.ts +6 -7
- package/src/__tests__/http-user-message-parity.test.ts +3 -103
- package/src/__tests__/inbound-invite-redemption.test.ts +0 -4
- package/src/__tests__/inline-skill-load-permissions.test.ts +6 -8
- package/src/__tests__/intent-routing.test.ts +0 -13
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +178 -0
- package/src/__tests__/journal-context.test.ts +335 -0
- package/src/__tests__/keychain-broker-client.test.ts +161 -22
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -3
- package/src/__tests__/memory-jobs-worker-backoff.test.ts +150 -0
- package/src/__tests__/memory-lifecycle-e2e.test.ts +70 -25
- package/src/__tests__/memory-recall-quality.test.ts +48 -17
- package/src/__tests__/memory-regressions.test.ts +408 -363
- package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -3
- package/src/__tests__/migration-export-http.test.ts +2 -2
- package/src/__tests__/migration-import-commit-http.test.ts +2 -2
- package/src/__tests__/migration-import-preflight-http.test.ts +2 -2
- package/src/__tests__/migration-validate-http.test.ts +2 -2
- package/src/__tests__/non-member-access-request.test.ts +2 -7
- package/src/__tests__/notification-decision-fallback.test.ts +4 -0
- package/src/__tests__/notification-decision-identity.test.ts +4 -0
- package/src/__tests__/notification-decision-strategy.test.ts +71 -0
- package/src/__tests__/oauth-cli.test.ts +5 -1
- package/src/__tests__/permission-types.test.ts +1 -0
- package/src/__tests__/provider-commit-message-generator.test.ts +0 -37
- package/src/__tests__/provider-error-scenarios.test.ts +0 -267
- package/src/__tests__/provider-managed-proxy-integration.test.ts +5 -6
- package/src/__tests__/provider-streaming.benchmark.test.ts +2 -81
- package/src/__tests__/qdrant-manager.test.ts +28 -2
- package/src/__tests__/registry.test.ts +0 -6
- package/src/__tests__/relay-server.test.ts +1 -2
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -4
- package/src/__tests__/script-proxy-injection-runtime.test.ts +1 -1
- package/src/__tests__/secret-onetime-send.test.ts +1 -1
- package/src/__tests__/secret-routes-managed-proxy.test.ts +0 -4
- package/src/__tests__/secure-keys.test.ts +95 -272
- package/src/__tests__/shell-identity.test.ts +96 -6
- package/src/__tests__/skill-feature-flags-integration.test.ts +22 -14
- package/src/__tests__/skill-feature-flags.test.ts +46 -45
- package/src/__tests__/skill-load-feature-flag.test.ts +7 -10
- package/src/__tests__/skill-load-inline-command.test.ts +8 -12
- package/src/__tests__/skill-load-inline-includes.test.ts +6 -10
- package/src/__tests__/skill-load-tool.test.ts +0 -2
- package/src/__tests__/skill-memory.test.ts +17 -3
- package/src/__tests__/skill-projection-feature-flag.test.ts +33 -29
- package/src/__tests__/skills.test.ts +0 -2
- package/src/__tests__/slack-inbound-verification.test.ts +0 -4
- package/src/__tests__/stale-approval-dedup.test.ts +171 -0
- package/src/__tests__/stt-hints.test.ts +437 -0
- package/src/__tests__/suggestion-routes.test.ts +1 -32
- package/src/__tests__/system-prompt.test.ts +0 -1
- package/src/__tests__/task-memory-cleanup.test.ts +14 -0
- package/src/__tests__/tool-executor-shell-integration.test.ts +5 -3
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +0 -5
- package/src/__tests__/trusted-contact-multichannel.test.ts +0 -4
- package/src/__tests__/twilio-routes-twiml.test.ts +139 -1
- package/src/__tests__/update-bulletin.test.ts +0 -2
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +6 -9
- package/src/__tests__/voice-quality.test.ts +58 -0
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -7
- package/src/__tests__/workspace-migration-015-migrate-credentials-to-keychain.test.ts +252 -0
- package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +220 -0
- package/src/__tests__/workspace-migration-down-functions.test.ts +1009 -0
- package/src/__tests__/workspace-migrations-runner.test.ts +114 -0
- package/src/acp/agent-process.ts +9 -1
- package/src/agent/loop.ts +1 -1
- package/src/approvals/guardian-request-resolvers.ts +164 -38
- package/src/calls/__tests__/tts-text-sanitizer.test.ts +254 -0
- package/src/calls/audio-store.test.ts +97 -0
- package/src/calls/audio-store.ts +205 -0
- package/src/calls/call-controller.ts +90 -8
- package/src/calls/call-domain.ts +3 -0
- package/src/calls/call-store.ts +10 -3
- package/src/calls/fish-audio-client.ts +129 -0
- package/src/calls/relay-server.ts +27 -0
- package/src/calls/stt-hints.ts +189 -0
- package/src/calls/tts-text-sanitizer.ts +61 -0
- package/src/calls/twilio-routes.ts +34 -5
- package/src/calls/types.ts +1 -0
- package/src/calls/voice-ingress-preflight.ts +0 -42
- package/src/calls/voice-quality.ts +38 -5
- package/src/calls/voice-session-bridge.ts +7 -12
- package/src/cli/commands/avatar.ts +2 -2
- package/src/cli/commands/config.ts +1 -4
- package/src/cli/commands/credentials.ts +128 -82
- package/src/cli/commands/doctor.ts +2 -2
- package/src/cli/commands/keys.ts +7 -7
- package/src/cli/commands/memory.ts +1 -1
- package/src/cli/commands/oauth/connections.ts +11 -29
- package/src/cli/commands/oauth/index.ts +7 -0
- package/src/cli/commands/oauth/platform.ts +525 -0
- package/src/cli/commands/platform.ts +3 -3
- package/src/cli/lib/daemon-credential-client.ts +284 -0
- package/src/cli.ts +1 -1
- package/src/config/assistant-feature-flags.ts +186 -5
- package/src/config/bundled-skills/AGENTS.md +34 -0
- package/src/config/bundled-skills/acp/SKILL.md +10 -0
- package/src/config/bundled-skills/app-builder/SKILL.md +0 -4
- package/src/config/bundled-skills/messaging/SKILL.md +5 -5
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -2
- package/src/config/bundled-skills/phone-calls/TOOLS.json +4 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +1 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +1 -0
- package/src/config/bundled-skills/settings/SKILL.md +15 -2
- package/src/config/bundled-skills/settings/TOOLS.json +47 -2
- package/src/config/bundled-skills/settings/tools/avatar-remove.ts +59 -0
- package/src/config/bundled-skills/settings/tools/avatar-update.ts +80 -0
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +42 -0
- package/src/config/bundled-skills/slack/SKILL.md +1 -1
- package/src/config/bundled-tool-registry.ts +5 -11
- package/src/config/defaults.ts +0 -2
- package/src/config/env-registry.ts +5 -5
- package/src/config/env.ts +21 -14
- package/src/config/feature-flag-registry.json +49 -9
- package/src/config/loader.ts +106 -42
- package/src/config/schema.ts +9 -29
- package/src/config/schemas/calls.ts +30 -0
- package/src/config/schemas/fish-audio.ts +39 -0
- package/src/config/schemas/inference.ts +2 -2
- package/src/config/schemas/journal.ts +16 -0
- package/src/config/schemas/memory-processing.ts +2 -2
- package/src/config/schemas/security.ts +0 -4
- package/src/config/types.ts +1 -1
- package/src/contacts/contact-store.ts +39 -0
- package/src/contacts/types.ts +2 -0
- package/src/credential-execution/approval-bridge.ts +1 -0
- package/src/credential-execution/executable-discovery.ts +28 -4
- package/src/credential-execution/feature-gates.ts +16 -0
- package/src/credential-execution/process-manager.ts +38 -0
- package/src/credential-execution/startup-timeout.ts +36 -0
- package/src/daemon/approval-generators.ts +3 -9
- package/src/daemon/assistant-attachments.ts +9 -0
- package/src/daemon/config-watcher.ts +5 -0
- package/src/daemon/conversation-error.ts +13 -1
- package/src/daemon/conversation-memory.ts +1 -2
- package/src/daemon/conversation-process.ts +18 -1
- package/src/daemon/conversation-surfaces.ts +30 -1
- package/src/daemon/conversation-tool-setup.ts +0 -105
- package/src/daemon/conversation.ts +21 -1
- package/src/daemon/guardian-action-generators.ts +3 -9
- package/src/daemon/handlers/config-vercel.ts +92 -0
- package/src/daemon/handlers/skills.ts +2 -15
- package/src/daemon/install-symlink.ts +195 -0
- package/src/daemon/lifecycle.ts +234 -51
- package/src/daemon/message-types/conversations.ts +4 -4
- package/src/daemon/message-types/diagnostics.ts +3 -22
- package/src/daemon/message-types/messages.ts +0 -2
- package/src/daemon/message-types/upgrades.ts +8 -0
- package/src/daemon/server.ts +32 -95
- package/src/events/domain-events.ts +2 -1
- package/src/inbound/platform-callback-registration.ts +3 -3
- package/src/instrument.ts +8 -5
- package/src/memory/app-store.ts +31 -0
- package/src/memory/conversation-title-service.ts +50 -1
- package/src/memory/db-init.ts +16 -0
- package/src/memory/indexer.ts +19 -10
- package/src/memory/items-extractor.ts +328 -321
- package/src/memory/job-handlers/conversation-starters.ts +4 -1
- package/src/memory/job-handlers/summarization.ts +26 -16
- package/src/memory/jobs-store.ts +63 -6
- package/src/memory/jobs-worker.ts +31 -7
- package/src/memory/journal-memory.ts +214 -0
- package/src/memory/migrations/001-job-deferrals.ts +19 -0
- package/src/memory/migrations/004-entity-relation-dedup.ts +10 -0
- package/src/memory/migrations/005-fingerprint-scope-unique.ts +76 -0
- package/src/memory/migrations/006-scope-salted-fingerprints.ts +50 -0
- package/src/memory/migrations/007-assistant-id-to-self.ts +10 -0
- package/src/memory/migrations/008-remove-assistant-id-columns.ts +34 -0
- package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +26 -0
- package/src/memory/migrations/014-backfill-inbox-thread-state.ts +10 -0
- package/src/memory/migrations/015-drop-active-search-index.ts +17 -0
- package/src/memory/migrations/019-notification-tables-schema-migration.ts +12 -0
- package/src/memory/migrations/020-rename-macos-ios-channel-to-vellum.ts +121 -0
- package/src/memory/migrations/024-embedding-vector-blob.ts +74 -0
- package/src/memory/migrations/026a-embeddings-nullable-vector-json.ts +82 -0
- package/src/memory/migrations/036-normalize-phone-identities.ts +11 -0
- package/src/memory/migrations/116-messages-fts.ts +106 -1
- package/src/memory/migrations/126-backfill-guardian-principal-id.ts +52 -0
- package/src/memory/migrations/127-guardian-principal-id-not-null.ts +77 -0
- package/src/memory/migrations/134-contacts-notes-column.ts +13 -0
- package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +20 -0
- package/src/memory/migrations/136-drop-assistant-id-columns.ts +52 -0
- package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +13 -0
- package/src/memory/migrations/141-rename-verification-table.ts +54 -0
- package/src/memory/migrations/142-rename-verification-session-id-column.ts +25 -0
- package/src/memory/migrations/143-rename-guardian-verification-values.ts +35 -0
- package/src/memory/migrations/144-rename-voice-to-phone.ts +136 -0
- package/src/memory/migrations/145-drop-accounts-table.ts +32 -0
- package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +14 -1
- package/src/memory/migrations/148-drop-reminders-table.ts +35 -1
- package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +69 -1
- package/src/memory/migrations/162-guardian-timestamps-epoch-ms.ts +290 -0
- package/src/memory/migrations/169-rename-gmail-provider-key-to-google.ts +51 -1
- package/src/memory/migrations/174-rename-thread-starters-table.ts +47 -1
- package/src/memory/migrations/176-drop-capability-card-state.ts +13 -0
- package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +16 -0
- package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +28 -1
- package/src/memory/migrations/190-call-session-skip-disclosure.ts +15 -0
- package/src/memory/migrations/191-backfill-audio-attachment-mime-types.ts +64 -0
- package/src/memory/migrations/192-contacts-user-file-column.ts +15 -0
- package/src/memory/migrations/193-add-source-type-columns.ts +81 -0
- package/src/memory/migrations/index.ts +5 -0
- package/src/memory/migrations/registry.ts +98 -0
- package/src/memory/migrations/validate-migration-state.ts +137 -11
- package/src/memory/qdrant-circuit-breaker.ts +9 -0
- package/src/memory/qdrant-manager.ts +64 -7
- package/src/memory/retriever.test.ts +37 -25
- package/src/memory/retriever.ts +24 -49
- package/src/memory/schema/calls.ts +1 -0
- package/src/memory/schema/contacts.ts +1 -0
- package/src/memory/schema/memory-core.ts +2 -0
- package/src/memory/search/formatting.ts +7 -44
- package/src/memory/search/staleness.ts +4 -0
- package/src/memory/search/tier-classifier.ts +10 -2
- package/src/memory/search/types.ts +2 -5
- package/src/memory/task-memory-cleanup.ts +4 -3
- package/src/notifications/adapters/slack.ts +168 -6
- package/src/notifications/broadcaster.ts +1 -0
- package/src/notifications/copy-composer.ts +59 -2
- package/src/notifications/decision-engine.ts +4 -1
- package/src/notifications/signal.ts +2 -0
- package/src/notifications/types.ts +2 -0
- package/src/oauth/connection-resolver.ts +6 -4
- package/src/permissions/checker.ts +0 -38
- package/src/permissions/shell-identity.ts +76 -22
- package/src/permissions/types.ts +4 -2
- package/src/platform/client.ts +35 -7
- package/src/prompts/journal-context.ts +133 -0
- package/src/prompts/persona-resolver.ts +194 -0
- package/src/prompts/system-prompt.ts +44 -4
- package/src/prompts/templates/SOUL.md +10 -0
- package/src/prompts/templates/users/default.md +1 -0
- package/src/providers/provider-send-message.ts +3 -32
- package/src/providers/registry.ts +29 -179
- package/src/providers/types.ts +1 -1
- package/src/runtime/access-request-helper.ts +4 -0
- package/src/runtime/auth/__tests__/credential-service.test.ts +0 -1
- package/src/runtime/auth/__tests__/external-assistant-id.test.ts +13 -68
- package/src/runtime/auth/__tests__/guard-tests.test.ts +9 -50
- package/src/runtime/auth/external-assistant-id.ts +13 -59
- package/src/runtime/auth/route-policy.ts +17 -1
- package/src/runtime/auth/token-service.ts +43 -138
- package/src/runtime/channel-readiness-service.ts +1 -16
- package/src/runtime/gateway-client.ts +47 -4
- package/src/runtime/guardian-decision-types.ts +45 -4
- package/src/runtime/http-server.ts +31 -3
- package/src/runtime/middleware/error-handler.ts +1 -9
- package/src/runtime/routes/access-request-decision.ts +2 -2
- package/src/runtime/routes/app-management-routes.ts +2 -1
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +219 -30
- package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +37 -14
- package/src/runtime/routes/audio-routes.ts +40 -0
- package/src/runtime/routes/btw-routes.ts +0 -17
- package/src/runtime/routes/channel-readiness-routes.ts +9 -4
- package/src/runtime/routes/conversation-query-routes.ts +63 -1
- package/src/runtime/routes/conversation-routes.ts +4 -44
- package/src/runtime/routes/debug-routes.ts +12 -9
- package/src/runtime/routes/diagnostics-routes.ts +1 -477
- package/src/runtime/routes/guardian-approval-interception.ts +168 -11
- package/src/runtime/routes/guardian-approval-prompt.ts +6 -1
- package/src/runtime/routes/guardian-approval-reply-helpers.ts +103 -21
- package/src/runtime/routes/identity-routes.ts +19 -30
- package/src/runtime/routes/inbound-message-handler.ts +31 -1
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +64 -5
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +52 -40
- package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +4 -33
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +1 -1
- package/src/runtime/routes/integrations/twilio.ts +52 -10
- package/src/runtime/routes/integrations/vercel.ts +89 -0
- package/src/runtime/routes/log-export-routes.ts +5 -0
- package/src/runtime/routes/memory-item-routes.test.ts +3 -3
- package/src/runtime/routes/memory-item-routes.ts +46 -14
- package/src/runtime/routes/migration-rollback-routes.ts +209 -0
- package/src/runtime/routes/migration-routes.ts +17 -1
- package/src/runtime/routes/notification-routes.ts +58 -0
- package/src/runtime/routes/schedule-routes.ts +65 -0
- package/src/runtime/routes/secret-routes.ts +141 -10
- package/src/runtime/routes/settings-routes.ts +41 -1
- package/src/runtime/routes/tts-routes.ts +96 -0
- package/src/runtime/routes/upgrade-broadcast-routes.ts +26 -2
- package/src/runtime/routes/workspace-commit-routes.ts +62 -0
- package/src/runtime/routes/workspace-routes.test.ts +22 -1
- package/src/runtime/routes/workspace-routes.ts +1 -1
- package/src/runtime/routes/workspace-utils.ts +86 -2
- package/src/security/ces-credential-client.ts +75 -29
- package/src/security/ces-rpc-credential-backend.ts +86 -0
- package/src/security/credential-backend.ts +22 -92
- package/src/security/keychain-broker-client.ts +10 -2
- package/src/security/secure-keys.ts +113 -115
- package/src/skills/catalog-install.ts +6 -32
- package/src/skills/skill-memory.ts +1 -0
- package/src/subagent/manager.ts +2 -5
- package/src/telemetry/usage-telemetry-reporter.ts +4 -2
- package/src/tools/acp/spawn.ts +78 -1
- package/src/tools/calls/call-start.ts +1 -0
- package/src/tools/credentials/vault.ts +5 -3
- package/src/tools/executor.ts +0 -4
- package/src/tools/memory/definitions.ts +3 -2
- package/src/tools/memory/handlers.ts +10 -7
- package/src/tools/network/script-proxy/session-manager.ts +19 -4
- package/src/tools/network/web-fetch.ts +3 -1
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/terminal/safe-env.ts +1 -0
- package/src/tools/types.ts +0 -8
- package/src/util/browser.ts +15 -0
- package/src/util/errors.ts +0 -12
- package/src/util/platform.ts +4 -51
- package/src/workspace/git-service.ts +5 -2
- package/src/workspace/migrations/001-avatar-rename.ts +15 -0
- package/src/workspace/migrations/003-seed-device-id.ts +17 -1
- package/src/workspace/migrations/004-extract-collect-usage-data.ts +33 -0
- package/src/workspace/migrations/005-add-send-diagnostics.ts +3 -0
- package/src/workspace/migrations/006-services-config.ts +49 -0
- package/src/workspace/migrations/007-web-search-provider-rename.ts +27 -0
- package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +3 -0
- package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +4 -0
- package/src/workspace/migrations/010-app-dir-rename.ts +78 -0
- package/src/workspace/migrations/011-backfill-installation-id.ts +11 -0
- package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +44 -0
- package/src/workspace/migrations/013-repair-conversation-disk-view.ts +5 -0
- package/src/workspace/migrations/015-migrate-credentials-to-keychain.ts +153 -0
- package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +156 -0
- package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +150 -0
- package/src/workspace/migrations/017-seed-persona-dirs.ts +96 -0
- package/src/workspace/migrations/018-rekey-compound-credential-keys.ts +184 -0
- package/src/workspace/migrations/019-scope-journal-to-guardian.ts +103 -0
- package/src/workspace/migrations/migrate-to-workspace-volume.ts +27 -5
- package/src/workspace/migrations/registry.ts +12 -0
- package/src/workspace/migrations/runner.ts +106 -2
- package/src/workspace/migrations/types.ts +4 -0
- package/src/workspace/provider-commit-message-generator.ts +12 -21
- package/src/__tests__/claude-code-skill-regression.test.ts +0 -206
- package/src/__tests__/claude-code-tool-profiles.test.ts +0 -99
- package/src/__tests__/diagnostics-export.test.ts +0 -288
- package/src/__tests__/local-gateway-health.test.ts +0 -209
- package/src/__tests__/provider-fail-open-selection.test.ts +0 -271
- package/src/__tests__/provider-failover-actual-provider.test.ts +0 -66
- package/src/__tests__/secret-ingress-handler.test.ts +0 -120
- package/src/__tests__/swarm-conversation-integration.test.ts +0 -358
- package/src/__tests__/swarm-dag-pathological.test.ts +0 -547
- package/src/__tests__/swarm-orchestrator.test.ts +0 -463
- package/src/__tests__/swarm-plan-validator.test.ts +0 -384
- package/src/__tests__/swarm-recursion.test.ts +0 -197
- package/src/__tests__/swarm-router-planner.test.ts +0 -234
- package/src/__tests__/swarm-tool.test.ts +0 -185
- package/src/__tests__/swarm-worker-backend.test.ts +0 -144
- package/src/__tests__/swarm-worker-runner.test.ts +0 -288
- package/src/commands/__tests__/cc-command-registry.test.ts +0 -396
- package/src/commands/cc-command-registry.ts +0 -248
- package/src/config/bundled-skills/claude-code/SKILL.md +0 -53
- package/src/config/bundled-skills/claude-code/TOOLS.json +0 -47
- package/src/config/bundled-skills/claude-code/tools/claude-code.ts +0 -12
- package/src/config/bundled-skills/orchestration/SKILL.md +0 -33
- package/src/config/bundled-skills/orchestration/TOOLS.json +0 -35
- package/src/config/bundled-skills/orchestration/tools/swarm-delegate.ts +0 -12
- package/src/config/schemas/swarm.ts +0 -82
- package/src/logfire.ts +0 -135
- package/src/memory/search/lexical.ts +0 -48
- package/src/providers/failover.ts +0 -186
- package/src/runtime/local-gateway-health.ts +0 -275
- package/src/security/secret-ingress.ts +0 -68
- package/src/swarm/backend-claude-code.ts +0 -225
- package/src/swarm/checkpoint.ts +0 -137
- package/src/swarm/graph-utils.ts +0 -53
- package/src/swarm/index.ts +0 -55
- package/src/swarm/limits.ts +0 -66
- package/src/swarm/orchestrator.ts +0 -424
- package/src/swarm/plan-validator.ts +0 -117
- package/src/swarm/router-planner.ts +0 -162
- package/src/swarm/router-prompts.ts +0 -39
- package/src/swarm/synthesizer.ts +0 -81
- package/src/swarm/types.ts +0 -72
- package/src/swarm/worker-backend.ts +0 -131
- package/src/swarm/worker-prompts.ts +0 -80
- package/src/swarm/worker-runner.ts +0 -170
- package/src/tools/claude-code/claude-code.ts +0 -610
- package/src/tools/swarm/delegate.ts +0 -205
|
@@ -20,7 +20,10 @@ import { initializeProviders } from "../../providers/registry.js";
|
|
|
20
20
|
import { credentialKey } from "../../security/credential-key.js";
|
|
21
21
|
import {
|
|
22
22
|
deleteSecureKeyAsync,
|
|
23
|
+
getActiveBackendName,
|
|
23
24
|
getSecureKeyAsync,
|
|
25
|
+
getSecureKeyResultAsync,
|
|
26
|
+
listSecureKeysAsync,
|
|
24
27
|
setSecureKeyAsync,
|
|
25
28
|
} from "../../security/secure-keys.js";
|
|
26
29
|
import {
|
|
@@ -103,11 +106,16 @@ export async function handleAddSecret(
|
|
|
103
106
|
req: Request,
|
|
104
107
|
getCesClient?: () => CesClient | undefined,
|
|
105
108
|
): Promise<Response> {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
let body: { type?: string; name?: string; value?: string };
|
|
110
|
+
try {
|
|
111
|
+
body = (await req.json()) as {
|
|
112
|
+
type?: string;
|
|
113
|
+
name?: string;
|
|
114
|
+
value?: string;
|
|
115
|
+
};
|
|
116
|
+
} catch {
|
|
117
|
+
return httpError("BAD_REQUEST", "Request body must be valid JSON", 400);
|
|
118
|
+
}
|
|
111
119
|
|
|
112
120
|
const { type, name, value } = body;
|
|
113
121
|
|
|
@@ -178,7 +186,7 @@ export async function handleAddSecret(
|
|
|
178
186
|
if (!stored) {
|
|
179
187
|
return httpError(
|
|
180
188
|
"INTERNAL_ERROR",
|
|
181
|
-
|
|
189
|
+
`Failed to store API key in secure storage (backend: ${getActiveBackendName()})`,
|
|
182
190
|
500,
|
|
183
191
|
);
|
|
184
192
|
}
|
|
@@ -190,7 +198,7 @@ export async function handleAddSecret(
|
|
|
190
198
|
}
|
|
191
199
|
|
|
192
200
|
if (type === "credential") {
|
|
193
|
-
const colonIdx = name.
|
|
201
|
+
const colonIdx = name.lastIndexOf(":");
|
|
194
202
|
if (colonIdx < 1 || colonIdx === name.length - 1) {
|
|
195
203
|
return httpError(
|
|
196
204
|
"BAD_REQUEST",
|
|
@@ -243,7 +251,7 @@ export async function handleAddSecret(
|
|
|
243
251
|
if (!stored) {
|
|
244
252
|
return httpError(
|
|
245
253
|
"INTERNAL_ERROR",
|
|
246
|
-
|
|
254
|
+
`Failed to store credential in secure storage (backend: ${getActiveBackendName()})`,
|
|
247
255
|
500,
|
|
248
256
|
);
|
|
249
257
|
}
|
|
@@ -308,12 +316,90 @@ export async function handleAddSecret(
|
|
|
308
316
|
}
|
|
309
317
|
}
|
|
310
318
|
|
|
311
|
-
export async function
|
|
319
|
+
export async function handleReadSecret(req: Request): Promise<Response> {
|
|
312
320
|
const body = (await req.json()) as {
|
|
313
321
|
type?: string;
|
|
314
322
|
name?: string;
|
|
323
|
+
reveal?: boolean;
|
|
315
324
|
};
|
|
316
325
|
|
|
326
|
+
const { type, name, reveal } = body;
|
|
327
|
+
|
|
328
|
+
if (!type || typeof type !== "string") {
|
|
329
|
+
return httpError("BAD_REQUEST", "type is required", 400);
|
|
330
|
+
}
|
|
331
|
+
if (!name || typeof name !== "string") {
|
|
332
|
+
return httpError("BAD_REQUEST", "name is required", 400);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
try {
|
|
336
|
+
let accountKey: string;
|
|
337
|
+
|
|
338
|
+
if (type === "api_key") {
|
|
339
|
+
if (
|
|
340
|
+
!API_KEY_PROVIDERS.includes(name as (typeof API_KEY_PROVIDERS)[number])
|
|
341
|
+
) {
|
|
342
|
+
return httpError(
|
|
343
|
+
"BAD_REQUEST",
|
|
344
|
+
`Unknown API key provider: ${name}. Valid providers: ${API_KEY_PROVIDERS.join(
|
|
345
|
+
", ",
|
|
346
|
+
)}`,
|
|
347
|
+
400,
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
accountKey = name;
|
|
351
|
+
} else if (type === "credential") {
|
|
352
|
+
const colonIdx = name.lastIndexOf(":");
|
|
353
|
+
if (colonIdx < 1 || colonIdx === name.length - 1) {
|
|
354
|
+
return httpError(
|
|
355
|
+
"BAD_REQUEST",
|
|
356
|
+
'For credential type, name must be in "service:field" format (e.g. "github:api_token")',
|
|
357
|
+
400,
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
const service = name.slice(0, colonIdx);
|
|
361
|
+
const field = name.slice(colonIdx + 1);
|
|
362
|
+
accountKey = credentialKey(service, field);
|
|
363
|
+
} else {
|
|
364
|
+
return httpError(
|
|
365
|
+
"BAD_REQUEST",
|
|
366
|
+
`Unknown secret type: ${type}. Valid types: api_key, credential`,
|
|
367
|
+
400,
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const { value, unreachable } = await getSecureKeyResultAsync(accountKey);
|
|
372
|
+
if (value === undefined) {
|
|
373
|
+
return Response.json({ found: false, unreachable });
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (reveal) {
|
|
377
|
+
return Response.json({ found: true, value, unreachable: false });
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Mask the value: show first 10 chars and last 4, hiding at least 3
|
|
381
|
+
const minHidden = 3;
|
|
382
|
+
const maxVisible = Math.max(1, value.length - minHidden);
|
|
383
|
+
const prefixLen = Math.min(10, maxVisible);
|
|
384
|
+
const suffixLen = Math.min(4, Math.max(0, maxVisible - prefixLen));
|
|
385
|
+
const masked = `${value.slice(0, prefixLen)}...${suffixLen > 0 ? value.slice(-suffixLen) : ""}`;
|
|
386
|
+
|
|
387
|
+
return Response.json({ found: true, masked, unreachable: false });
|
|
388
|
+
} catch (err) {
|
|
389
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
390
|
+
log.error({ err, type, name }, "Failed to read secret via HTTP");
|
|
391
|
+
return httpError("INTERNAL_ERROR", message, 500);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
export async function handleDeleteSecret(req: Request): Promise<Response> {
|
|
396
|
+
let body: { type?: string; name?: string };
|
|
397
|
+
try {
|
|
398
|
+
body = (await req.json()) as { type?: string; name?: string };
|
|
399
|
+
} catch {
|
|
400
|
+
return httpError("BAD_REQUEST", "Request body must be valid JSON", 400);
|
|
401
|
+
}
|
|
402
|
+
|
|
317
403
|
const { type, name } = body;
|
|
318
404
|
|
|
319
405
|
if (!type || typeof type !== "string") {
|
|
@@ -358,7 +444,7 @@ export async function handleDeleteSecret(req: Request): Promise<Response> {
|
|
|
358
444
|
}
|
|
359
445
|
|
|
360
446
|
if (type === "credential") {
|
|
361
|
-
const colonIdx = name.
|
|
447
|
+
const colonIdx = name.lastIndexOf(":");
|
|
362
448
|
if (colonIdx < 1 || colonIdx === name.length - 1) {
|
|
363
449
|
return httpError(
|
|
364
450
|
"BAD_REQUEST",
|
|
@@ -418,6 +504,41 @@ export async function handleDeleteSecret(req: Request): Promise<Response> {
|
|
|
418
504
|
}
|
|
419
505
|
}
|
|
420
506
|
|
|
507
|
+
const CREDENTIAL_KEY_PREFIX = "credential/";
|
|
508
|
+
|
|
509
|
+
export async function handleListSecrets(): Promise<Response> {
|
|
510
|
+
try {
|
|
511
|
+
const { accounts, unreachable } = await listSecureKeysAsync();
|
|
512
|
+
if (unreachable) {
|
|
513
|
+
return Response.json(
|
|
514
|
+
{ error: "Credential store is unreachable" },
|
|
515
|
+
{ status: 503 },
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const secrets = accounts.map((account) => {
|
|
520
|
+
if (account.startsWith(CREDENTIAL_KEY_PREFIX)) {
|
|
521
|
+
// credential/{service}/{field} → service:field
|
|
522
|
+
const rest = account.slice(CREDENTIAL_KEY_PREFIX.length);
|
|
523
|
+
const slashIdx = rest.indexOf("/");
|
|
524
|
+
if (slashIdx > 0 && slashIdx < rest.length - 1) {
|
|
525
|
+
return {
|
|
526
|
+
type: "credential" as const,
|
|
527
|
+
name: `${rest.slice(0, slashIdx)}:${rest.slice(slashIdx + 1)}`,
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
// API key providers are stored with their raw provider name
|
|
532
|
+
return { type: "api_key" as const, name: account };
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
return Response.json({ secrets });
|
|
536
|
+
} catch (err) {
|
|
537
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
538
|
+
return httpError("INTERNAL_ERROR", message, 500);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
421
542
|
// ---------------------------------------------------------------------------
|
|
422
543
|
// Route definitions
|
|
423
544
|
// ---------------------------------------------------------------------------
|
|
@@ -441,5 +562,15 @@ export function secretRouteDefinitions(
|
|
|
441
562
|
method: "DELETE",
|
|
442
563
|
handler: async ({ req }) => handleDeleteSecret(req),
|
|
443
564
|
},
|
|
565
|
+
{
|
|
566
|
+
endpoint: "secrets",
|
|
567
|
+
method: "GET",
|
|
568
|
+
handler: async () => handleListSecrets(),
|
|
569
|
+
},
|
|
570
|
+
{
|
|
571
|
+
endpoint: "secrets/read",
|
|
572
|
+
method: "POST",
|
|
573
|
+
handler: async ({ req }) => handleReadSecret(req),
|
|
574
|
+
},
|
|
444
575
|
];
|
|
445
576
|
}
|
|
@@ -10,7 +10,10 @@
|
|
|
10
10
|
import { readFileSync } from "node:fs";
|
|
11
11
|
import { join } from "node:path";
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
getPlatformBaseUrl,
|
|
15
|
+
setIngressPublicBaseUrl,
|
|
16
|
+
} from "../../config/env.js";
|
|
14
17
|
import { loadRawConfig, saveRawConfig } from "../../config/loader.js";
|
|
15
18
|
import { loadSkillCatalog } from "../../config/skills.js";
|
|
16
19
|
import {
|
|
@@ -728,6 +731,43 @@ export function settingsRouteDefinitions(): RouteDefinition[] {
|
|
|
728
731
|
handler: () => handleEnvVars(),
|
|
729
732
|
},
|
|
730
733
|
|
|
734
|
+
// Platform config (GET / PUT)
|
|
735
|
+
{
|
|
736
|
+
endpoint: "config/platform",
|
|
737
|
+
method: "GET",
|
|
738
|
+
policyKey: "config/platform:GET",
|
|
739
|
+
handler: () => {
|
|
740
|
+
const raw = loadRawConfig();
|
|
741
|
+
const platform = (raw?.platform ?? {}) as Record<string, unknown>;
|
|
742
|
+
const baseUrl =
|
|
743
|
+
(platform.baseUrl as string | undefined) || getPlatformBaseUrl();
|
|
744
|
+
return Response.json({ baseUrl, success: true });
|
|
745
|
+
},
|
|
746
|
+
},
|
|
747
|
+
{
|
|
748
|
+
endpoint: "config/platform",
|
|
749
|
+
method: "PUT",
|
|
750
|
+
policyKey: "config/platform",
|
|
751
|
+
handler: async ({ req }) => {
|
|
752
|
+
try {
|
|
753
|
+
const body = (await req.json()) as { baseUrl?: string };
|
|
754
|
+
const value = (body.baseUrl ?? "").trim().replace(/\/+$/, "");
|
|
755
|
+
const raw = loadRawConfig();
|
|
756
|
+
const platform = (raw?.platform ?? {}) as Record<string, unknown>;
|
|
757
|
+
platform.baseUrl = value || undefined;
|
|
758
|
+
saveRawConfig({ ...raw, platform });
|
|
759
|
+
return Response.json({ baseUrl: value, success: true });
|
|
760
|
+
} catch (err) {
|
|
761
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
762
|
+
log.error({ err }, "Failed to update platform config via HTTP");
|
|
763
|
+
return Response.json(
|
|
764
|
+
{ baseUrl: "", success: false, error: message },
|
|
765
|
+
{ status: 500 },
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
},
|
|
769
|
+
},
|
|
770
|
+
|
|
731
771
|
// Ingress config (GET / PUT)
|
|
732
772
|
{
|
|
733
773
|
endpoint: "integrations/ingress/config",
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP route definitions for message text-to-speech synthesis.
|
|
3
|
+
*
|
|
4
|
+
* POST /v1/messages/:id/tts?conversationId=... — synthesize message text to audio
|
|
5
|
+
*
|
|
6
|
+
* Gated behind the `feature_flags.message-tts.enabled` assistant feature flag.
|
|
7
|
+
* Uses Fish Audio for synthesis when configured.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { synthesizeWithFishAudio } from "../../calls/fish-audio-client.js";
|
|
11
|
+
import { sanitizeForTts } from "../../calls/tts-text-sanitizer.js";
|
|
12
|
+
import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-flags.js";
|
|
13
|
+
import { getConfig } from "../../config/loader.js";
|
|
14
|
+
import { getMessageContent } from "../../daemon/handlers/conversation-history.js";
|
|
15
|
+
import { getLogger } from "../../util/logger.js";
|
|
16
|
+
import { httpError } from "../http-errors.js";
|
|
17
|
+
import type { RouteDefinition } from "../http-router.js";
|
|
18
|
+
|
|
19
|
+
const log = getLogger("tts-routes");
|
|
20
|
+
|
|
21
|
+
const MESSAGE_TTS_FLAG = "feature_flags.message-tts.enabled" as const;
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Route definitions
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
export function ttsRouteDefinitions(): RouteDefinition[] {
|
|
28
|
+
return [
|
|
29
|
+
{
|
|
30
|
+
endpoint: "messages/:id/tts",
|
|
31
|
+
method: "POST",
|
|
32
|
+
policyKey: "messages/tts",
|
|
33
|
+
handler: async ({ url, params }) => {
|
|
34
|
+
const config = getConfig();
|
|
35
|
+
|
|
36
|
+
if (!isAssistantFeatureFlagEnabled(MESSAGE_TTS_FLAG, config)) {
|
|
37
|
+
return httpError("FORBIDDEN", "Message TTS is not enabled", 403);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const messageId = params.id;
|
|
41
|
+
const conversationId =
|
|
42
|
+
url.searchParams.get("conversationId") ?? undefined;
|
|
43
|
+
|
|
44
|
+
const result = getMessageContent(messageId, conversationId);
|
|
45
|
+
if (!result) {
|
|
46
|
+
return httpError("NOT_FOUND", `Message ${messageId} not found`, 404);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!result.text) {
|
|
50
|
+
return httpError("BAD_REQUEST", "Message has no text content", 400);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const sanitizedText = sanitizeForTts(result.text);
|
|
54
|
+
if (!sanitizedText.trim()) {
|
|
55
|
+
return httpError(
|
|
56
|
+
"BAD_REQUEST",
|
|
57
|
+
"Message has no speakable text content",
|
|
58
|
+
400,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const { fishAudio } = config;
|
|
63
|
+
if (!fishAudio?.referenceId) {
|
|
64
|
+
return httpError(
|
|
65
|
+
"SERVICE_UNAVAILABLE",
|
|
66
|
+
"Fish Audio TTS is not configured",
|
|
67
|
+
503,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const audioBuffer = await synthesizeWithFishAudio(
|
|
73
|
+
sanitizedText,
|
|
74
|
+
fishAudio,
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const format = fishAudio.format ?? "mp3";
|
|
78
|
+
const contentType =
|
|
79
|
+
format === "wav"
|
|
80
|
+
? "audio/wav"
|
|
81
|
+
: format === "opus"
|
|
82
|
+
? "audio/opus"
|
|
83
|
+
: "audio/mpeg";
|
|
84
|
+
|
|
85
|
+
return new Response(new Uint8Array(audioBuffer), {
|
|
86
|
+
status: 200,
|
|
87
|
+
headers: { "Content-Type": contentType },
|
|
88
|
+
});
|
|
89
|
+
} catch (err) {
|
|
90
|
+
log.error({ err, messageId }, "TTS synthesis failed");
|
|
91
|
+
return httpError("INTERNAL_ERROR", "TTS synthesis failed", 502);
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
];
|
|
96
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Upgrade broadcast endpoint — publishes service group update lifecycle
|
|
3
|
-
* events (starting / complete) to all connected SSE clients.
|
|
3
|
+
* events (starting / progress / complete) to all connected SSE clients.
|
|
4
4
|
*
|
|
5
5
|
* Protected by a route policy restricting access to gateway service
|
|
6
6
|
* principals only (`svc_gateway` with `internal.write` scope), following
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import type {
|
|
13
13
|
ServiceGroupUpdateComplete,
|
|
14
|
+
ServiceGroupUpdateProgress,
|
|
14
15
|
ServiceGroupUpdateStarting,
|
|
15
16
|
} from "../../daemon/message-types/upgrades.js";
|
|
16
17
|
import { buildAssistantEvent } from "../assistant-event.js";
|
|
@@ -86,6 +87,29 @@ export function upgradeBroadcastRouteDefinitions(): RouteDefinition[] {
|
|
|
86
87
|
return Response.json({ ok: true });
|
|
87
88
|
}
|
|
88
89
|
|
|
90
|
+
if (type === "progress") {
|
|
91
|
+
const { statusMessage } = body as { statusMessage?: unknown };
|
|
92
|
+
|
|
93
|
+
if (typeof statusMessage !== "string" || statusMessage.length === 0) {
|
|
94
|
+
return httpError(
|
|
95
|
+
"BAD_REQUEST",
|
|
96
|
+
"statusMessage is required and must be a non-empty string",
|
|
97
|
+
400,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const message: ServiceGroupUpdateProgress = {
|
|
102
|
+
type: "service_group_update_progress",
|
|
103
|
+
statusMessage,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
await assistantEventHub.publish(
|
|
107
|
+
buildAssistantEvent(DAEMON_INTERNAL_ASSISTANT_ID, message),
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
return Response.json({ ok: true });
|
|
111
|
+
}
|
|
112
|
+
|
|
89
113
|
if (type === "complete") {
|
|
90
114
|
const { installedVersion, success, rolledBackToVersion } = body as {
|
|
91
115
|
installedVersion?: unknown;
|
|
@@ -142,7 +166,7 @@ export function upgradeBroadcastRouteDefinitions(): RouteDefinition[] {
|
|
|
142
166
|
|
|
143
167
|
return httpError(
|
|
144
168
|
"BAD_REQUEST",
|
|
145
|
-
'type must be "starting" or "complete"',
|
|
169
|
+
'type must be "starting", "progress", or "complete"',
|
|
146
170
|
400,
|
|
147
171
|
);
|
|
148
172
|
},
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace commit endpoint — creates a git commit in the workspace
|
|
3
|
+
* directory with all pending changes.
|
|
4
|
+
*
|
|
5
|
+
* Protected by a route policy restricting access to gateway service
|
|
6
|
+
* principals only (`svc_gateway` with `internal.write` scope), following
|
|
7
|
+
* the same pattern as other gateway-forwarded control-plane endpoints.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { getWorkspaceDir } from "../../util/platform.js";
|
|
11
|
+
import { getWorkspaceGitService } from "../../workspace/git-service.js";
|
|
12
|
+
import { httpError } from "../http-errors.js";
|
|
13
|
+
import type { RouteDefinition } from "../http-router.js";
|
|
14
|
+
|
|
15
|
+
export function workspaceCommitRouteDefinitions(): RouteDefinition[] {
|
|
16
|
+
return [
|
|
17
|
+
{
|
|
18
|
+
endpoint: "admin/workspace-commit",
|
|
19
|
+
method: "POST",
|
|
20
|
+
handler: async ({ req }) => {
|
|
21
|
+
let body: unknown;
|
|
22
|
+
try {
|
|
23
|
+
body = await req.json();
|
|
24
|
+
} catch {
|
|
25
|
+
return httpError("BAD_REQUEST", "Invalid JSON body", 400);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!body || typeof body !== "object") {
|
|
29
|
+
return httpError(
|
|
30
|
+
"BAD_REQUEST",
|
|
31
|
+
"Request body must be a JSON object",
|
|
32
|
+
400,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const { message } = body as { message?: unknown };
|
|
37
|
+
|
|
38
|
+
if (typeof message !== "string" || message.length === 0) {
|
|
39
|
+
return httpError(
|
|
40
|
+
"BAD_REQUEST",
|
|
41
|
+
"message is required and must be a non-empty string",
|
|
42
|
+
400,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
await getWorkspaceGitService(getWorkspaceDir()).commitChanges(
|
|
48
|
+
message,
|
|
49
|
+
);
|
|
50
|
+
return Response.json({ ok: true });
|
|
51
|
+
} catch (err) {
|
|
52
|
+
const detail = err instanceof Error ? err.message : "Unknown error";
|
|
53
|
+
return httpError(
|
|
54
|
+
"INTERNAL_ERROR",
|
|
55
|
+
`Workspace commit failed: ${detail}`,
|
|
56
|
+
500,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
}
|
|
@@ -190,9 +190,30 @@ describe("isTextMimeType", () => {
|
|
|
190
190
|
expect(isTextMimeType("video/mp4")).toBe(false);
|
|
191
191
|
});
|
|
192
192
|
|
|
193
|
-
test("application/octet-stream is not text", () => {
|
|
193
|
+
test("application/octet-stream is not text without filename", () => {
|
|
194
194
|
expect(isTextMimeType("application/octet-stream")).toBe(false);
|
|
195
195
|
});
|
|
196
|
+
|
|
197
|
+
test("application/octet-stream with .py filename is text", () => {
|
|
198
|
+
expect(isTextMimeType("application/octet-stream", "script.py")).toBe(true);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("application/octet-stream with .go filename is text", () => {
|
|
202
|
+
expect(isTextMimeType("application/octet-stream", "main.go")).toBe(true);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test("application/octet-stream with .rs filename is text", () => {
|
|
206
|
+
expect(isTextMimeType("application/octet-stream", "lib.rs")).toBe(true);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test("application/octet-stream with unknown extension is not text", () => {
|
|
210
|
+
expect(isTextMimeType("application/octet-stream", "data.bin")).toBe(false);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test("extension fallback only applies to application/octet-stream", () => {
|
|
214
|
+
// A binary plist has a specific MIME type — extension should not override it
|
|
215
|
+
expect(isTextMimeType("application/x-plist", "Info.plist")).toBe(false);
|
|
216
|
+
});
|
|
196
217
|
});
|
|
197
218
|
|
|
198
219
|
// ===========================================================================
|
|
@@ -119,7 +119,7 @@ function handleWorkspaceFile(ctx: RouteContext): Response {
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
const mimeType = Bun.file(resolved).type;
|
|
122
|
-
const isText = isTextMimeType(mimeType);
|
|
122
|
+
const isText = isTextMimeType(mimeType, basename(resolved));
|
|
123
123
|
const isBinary = !isText;
|
|
124
124
|
|
|
125
125
|
let content: string | undefined = undefined;
|
|
@@ -85,10 +85,94 @@ const TEXT_MIME_PREFIXES = [
|
|
|
85
85
|
"application/x-yaml",
|
|
86
86
|
"application/toml",
|
|
87
87
|
"application/x-sh",
|
|
88
|
+
"application/x-httpd-php",
|
|
89
|
+
"application/x-perl",
|
|
90
|
+
"application/x-sql",
|
|
91
|
+
"application/x-tex",
|
|
92
|
+
"application/vnd.dart",
|
|
88
93
|
];
|
|
89
94
|
|
|
90
|
-
|
|
91
|
-
|
|
95
|
+
/**
|
|
96
|
+
* File extensions that are known text/code files but that Bun's MIME
|
|
97
|
+
* detection reports as `application/octet-stream`.
|
|
98
|
+
*/
|
|
99
|
+
const TEXT_FILE_EXTENSIONS = new Set([
|
|
100
|
+
// Programming languages
|
|
101
|
+
"py",
|
|
102
|
+
"rb",
|
|
103
|
+
"go",
|
|
104
|
+
"rs",
|
|
105
|
+
"swift",
|
|
106
|
+
"kt",
|
|
107
|
+
"kts",
|
|
108
|
+
"cs",
|
|
109
|
+
"scala",
|
|
110
|
+
"ex",
|
|
111
|
+
"exs",
|
|
112
|
+
"erl",
|
|
113
|
+
"hs",
|
|
114
|
+
"clj",
|
|
115
|
+
"cljs",
|
|
116
|
+
"jl",
|
|
117
|
+
"zig",
|
|
118
|
+
"nim",
|
|
119
|
+
"v",
|
|
120
|
+
"sol",
|
|
121
|
+
"r",
|
|
122
|
+
"java",
|
|
123
|
+
"lua",
|
|
124
|
+
// Shell / scripting
|
|
125
|
+
"bash",
|
|
126
|
+
"zsh",
|
|
127
|
+
"fish",
|
|
128
|
+
"ps1",
|
|
129
|
+
"bat",
|
|
130
|
+
"cmd",
|
|
131
|
+
"awk",
|
|
132
|
+
// Web frameworks
|
|
133
|
+
"vue",
|
|
134
|
+
"svelte",
|
|
135
|
+
"scss",
|
|
136
|
+
"sass",
|
|
137
|
+
"less",
|
|
138
|
+
// Config / data
|
|
139
|
+
"cfg",
|
|
140
|
+
"conf",
|
|
141
|
+
"ini",
|
|
142
|
+
"properties",
|
|
143
|
+
"env",
|
|
144
|
+
"gradle",
|
|
145
|
+
"cmake",
|
|
146
|
+
// Markup / docs
|
|
147
|
+
"rst",
|
|
148
|
+
"adoc",
|
|
149
|
+
"org",
|
|
150
|
+
"tex",
|
|
151
|
+
"latex",
|
|
152
|
+
// Other text formats
|
|
153
|
+
"graphql",
|
|
154
|
+
"gql",
|
|
155
|
+
"proto",
|
|
156
|
+
"tf",
|
|
157
|
+
"hcl",
|
|
158
|
+
"diff",
|
|
159
|
+
"patch",
|
|
160
|
+
"log",
|
|
161
|
+
"lock",
|
|
162
|
+
]);
|
|
163
|
+
|
|
164
|
+
export function isTextMimeType(mimeType: string, fileName?: string): boolean {
|
|
165
|
+
if (TEXT_MIME_PREFIXES.some((prefix) => mimeType.startsWith(prefix))) {
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
// Only fall back to extension check when the MIME type is genuinely unknown.
|
|
169
|
+
// Specific MIME types (e.g. application/x-plist for binary plists) should be
|
|
170
|
+
// trusted over the extension — overriding them risks corrupting binary files.
|
|
171
|
+
if (fileName && mimeType === "application/octet-stream") {
|
|
172
|
+
const ext = fileName.split(".").pop()?.toLowerCase();
|
|
173
|
+
if (ext && TEXT_FILE_EXTENSIONS.has(ext)) return true;
|
|
174
|
+
}
|
|
175
|
+
return false;
|
|
92
176
|
}
|
|
93
177
|
|
|
94
178
|
export const MAX_INLINE_TEXT_SIZE = 2 * 1024 * 1024; // 2 MB
|