@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
|
@@ -42,6 +42,18 @@ import { deliverStaleApprovalReply } from "./guardian-approval-reply-helpers.js"
|
|
|
42
42
|
|
|
43
43
|
const log = getLogger("runtime-http");
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Resolve the Slack ephemeral user ID when the source channel is Slack.
|
|
47
|
+
* Returns `undefined` for non-Slack channels so callers can pass the
|
|
48
|
+
* result directly to `ephemeralUserId` without branching.
|
|
49
|
+
*/
|
|
50
|
+
function slackEphemeralUserId(
|
|
51
|
+
sourceChannel: ChannelId,
|
|
52
|
+
userId: string | undefined,
|
|
53
|
+
): string | undefined {
|
|
54
|
+
return sourceChannel === "slack" && userId ? userId : undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
45
57
|
export interface ApprovalInterceptionParams {
|
|
46
58
|
conversationId: string;
|
|
47
59
|
callbackData?: string;
|
|
@@ -55,6 +67,8 @@ export interface ApprovalInterceptionParams {
|
|
|
55
67
|
assistantId: string;
|
|
56
68
|
approvalCopyGenerator?: ApprovalCopyGenerator;
|
|
57
69
|
approvalConversationGenerator?: ApprovalConversationGenerator;
|
|
70
|
+
/** Original approval message timestamp (Slack ts) for editing after resolution. */
|
|
71
|
+
approvalMessageTs?: string;
|
|
58
72
|
}
|
|
59
73
|
|
|
60
74
|
export interface ApprovalInterceptionResult {
|
|
@@ -92,6 +106,7 @@ export async function handleApprovalInterception(
|
|
|
92
106
|
assistantId,
|
|
93
107
|
approvalCopyGenerator,
|
|
94
108
|
approvalConversationGenerator,
|
|
109
|
+
approvalMessageTs,
|
|
95
110
|
} = params;
|
|
96
111
|
|
|
97
112
|
// ── Guardian approval decision path ──
|
|
@@ -110,6 +125,7 @@ export async function handleApprovalInterception(
|
|
|
110
125
|
assistantId,
|
|
111
126
|
approvalCopyGenerator,
|
|
112
127
|
approvalConversationGenerator,
|
|
128
|
+
approvalMessageTs,
|
|
113
129
|
});
|
|
114
130
|
if (guardianResult) {
|
|
115
131
|
return guardianResult;
|
|
@@ -262,13 +278,23 @@ export async function handleApprovalInterception(
|
|
|
262
278
|
approvalCopyGenerator,
|
|
263
279
|
));
|
|
264
280
|
try {
|
|
265
|
-
|
|
266
|
-
replyCallbackUrl,
|
|
281
|
+
const cancelPayload: Parameters<typeof deliverChannelReply>[1] =
|
|
267
282
|
{
|
|
268
283
|
chatId: conversationExternalId,
|
|
269
284
|
text: replyText,
|
|
270
285
|
assistantId,
|
|
271
|
-
}
|
|
286
|
+
};
|
|
287
|
+
const requesterEphemeral = slackEphemeralUserId(
|
|
288
|
+
sourceChannel,
|
|
289
|
+
actorExternalId,
|
|
290
|
+
);
|
|
291
|
+
if (requesterEphemeral) {
|
|
292
|
+
cancelPayload.ephemeral = true;
|
|
293
|
+
cancelPayload.user = requesterEphemeral;
|
|
294
|
+
}
|
|
295
|
+
await deliverChannelReply(
|
|
296
|
+
replyCallbackUrl,
|
|
297
|
+
cancelPayload,
|
|
272
298
|
bearerToken,
|
|
273
299
|
);
|
|
274
300
|
} catch (err) {
|
|
@@ -290,13 +316,24 @@ export async function handleApprovalInterception(
|
|
|
290
316
|
{},
|
|
291
317
|
approvalCopyGenerator,
|
|
292
318
|
);
|
|
319
|
+
const guardianCancelPayload: Parameters<
|
|
320
|
+
typeof deliverChannelReply
|
|
321
|
+
>[1] = {
|
|
322
|
+
chatId: guardianApprovalForRequest.guardianChatId,
|
|
323
|
+
text: guardianNotice,
|
|
324
|
+
assistantId,
|
|
325
|
+
};
|
|
326
|
+
const guardianEphemeral = slackEphemeralUserId(
|
|
327
|
+
sourceChannel,
|
|
328
|
+
guardianApprovalForRequest.guardianExternalUserId,
|
|
329
|
+
);
|
|
330
|
+
if (guardianEphemeral) {
|
|
331
|
+
guardianCancelPayload.ephemeral = true;
|
|
332
|
+
guardianCancelPayload.user = guardianEphemeral;
|
|
333
|
+
}
|
|
293
334
|
await deliverChannelReply(
|
|
294
335
|
replyCallbackUrl,
|
|
295
|
-
|
|
296
|
-
chatId: guardianApprovalForRequest.guardianChatId,
|
|
297
|
-
text: guardianNotice,
|
|
298
|
-
assistantId,
|
|
299
|
-
},
|
|
336
|
+
guardianCancelPayload,
|
|
300
337
|
bearerToken,
|
|
301
338
|
);
|
|
302
339
|
} catch (err) {
|
|
@@ -322,19 +359,33 @@ export async function handleApprovalInterception(
|
|
|
322
359
|
errorLogMessage:
|
|
323
360
|
"Failed to deliver stale requester-cancel notice",
|
|
324
361
|
errorLogContext: { conversationId },
|
|
362
|
+
ephemeralUserId: slackEphemeralUserId(
|
|
363
|
+
sourceChannel,
|
|
364
|
+
actorExternalId,
|
|
365
|
+
),
|
|
325
366
|
});
|
|
326
367
|
return { handled: true, type: "stale_ignored" };
|
|
327
368
|
}
|
|
328
369
|
|
|
329
370
|
if (requesterFollowupReplyText) {
|
|
330
371
|
try {
|
|
331
|
-
|
|
332
|
-
replyCallbackUrl,
|
|
372
|
+
const followupPayload: Parameters<typeof deliverChannelReply>[1] =
|
|
333
373
|
{
|
|
334
374
|
chatId: conversationExternalId,
|
|
335
375
|
text: requesterFollowupReplyText,
|
|
336
376
|
assistantId,
|
|
337
|
-
}
|
|
377
|
+
};
|
|
378
|
+
const followupEphemeral = slackEphemeralUserId(
|
|
379
|
+
sourceChannel,
|
|
380
|
+
actorExternalId,
|
|
381
|
+
);
|
|
382
|
+
if (followupEphemeral) {
|
|
383
|
+
followupPayload.ephemeral = true;
|
|
384
|
+
followupPayload.user = followupEphemeral;
|
|
385
|
+
}
|
|
386
|
+
await deliverChannelReply(
|
|
387
|
+
replyCallbackUrl,
|
|
388
|
+
followupPayload,
|
|
338
389
|
bearerToken,
|
|
339
390
|
);
|
|
340
391
|
} catch (err) {
|
|
@@ -360,6 +411,7 @@ export async function handleApprovalInterception(
|
|
|
360
411
|
errorLogMessage:
|
|
361
412
|
"Failed to deliver guardian-pending notice to requester",
|
|
362
413
|
errorLogContext: { conversationId },
|
|
414
|
+
ephemeralUserId: slackEphemeralUserId(sourceChannel, actorExternalId),
|
|
363
415
|
});
|
|
364
416
|
return { handled: true, type: "assistant_turn" };
|
|
365
417
|
}
|
|
@@ -394,6 +446,7 @@ export async function handleApprovalInterception(
|
|
|
394
446
|
"Failed to deliver guardian-expiry notice to requester",
|
|
395
447
|
extraContext: { toolName: pending[0].toolName },
|
|
396
448
|
errorLogContext: { conversationId },
|
|
449
|
+
ephemeralUserId: slackEphemeralUserId(sourceChannel, actorExternalId),
|
|
397
450
|
});
|
|
398
451
|
return { handled: true, type: "decision_applied" };
|
|
399
452
|
}
|
|
@@ -430,6 +483,7 @@ export async function handleApprovalInterception(
|
|
|
430
483
|
errorLogMessage:
|
|
431
484
|
"Failed to deliver guardian-pending notice to non-guardian actor (pre-row guard)",
|
|
432
485
|
errorLogContext: { conversationId },
|
|
486
|
+
ephemeralUserId: slackEphemeralUserId(sourceChannel, actorExternalId),
|
|
433
487
|
});
|
|
434
488
|
return { handled: true, type: "assistant_turn" };
|
|
435
489
|
}
|
|
@@ -478,6 +532,19 @@ export async function handleApprovalInterception(
|
|
|
478
532
|
{ conversationId, callbackRequestId: cbDecision.requestId },
|
|
479
533
|
"Callback request ID does not match any pending interaction, ignoring stale button press",
|
|
480
534
|
);
|
|
535
|
+
|
|
536
|
+
// Edit the original Slack approval message to remove stale buttons
|
|
537
|
+
if (sourceChannel === "slack" && approvalMessageTs) {
|
|
538
|
+
editStaleSlackApprovalMessage({
|
|
539
|
+
replyCallbackUrl,
|
|
540
|
+
chatId: conversationExternalId,
|
|
541
|
+
messageTs: approvalMessageTs,
|
|
542
|
+
assistantId,
|
|
543
|
+
bearerToken,
|
|
544
|
+
conversationId,
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
|
|
481
548
|
return { handled: true, type: "stale_ignored" };
|
|
482
549
|
}
|
|
483
550
|
}
|
|
@@ -485,6 +552,32 @@ export async function handleApprovalInterception(
|
|
|
485
552
|
const result = handleChannelDecision(conversationId, cbDecision);
|
|
486
553
|
|
|
487
554
|
if (result.applied) {
|
|
555
|
+
// Edit the original Slack approval message to show the decision
|
|
556
|
+
// and remove stale action buttons.
|
|
557
|
+
if (sourceChannel === "slack" && approvalMessageTs) {
|
|
558
|
+
const decisionOutcome: "approved" | "denied" =
|
|
559
|
+
cbDecision.action === "reject" ? "denied" : "approved";
|
|
560
|
+
const statusEmoji =
|
|
561
|
+
decisionOutcome === "approved" ? "\u2713" : "\u2717";
|
|
562
|
+
const statusLabel =
|
|
563
|
+
decisionOutcome === "approved" ? "Approved" : "Denied";
|
|
564
|
+
deliverChannelReply(
|
|
565
|
+
replyCallbackUrl,
|
|
566
|
+
{
|
|
567
|
+
chatId: conversationExternalId,
|
|
568
|
+
text: `${statusEmoji} ${statusLabel}`,
|
|
569
|
+
messageTs: approvalMessageTs,
|
|
570
|
+
assistantId,
|
|
571
|
+
},
|
|
572
|
+
bearerToken,
|
|
573
|
+
).catch((err) => {
|
|
574
|
+
log.error(
|
|
575
|
+
{ err, conversationId, messageTs: approvalMessageTs },
|
|
576
|
+
"Failed to edit Slack approval message after decision",
|
|
577
|
+
);
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
|
|
488
581
|
// Post-decision delivery is handled by the onEvent callback
|
|
489
582
|
// in the session that registered the pending interaction.
|
|
490
583
|
return { handled: true, type: "decision_applied" };
|
|
@@ -492,6 +585,18 @@ export async function handleApprovalInterception(
|
|
|
492
585
|
|
|
493
586
|
// Race condition: request was already resolved between the stale check
|
|
494
587
|
// above and the decision attempt.
|
|
588
|
+
// Edit the original Slack approval message to remove stale buttons
|
|
589
|
+
if (sourceChannel === "slack" && approvalMessageTs) {
|
|
590
|
+
editStaleSlackApprovalMessage({
|
|
591
|
+
replyCallbackUrl,
|
|
592
|
+
chatId: conversationExternalId,
|
|
593
|
+
messageTs: approvalMessageTs,
|
|
594
|
+
assistantId,
|
|
595
|
+
bearerToken,
|
|
596
|
+
conversationId,
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
|
|
495
600
|
return { handled: true, type: "stale_ignored" };
|
|
496
601
|
}
|
|
497
602
|
}
|
|
@@ -514,6 +619,7 @@ export async function handleApprovalInterception(
|
|
|
514
619
|
approvalConversationGenerator,
|
|
515
620
|
pending,
|
|
516
621
|
allowedActions,
|
|
622
|
+
actorExternalId,
|
|
517
623
|
});
|
|
518
624
|
}
|
|
519
625
|
|
|
@@ -556,7 +662,58 @@ export async function handleApprovalInterception(
|
|
|
556
662
|
toolName: pending.length > 0 ? pending[0].toolName : undefined,
|
|
557
663
|
},
|
|
558
664
|
errorLogContext: { conversationId },
|
|
665
|
+
ephemeralUserId: slackEphemeralUserId(sourceChannel, actorExternalId),
|
|
559
666
|
});
|
|
560
667
|
|
|
561
668
|
return { handled: true, type: "assistant_turn" };
|
|
562
669
|
}
|
|
670
|
+
|
|
671
|
+
// ---------------------------------------------------------------------------
|
|
672
|
+
// Slack approval message edit helper
|
|
673
|
+
// ---------------------------------------------------------------------------
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* Fire-and-forget: edit a stale Slack approval message to indicate it has
|
|
677
|
+
* been resolved and remove the action buttons. Used when a button click
|
|
678
|
+
* arrives for an already-resolved approval.
|
|
679
|
+
*/
|
|
680
|
+
function editStaleSlackApprovalMessage(params: {
|
|
681
|
+
replyCallbackUrl: string;
|
|
682
|
+
chatId: string;
|
|
683
|
+
messageTs: string;
|
|
684
|
+
assistantId: string;
|
|
685
|
+
bearerToken?: string;
|
|
686
|
+
conversationId: string;
|
|
687
|
+
}): void {
|
|
688
|
+
const statusText = "This approval request has been resolved.";
|
|
689
|
+
const blocks = [
|
|
690
|
+
{
|
|
691
|
+
type: "section",
|
|
692
|
+
text: { type: "mrkdwn", text: statusText },
|
|
693
|
+
},
|
|
694
|
+
{
|
|
695
|
+
type: "context",
|
|
696
|
+
elements: [{ type: "mrkdwn", text: statusText }],
|
|
697
|
+
},
|
|
698
|
+
];
|
|
699
|
+
deliverChannelReply(
|
|
700
|
+
params.replyCallbackUrl,
|
|
701
|
+
{
|
|
702
|
+
chatId: params.chatId,
|
|
703
|
+
text: statusText,
|
|
704
|
+
blocks,
|
|
705
|
+
messageTs: params.messageTs,
|
|
706
|
+
assistantId: params.assistantId,
|
|
707
|
+
},
|
|
708
|
+
params.bearerToken,
|
|
709
|
+
).catch((err) => {
|
|
710
|
+
log.error(
|
|
711
|
+
{
|
|
712
|
+
err,
|
|
713
|
+
conversationId: params.conversationId,
|
|
714
|
+
messageTs: params.messageTs,
|
|
715
|
+
},
|
|
716
|
+
"Failed to edit stale Slack approval message",
|
|
717
|
+
);
|
|
718
|
+
});
|
|
719
|
+
}
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
deliverApprovalPrompt,
|
|
15
15
|
deliverChannelReply,
|
|
16
16
|
} from "../gateway-client.js";
|
|
17
|
+
import { buildActionLegend } from "../guardian-decision-types.js";
|
|
17
18
|
import type { ApprovalCopyGenerator } from "../http-types.js";
|
|
18
19
|
import { requiredDecisionKeywords } from "./channel-route-shared.js";
|
|
19
20
|
|
|
@@ -60,11 +61,15 @@ export async function deliverGeneratedApprovalPrompt(
|
|
|
60
61
|
approvalCopyGenerator,
|
|
61
62
|
);
|
|
62
63
|
|
|
64
|
+
// Append a legend explaining what each button does
|
|
65
|
+
const legend = buildActionLegend(uiMetadata.actions);
|
|
66
|
+
const richTextWithLegend = legend ? `${richText}\n\n${legend}` : richText;
|
|
67
|
+
|
|
63
68
|
try {
|
|
64
69
|
await deliverApprovalPrompt(
|
|
65
70
|
replyCallbackUrl,
|
|
66
71
|
chatId,
|
|
67
|
-
|
|
72
|
+
richTextWithLegend,
|
|
68
73
|
uiMetadata,
|
|
69
74
|
assistantId,
|
|
70
75
|
bearerToken,
|
|
@@ -11,6 +11,25 @@ import { composeApprovalMessageGenerative } from "../approval-message-composer.j
|
|
|
11
11
|
import { deliverChannelReply } from "../gateway-client.js";
|
|
12
12
|
import type { ApprovalCopyGenerator } from "../http-types.js";
|
|
13
13
|
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Deduplication for "already resolved" ephemeral messages
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Tracks recently sent stale approval notifications to prevent flooding the
|
|
20
|
+
* user when they rapidly click stale approval buttons. Keyed by
|
|
21
|
+
* `${chatId}:${scenario}` with a 30-second TTL per entry.
|
|
22
|
+
*/
|
|
23
|
+
const recentStaleNotifications = new Set<string>();
|
|
24
|
+
|
|
25
|
+
/** TTL in milliseconds for dedup entries. Exported for testing. */
|
|
26
|
+
export const STALE_DEDUP_TTL_MS = 30_000;
|
|
27
|
+
|
|
28
|
+
/** Clear the dedup cache. Exported for testing only. */
|
|
29
|
+
export function clearStaleNotificationCache(): void {
|
|
30
|
+
recentStaleNotifications.clear();
|
|
31
|
+
}
|
|
32
|
+
|
|
14
33
|
interface DeliverApprovalReplyParams {
|
|
15
34
|
context: ApprovalMessageContext;
|
|
16
35
|
replyCallbackUrl: string;
|
|
@@ -22,14 +41,19 @@ interface DeliverApprovalReplyParams {
|
|
|
22
41
|
errorLogMessage: string;
|
|
23
42
|
/** Extra fields merged into the pino error context. */
|
|
24
43
|
errorLogContext?: Record<string, unknown>;
|
|
44
|
+
/**
|
|
45
|
+
* When set, deliver via `chat.postEphemeral` so only this Slack user
|
|
46
|
+
* sees the message. Used to keep approval-related noise out of shared
|
|
47
|
+
* channels.
|
|
48
|
+
*/
|
|
49
|
+
ephemeralUserId?: string;
|
|
25
50
|
}
|
|
26
51
|
|
|
27
52
|
/**
|
|
28
53
|
* Compose a generative approval message and deliver it as a channel reply.
|
|
29
|
-
*
|
|
30
|
-
* try/catch blocks.
|
|
54
|
+
* Throws on failure — callers decide whether to swallow or propagate.
|
|
31
55
|
*/
|
|
32
|
-
async function
|
|
56
|
+
async function composeAndDeliver(
|
|
33
57
|
params: DeliverApprovalReplyParams,
|
|
34
58
|
): Promise<void> {
|
|
35
59
|
const {
|
|
@@ -39,24 +63,41 @@ async function deliverApprovalReply(
|
|
|
39
63
|
assistantId,
|
|
40
64
|
bearerToken,
|
|
41
65
|
approvalCopyGenerator,
|
|
42
|
-
|
|
43
|
-
errorLogMessage,
|
|
44
|
-
errorLogContext,
|
|
66
|
+
ephemeralUserId,
|
|
45
67
|
} = params;
|
|
46
68
|
|
|
69
|
+
const text = await composeApprovalMessageGenerative(
|
|
70
|
+
context,
|
|
71
|
+
{},
|
|
72
|
+
approvalCopyGenerator,
|
|
73
|
+
);
|
|
74
|
+
const payload: Parameters<typeof deliverChannelReply>[1] = {
|
|
75
|
+
chatId,
|
|
76
|
+
text,
|
|
77
|
+
assistantId,
|
|
78
|
+
};
|
|
79
|
+
if (ephemeralUserId) {
|
|
80
|
+
payload.ephemeral = true;
|
|
81
|
+
payload.user = ephemeralUserId;
|
|
82
|
+
}
|
|
83
|
+
await deliverChannelReply(replyCallbackUrl, payload, bearerToken);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Compose a generative approval message and deliver it as a channel reply.
|
|
88
|
+
* Swallows delivery errors and logs them — callers don't need their own
|
|
89
|
+
* try/catch blocks.
|
|
90
|
+
*/
|
|
91
|
+
async function deliverApprovalReply(
|
|
92
|
+
params: DeliverApprovalReplyParams,
|
|
93
|
+
): Promise<void> {
|
|
47
94
|
try {
|
|
48
|
-
|
|
49
|
-
context,
|
|
50
|
-
{},
|
|
51
|
-
approvalCopyGenerator,
|
|
52
|
-
);
|
|
53
|
-
await deliverChannelReply(
|
|
54
|
-
replyCallbackUrl,
|
|
55
|
-
{ chatId, text, assistantId },
|
|
56
|
-
bearerToken,
|
|
57
|
-
);
|
|
95
|
+
await composeAndDeliver(params);
|
|
58
96
|
} catch (err) {
|
|
59
|
-
logger.error(
|
|
97
|
+
params.logger.error(
|
|
98
|
+
{ err, ...params.errorLogContext },
|
|
99
|
+
params.errorLogMessage,
|
|
100
|
+
);
|
|
60
101
|
}
|
|
61
102
|
}
|
|
62
103
|
|
|
@@ -78,25 +119,60 @@ export interface DeliverStaleApprovalReplyParams {
|
|
|
78
119
|
extraContext?: Partial<ApprovalMessageContext>;
|
|
79
120
|
/** Extra fields merged into the pino error context. */
|
|
80
121
|
errorLogContext?: Record<string, unknown>;
|
|
122
|
+
/**
|
|
123
|
+
* When set, deliver via `chat.postEphemeral` so only this Slack user
|
|
124
|
+
* sees the message. Keeps approval noise out of shared channels.
|
|
125
|
+
*/
|
|
126
|
+
ephemeralUserId?: string;
|
|
81
127
|
}
|
|
82
128
|
|
|
83
129
|
/**
|
|
84
130
|
* Deliver a stale/already-resolved approval notice to a channel chat.
|
|
85
131
|
* Consolidates the repeated compose + deliver + try/catch pattern.
|
|
132
|
+
*
|
|
133
|
+
* For `approval_already_resolved` scenarios, deduplicates notifications
|
|
134
|
+
* per chat so rapid stale button clicks don't flood the user with
|
|
135
|
+
* repeated ephemeral warnings.
|
|
86
136
|
*/
|
|
87
137
|
export async function deliverStaleApprovalReply(
|
|
88
138
|
params: DeliverStaleApprovalReplyParams,
|
|
89
139
|
): Promise<void> {
|
|
90
|
-
const { scenario, sourceChannel, extraContext, ...rest } =
|
|
140
|
+
const { scenario, sourceChannel, extraContext, ephemeralUserId, ...rest } =
|
|
141
|
+
params;
|
|
91
142
|
|
|
92
|
-
|
|
143
|
+
const replyParams: DeliverApprovalReplyParams = {
|
|
93
144
|
...rest,
|
|
145
|
+
ephemeralUserId,
|
|
94
146
|
context: {
|
|
95
147
|
scenario,
|
|
96
148
|
channel: sourceChannel,
|
|
97
149
|
...extraContext,
|
|
98
150
|
},
|
|
99
|
-
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// Deduplicate "already resolved" ephemeral messages per chat.
|
|
154
|
+
// If the same (chatId, scenario) pair was notified within the TTL, skip.
|
|
155
|
+
if (scenario === "approval_already_resolved") {
|
|
156
|
+
const dedupeKey = `${rest.chatId}:${scenario}`;
|
|
157
|
+
if (recentStaleNotifications.has(dedupeKey)) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Cache the dedup key only after successful delivery so that failures
|
|
162
|
+
// don't silently suppress retries for the TTL window.
|
|
163
|
+
try {
|
|
164
|
+
await composeAndDeliver(replyParams);
|
|
165
|
+
recentStaleNotifications.add(dedupeKey);
|
|
166
|
+
setTimeout(() => {
|
|
167
|
+
recentStaleNotifications.delete(dedupeKey);
|
|
168
|
+
}, STALE_DEDUP_TTL_MS);
|
|
169
|
+
} catch (err) {
|
|
170
|
+
rest.logger.error({ err, ...rest.errorLogContext }, rest.errorLogMessage);
|
|
171
|
+
}
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
await deliverApprovalReply(replyParams);
|
|
100
176
|
}
|
|
101
177
|
|
|
102
178
|
// ---------------------------------------------------------------------------
|
|
@@ -114,6 +190,11 @@ export interface DeliverIdentityMismatchReplyParams {
|
|
|
114
190
|
errorLogMessage: string;
|
|
115
191
|
/** Extra fields merged into the pino error context. */
|
|
116
192
|
errorLogContext?: Record<string, unknown>;
|
|
193
|
+
/**
|
|
194
|
+
* When set, deliver via `chat.postEphemeral` so only this Slack user
|
|
195
|
+
* sees the message.
|
|
196
|
+
*/
|
|
197
|
+
ephemeralUserId?: string;
|
|
117
198
|
}
|
|
118
199
|
|
|
119
200
|
/**
|
|
@@ -123,10 +204,11 @@ export interface DeliverIdentityMismatchReplyParams {
|
|
|
123
204
|
export async function deliverIdentityMismatchReply(
|
|
124
205
|
params: DeliverIdentityMismatchReplyParams,
|
|
125
206
|
): Promise<void> {
|
|
126
|
-
const { sourceChannel, ...rest } = params;
|
|
207
|
+
const { sourceChannel, ephemeralUserId, ...rest } = params;
|
|
127
208
|
|
|
128
209
|
await deliverApprovalReply({
|
|
129
210
|
...rest,
|
|
211
|
+
ephemeralUserId,
|
|
130
212
|
context: {
|
|
131
213
|
scenario: "guardian_identity_mismatch",
|
|
132
214
|
channel: sourceChannel,
|
|
@@ -9,7 +9,10 @@ import { fileURLToPath } from "node:url";
|
|
|
9
9
|
|
|
10
10
|
import { getBaseDataDir } from "../../config/env-registry.js";
|
|
11
11
|
import { parseIdentityFields } from "../../daemon/handlers/identity.js";
|
|
12
|
-
import {
|
|
12
|
+
import { getMaxMigrationVersion } from "../../memory/migrations/registry.js";
|
|
13
|
+
import { getWorkspacePromptPath } from "../../util/platform.js";
|
|
14
|
+
import { WORKSPACE_MIGRATIONS } from "../../workspace/migrations/registry.js";
|
|
15
|
+
import { getLastWorkspaceMigrationId } from "../../workspace/migrations/runner.js";
|
|
13
16
|
import { httpError } from "../http-errors.js";
|
|
14
17
|
import type { RouteDefinition } from "../http-router.js";
|
|
15
18
|
import { getCachedIntro } from "./identity-intro-cache.js";
|
|
@@ -86,7 +89,7 @@ interface CpuInfo {
|
|
|
86
89
|
maxCores: number;
|
|
87
90
|
}
|
|
88
91
|
|
|
89
|
-
// Track CPU usage over a rolling window so /
|
|
92
|
+
// Track CPU usage over a rolling window so /v1/health reports near-real-time
|
|
90
93
|
// utilization instead of a lifetime average (total CPU time / total uptime).
|
|
91
94
|
const CPU_SAMPLE_INTERVAL_MS = 5_000;
|
|
92
95
|
let _lastCpuUsage: NodeJS.CpuUsage = process.cpuUsage();
|
|
@@ -133,6 +136,10 @@ function getPackageVersion(): string | undefined {
|
|
|
133
136
|
}
|
|
134
137
|
|
|
135
138
|
export function handleHealth(): Response {
|
|
139
|
+
return Response.json({ status: "ok" });
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function handleDetailedHealth(): Response {
|
|
136
143
|
return Response.json({
|
|
137
144
|
status: "healthy",
|
|
138
145
|
timestamp: new Date().toISOString(),
|
|
@@ -140,9 +147,18 @@ export function handleHealth(): Response {
|
|
|
140
147
|
disk: getDiskSpaceInfo(),
|
|
141
148
|
memory: getMemoryInfo(),
|
|
142
149
|
cpu: getCpuInfo(),
|
|
150
|
+
migrations: {
|
|
151
|
+
dbVersion: getMaxMigrationVersion(),
|
|
152
|
+
lastWorkspaceMigrationId:
|
|
153
|
+
getLastWorkspaceMigrationId(WORKSPACE_MIGRATIONS),
|
|
154
|
+
},
|
|
143
155
|
});
|
|
144
156
|
}
|
|
145
157
|
|
|
158
|
+
export function handleReadyz(): Response {
|
|
159
|
+
return Response.json({ status: "ok" });
|
|
160
|
+
}
|
|
161
|
+
|
|
146
162
|
export function handleGetIdentity(): Response {
|
|
147
163
|
const identityPath = getWorkspacePromptPath("IDENTITY.md");
|
|
148
164
|
if (!existsSync(identityPath)) {
|
|
@@ -163,31 +179,6 @@ export function handleGetIdentity(): Response {
|
|
|
163
179
|
// ignore
|
|
164
180
|
}
|
|
165
181
|
|
|
166
|
-
// Read lockfile for assistantId, cloud, and originSystem
|
|
167
|
-
let assistantId: string | undefined;
|
|
168
|
-
let cloud: string | undefined;
|
|
169
|
-
let originSystem: string | undefined;
|
|
170
|
-
try {
|
|
171
|
-
const lockData = readLockfile();
|
|
172
|
-
const assistants = lockData?.assistants as
|
|
173
|
-
| Array<Record<string, unknown>>
|
|
174
|
-
| undefined;
|
|
175
|
-
if (assistants && assistants.length > 0) {
|
|
176
|
-
// Use the most recently hatched assistant
|
|
177
|
-
const sorted = [...assistants].sort((a, b) => {
|
|
178
|
-
const dateA = new Date((a.hatchedAt as string) || 0).getTime();
|
|
179
|
-
const dateB = new Date((b.hatchedAt as string) || 0).getTime();
|
|
180
|
-
return dateB - dateA;
|
|
181
|
-
});
|
|
182
|
-
const latest = sorted[0];
|
|
183
|
-
assistantId = latest.assistantId as string | undefined;
|
|
184
|
-
cloud = latest.cloud as string | undefined;
|
|
185
|
-
originSystem = cloud === "local" ? "local" : cloud;
|
|
186
|
-
}
|
|
187
|
-
} catch {
|
|
188
|
-
// ignore -- lockfile may not exist
|
|
189
|
-
}
|
|
190
|
-
|
|
191
182
|
return Response.json({
|
|
192
183
|
name: fields.name ?? "",
|
|
193
184
|
role: fields.role ?? "",
|
|
@@ -195,9 +186,7 @@ export function handleGetIdentity(): Response {
|
|
|
195
186
|
emoji: fields.emoji ?? "",
|
|
196
187
|
home: fields.home ?? "",
|
|
197
188
|
version,
|
|
198
|
-
assistantId,
|
|
199
189
|
createdAt,
|
|
200
|
-
originSystem,
|
|
201
190
|
});
|
|
202
191
|
}
|
|
203
192
|
|
|
@@ -255,7 +244,7 @@ export function identityRouteDefinitions(): RouteDefinition[] {
|
|
|
255
244
|
{
|
|
256
245
|
endpoint: "health",
|
|
257
246
|
method: "GET",
|
|
258
|
-
handler: () =>
|
|
247
|
+
handler: () => handleDetailedHealth(),
|
|
259
248
|
},
|
|
260
249
|
{
|
|
261
250
|
endpoint: "identity",
|
|
@@ -507,6 +507,15 @@ export async function handleChannelInbound(
|
|
|
507
507
|
!result.duplicate &&
|
|
508
508
|
!guardianReplyResult.skipApprovalInterception
|
|
509
509
|
) {
|
|
510
|
+
// Extract the original approval message timestamp for Slack button
|
|
511
|
+
// cleanup. When a Slack block_actions payload is forwarded, the gateway
|
|
512
|
+
// sets sourceMetadata.messageId to the ts of the message containing
|
|
513
|
+
// the button. This lets us edit the message after resolution.
|
|
514
|
+
const approvalMessageTs =
|
|
515
|
+
sourceChannel === "slack" && typeof sourceMetadata?.messageId === "string"
|
|
516
|
+
? sourceMetadata.messageId
|
|
517
|
+
: undefined;
|
|
518
|
+
|
|
510
519
|
const approvalResult = await handleApprovalInterception({
|
|
511
520
|
conversationId: result.conversationId,
|
|
512
521
|
callbackData: body.callbackData,
|
|
@@ -520,6 +529,7 @@ export async function handleChannelInbound(
|
|
|
520
529
|
assistantId: canonicalAssistantId,
|
|
521
530
|
approvalCopyGenerator,
|
|
522
531
|
approvalConversationGenerator,
|
|
532
|
+
approvalMessageTs,
|
|
523
533
|
});
|
|
524
534
|
|
|
525
535
|
if (approvalResult.handled) {
|
|
@@ -598,6 +608,27 @@ export async function handleChannelInbound(
|
|
|
598
608
|
}
|
|
599
609
|
}
|
|
600
610
|
|
|
611
|
+
// On Slack, edit the original approval message to remove stale buttons
|
|
612
|
+
// and deliver an ephemeral error so the user gets visible feedback
|
|
613
|
+
// instead of a silent no-op (JARVIS-299).
|
|
614
|
+
if (sourceChannel === "slack" && replyCallbackUrl && approvalMessageTs) {
|
|
615
|
+
deliverChannelReply(
|
|
616
|
+
replyCallbackUrl,
|
|
617
|
+
{
|
|
618
|
+
chatId: conversationExternalId,
|
|
619
|
+
text: "This approval request has been resolved.",
|
|
620
|
+
messageTs: approvalMessageTs,
|
|
621
|
+
assistantId: canonicalAssistantId,
|
|
622
|
+
},
|
|
623
|
+
mintBearerToken(),
|
|
624
|
+
).catch((err) => {
|
|
625
|
+
log.error(
|
|
626
|
+
{ err, conversationId: result.conversationId },
|
|
627
|
+
"Failed to edit stale Slack approval message",
|
|
628
|
+
);
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
|
|
601
632
|
return Response.json({
|
|
602
633
|
accepted: true,
|
|
603
634
|
duplicate: false,
|
|
@@ -650,7 +681,6 @@ export async function handleChannelInbound(
|
|
|
650
681
|
mintBearerToken,
|
|
651
682
|
assistantId: canonicalAssistantId,
|
|
652
683
|
approvalCopyGenerator,
|
|
653
|
-
externalMessageId: sourceMessageId ?? externalMessageId,
|
|
654
684
|
chatType: sourceChatType,
|
|
655
685
|
});
|
|
656
686
|
}
|