@vellumai/assistant 0.11.7 → 0.11.8-staging.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +10 -10
- package/Dockerfile +1 -0
- package/docs/architecture/security.md +1 -1
- package/docs/error-handling.md +1 -2
- package/docs/guardian-request-flow.md +51 -5
- package/docs/trusted-contact-access.md +13 -12
- package/knip.json +1 -0
- package/node_modules/@vellumai/avatar-catalog/package.json +19 -0
- package/{src/avatar/character-components.ts → node_modules/@vellumai/avatar-catalog/src/catalog.ts} +12 -44
- package/node_modules/@vellumai/avatar-catalog/src/colors.ts +19 -0
- package/node_modules/@vellumai/avatar-catalog/src/index.ts +25 -0
- package/node_modules/@vellumai/avatar-catalog/src/types.ts +47 -0
- package/node_modules/@vellumai/avatar-catalog/tsconfig.json +20 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +7 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/no-response.ts +84 -0
- package/node_modules/@vellumai/credential-storage/src/static-credentials.ts +43 -12
- package/node_modules/@vellumai/environments/package.json +2 -1
- package/node_modules/@vellumai/environments/src/__tests__/test-path-guard.test.ts +89 -0
- package/node_modules/@vellumai/environments/src/test-path-guard.ts +113 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +7 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/no-response.ts +84 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-request-contract.test.ts +8 -18
- package/node_modules/@vellumai/gateway-client/src/__tests__/inbound-event-kind.test.ts +46 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/invite-contract.test.ts +1 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +46 -0
- package/node_modules/@vellumai/gateway-client/src/guardian-request-contract.ts +62 -21
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +14 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-event-kind.ts +53 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +11 -5
- package/node_modules/@vellumai/gateway-client/src/invite-contract.ts +1 -0
- package/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/service-contracts/src/channels.ts +7 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/no-response.ts +84 -0
- package/openapi.yaml +481 -9
- package/package.json +3 -1
- package/scripts/sync-llm-catalog.ts +3 -0
- package/src/__tests__/agent-loop-callsite-precedence.test.ts +16 -0
- package/src/__tests__/agent-loop-compacted-history-transform.test.ts +304 -0
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +3 -0
- package/src/__tests__/agent-wake-override-profile.test.ts +3 -0
- package/src/__tests__/anthropic-provider.test.ts +56 -0
- package/src/__tests__/approval-cascade.test.ts +0 -13
- package/src/__tests__/attachments-store.test.ts +117 -6
- package/src/__tests__/attachments.test.ts +122 -0
- package/src/__tests__/bundled-asset.test.ts +22 -1
- package/src/__tests__/channel-approval.test.ts +14 -7
- package/src/__tests__/channel-approvals.test.ts +3 -3
- package/src/__tests__/channel-delivery-store.test.ts +58 -0
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +1 -0
- package/src/__tests__/channel-invite-transport.test.ts +1 -0
- package/src/__tests__/channel-readiness-slack-remote.test.ts +3 -3
- package/src/__tests__/compaction-events.test.ts +0 -12
- package/src/__tests__/compactor-call-site-logging.test.ts +7 -1
- package/src/__tests__/compactor-retained-image-validation.test.ts +15 -0
- package/src/__tests__/config-schema.test.ts +35 -0
- package/src/__tests__/contacts-write.test.ts +141 -3
- package/src/__tests__/conversation-abort-tool-results.test.ts +0 -13
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +0 -12
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -13
- package/src/__tests__/conversation-agent-loop.test.ts +95 -13
- package/src/__tests__/conversation-confirmation-signals.test.ts +0 -13
- package/src/__tests__/conversation-load-history-repair.test.ts +418 -0
- package/src/__tests__/conversation-media-retry.test.ts +33 -2
- package/src/__tests__/conversation-pairing.test.ts +54 -0
- package/src/__tests__/conversation-process-callsite.test.ts +0 -12
- package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -13
- package/src/__tests__/conversation-queue.test.ts +4 -13
- package/src/__tests__/conversation-retry-route.test.ts +5 -0
- package/src/__tests__/conversation-routes-disk-view.test.ts +1 -1
- package/src/__tests__/conversation-routes-enabled-plugins.test.ts +1 -1
- package/src/__tests__/conversation-routes-hidden-queue.test.ts +1 -1
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -1
- package/src/__tests__/conversation-sight-frames.test.ts +615 -0
- package/src/__tests__/conversation-slash-queue.test.ts +0 -13
- package/src/__tests__/conversation-slash-unknown.test.ts +0 -13
- package/src/__tests__/conversation-speed-override.test.ts +0 -12
- package/src/__tests__/conversation-store-ephemeral.test.ts +0 -12
- package/src/__tests__/conversation-summarize-route.test.ts +5 -0
- package/src/__tests__/conversation-summarize-up-to.test.ts +102 -0
- package/src/__tests__/conversation-wait-for-idle.test.ts +0 -12
- package/src/__tests__/conversation-workspace-cache-state.test.ts +0 -5
- package/src/__tests__/conversation-workspace-injection.test.ts +0 -17
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -17
- package/src/__tests__/credential-health-service.test.ts +45 -0
- package/src/__tests__/credential-record-retire.test.ts +256 -0
- package/src/__tests__/credential-record-write-through.test.ts +8 -4
- package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +0 -4
- package/src/__tests__/db-conversation-inference-profile-migration.test.ts +0 -4
- package/src/__tests__/db-llm-request-log-provider-migration.test.ts +0 -8
- package/src/__tests__/delete-propagation.test.ts +121 -3
- package/src/__tests__/evict-conversations-for-reload.test.ts +46 -6
- package/src/__tests__/file-ops-service.test.ts +8 -3
- package/src/__tests__/gemini-provider.test.ts +64 -0
- package/src/__tests__/guardian-action-service-scope.test.ts +118 -0
- package/src/__tests__/guardian-card-withdrawal.test.ts +299 -5
- package/src/__tests__/guardian-expiry-notifier.test.ts +117 -13
- package/src/__tests__/guardian-gateway-sim.test.ts +119 -0
- package/src/__tests__/guardian-gateway-sim.ts +56 -20
- package/src/__tests__/guardian-reply-router-answer-mode.test.ts +6 -0
- package/src/__tests__/guardian-request-status-sync.test.ts +162 -0
- package/src/__tests__/guardian-wait-controller.test.ts +6 -0
- package/src/__tests__/headless-browser-mode.test.ts +27 -0
- package/src/__tests__/helpers/mock-conversation.ts +2 -0
- package/src/__tests__/list-messages-system-card.test.ts +70 -0
- package/src/__tests__/llm-catalog-parity.test.ts +2 -0
- package/src/__tests__/managed-profile-guard.test.ts +2 -2
- package/src/__tests__/media-resolve-image-validation.test.ts +106 -0
- package/src/__tests__/media-stream-server-integration.test.ts +6 -0
- package/src/__tests__/non-member-access-request.test.ts +12 -0
- package/src/__tests__/notification-discord-adapter.test.ts +76 -4
- package/src/__tests__/oauth-commands-routes.test.ts +21 -0
- package/src/__tests__/openai-provider.test.ts +63 -0
- package/src/__tests__/outbound-slack-persistence.test.ts +114 -2
- package/src/__tests__/persist-unsendable-image-downscale.test.ts +63 -1
- package/src/__tests__/persist-unsendable-image.test.ts +19 -0
- package/src/__tests__/plugin-app-serve-routes.test.ts +2 -2
- package/src/__tests__/plugin-import-boundary-guard.test.ts +1 -0
- package/src/__tests__/process-message-background-slack.test.ts +1 -1
- package/src/__tests__/process-message-display-content.test.ts +1 -1
- package/src/__tests__/processing-flag-persist-failure.test.ts +0 -5
- package/src/__tests__/prompt-cache-cross-turn-stability.test.ts +0 -17
- package/src/__tests__/qdrant-manager.test.ts +6 -0
- package/src/__tests__/reaction-persistence.test.ts +58 -62
- package/src/__tests__/retry-thinking-adaptive-only.test.ts +67 -0
- package/src/__tests__/server-history-render.test.ts +22 -0
- package/src/__tests__/sight-frame-retention.test.ts +362 -0
- package/src/__tests__/slack-reaction-approvals.test.ts +44 -24
- package/src/__tests__/slack-reaction-guardian-approval.test.ts +29 -1
- package/src/__tests__/subagent-fork-notifications.test.ts +2 -0
- package/src/__tests__/subagent-manager-notify.test.ts +2 -0
- package/src/__tests__/subagent-notify-parent.test.ts +2 -0
- package/src/__tests__/subagent-notify-stale-rebuild.test.ts +85 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +4 -4
- package/src/__tests__/terminal-tools.test.ts +9 -9
- package/src/__tests__/tool-result-spool.test.ts +5 -8
- package/src/__tests__/usage-attribution.test.ts +26 -0
- package/src/acp/__tests__/acp-auth-marker-store.test.ts +191 -0
- package/src/acp/__tests__/acp-claude-oauth.test.ts +262 -0
- package/src/acp/__tests__/acp-connect-card-state.test.ts +17 -0
- package/src/acp/__tests__/helpers/acp-history-db.ts +10 -3
- package/src/acp/acp-auth-marker-store.ts +196 -0
- package/src/acp/acp-claude-oauth.ts +154 -4
- package/src/acp/acp-connect-card-state.ts +49 -8
- package/src/acp/agent-process.ts +1 -0
- package/src/acp/auto-install.ts +1 -0
- package/src/acp/client-handler.ts +1 -0
- package/src/acp/prepare-agent-env.ts +79 -1
- package/src/acp/resolve-agent.ts +12 -0
- package/src/acp/session-manager.test.ts +183 -0
- package/src/acp/session-manager.ts +110 -6
- package/src/acp/types.ts +17 -0
- package/src/agent/attachments.ts +7 -2
- package/src/agent/loop.ts +59 -1
- package/src/api/events/contact-form-closed.ts +32 -0
- package/src/api/events/contact-record-request.ts +63 -0
- package/src/api/events/contact-request.ts +10 -3
- package/src/api/index.ts +20 -0
- package/src/api/responses/conversation-message.ts +20 -0
- package/src/api/responses/home.ts +101 -0
- package/src/approvals/AGENTS.md +1 -1
- package/src/approvals/guardian-card-withdrawal.ts +127 -30
- package/src/approvals/guardian-decision-primitive.ts +9 -0
- package/src/approvals/guardian-expiry-notifier.ts +22 -16
- package/src/approvals/guardian-request-status-sync.ts +98 -0
- package/src/avatar/svg-compositor.ts +1 -1
- package/src/avatar/traits-png-sync.ts +1 -1
- package/src/bundler/app-compiler.ts +1 -0
- package/src/bundler/compiler-tools.ts +1 -0
- package/src/bundler/package-resolver.ts +1 -0
- package/src/calls/__tests__/voice-session-bridge.test.ts +183 -3
- package/src/calls/guardian-action-sweep.ts +6 -1
- package/src/calls/voice-session-bridge.ts +46 -1
- package/src/channels/__tests__/gateway-guardian-requests.test.ts +13 -18
- package/src/channels/gateway-guardian-requests.ts +37 -18
- package/src/cli/__tests__/contacts-record-prompt-cli.test.ts +329 -0
- package/src/cli/commands/__tests__/browser.test.ts +65 -1
- package/src/cli/commands/browser.ts +30 -10
- package/src/cli/commands/contacts.help.ts +173 -11
- package/src/cli/commands/contacts.ts +343 -4
- package/src/cli/commands/db/refresh.ts +1 -0
- package/src/cli/commands/oauth/index.help.ts +6 -3
- package/src/cli/commands/oauth/request.test.ts +37 -0
- package/src/cli/commands/oauth/request.ts +7 -6
- package/src/cli/lib/bundled-marketplace.json +1 -1
- package/src/cli/lib/install-from-github.ts +20 -19
- package/src/cli/lib/install-plugin-dependencies.ts +3 -5
- package/src/cli/lib/merge-plugin-tree.ts +1 -0
- package/src/cli/lib/publish-plugin.ts +4 -1
- package/src/config/__tests__/balanced-model-experiment.test.ts +63 -31
- package/src/config/__tests__/default-profile-catalog.test.ts +2 -2
- package/src/config/balanced-model-experiment.ts +15 -9
- package/src/config/bundled-skills/contacts/SKILL.md +61 -18
- package/src/config/default-profile-catalog.ts +12 -11
- package/src/config/feature-flag-registry.json +22 -13
- package/src/config/schema.ts +2 -0
- package/src/config/schemas/__tests__/live-voice.test.ts +13 -0
- package/src/config/schemas/live-voice.ts +7 -0
- package/src/config/schemas/sight.ts +45 -0
- package/src/config/skills.ts +2 -1
- package/src/contacts/__tests__/mirror-reconciler.test.ts +352 -0
- package/src/contacts/contact-store.ts +47 -4
- package/src/contacts/contacts-write.ts +5 -2
- package/src/contacts/mirror-reconciler.ts +210 -0
- package/src/contacts/mirror-tombstones.ts +42 -0
- package/src/contacts/notify-contacts-changed.ts +11 -2
- package/src/context/compactor.ts +47 -19
- package/src/context/tool-result-spool.ts +21 -14
- package/src/conversations/message-consolidation.ts +2 -2
- package/src/credential-health/credential-health-service.ts +37 -6
- package/src/daemon/conversation-agent-loop-handlers.ts +19 -2
- package/src/daemon/conversation-agent-loop.ts +100 -1
- package/src/daemon/conversation-media-retry.ts +11 -6
- package/src/daemon/conversation-messaging.ts +293 -30
- package/src/daemon/conversation-runtime-assembly.ts +67 -0
- package/src/daemon/conversation-sight-frames.ts +187 -0
- package/src/daemon/conversation-store.ts +18 -28
- package/src/daemon/conversation-surfaces.ts +12 -2
- package/src/daemon/conversation-tool-setup.ts +39 -10
- package/src/daemon/conversation.ts +159 -16
- package/src/daemon/daemon-control.ts +6 -17
- package/src/daemon/handlers/config-model.test.ts +1 -0
- package/src/daemon/handlers/conversations.ts +10 -11
- package/src/daemon/install-assistant-command.ts +1 -0
- package/src/daemon/lifecycle.ts +10 -13
- package/src/daemon/message-types/sync.ts +4 -0
- package/src/daemon/process-message.ts +2 -17
- package/src/daemon/reaction-history-render.test.ts +162 -0
- package/src/daemon/reaction-history-render.ts +115 -0
- package/src/daemon/reaction-record.test.ts +162 -0
- package/src/daemon/reaction-record.ts +83 -0
- package/src/daemon/shutdown-handlers.ts +0 -2
- package/src/daemon/summarize-boundary.ts +12 -0
- package/src/daemon/video-thumbnail.ts +1 -1
- package/src/home/feed-types.ts +4 -0
- package/src/home/feed-writer.ts +25 -0
- package/src/live-voice/__tests__/live-voice-attach-frame.test.ts +679 -0
- package/src/live-voice/__tests__/live-voice-attach-image.test.ts +8 -5
- package/src/live-voice/__tests__/live-voice-photo.test.ts +637 -1
- package/src/live-voice/__tests__/live-voice-sight-frame-inline.test.ts +787 -0
- package/src/live-voice/__tests__/live-voice-sight-frame.test.ts +347 -0
- package/src/live-voice/__tests__/live-voice-vad.test.ts +109 -0
- package/src/live-voice/live-voice-photo.ts +420 -65
- package/src/live-voice/live-voice-session.ts +311 -27
- package/src/live-voice/protocol.ts +147 -11
- package/src/messaging/provider-message-metadata.ts +7 -0
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +113 -1
- package/src/messaging/providers/channel-transport.ts +37 -0
- package/src/messaging/providers/discord/api.ts +4 -4
- package/src/messaging/providers/discord/send.test.ts +177 -2
- package/src/messaging/providers/discord/send.ts +197 -10
- package/src/messaging/providers/discord/transport.ts +10 -0
- package/src/messaging/providers/index.ts +42 -0
- package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +129 -3
- package/src/messaging/providers/slack/__tests__/auth.test.ts +35 -0
- package/src/messaging/providers/slack/__tests__/message-content.test.ts +152 -0
- package/src/messaging/providers/slack/adapter.ts +112 -27
- package/src/messaging/providers/slack/api.test.ts +10 -0
- package/src/messaging/providers/slack/auth.ts +32 -6
- package/src/messaging/providers/slack/client.ts +0 -15
- package/src/messaging/providers/slack/message-content.ts +137 -0
- package/src/messaging/providers/slack/send.test.ts +43 -2
- package/src/messaging/providers/slack/send.ts +18 -5
- package/src/messaging/providers/slack/transport.ts +10 -0
- package/src/messaging/providers/slack/types.ts +21 -2
- package/src/messaging/providers/telegram-bot/adapter.ts +5 -1
- package/src/messaging/providers/telegram-bot/send.test.ts +49 -2
- package/src/messaging/providers/telegram-bot/send.ts +41 -1
- package/src/messaging/providers/telegram-bot/transport.ts +10 -0
- package/src/messaging/providers/telegram-bot/withdraw.ts +13 -5
- package/src/messaging/reaction-envelopes.ts +79 -0
- package/src/monitoring/control.ts +2 -2
- package/src/monitoring/page-cache.ts +5 -1
- package/src/notifications/AGENTS.md +1 -1
- package/src/notifications/__tests__/guardian-feed-projection.test.ts +360 -0
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +8 -0
- package/src/notifications/adapters/discord.ts +62 -9
- package/src/notifications/conversation-pairing.ts +31 -4
- package/src/notifications/emit-signal.ts +8 -5
- package/src/notifications/guardian-delivery-recorder.ts +47 -6
- package/src/notifications/guardian-feed-projection.ts +492 -0
- package/src/notifications/home-feed-side-effect.ts +59 -11
- package/src/oauth/byo-connection.test.ts +22 -0
- package/src/oauth/byo-connection.ts +11 -5
- package/src/oauth/connection.ts +3 -2
- package/src/oauth/platform-connection.test.ts +35 -0
- package/src/oauth/platform-connection.ts +18 -13
- package/src/oauth/scope-utils.ts +48 -0
- package/src/oauth/seed-providers.ts +12 -0
- package/src/outbound-proxy/certs.ts +5 -4
- package/src/permissions/question-guardian-request.test.ts +7 -1
- package/src/persistence/attachments-store.ts +134 -31
- package/src/persistence/conversation-crud.ts +137 -4
- package/src/persistence/conversation-types.ts +109 -0
- package/src/persistence/db-connection.ts +3 -21
- package/src/persistence/delivery-crud.ts +42 -12
- package/src/persistence/embeddings/__tests__/embedding-local-lifecycle.test.ts +15 -2
- package/src/persistence/embeddings/embedding-local.ts +7 -112
- package/src/persistence/embeddings/qdrant-manager.ts +4 -1
- package/src/persistence/llm-request-log-store.ts +1 -1
- package/src/persistence/migrations/370-acp-session-history-auth-error-code.ts +36 -0
- package/src/persistence/migrations/371-acp-session-history-auth-error-credential.ts +40 -0
- package/src/persistence/migrations/372-create-acp-refused-credentials.ts +34 -0
- package/src/persistence/migrations/373-acp-auth-marker-index.ts +27 -0
- package/src/persistence/migrations/__tests__/370-acp-session-history-auth-error-code.test.ts +65 -0
- package/src/persistence/migrations/__tests__/371-acp-session-history-auth-error-credential.test.ts +68 -0
- package/src/persistence/migrations/__tests__/372-create-acp-refused-credentials.test.ts +64 -0
- package/src/persistence/migrations/__tests__/373-acp-auth-marker-index.test.ts +82 -0
- package/src/persistence/schema/acp.ts +25 -0
- package/src/persistence/steps.ts +8 -0
- package/src/platform/platform-patch-queue.test.ts +43 -41
- package/src/plugin-api/index.ts +12 -0
- package/src/plugins/defaults/compaction/window-manager.ts +2 -0
- package/src/plugins/defaults/image-recovery/recover.ts +12 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +166 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +3 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +3 -0
- package/src/plugins/defaults/memory/jobs-worker.ts +1 -2
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +53 -9
- package/src/plugins/defaults/memory/src/__tests__/memory-worker-routes.test.ts +4 -7
- package/src/plugins/defaults/memory/src/memory-worker-routes.ts +1 -1
- package/src/plugins/defaults/memory/v1/job-handlers/index-maintenance.ts +15 -1
- package/src/plugins/defaults/memory/worker-control.ts +1 -2
- package/src/providers/content-block-schema.ts +1 -0
- package/src/providers/fireworks/client.ts +9 -1
- package/src/providers/gemini/client.ts +22 -15
- package/src/providers/media-resolve.ts +24 -10
- package/src/providers/model-catalog.ts +91 -0
- package/src/providers/openai/chat-completions-provider.ts +44 -5
- package/src/providers/retry.ts +16 -11
- package/src/providers/types.ts +56 -0
- package/src/routes/__tests__/control.test.ts +4 -3
- package/src/routes/control.ts +1 -1
- package/src/routes/route-host-client.ts +1 -1
- package/src/runtime/AGENTS.md +30 -12
- package/src/runtime/__tests__/agent-wake.test.ts +71 -6
- package/src/runtime/__tests__/guardian-form-registry.test.ts +242 -0
- package/src/runtime/access-request-helper.ts +15 -7
- package/src/runtime/agent-wake.ts +16 -4
- package/src/runtime/channel-approval-types.ts +8 -5
- package/src/runtime/channel-invite-transport.ts +2 -0
- package/src/runtime/channel-invite-transports/discord.ts +27 -0
- package/src/runtime/channel-readiness-service.ts +4 -4
- package/src/runtime/channel-reply-delivery.ts +72 -28
- package/src/runtime/guardian-action-service.ts +9 -5
- package/src/runtime/guardian-form-registry.ts +256 -0
- package/src/runtime/guardian-reply-router.ts +33 -14
- package/src/runtime/invite-instruction-generator.ts +14 -2
- package/src/runtime/migrations/vbundle-metadata-merge.ts +4 -7
- package/src/runtime/no-response.ts +13 -74
- package/src/runtime/routes/__tests__/acp-routes.test.ts +527 -0
- package/src/runtime/routes/__tests__/app-management-shared-apps.test.ts +74 -0
- package/src/runtime/routes/__tests__/contact-form-kinds.test.ts +22 -0
- package/src/runtime/routes/__tests__/contact-record-prompt-route.test.ts +303 -0
- package/src/runtime/routes/__tests__/monitoring-routes.test.ts +3 -4
- package/src/runtime/routes/__tests__/pending-interactions-route.test.ts +6 -0
- package/src/runtime/routes/__tests__/route-host-worker-routes.test.ts +3 -4
- package/src/runtime/routes/__tests__/schedule-worker-routes.test.ts +5 -6
- package/src/runtime/routes/acp-routes-list.test.ts +58 -0
- package/src/runtime/routes/acp-routes.ts +253 -47
- package/src/runtime/routes/app-management-routes.ts +80 -47
- package/src/runtime/routes/approval-routes.ts +9 -0
- package/src/runtime/routes/archive-utils.ts +6 -1
- package/src/runtime/routes/avatar-routes.ts +1 -1
- package/src/runtime/routes/channel-route-shared.ts +18 -20
- package/src/runtime/routes/contact-prompt-routes.ts +271 -105
- package/src/runtime/routes/conversation-routes.ts +41 -32
- package/src/runtime/routes/credential-prompt-routes.ts +10 -1
- package/src/runtime/routes/debug-bash-routes.ts +1 -0
- package/src/runtime/routes/guardian-expiry-sweep.ts +89 -31
- package/src/runtime/routes/guardian-form-routes.ts +115 -0
- package/src/runtime/routes/inbound-message-handler.ts +82 -29
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +16 -3
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +6 -0
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +6 -0
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +6 -1
- package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +50 -17
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +97 -94
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/integrations/slack/channel.ts +39 -18
- package/src/runtime/routes/migration-routes.ts +1 -3
- package/src/runtime/routes/monitoring-routes.ts +0 -3
- package/src/runtime/routes/oauth-commands-routes.ts +18 -5
- package/src/runtime/routes/route-host-worker-routes.ts +1 -1
- package/src/runtime/routes/schedule-worker-routes.ts +1 -1
- package/src/runtime/tool-grant-request-helper.ts +4 -0
- package/src/schedule/run-script.ts +1 -0
- package/src/schedule/scheduler.ts +1 -1
- package/src/schedule/worker-control.ts +2 -2
- package/src/security/oauth2.ts +2 -11
- package/src/security/secure-keys.ts +55 -8
- package/src/skills/catalog-install.ts +7 -4
- package/src/skills/clawhub.ts +1 -0
- package/src/skills/inline-command-runner.ts +1 -0
- package/src/stt/__tests__/room-noise-floor.test.ts +108 -0
- package/src/stt/room-noise-floor.ts +140 -0
- package/src/subagent/notify.ts +49 -3
- package/src/tools/__tests__/tool-input-schemas.test.ts +2 -0
- package/src/tools/acp/spawn.ts +2 -2
- package/src/tools/browser/__tests__/browser-status.test.ts +9 -0
- package/src/tools/browser/browser-execution.ts +10 -43
- package/src/tools/browser/browser-manager.ts +1 -0
- package/src/tools/browser/browser-status-constants.ts +6 -5
- package/src/tools/browser/runtime-check.ts +2 -0
- package/src/tools/channel/react-to-message.test.ts +193 -0
- package/src/tools/channel/react-to-message.ts +159 -0
- package/src/tools/credentials/metadata-store.ts +115 -19
- package/src/tools/credentials/store.ts +0 -2
- package/src/tools/filesystem/read.ts +2 -2
- package/src/tools/host-filesystem/read.ts +2 -2
- package/src/tools/host-terminal/host-shell.test.ts +24 -0
- package/src/tools/host-terminal/host-shell.ts +11 -38
- package/src/tools/shared/filesystem/file-ops-service.ts +10 -7
- package/src/tools/shared/shell-output.test.ts +104 -0
- package/src/tools/shared/shell-output.ts +112 -39
- package/src/tools/skills/sandbox-runner.ts +1 -0
- package/src/tools/system/request-permission.test.ts +34 -0
- package/src/tools/system/request-permission.ts +29 -5
- package/src/tools/terminal/shell.test.ts +63 -0
- package/src/tools/terminal/shell.ts +12 -44
- package/src/tools/tool-input-schemas.ts +2 -0
- package/src/tools/tool-manifest.ts +2 -0
- package/src/usage/attribution.ts +12 -4
- package/src/util/__tests__/worker-ownership.test.ts +77 -0
- package/src/util/__tests__/worker-process-command.test.ts +246 -1
- package/src/util/bun-runtime.ts +44 -13
- package/src/util/bundled-asset.ts +9 -1
- package/src/util/disk-usage.ts +1 -0
- package/src/util/file-use.ts +4 -1
- package/src/util/guardian-form-timeouts.ts +50 -0
- package/src/util/image-conversion.ts +1 -0
- package/src/util/oauth-request-body.test.ts +59 -0
- package/src/util/oauth-request-body.ts +56 -5
- package/src/util/platform.ts +69 -73
- package/src/util/process-table.ts +16 -5
- package/src/util/read-stdin.ts +5 -0
- package/src/util/spawn.ts +1 -0
- package/src/util/sqlite3-runtime.ts +25 -5
- package/src/util/worker-ownership.ts +193 -0
- package/src/util/worker-process.ts +375 -37
- package/src/workspace/git-service.ts +4 -12
- package/src/workspace/migrations/010-app-dir-rename.ts +1 -0
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for what `contacts create` / `update` / `delete` put in front of the
|
|
3
|
+
* guardian.
|
|
4
|
+
*
|
|
5
|
+
* The form is seeded from these bodies and the guardian submits what it shows,
|
|
6
|
+
* so a field the CLI leaves empty is a field the guardian unknowingly writes
|
|
7
|
+
* over. That makes the request body, not just the eventual write, the thing
|
|
8
|
+
* worth pinning.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
12
|
+
|
|
13
|
+
import { GUARDIAN_FORM_SETTLE_MS } from "../../util/guardian-form-timeouts.js";
|
|
14
|
+
|
|
15
|
+
interface IpcCall {
|
|
16
|
+
operationId: string;
|
|
17
|
+
options?: Record<string, unknown>;
|
|
18
|
+
/** Transport options, where the socket deadline lives. */
|
|
19
|
+
callOptions?: { timeoutMs?: number };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let calls: IpcCall[] = [];
|
|
23
|
+
|
|
24
|
+
const contact = {
|
|
25
|
+
id: "ct_1",
|
|
26
|
+
displayName: "Alice",
|
|
27
|
+
notes: "Dentist, referred by Bob",
|
|
28
|
+
contactType: "human",
|
|
29
|
+
createdAt: 0,
|
|
30
|
+
updatedAt: 0,
|
|
31
|
+
interactionCount: 0,
|
|
32
|
+
channels: [
|
|
33
|
+
{
|
|
34
|
+
id: "ch_1",
|
|
35
|
+
contactId: "ct_1",
|
|
36
|
+
type: "email",
|
|
37
|
+
address: "alice@example.com",
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/** What a contact with no notes actually looks like on the wire. */
|
|
43
|
+
const contactWithoutNotes = { ...contact, notes: null };
|
|
44
|
+
|
|
45
|
+
let contactForRead: Record<string, unknown> = contact;
|
|
46
|
+
/** Make the display readback fail, as a transient IPC hiccup would. */
|
|
47
|
+
let readFails = false;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The ordinary answers. Held as a named function so `beforeEach` can put it
|
|
51
|
+
* back: a test that installs its own implementation would otherwise leave it
|
|
52
|
+
* in place for everything after it, since clearing a mock keeps the last
|
|
53
|
+
* implementation.
|
|
54
|
+
*/
|
|
55
|
+
/** Whether the daemon reports the submitted notes as having reached storage. */
|
|
56
|
+
let notesSaved: boolean | undefined;
|
|
57
|
+
/** Whether the daemon reports that nothing the guardian submitted landed. */
|
|
58
|
+
let nothingWritten: boolean | undefined;
|
|
59
|
+
|
|
60
|
+
const baseIpcImplementation = async (
|
|
61
|
+
operationId: string,
|
|
62
|
+
options?: Record<string, unknown>,
|
|
63
|
+
callOptions?: { timeoutMs?: number },
|
|
64
|
+
) => {
|
|
65
|
+
calls.push({ operationId, options, callOptions });
|
|
66
|
+
if (operationId === "getContact") {
|
|
67
|
+
if (readFails) {
|
|
68
|
+
// 404-shaped, as a gateway-backed read reports a contact it cannot see.
|
|
69
|
+
return { ok: false, error: "Contact not found", statusCode: 404 };
|
|
70
|
+
}
|
|
71
|
+
return { ok: true, result: { ok: true, contact: contactForRead } };
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
ok: true,
|
|
75
|
+
result: { ok: true, contactId: contact.id, notesSaved, nothingWritten },
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const cliIpcCallMock = mock(baseIpcImplementation);
|
|
80
|
+
|
|
81
|
+
const actualCliClient = await import("../../ipc/cli-client.js");
|
|
82
|
+
mock.module("../../ipc/cli-client.js", () => ({
|
|
83
|
+
...actualCliClient,
|
|
84
|
+
cliIpcCall: cliIpcCallMock,
|
|
85
|
+
}));
|
|
86
|
+
|
|
87
|
+
const { runAssistantCommand } = await import("./run-assistant-command.js");
|
|
88
|
+
|
|
89
|
+
function recordPromptBody(): Record<string, unknown> {
|
|
90
|
+
const call = calls.find((c) => c.operationId === "contacts_record_prompt");
|
|
91
|
+
expect(call).toBeDefined();
|
|
92
|
+
return (call!.options as { body: Record<string, unknown> }).body;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
describe("contacts record prompts", () => {
|
|
96
|
+
beforeEach(() => {
|
|
97
|
+
calls = [];
|
|
98
|
+
contactForRead = contact;
|
|
99
|
+
readFails = false;
|
|
100
|
+
notesSaved = undefined;
|
|
101
|
+
nothingWritten = undefined;
|
|
102
|
+
// Global and sticky: the failure-path cases below set it, and a later test
|
|
103
|
+
// asserting success would otherwise read their exit code as its own.
|
|
104
|
+
// Cleared to 0 rather than undefined, which does not reset it.
|
|
105
|
+
process.exitCode = 0;
|
|
106
|
+
cliIpcCallMock.mockClear();
|
|
107
|
+
cliIpcCallMock.mockImplementation(baseIpcImplementation);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("a name-only update carries the notes the contact already has", async () => {
|
|
111
|
+
await runAssistantCommand(
|
|
112
|
+
"contacts",
|
|
113
|
+
"update",
|
|
114
|
+
"ct_1",
|
|
115
|
+
"--name",
|
|
116
|
+
"Alice Chen",
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const body = recordPromptBody();
|
|
120
|
+
expect(body.operation).toBe("update");
|
|
121
|
+
expect(body.displayName).toBe("Alice Chen");
|
|
122
|
+
// The form compares against what is stored, so it has to be told.
|
|
123
|
+
expect(body.currentNotes).toBe("Dentist, referred by Bob");
|
|
124
|
+
// Without this the form shows an empty notes box and the guardian submits
|
|
125
|
+
// it, erasing the notes as a side effect of a rename.
|
|
126
|
+
expect(body.notes).toBe("Dentist, referred by Bob");
|
|
127
|
+
expect(body.currentDisplayName).toBe("Alice");
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("an explicit empty --notes stays empty, so clearing still works", async () => {
|
|
131
|
+
await runAssistantCommand("contacts", "update", "ct_1", "--notes", "");
|
|
132
|
+
|
|
133
|
+
expect(recordPromptBody().notes).toBe("");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("--notes overrides the existing value", async () => {
|
|
137
|
+
await runAssistantCommand(
|
|
138
|
+
"contacts",
|
|
139
|
+
"update",
|
|
140
|
+
"ct_1",
|
|
141
|
+
"--notes",
|
|
142
|
+
"Moved to Berlin",
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
expect(recordPromptBody().notes).toBe("Moved to Berlin");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("a contact with no notes still opens the form", async () => {
|
|
149
|
+
// `notes` is nullable on the wire, and the daemon's schema takes a string
|
|
150
|
+
// or nothing. Passing the null through would reject the request and the
|
|
151
|
+
// guardian would never see a form.
|
|
152
|
+
contactForRead = contactWithoutNotes;
|
|
153
|
+
|
|
154
|
+
await runAssistantCommand(
|
|
155
|
+
"contacts",
|
|
156
|
+
"update",
|
|
157
|
+
"ct_1",
|
|
158
|
+
"--name",
|
|
159
|
+
"Alice Chen",
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
const body = recordPromptBody();
|
|
163
|
+
expect(body.notes).toBeUndefined();
|
|
164
|
+
expect("notes" in body ? body.notes : undefined).not.toBeNull();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("the requested timeout bounds the form, not just the socket", async () => {
|
|
168
|
+
// A CLI that gave up first would report failure while the form stayed
|
|
169
|
+
// open, and a later answer would write something the caller was told had
|
|
170
|
+
// not happened.
|
|
171
|
+
await runAssistantCommand(
|
|
172
|
+
"contacts",
|
|
173
|
+
"create",
|
|
174
|
+
"--name",
|
|
175
|
+
"Alice",
|
|
176
|
+
"--timeout",
|
|
177
|
+
"1000",
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
const call = calls.find((c) => c.operationId === "contacts_record_prompt");
|
|
181
|
+
const body = (call!.options as { body: Record<string, unknown> }).body;
|
|
182
|
+
expect(body.timeoutMs).toBe(1000);
|
|
183
|
+
// The socket has to outlast an answer arriving at the deadline, which
|
|
184
|
+
// starts the settle window from there. Giving up sooner would report a
|
|
185
|
+
// failure while the write went on to commit.
|
|
186
|
+
expect(call!.callOptions?.timeoutMs).toBeGreaterThan(
|
|
187
|
+
1000 + GUARDIAN_FORM_SETTLE_MS,
|
|
188
|
+
);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test.each(["1e3", "100.5", "300000ms", "abc", "-5"])(
|
|
192
|
+
"a timeout of %p is refused rather than partly parsed",
|
|
193
|
+
async (raw) => {
|
|
194
|
+
// parseInt takes a valid prefix, so "1e3" would otherwise become a 1ms
|
|
195
|
+
// form: open and shut before anyone could answer it.
|
|
196
|
+
await runAssistantCommand(
|
|
197
|
+
"contacts",
|
|
198
|
+
"create",
|
|
199
|
+
"--name",
|
|
200
|
+
"Alice",
|
|
201
|
+
"--timeout",
|
|
202
|
+
raw,
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
expect(
|
|
206
|
+
calls.some((c) => c.operationId === "contacts_record_prompt"),
|
|
207
|
+
).toBe(false);
|
|
208
|
+
},
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
test("a timeout past the ceiling is refused before the guardian is bothered", async () => {
|
|
212
|
+
await runAssistantCommand(
|
|
213
|
+
"contacts",
|
|
214
|
+
"create",
|
|
215
|
+
"--name",
|
|
216
|
+
"Alice",
|
|
217
|
+
"--timeout",
|
|
218
|
+
"99999999",
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
expect(calls.some((c) => c.operationId === "contacts_record_prompt")).toBe(
|
|
222
|
+
false,
|
|
223
|
+
);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test("a write still reports success when only the display readback fails", async () => {
|
|
227
|
+
// The guardian has answered and the write is done. Exiting non-zero here
|
|
228
|
+
// says otherwise, and a retried create makes a second contact.
|
|
229
|
+
readFails = true;
|
|
230
|
+
|
|
231
|
+
await runAssistantCommand("contacts", "create", "--name", "Alice");
|
|
232
|
+
|
|
233
|
+
expect(calls.some((c) => c.operationId === "contacts_record_prompt")).toBe(
|
|
234
|
+
true,
|
|
235
|
+
);
|
|
236
|
+
expect(process.exitCode).toBeFalsy();
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test("an update that wrote nothing exits nonzero", async () => {
|
|
240
|
+
// The guardian submits only what they changed, so a proposed name they
|
|
241
|
+
// left at its stored value never reaches the write. Whether anything
|
|
242
|
+
// landed is the gateway's to report, not this command's to infer.
|
|
243
|
+
notesSaved = false;
|
|
244
|
+
nothingWritten = true;
|
|
245
|
+
|
|
246
|
+
await runAssistantCommand(
|
|
247
|
+
"contacts",
|
|
248
|
+
"update",
|
|
249
|
+
"ct_1",
|
|
250
|
+
"--name",
|
|
251
|
+
"Alice",
|
|
252
|
+
"--notes",
|
|
253
|
+
"Moved",
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
expect(process.exitCode).toBe(1);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
test("a rename that loses its notes still succeeds, since the rename landed", async () => {
|
|
260
|
+
notesSaved = false;
|
|
261
|
+
nothingWritten = false;
|
|
262
|
+
|
|
263
|
+
await runAssistantCommand(
|
|
264
|
+
"contacts",
|
|
265
|
+
"update",
|
|
266
|
+
"ct_1",
|
|
267
|
+
"--name",
|
|
268
|
+
"Alice Chen",
|
|
269
|
+
"--notes",
|
|
270
|
+
"Moved",
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
expect(process.exitCode).toBeFalsy();
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test("create proposes only what was asked for", async () => {
|
|
277
|
+
await runAssistantCommand("contacts", "create", "--name", "Alice");
|
|
278
|
+
|
|
279
|
+
const body = recordPromptBody();
|
|
280
|
+
expect(body.operation).toBe("create");
|
|
281
|
+
expect(body.displayName).toBe("Alice");
|
|
282
|
+
expect(body.contactId).toBeUndefined();
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
test("delete reads the contact first, so a bad id fails before the guardian sees a form", async () => {
|
|
286
|
+
await runAssistantCommand("contacts", "delete", "ct_1");
|
|
287
|
+
|
|
288
|
+
const body = recordPromptBody();
|
|
289
|
+
expect(body.operation).toBe("delete");
|
|
290
|
+
expect(body.contactId).toBe("ct_1");
|
|
291
|
+
expect(body.currentDisplayName).toBe("Alice");
|
|
292
|
+
// Two contacts can share a name, so the confirmation carries the channels
|
|
293
|
+
// that identify this one and that the delete will take with it.
|
|
294
|
+
expect(body.channels).toEqual([
|
|
295
|
+
{ type: "email", address: "alice@example.com" },
|
|
296
|
+
]);
|
|
297
|
+
// The read comes first: the form names the contact it is about.
|
|
298
|
+
expect(calls[0]!.operationId).toBe("getContact");
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
test("delete reaches the confirmation for a contact the read cannot see", async () => {
|
|
302
|
+
// A dual-write gap leaves contacts in the assistant mirror that this
|
|
303
|
+
// gateway-backed read misses. `contacts list --query` surfaces them and
|
|
304
|
+
// the delete supports removing them, so refusing here would strand an id
|
|
305
|
+
// the guardian can see.
|
|
306
|
+
readFails = true;
|
|
307
|
+
|
|
308
|
+
await runAssistantCommand("contacts", "delete", "ct_orphan");
|
|
309
|
+
|
|
310
|
+
const body = recordPromptBody();
|
|
311
|
+
expect(body.operation).toBe("delete");
|
|
312
|
+
expect(body.contactId).toBe("ct_orphan");
|
|
313
|
+
// Nothing to show but the id, and no channels to compare against.
|
|
314
|
+
expect(body.channels).toEqual([]);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// The refusing side of that branch (an update against a contact the read
|
|
318
|
+
// cannot see, or a delete for an id nothing knows about) has no test: it
|
|
319
|
+
// ends in `exitFromIpcResult`, which calls `process.exit` and takes the test
|
|
320
|
+
// runner with it.
|
|
321
|
+
|
|
322
|
+
test("update refuses when neither --name nor --notes is given", async () => {
|
|
323
|
+
await runAssistantCommand("contacts", "update", "ct_1");
|
|
324
|
+
|
|
325
|
+
expect(calls.some((c) => c.operationId === "contacts_record_prompt")).toBe(
|
|
326
|
+
false,
|
|
327
|
+
);
|
|
328
|
+
});
|
|
329
|
+
});
|
|
@@ -67,7 +67,9 @@ mock.module("../../../util/logger.js", () => ({
|
|
|
67
67
|
// Import module under test (after mocks)
|
|
68
68
|
// ---------------------------------------------------------------------------
|
|
69
69
|
|
|
70
|
-
const { registerBrowserCommand } = await import(
|
|
70
|
+
const { formatBrowserStatusLines, registerBrowserCommand } = await import(
|
|
71
|
+
"../browser.js"
|
|
72
|
+
);
|
|
71
73
|
|
|
72
74
|
// ---------------------------------------------------------------------------
|
|
73
75
|
// Test helper
|
|
@@ -533,6 +535,68 @@ describe("--json output", () => {
|
|
|
533
535
|
});
|
|
534
536
|
});
|
|
535
537
|
|
|
538
|
+
describe("formatBrowserStatusLines", () => {
|
|
539
|
+
test("prints userActions for a disconnected extension", () => {
|
|
540
|
+
const lines = formatBrowserStatusLines(
|
|
541
|
+
JSON.stringify({
|
|
542
|
+
requestedMode: "auto",
|
|
543
|
+
recommendedMode: "cdp-inspect",
|
|
544
|
+
stickyConversationMode: null,
|
|
545
|
+
modes: [
|
|
546
|
+
{
|
|
547
|
+
mode: "extension",
|
|
548
|
+
available: false,
|
|
549
|
+
autoCandidate: true,
|
|
550
|
+
summary:
|
|
551
|
+
"Extension mode is unavailable: no Chrome Extension is connected.",
|
|
552
|
+
userActions: [
|
|
553
|
+
"Install the Vellum Assistant Chrome extension from the Chrome Web Store: https://chromewebstore.google.com/detail/vellum-assistant-browser/hphbdmpffeigpcdjkckleobjmhhokpne",
|
|
554
|
+
"Tell the user to make sure a browser is open with the Vellum Chrome extension on.",
|
|
555
|
+
],
|
|
556
|
+
},
|
|
557
|
+
],
|
|
558
|
+
}),
|
|
559
|
+
);
|
|
560
|
+
|
|
561
|
+
expect(lines).not.toBeNull();
|
|
562
|
+
const rendered = lines!.join("\n");
|
|
563
|
+
expect(rendered).toContain("Requested mode: auto");
|
|
564
|
+
expect(rendered).toContain("✗ extension (auto-candidate)");
|
|
565
|
+
expect(rendered).toContain(
|
|
566
|
+
"Install the Vellum Assistant Chrome extension from the Chrome Web Store:",
|
|
567
|
+
);
|
|
568
|
+
expect(rendered).toContain(
|
|
569
|
+
"Tell the user to make sure a browser is open with the Vellum Chrome extension on.",
|
|
570
|
+
);
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
test("omits userActions when the mode is ready", () => {
|
|
574
|
+
const lines = formatBrowserStatusLines(
|
|
575
|
+
JSON.stringify({
|
|
576
|
+
requestedMode: "extension",
|
|
577
|
+
recommendedMode: "extension",
|
|
578
|
+
stickyConversationMode: null,
|
|
579
|
+
modes: [
|
|
580
|
+
{
|
|
581
|
+
mode: "extension",
|
|
582
|
+
available: true,
|
|
583
|
+
autoCandidate: true,
|
|
584
|
+
summary: "Extension mode is ready.",
|
|
585
|
+
userActions: [],
|
|
586
|
+
},
|
|
587
|
+
],
|
|
588
|
+
}),
|
|
589
|
+
);
|
|
590
|
+
|
|
591
|
+
expect(lines).not.toBeNull();
|
|
592
|
+
expect(lines!.some((line) => line.trim().startsWith("- "))).toBe(false);
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
test("returns null for non-JSON status content", () => {
|
|
596
|
+
expect(formatBrowserStatusLines("not json")).toBeNull();
|
|
597
|
+
});
|
|
598
|
+
});
|
|
599
|
+
|
|
536
600
|
describe("error exit codes", () => {
|
|
537
601
|
test("exits with non-zero code when IPC fails", async () => {
|
|
538
602
|
mockIpcResult = {
|
|
@@ -262,6 +262,7 @@ interface StatusModeEntry {
|
|
|
262
262
|
available: boolean;
|
|
263
263
|
autoCandidate: boolean;
|
|
264
264
|
summary: string;
|
|
265
|
+
userActions?: string[];
|
|
265
266
|
}
|
|
266
267
|
|
|
267
268
|
interface StatusPayload {
|
|
@@ -271,31 +272,50 @@ interface StatusPayload {
|
|
|
271
272
|
modes: StatusModeEntry[];
|
|
272
273
|
}
|
|
273
274
|
|
|
274
|
-
|
|
275
|
+
/** Render `assistant browser status` JSON as human-readable lines. */
|
|
276
|
+
export function formatBrowserStatusLines(content: string): string[] | null {
|
|
275
277
|
let data: StatusPayload;
|
|
276
278
|
try {
|
|
277
279
|
data = JSON.parse(content);
|
|
278
280
|
} catch {
|
|
279
|
-
|
|
280
|
-
return;
|
|
281
|
+
return null;
|
|
281
282
|
}
|
|
282
283
|
|
|
283
|
-
|
|
284
|
+
const lines: string[] = [];
|
|
285
|
+
lines.push(`Requested mode: ${data.requestedMode}`);
|
|
284
286
|
if (data.recommendedMode) {
|
|
285
|
-
|
|
287
|
+
lines.push(`Recommended: ${data.recommendedMode}`);
|
|
286
288
|
}
|
|
287
289
|
if (data.stickyConversationMode) {
|
|
288
|
-
|
|
290
|
+
lines.push(`Sticky mode: ${data.stickyConversationMode}`);
|
|
289
291
|
}
|
|
290
|
-
|
|
292
|
+
lines.push("");
|
|
291
293
|
|
|
292
294
|
const modes = data.modes ?? [];
|
|
293
295
|
for (const mode of modes) {
|
|
294
296
|
const icon = mode.available ? "✓" : "✗";
|
|
295
297
|
const auto = mode.autoCandidate ? " (auto-candidate)" : "";
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
298
|
+
lines.push(` ${icon} ${mode.mode}${auto}`);
|
|
299
|
+
lines.push(` ${mode.summary}`);
|
|
300
|
+
for (const action of mode.userActions ?? []) {
|
|
301
|
+
if (!action) {
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
lines.push(` - ${action}`);
|
|
305
|
+
}
|
|
306
|
+
lines.push("");
|
|
307
|
+
}
|
|
308
|
+
return lines;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function formatBrowserStatus(content: string): void {
|
|
312
|
+
const lines = formatBrowserStatusLines(content);
|
|
313
|
+
if (lines == null) {
|
|
314
|
+
log.info(content);
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
for (const line of lines) {
|
|
318
|
+
log.info(line);
|
|
299
319
|
}
|
|
300
320
|
}
|
|
301
321
|
|