@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
|
@@ -1,26 +1,8 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
|
|
6
1
|
import { safeStringSlice } from "../../util/unicode.js";
|
|
7
2
|
|
|
8
3
|
export const MAX_OUTPUT_LENGTH = 20_000;
|
|
9
4
|
|
|
10
|
-
|
|
11
|
-
const trackedTempFiles = new Set<string>();
|
|
12
|
-
|
|
13
|
-
/** Remove all tracked truncated-output temp files. Safe to call multiple times. */
|
|
14
|
-
export function cleanupShellOutputTempFiles(): void {
|
|
15
|
-
for (const filePath of trackedTempFiles) {
|
|
16
|
-
try {
|
|
17
|
-
unlinkSync(filePath);
|
|
18
|
-
} catch {
|
|
19
|
-
// File may already be gone — ignore.
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
trackedTempFiles.clear();
|
|
23
|
-
}
|
|
5
|
+
export const OUTPUT_TRUNCATED_TAG = `<output_truncated limit="20K" />`;
|
|
24
6
|
|
|
25
7
|
export interface ShellOutputResult {
|
|
26
8
|
content: string;
|
|
@@ -28,11 +10,20 @@ export interface ShellOutputResult {
|
|
|
28
10
|
isError: boolean;
|
|
29
11
|
}
|
|
30
12
|
|
|
13
|
+
type StdioStream = "stdout" | "stderr";
|
|
14
|
+
|
|
15
|
+
type DataListener = (data: Buffer | string) => void;
|
|
16
|
+
|
|
17
|
+
type ReadableLike = {
|
|
18
|
+
on(event: "data", listener: DataListener): unknown;
|
|
19
|
+
};
|
|
20
|
+
|
|
31
21
|
/**
|
|
32
|
-
* Format
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* is the
|
|
22
|
+
* Format already-materialized stdout/stderr into the tool result. Spawn sites
|
|
23
|
+
* that read pipes should collect through {@link BoundedStdioCollector} so the
|
|
24
|
+
* oversized tail never lands in memory. This helper still truncates a string
|
|
25
|
+
* that is already over the cap (desktop host-bash payloads) without writing
|
|
26
|
+
* the discarded tail to disk.
|
|
36
27
|
*/
|
|
37
28
|
export function formatShellOutput(
|
|
38
29
|
stdout: string,
|
|
@@ -40,6 +31,7 @@ export function formatShellOutput(
|
|
|
40
31
|
code: number | null,
|
|
41
32
|
timedOut: boolean,
|
|
42
33
|
timeoutSec: number,
|
|
34
|
+
options?: { truncated?: boolean },
|
|
43
35
|
): ShellOutputResult {
|
|
44
36
|
let output = stdout;
|
|
45
37
|
if (stderr) {
|
|
@@ -54,22 +46,13 @@ export function formatShellOutput(
|
|
|
54
46
|
statusParts.push(msg);
|
|
55
47
|
}
|
|
56
48
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
writeFileSync(fullOutputPath, output, { encoding: "utf-8", mode: 0o600 });
|
|
65
|
-
trackedTempFiles.add(fullOutputPath);
|
|
66
|
-
} catch {
|
|
67
|
-
fullOutputPath = undefined;
|
|
68
|
-
}
|
|
69
|
-
const fileAttr = fullOutputPath ? ` file="${fullOutputPath}"` : "";
|
|
70
|
-
const msg = `<output_truncated limit="20K"${fileAttr} />`;
|
|
71
|
-
output = safeStringSlice(output, 0, MAX_OUTPUT_LENGTH) + `\n${msg}`;
|
|
72
|
-
statusParts.push(msg);
|
|
49
|
+
const truncated =
|
|
50
|
+
options?.truncated === true || output.length > MAX_OUTPUT_LENGTH;
|
|
51
|
+
if (truncated) {
|
|
52
|
+
output =
|
|
53
|
+
safeStringSlice(output, 0, MAX_OUTPUT_LENGTH) +
|
|
54
|
+
`\n${OUTPUT_TRUNCATED_TAG}`;
|
|
55
|
+
statusParts.push(OUTPUT_TRUNCATED_TAG);
|
|
73
56
|
}
|
|
74
57
|
|
|
75
58
|
if (!output.trim()) {
|
|
@@ -90,3 +73,93 @@ export function formatShellOutput(
|
|
|
90
73
|
isError: code !== 0 || timedOut,
|
|
91
74
|
};
|
|
92
75
|
}
|
|
76
|
+
|
|
77
|
+
function toBuffer(data: Buffer | string): Buffer {
|
|
78
|
+
if (Buffer.isBuffer(data)) {
|
|
79
|
+
return data;
|
|
80
|
+
}
|
|
81
|
+
return Buffer.from(data);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Caps bash stdout/stderr while they stream. Bytes past {@link MAX_OUTPUT_LENGTH}
|
|
86
|
+
* are dropped (the process is left running so the pipe can drain). stdout and
|
|
87
|
+
* stderr share one budget, matching the concatenated tool-result cap.
|
|
88
|
+
*/
|
|
89
|
+
export class BoundedStdioCollector {
|
|
90
|
+
private readonly stdoutParts: Buffer[] = [];
|
|
91
|
+
private readonly stderrParts: Buffer[] = [];
|
|
92
|
+
private keptBytes = 0;
|
|
93
|
+
private truncated = false;
|
|
94
|
+
|
|
95
|
+
consume(
|
|
96
|
+
stream: StdioStream,
|
|
97
|
+
chunk: Buffer,
|
|
98
|
+
onOutput?: (text: string) => void,
|
|
99
|
+
): void {
|
|
100
|
+
if (this.truncated || chunk.length === 0) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const remaining = MAX_OUTPUT_LENGTH - this.keptBytes;
|
|
105
|
+
if (remaining <= 0) {
|
|
106
|
+
this.truncated = true;
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Copy a prefix so a single oversized chunk can be GC'd. subarray()
|
|
111
|
+
// would keep the whole allocation alive through the kept view.
|
|
112
|
+
const kept =
|
|
113
|
+
chunk.length <= remaining
|
|
114
|
+
? chunk
|
|
115
|
+
: Buffer.from(chunk.subarray(0, remaining));
|
|
116
|
+
if (stream === "stdout") {
|
|
117
|
+
this.stdoutParts.push(kept);
|
|
118
|
+
} else {
|
|
119
|
+
this.stderrParts.push(kept);
|
|
120
|
+
}
|
|
121
|
+
this.keptBytes += kept.length;
|
|
122
|
+
if (kept.length < chunk.length) {
|
|
123
|
+
this.truncated = true;
|
|
124
|
+
}
|
|
125
|
+
onOutput?.(kept.toString());
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
get keptByteLength(): number {
|
|
129
|
+
return this.keptBytes;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
get didTruncate(): boolean {
|
|
133
|
+
return this.truncated;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
format(
|
|
137
|
+
code: number | null,
|
|
138
|
+
timedOut: boolean,
|
|
139
|
+
timeoutSec: number,
|
|
140
|
+
): ShellOutputResult {
|
|
141
|
+
return formatShellOutput(
|
|
142
|
+
Buffer.concat(this.stdoutParts).toString(),
|
|
143
|
+
Buffer.concat(this.stderrParts).toString(),
|
|
144
|
+
code,
|
|
145
|
+
timedOut,
|
|
146
|
+
timeoutSec,
|
|
147
|
+
{ truncated: this.truncated },
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Attach a shared-budget collector to a child's stdout and stderr pipes. */
|
|
153
|
+
export function attachBoundedStdio(
|
|
154
|
+
child: { stdout?: ReadableLike | null; stderr?: ReadableLike | null },
|
|
155
|
+
options?: { onOutput?: (text: string) => void },
|
|
156
|
+
): BoundedStdioCollector {
|
|
157
|
+
const collector = new BoundedStdioCollector();
|
|
158
|
+
child.stdout?.on("data", (data) => {
|
|
159
|
+
collector.consume("stdout", toBuffer(data), options?.onOutput);
|
|
160
|
+
});
|
|
161
|
+
child.stderr?.on("data", (data) => {
|
|
162
|
+
collector.consume("stderr", toBuffer(data), options?.onOutput);
|
|
163
|
+
});
|
|
164
|
+
return collector;
|
|
165
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import type { ToolContext } from "../types.js";
|
|
4
|
+
import { requestSystemPermissionTool } from "./request-permission.js";
|
|
5
|
+
|
|
6
|
+
const ctx = (clientOs?: string): ToolContext =>
|
|
7
|
+
({ conversationId: "c", workingDir: "/", clientOs }) as ToolContext;
|
|
8
|
+
|
|
9
|
+
describe("request_system_permission", () => {
|
|
10
|
+
test("uses Windows Settings URIs for Windows clients", async () => {
|
|
11
|
+
const result = await requestSystemPermissionTool.execute(
|
|
12
|
+
{ permission_type: "microphone" },
|
|
13
|
+
ctx("windows"),
|
|
14
|
+
);
|
|
15
|
+
expect(result.isError).toBe(false);
|
|
16
|
+
expect(result.content).toContain("ms-settings:privacy-microphone");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("errors for kinds the Windows client cannot route", async () => {
|
|
20
|
+
const result = await requestSystemPermissionTool.execute(
|
|
21
|
+
{ permission_type: "camera" },
|
|
22
|
+
ctx("windows"),
|
|
23
|
+
);
|
|
24
|
+
expect(result.isError).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("defaults to macOS System Settings", async () => {
|
|
28
|
+
const result = await requestSystemPermissionTool.execute(
|
|
29
|
+
{ permission_type: "camera" },
|
|
30
|
+
ctx(),
|
|
31
|
+
);
|
|
32
|
+
expect(result.content).toContain("x-apple.systempreferences");
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -25,7 +25,7 @@ const PERMISSION_TYPES = [
|
|
|
25
25
|
|
|
26
26
|
type PermissionType = (typeof PERMISSION_TYPES)[number];
|
|
27
27
|
|
|
28
|
-
const
|
|
28
|
+
const MACOS_SETTINGS_URLS: Record<PermissionType, string> = {
|
|
29
29
|
full_disk_access:
|
|
30
30
|
"x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles",
|
|
31
31
|
accessibility:
|
|
@@ -46,6 +46,21 @@ const SETTINGS_URLS: Record<PermissionType, string> = {
|
|
|
46
46
|
"x-apple.systempreferences:com.apple.preference.security?Privacy_Camera",
|
|
47
47
|
};
|
|
48
48
|
|
|
49
|
+
// Only the pages the Windows desktop client can route through its
|
|
50
|
+
// permissions bridge; other kinds have no in-app remediation there yet.
|
|
51
|
+
const WINDOWS_SETTINGS_URLS: Partial<Record<PermissionType, string>> = {
|
|
52
|
+
screen_recording: "ms-settings:privacy-graphicscaptureprogrammatic",
|
|
53
|
+
microphone: "ms-settings:privacy-microphone",
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const resolveSystemPermissionSettingsUrl = (
|
|
57
|
+
permType: PermissionType,
|
|
58
|
+
clientOs: string | undefined,
|
|
59
|
+
): string | undefined =>
|
|
60
|
+
clientOs === "windows"
|
|
61
|
+
? WINDOWS_SETTINGS_URLS[permType]
|
|
62
|
+
: MACOS_SETTINGS_URLS[permType];
|
|
63
|
+
|
|
49
64
|
const FRIENDLY_NAMES: Record<PermissionType, string> = {
|
|
50
65
|
full_disk_access: "Full Disk Access",
|
|
51
66
|
accessibility: "Accessibility",
|
|
@@ -68,7 +83,7 @@ const FRIENDLY_NAMES: Record<PermissionType, string> = {
|
|
|
68
83
|
export const requestSystemPermissionInputSchema = z.looseObject({
|
|
69
84
|
permission_type: z
|
|
70
85
|
.enum(PERMISSION_TYPES)
|
|
71
|
-
.describe("The
|
|
86
|
+
.describe("The system permission (macOS or Windows) to request"),
|
|
72
87
|
activity: z
|
|
73
88
|
.string()
|
|
74
89
|
.describe(
|
|
@@ -81,7 +96,7 @@ export const requestSystemPermissionInputSchema = z.looseObject({
|
|
|
81
96
|
export const requestSystemPermissionTool = {
|
|
82
97
|
name: "request_system_permission",
|
|
83
98
|
description:
|
|
84
|
-
"Request a
|
|
99
|
+
"Request a system permission via macOS System Settings or Windows Settings. " +
|
|
85
100
|
"Use when a tool fails with a permission/access error (e.g. 'Operation not permitted', 'EACCES', sandbox denial). " +
|
|
86
101
|
"Do not explain how to open System Settings manually - this tool handles it with a clickable button.",
|
|
87
102
|
category: "system",
|
|
@@ -94,7 +109,7 @@ export const requestSystemPermissionTool = {
|
|
|
94
109
|
|
|
95
110
|
async execute(
|
|
96
111
|
input: Record<string, unknown>,
|
|
97
|
-
|
|
112
|
+
context: ToolContext,
|
|
98
113
|
): Promise<ToolExecutionResult> {
|
|
99
114
|
const parsed = requestSystemPermissionInputSchema.safeParse(input);
|
|
100
115
|
if (!parsed.success) {
|
|
@@ -103,7 +118,16 @@ export const requestSystemPermissionTool = {
|
|
|
103
118
|
const permType = parsed.data.permission_type;
|
|
104
119
|
|
|
105
120
|
const friendly = FRIENDLY_NAMES[permType];
|
|
106
|
-
const settingsUrl =
|
|
121
|
+
const settingsUrl = resolveSystemPermissionSettingsUrl(
|
|
122
|
+
permType,
|
|
123
|
+
context.clientOs,
|
|
124
|
+
);
|
|
125
|
+
if (!settingsUrl) {
|
|
126
|
+
return {
|
|
127
|
+
content: `${friendly} cannot be requested in-app on ${context.clientOs}. Ask the user to check the matching Privacy page in Windows Settings.`,
|
|
128
|
+
isError: true,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
107
131
|
|
|
108
132
|
return {
|
|
109
133
|
content: [
|
|
@@ -1,6 +1,13 @@
|
|
|
1
|
+
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
1
4
|
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
5
|
|
|
3
6
|
import { setConfig } from "../../__tests__/helpers/set-config.js";
|
|
7
|
+
import {
|
|
8
|
+
MAX_OUTPUT_LENGTH,
|
|
9
|
+
OUTPUT_TRUNCATED_TAG,
|
|
10
|
+
} from "../shared/shell-output.js";
|
|
4
11
|
import type { ToolContext } from "../types.js";
|
|
5
12
|
|
|
6
13
|
// ---------------------------------------------------------------------------
|
|
@@ -199,3 +206,59 @@ describe("foreground stdin handling", () => {
|
|
|
199
206
|
expect(result.content).not.toContain("ENXIO");
|
|
200
207
|
});
|
|
201
208
|
});
|
|
209
|
+
|
|
210
|
+
describe("streaming stdout cap", () => {
|
|
211
|
+
let tmpDir = "";
|
|
212
|
+
|
|
213
|
+
beforeEach(async () => {
|
|
214
|
+
events.length = 0;
|
|
215
|
+
_clearRegistryForTesting();
|
|
216
|
+
tmpDir = await mkdtemp(join(tmpdir(), "bash-cap-"));
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
afterEach(async () => {
|
|
220
|
+
_clearRegistryForTesting();
|
|
221
|
+
if (tmpDir) {
|
|
222
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test("foreground drops stdout past the cap and lets the process finish", async () => {
|
|
227
|
+
const result = await shellTool.execute(
|
|
228
|
+
{
|
|
229
|
+
command: `${process.execPath} -e "process.stdout.write('x'.repeat(100000)); require('node:fs').writeFileSync('sentinel.txt','done')"`,
|
|
230
|
+
activity: "test",
|
|
231
|
+
},
|
|
232
|
+
{ ...makeContext(), workingDir: tmpDir },
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
expect(result.isError).toBeFalsy();
|
|
236
|
+
expect(result.content).toContain(OUTPUT_TRUNCATED_TAG);
|
|
237
|
+
expect(result.content).not.toContain("file=");
|
|
238
|
+
expect(result.content.length).toBeLessThan(MAX_OUTPUT_LENGTH + 80);
|
|
239
|
+
expect(await readFile(join(tmpDir, "sentinel.txt"), "utf-8")).toBe("done");
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test("background drops stdout past the cap and lets the process finish", async () => {
|
|
243
|
+
const result = await shellTool.execute(
|
|
244
|
+
{
|
|
245
|
+
command: `${process.execPath} -e "process.stdout.write('x'.repeat(100000)); require('node:fs').writeFileSync('sentinel.txt','done')"`,
|
|
246
|
+
activity: "test",
|
|
247
|
+
background: true,
|
|
248
|
+
},
|
|
249
|
+
{ ...makeContext(), workingDir: tmpDir },
|
|
250
|
+
);
|
|
251
|
+
expect(result.isError).toBeFalsy();
|
|
252
|
+
|
|
253
|
+
await waitFor(() => completedEvents().length > 0);
|
|
254
|
+
const completed = completedEvents();
|
|
255
|
+
expect(completed).toHaveLength(1);
|
|
256
|
+
expect(completed[0]).toMatchObject({ status: "completed", exitCode: 0 });
|
|
257
|
+
expect(String(completed[0]?.output)).toContain(OUTPUT_TRUNCATED_TAG);
|
|
258
|
+
expect(String(completed[0]?.output)).not.toContain("file=");
|
|
259
|
+
expect(String(completed[0]?.output).length).toBeLessThan(
|
|
260
|
+
MAX_OUTPUT_LENGTH + 80,
|
|
261
|
+
);
|
|
262
|
+
expect(await readFile(join(tmpDir, "sentinel.txt"), "utf-8")).toBe("done");
|
|
263
|
+
});
|
|
264
|
+
});
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
getSessionEnv,
|
|
33
33
|
} from "../network/script-proxy/index.js";
|
|
34
34
|
import {
|
|
35
|
-
|
|
35
|
+
attachBoundedStdio,
|
|
36
36
|
MAX_OUTPUT_LENGTH,
|
|
37
37
|
} from "../shared/shell-output.js";
|
|
38
38
|
import {
|
|
@@ -316,8 +316,6 @@ export const shellTool = {
|
|
|
316
316
|
}
|
|
317
317
|
|
|
318
318
|
const bgId = generateBackgroundToolId();
|
|
319
|
-
const stdoutChunks: Buffer[] = [];
|
|
320
|
-
const stderrChunks: Buffer[] = [];
|
|
321
319
|
let timedOut = false;
|
|
322
320
|
let aborted = false;
|
|
323
321
|
const startedAt = Date.now();
|
|
@@ -327,7 +325,9 @@ export const shellTool = {
|
|
|
327
325
|
env,
|
|
328
326
|
stdio: ["ignore", "pipe", "pipe"],
|
|
329
327
|
detached: true,
|
|
328
|
+
windowsHide: true,
|
|
330
329
|
});
|
|
330
|
+
const collector = attachBoundedStdio(child);
|
|
331
331
|
|
|
332
332
|
const killTree = buildKillTree(child, {
|
|
333
333
|
toolName: "bash",
|
|
@@ -342,14 +342,6 @@ export const shellTool = {
|
|
|
342
342
|
killTree("timeout");
|
|
343
343
|
}, timeoutMs);
|
|
344
344
|
|
|
345
|
-
child.stdout.on("data", (data: Buffer) => {
|
|
346
|
-
stdoutChunks.push(data);
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
child.stderr.on("data", (data: Buffer) => {
|
|
350
|
-
stderrChunks.push(data);
|
|
351
|
-
});
|
|
352
|
-
|
|
353
345
|
// Guard against double-wake: when spawn fails (e.g. invalid cwd),
|
|
354
346
|
// Node emits both 'error' and 'close' for the same child process.
|
|
355
347
|
// Only the first handler to fire should wake the agent.
|
|
@@ -375,15 +367,7 @@ export const shellTool = {
|
|
|
375
367
|
timedOut,
|
|
376
368
|
});
|
|
377
369
|
|
|
378
|
-
const
|
|
379
|
-
const stderr = Buffer.concat(stderrChunks).toString();
|
|
380
|
-
const fmtResult = formatShellOutput(
|
|
381
|
-
stdout,
|
|
382
|
-
stderr,
|
|
383
|
-
code,
|
|
384
|
-
timedOut,
|
|
385
|
-
timeoutSec,
|
|
386
|
-
);
|
|
370
|
+
const fmtResult = collector.format(code, timedOut, timeoutSec);
|
|
387
371
|
|
|
388
372
|
const status: BackgroundToolCompletedEvent["status"] = aborted
|
|
389
373
|
? "cancelled"
|
|
@@ -392,7 +376,7 @@ export const shellTool = {
|
|
|
392
376
|
: code === 0
|
|
393
377
|
? "completed"
|
|
394
378
|
: "failed";
|
|
395
|
-
// A cancelled command exits with a null code, which
|
|
379
|
+
// A cancelled command exits with a null code, which the formatter
|
|
396
380
|
// frames as "failed"; surface the cancellation instead.
|
|
397
381
|
const output =
|
|
398
382
|
status === "cancelled"
|
|
@@ -427,8 +411,8 @@ export const shellTool = {
|
|
|
427
411
|
untrustedOutput: {
|
|
428
412
|
content: output,
|
|
429
413
|
source: "tool_result",
|
|
430
|
-
//
|
|
431
|
-
//
|
|
414
|
+
// Bounded output plus the truncation marker; a larger budget
|
|
415
|
+
// keeps wrapUntrustedContent from slicing the marker off.
|
|
432
416
|
maxChars: MAX_OUTPUT_LENGTH * 2,
|
|
433
417
|
},
|
|
434
418
|
});
|
|
@@ -538,8 +522,6 @@ export const shellTool = {
|
|
|
538
522
|
// Foreground mode: await the process and return its output.
|
|
539
523
|
// -----------------------------------------------------------------------
|
|
540
524
|
const result = await new Promise<ToolExecutionResult>((resolve) => {
|
|
541
|
-
const stdoutChunks: Buffer[] = [];
|
|
542
|
-
const stderrChunks: Buffer[] = [];
|
|
543
525
|
let timedOut = false;
|
|
544
526
|
const startedAt = Date.now();
|
|
545
527
|
|
|
@@ -548,6 +530,10 @@ export const shellTool = {
|
|
|
548
530
|
env,
|
|
549
531
|
stdio: ["ignore", "pipe", "pipe"],
|
|
550
532
|
detached: true,
|
|
533
|
+
windowsHide: true,
|
|
534
|
+
});
|
|
535
|
+
const collector = attachBoundedStdio(child, {
|
|
536
|
+
onOutput: context.onOutput,
|
|
551
537
|
});
|
|
552
538
|
|
|
553
539
|
const killTree = buildKillTree(child, {
|
|
@@ -572,16 +558,6 @@ export const shellTool = {
|
|
|
572
558
|
}
|
|
573
559
|
}
|
|
574
560
|
|
|
575
|
-
child.stdout.on("data", (data: Buffer) => {
|
|
576
|
-
stdoutChunks.push(data);
|
|
577
|
-
context.onOutput?.(data.toString());
|
|
578
|
-
});
|
|
579
|
-
|
|
580
|
-
child.stderr.on("data", (data: Buffer) => {
|
|
581
|
-
stderrChunks.push(data);
|
|
582
|
-
context.onOutput?.(data.toString());
|
|
583
|
-
});
|
|
584
|
-
|
|
585
561
|
child.on("close", (code, signal) => {
|
|
586
562
|
clearTimeout(timer);
|
|
587
563
|
context.signal?.removeEventListener("abort", onAbort);
|
|
@@ -597,15 +573,7 @@ export const shellTool = {
|
|
|
597
573
|
timedOut,
|
|
598
574
|
});
|
|
599
575
|
|
|
600
|
-
const
|
|
601
|
-
const stderr = Buffer.concat(stderrChunks).toString();
|
|
602
|
-
const fmtResult = formatShellOutput(
|
|
603
|
-
stdout,
|
|
604
|
-
stderr,
|
|
605
|
-
code,
|
|
606
|
-
timedOut,
|
|
607
|
-
timeoutSec,
|
|
608
|
-
);
|
|
576
|
+
const fmtResult = collector.format(code, timedOut, timeoutSec);
|
|
609
577
|
|
|
610
578
|
resolve({
|
|
611
579
|
content: fmtResult.content,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { z } from "zod";
|
|
2
2
|
|
|
3
3
|
import { askQuestionInputSchema } from "./ask-question/ask-question-tool.js";
|
|
4
|
+
import { reactToMessageInputSchema } from "./channel/react-to-message.js";
|
|
4
5
|
import { fileEditInputSchema } from "./filesystem/edit.js";
|
|
5
6
|
import { fileListInputSchema } from "./filesystem/list.js";
|
|
6
7
|
import { fileReadInputSchema } from "./filesystem/read.js";
|
|
@@ -56,6 +57,7 @@ export const TOOL_INPUT_SCHEMAS: Readonly<Record<string, z.ZodType>> = {
|
|
|
56
57
|
host_file_read: hostFileReadInputSchema,
|
|
57
58
|
host_file_write: hostFileWriteInputSchema,
|
|
58
59
|
notify_parent: notifyParentInputSchema,
|
|
60
|
+
react_to_message: reactToMessageInputSchema,
|
|
59
61
|
request_system_permission: requestSystemPermissionInputSchema,
|
|
60
62
|
};
|
|
61
63
|
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
rememberTool,
|
|
13
13
|
} from "../plugins/defaults/memory/tools.js";
|
|
14
14
|
import { askQuestionTool } from "./ask-question/ask-question-tool.js";
|
|
15
|
+
import { reactToMessageTool } from "./channel/react-to-message.js";
|
|
15
16
|
import { fileEditTool } from "./filesystem/edit.js";
|
|
16
17
|
import { fileListTool } from "./filesystem/list.js";
|
|
17
18
|
import { fileReadTool } from "./filesystem/read.js";
|
|
@@ -65,6 +66,7 @@ export const explicitTools: ToolDefinition[] = [
|
|
|
65
66
|
deleteMemoryPageTool,
|
|
66
67
|
notifyParentTool,
|
|
67
68
|
askQuestionTool,
|
|
69
|
+
reactToMessageTool,
|
|
68
70
|
hostFileReadTool,
|
|
69
71
|
hostFileWriteTool,
|
|
70
72
|
hostFileEditTool,
|
package/src/usage/attribution.ts
CHANGED
|
@@ -19,6 +19,13 @@ export type UsageAttributionProfileSource =
|
|
|
19
19
|
|
|
20
20
|
export interface UsageAttributionInput {
|
|
21
21
|
callSite: LLMCallSite | null;
|
|
22
|
+
/**
|
|
23
|
+
* Call site used to pick the winning profile. Defaults to {@link callSite}.
|
|
24
|
+
* Compaction invokes the provider as `mainAgent` (prefix cache) but tags
|
|
25
|
+
* the usage row as `compactionAgent`; pass `mainAgent` here so
|
|
26
|
+
* `appliedProfile` / `profileSource` match the request that ran.
|
|
27
|
+
*/
|
|
28
|
+
profileResolutionCallSite?: LLMCallSite | null;
|
|
22
29
|
overrideProfile?: string | null;
|
|
23
30
|
/**
|
|
24
31
|
* Mirrors `ResolveCallSiteOpts.forceOverrideProfile`: the override profile
|
|
@@ -130,9 +137,10 @@ export function resolveUsageAttribution(
|
|
|
130
137
|
): UsageAttributionSnapshot {
|
|
131
138
|
const llm = getConfig().llm;
|
|
132
139
|
const callSite = input.callSite;
|
|
140
|
+
const resolutionCallSite = input.profileResolutionCallSite ?? callSite;
|
|
133
141
|
const overrideProfile = normalizeProfileId(input.overrideProfile);
|
|
134
142
|
|
|
135
|
-
if (
|
|
143
|
+
if (resolutionCallSite == null) {
|
|
136
144
|
const resolvedMainAgent = resolveCallSiteConfig("mainAgent", llm, {
|
|
137
145
|
...(input.selectionSeed != null
|
|
138
146
|
? { selectionSeed: input.selectionSeed }
|
|
@@ -158,7 +166,7 @@ export function resolveUsageAttribution(
|
|
|
158
166
|
// Capture which arm each expanded mix resolved to so we can attribute usage
|
|
159
167
|
// to the arm behind the applied (mix) profile below.
|
|
160
168
|
const mixSelections = new Map<string, string>();
|
|
161
|
-
const resolved = resolveCallSiteConfig(
|
|
169
|
+
const resolved = resolveCallSiteConfig(resolutionCallSite, llm, {
|
|
162
170
|
...(overrideProfile != null ? { overrideProfile } : {}),
|
|
163
171
|
...(input.forceOverrideProfile === true
|
|
164
172
|
? { forceOverrideProfile: true }
|
|
@@ -171,13 +179,13 @@ export function resolveUsageAttribution(
|
|
|
171
179
|
});
|
|
172
180
|
const activeProfile = normalizeProfileId(llm.activeProfile);
|
|
173
181
|
const callSiteProfile = normalizeProfileId(
|
|
174
|
-
llm.callSites?.[
|
|
182
|
+
llm.callSites?.[resolutionCallSite]?.profile,
|
|
175
183
|
);
|
|
176
184
|
// The resolver's own winner selection is the single source of truth for which
|
|
177
185
|
// profile applied — attribution must never re-derive precedence and drift
|
|
178
186
|
// from dispatch.
|
|
179
187
|
const profile = appliedProfileFromWinnerSelection(
|
|
180
|
-
|
|
188
|
+
resolutionCallSite,
|
|
181
189
|
llm,
|
|
182
190
|
overrideProfile,
|
|
183
191
|
input,
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { isDaemonCommand } from "../worker-ownership.js";
|
|
4
|
+
|
|
5
|
+
// This predicate authorises an irreversible signal in killStaleDaemon, so it
|
|
6
|
+
// judges argv shape rather than substring. The negative cases are the
|
|
7
|
+
// collision cli/src/lib/orphan-detection.test.ts already pins as something we
|
|
8
|
+
// must not claim.
|
|
9
|
+
describe("isDaemonCommand", () => {
|
|
10
|
+
test("source daemon: bun with the entry script as an argument", () => {
|
|
11
|
+
expect(
|
|
12
|
+
isDaemonCommand(
|
|
13
|
+
"bun run /app/runtime/0.11.7/node_modules/@vellumai/assistant/src/daemon/main.ts",
|
|
14
|
+
),
|
|
15
|
+
).toBe(true);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("packaged daemon as argv0", () => {
|
|
19
|
+
expect(isDaemonCommand("/Applications/Vellum/vellum-daemon")).toBe(true);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// The ordinary Windows install path contains a space, so splitting on
|
|
23
|
+
// whitespace before honouring quotes would yield argv0 "Program" and make
|
|
24
|
+
// the packaged daemon invisible to the recovery path this predicate serves.
|
|
25
|
+
test("packaged Windows daemon under an install path containing spaces", () => {
|
|
26
|
+
expect(
|
|
27
|
+
isDaemonCommand(
|
|
28
|
+
'"C:\\Program Files\\Vellum\\resources\\cli-runtime\\vellum-daemon.exe"',
|
|
29
|
+
),
|
|
30
|
+
).toBe(true);
|
|
31
|
+
expect(
|
|
32
|
+
isDaemonCommand(
|
|
33
|
+
'"C:\\Program Files\\Vellum\\resources\\cli-runtime\\vellum-daemon.exe" --serve',
|
|
34
|
+
),
|
|
35
|
+
).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("quoted bun entry-script argument under a path containing spaces", () => {
|
|
39
|
+
expect(
|
|
40
|
+
isDaemonCommand(
|
|
41
|
+
'"C:\\Program Files\\bun\\bun.exe" run "C:\\Program Files\\Vellum\\src\\daemon\\main.ts"',
|
|
42
|
+
),
|
|
43
|
+
).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// POSIX joins argv with spaces, so an entry path containing a space arrives
|
|
47
|
+
// pre-split; the tail token still carries the entry, which is what matches.
|
|
48
|
+
test("unquoted POSIX entry path containing a space", () => {
|
|
49
|
+
expect(
|
|
50
|
+
isDaemonCommand(
|
|
51
|
+
"/home/mary/.bun/bin/bun run /home/Mary Jane/runtime/src/daemon/main.ts",
|
|
52
|
+
),
|
|
53
|
+
).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// process.title rewrites the source daemon's argv on Linux.
|
|
57
|
+
test("a daemon that renamed its own process title", () => {
|
|
58
|
+
expect(isDaemonCommand("vellum-daemon")).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("unrelated service whose argv merely contains a daemon/main path", () => {
|
|
62
|
+
expect(isDaemonCommand("node /opt/unrelated-service/daemon/main.ts")).toBe(
|
|
63
|
+
false,
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("an editor or tool holding a recycled PID", () => {
|
|
68
|
+
expect(isDaemonCommand("vim /app/runtime/0.11.7/src/daemon/main.ts")).toBe(
|
|
69
|
+
false,
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("unreadable or empty command line", () => {
|
|
74
|
+
expect(isDaemonCommand(null)).toBe(false);
|
|
75
|
+
expect(isDaemonCommand("")).toBe(false);
|
|
76
|
+
});
|
|
77
|
+
});
|