@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,16 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Credential metadata store.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* CES
|
|
4
|
+
* Production: CES owns identity + policy records. This module keeps a
|
|
5
|
+
* synchronous in-process cache so existing call sites stay sync, and
|
|
6
|
+
* write-throughs to CES when a record backend is injected.
|
|
7
|
+
*
|
|
8
|
+
* The cache is filled lazily: the first catalog read or write starts a
|
|
9
|
+
* single-flight CES `list()`. Leftover workspace `metadata.json` is not
|
|
10
|
+
* read. CES import (003) may leave that file in place; this module
|
|
11
|
+
* ignores it.
|
|
7
12
|
*
|
|
8
13
|
* Tests: `_setMetadataPath` keeps the file-backed store and skips CES
|
|
9
|
-
* write-through.
|
|
14
|
+
* load and write-through.
|
|
10
15
|
*/
|
|
11
16
|
|
|
12
|
-
import { join } from "node:path";
|
|
13
|
-
|
|
14
17
|
import {
|
|
15
18
|
credentialKey,
|
|
16
19
|
StaticCredentialMetadataStore,
|
|
@@ -19,7 +22,6 @@ import type { CredentialRecord } from "@vellumai/service-contracts/credential-rp
|
|
|
19
22
|
|
|
20
23
|
import type { CredentialRecordBackend } from "../../security/ces-rpc-record-backend.js";
|
|
21
24
|
import { getLogger } from "../../util/logger.js";
|
|
22
|
-
import { getDataDir } from "../../util/platform.js";
|
|
23
25
|
import type { CredentialInjectionTemplate } from "./policy-types.js";
|
|
24
26
|
|
|
25
27
|
const log = getLogger("credential-metadata-store");
|
|
@@ -48,15 +50,23 @@ export interface CredentialMetadata {
|
|
|
48
50
|
// Singleton store instance
|
|
49
51
|
// ---------------------------------------------------------------------------
|
|
50
52
|
|
|
53
|
+
const CES_MEMORY_PATH = "ces-in-memory-credential-metadata";
|
|
54
|
+
|
|
51
55
|
let _store: StaticCredentialMetadataStore | undefined;
|
|
52
56
|
let _overridePath: string | null = null;
|
|
53
57
|
let _recordBackend: CredentialRecordBackend | undefined;
|
|
58
|
+
let _cesRecordsLoaded = false;
|
|
59
|
+
let _cesLoad: Promise<void> | undefined;
|
|
60
|
+
let _cesLoadGeneration = 0;
|
|
54
61
|
|
|
55
62
|
function getStore(): StaticCredentialMetadataStore {
|
|
56
63
|
if (!_store) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
64
|
+
if (_overridePath) {
|
|
65
|
+
_store = new StaticCredentialMetadataStore(_overridePath);
|
|
66
|
+
} else {
|
|
67
|
+
_store = new StaticCredentialMetadataStore(CES_MEMORY_PATH);
|
|
68
|
+
_store.useMemory([]);
|
|
69
|
+
}
|
|
60
70
|
}
|
|
61
71
|
return _store;
|
|
62
72
|
}
|
|
@@ -76,7 +86,15 @@ function toRecord(metadata: CredentialMetadata): CredentialRecord {
|
|
|
76
86
|
};
|
|
77
87
|
}
|
|
78
88
|
|
|
79
|
-
|
|
89
|
+
function resetCesLoad(): void {
|
|
90
|
+
_cesRecordsLoaded = false;
|
|
91
|
+
_cesLoad = undefined;
|
|
92
|
+
_cesLoadGeneration += 1;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function persistCredentialMetadata(
|
|
96
|
+
metadata: CredentialMetadata,
|
|
97
|
+
): Promise<void> {
|
|
80
98
|
if (!_recordBackend || _overridePath) {
|
|
81
99
|
return;
|
|
82
100
|
}
|
|
@@ -90,17 +108,81 @@ async function persistRecord(metadata: CredentialMetadata): Promise<void> {
|
|
|
90
108
|
}
|
|
91
109
|
}
|
|
92
110
|
|
|
93
|
-
/** Await CES write-through for a record already in the local file store. */
|
|
94
|
-
export async function persistCredentialMetadata(
|
|
95
|
-
metadata: CredentialMetadata,
|
|
96
|
-
): Promise<void> {
|
|
97
|
-
await persistRecord(metadata);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
111
|
export function setCredentialRecordBackend(
|
|
101
112
|
backend: CredentialRecordBackend | undefined,
|
|
102
113
|
): void {
|
|
103
114
|
_recordBackend = backend;
|
|
115
|
+
resetCesLoad();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function ensureCesRecordsLoaded(): Promise<void> {
|
|
119
|
+
if (_overridePath || !_recordBackend || _cesRecordsLoaded) {
|
|
120
|
+
return Promise.resolve();
|
|
121
|
+
}
|
|
122
|
+
if (_cesLoad) {
|
|
123
|
+
return _cesLoad;
|
|
124
|
+
}
|
|
125
|
+
const generation = _cesLoadGeneration;
|
|
126
|
+
_cesLoad = loadCesRecords(generation).finally(() => {
|
|
127
|
+
if (_cesLoadGeneration === generation) {
|
|
128
|
+
_cesLoad = undefined;
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
return _cesLoad;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function loadCesRecords(generation: number): Promise<void> {
|
|
135
|
+
try {
|
|
136
|
+
if (!_recordBackend || _overridePath || generation !== _cesLoadGeneration) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
let available = false;
|
|
140
|
+
try {
|
|
141
|
+
available = _recordBackend.isAvailable();
|
|
142
|
+
} catch {
|
|
143
|
+
available = false;
|
|
144
|
+
}
|
|
145
|
+
if (!available) {
|
|
146
|
+
log.warn("CES record backend unavailable; keeping in-process cache");
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const remote = await _recordBackend.list();
|
|
151
|
+
if (generation !== _cesLoadGeneration) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (remote === null) {
|
|
155
|
+
log.warn("CES record list failed; keeping in-process cache");
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const store = getStore();
|
|
160
|
+
const localByAccount = new Map(
|
|
161
|
+
store
|
|
162
|
+
.list()
|
|
163
|
+
.map((record) => [credentialKey(record.service, record.field), record]),
|
|
164
|
+
);
|
|
165
|
+
const merged = remote.map((entry) => {
|
|
166
|
+
const local = localByAccount.get(entry.account);
|
|
167
|
+
if (local) {
|
|
168
|
+
localByAccount.delete(entry.account);
|
|
169
|
+
return local;
|
|
170
|
+
}
|
|
171
|
+
return entry.record;
|
|
172
|
+
});
|
|
173
|
+
for (const leftover of localByAccount.values()) {
|
|
174
|
+
merged.push(leftover);
|
|
175
|
+
}
|
|
176
|
+
store.useMemory(merged);
|
|
177
|
+
_cesRecordsLoaded = true;
|
|
178
|
+
log.info({ count: remote.length }, "Loaded credential records from CES");
|
|
179
|
+
} catch (err) {
|
|
180
|
+
log.warn({ err }, "CES record load failed");
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function touchCesCache(): void {
|
|
185
|
+
void ensureCesRecordsLoaded();
|
|
104
186
|
}
|
|
105
187
|
|
|
106
188
|
// ---------------------------------------------------------------------------
|
|
@@ -113,12 +195,15 @@ export function setCredentialRecordBackend(
|
|
|
113
195
|
* so the operation fails cleanly before any side effects.
|
|
114
196
|
*/
|
|
115
197
|
export function assertMetadataWritable(): void {
|
|
198
|
+
touchCesCache();
|
|
116
199
|
getStore().assertWritable();
|
|
117
200
|
}
|
|
118
201
|
|
|
119
202
|
/**
|
|
120
203
|
* Create or update a credential metadata record.
|
|
121
204
|
* If a record with the same service+field exists, it is updated.
|
|
205
|
+
* When a CES record backend is attached, the upsert also write-throughs
|
|
206
|
+
* to CES.
|
|
122
207
|
*/
|
|
123
208
|
export function upsertCredentialMetadata(
|
|
124
209
|
service: string,
|
|
@@ -133,8 +218,9 @@ export function upsertCredentialMetadata(
|
|
|
133
218
|
injectionTemplates?: CredentialInjectionTemplate[] | null;
|
|
134
219
|
},
|
|
135
220
|
): CredentialMetadata {
|
|
221
|
+
touchCesCache();
|
|
136
222
|
const record = getStore().upsert(service, field, policy) as CredentialMetadata;
|
|
137
|
-
void
|
|
223
|
+
void persistCredentialMetadata(record);
|
|
138
224
|
return record;
|
|
139
225
|
}
|
|
140
226
|
|
|
@@ -145,6 +231,7 @@ export function getCredentialMetadata(
|
|
|
145
231
|
service: string,
|
|
146
232
|
field: string,
|
|
147
233
|
): CredentialMetadata | undefined {
|
|
234
|
+
touchCesCache();
|
|
148
235
|
return getStore().getByServiceField(service, field) as
|
|
149
236
|
| CredentialMetadata
|
|
150
237
|
| undefined;
|
|
@@ -156,6 +243,7 @@ export function getCredentialMetadata(
|
|
|
156
243
|
export function getCredentialMetadataById(
|
|
157
244
|
credentialId: string,
|
|
158
245
|
): CredentialMetadata | undefined {
|
|
246
|
+
touchCesCache();
|
|
159
247
|
return getStore().getById(credentialId) as CredentialMetadata | undefined;
|
|
160
248
|
}
|
|
161
249
|
|
|
@@ -163,6 +251,7 @@ export function getCredentialMetadataById(
|
|
|
163
251
|
* List all credential metadata records.
|
|
164
252
|
*/
|
|
165
253
|
export function listCredentialMetadata(): CredentialMetadata[] {
|
|
254
|
+
touchCesCache();
|
|
166
255
|
return getStore().list() as CredentialMetadata[];
|
|
167
256
|
}
|
|
168
257
|
|
|
@@ -173,6 +262,7 @@ export function deleteCredentialMetadata(
|
|
|
173
262
|
service: string,
|
|
174
263
|
field: string,
|
|
175
264
|
): boolean {
|
|
265
|
+
touchCesCache();
|
|
176
266
|
const deleted = getStore().delete(service, field);
|
|
177
267
|
if (deleted && _recordBackend && !_overridePath) {
|
|
178
268
|
void _recordBackend.delete(credentialKey(service, field));
|
|
@@ -180,9 +270,15 @@ export function deleteCredentialMetadata(
|
|
|
180
270
|
return deleted;
|
|
181
271
|
}
|
|
182
272
|
|
|
273
|
+
/** @internal Test-only: wait for the in-flight CES list to populate the cache. */
|
|
274
|
+
export function _ensureCesRecordsLoaded(): Promise<void> {
|
|
275
|
+
return ensureCesRecordsLoaded();
|
|
276
|
+
}
|
|
277
|
+
|
|
183
278
|
/** @internal Test-only: override the metadata file path. */
|
|
184
279
|
export function _setMetadataPath(path: string | null): void {
|
|
185
280
|
_overridePath = path;
|
|
281
|
+
resetCesLoad();
|
|
186
282
|
if (_store) {
|
|
187
283
|
if (path) {
|
|
188
284
|
_store.setPath(path);
|
|
@@ -37,7 +37,6 @@ import {
|
|
|
37
37
|
import { getLogger } from "../../util/logger.js";
|
|
38
38
|
import {
|
|
39
39
|
assertMetadataWritable,
|
|
40
|
-
persistCredentialMetadata,
|
|
41
40
|
upsertCredentialMetadata,
|
|
42
41
|
} from "./metadata-store.js";
|
|
43
42
|
import type { CredentialInjectionTemplate } from "./policy-types.js";
|
|
@@ -162,7 +161,6 @@ export async function storeCredentialValue(
|
|
|
162
161
|
allowedDomains: input.allowedDomains,
|
|
163
162
|
injectionTemplates: input.injectionTemplates,
|
|
164
163
|
});
|
|
165
|
-
await persistCredentialMetadata(metadata);
|
|
166
164
|
const { syncManualTokenConnection } =
|
|
167
165
|
await import("../../oauth/manual-token-connection.js");
|
|
168
166
|
await syncManualTokenConnection(service);
|
|
@@ -45,7 +45,7 @@ export const fileReadInputSchema = z.looseObject({
|
|
|
45
45
|
max_chars: z
|
|
46
46
|
.number()
|
|
47
47
|
.describe(
|
|
48
|
-
"Maximum number of characters to read. Defaults to
|
|
48
|
+
"Maximum number of characters to read. Defaults to 100000, which is also the ceiling. Text files only.",
|
|
49
49
|
)
|
|
50
50
|
.optional()
|
|
51
51
|
.catch(undefined),
|
|
@@ -61,7 +61,7 @@ export const fileReadInputSchema = z.looseObject({
|
|
|
61
61
|
export const fileReadTool = {
|
|
62
62
|
name: "file_read",
|
|
63
63
|
description:
|
|
64
|
-
"Read the contents of a file on your own machine. Text reads return the first
|
|
64
|
+
"Read the contents of a file on your own machine. Text reads return the first 100000 characters unless you pass `max_chars`; when a read stops short the result says so, and `start_index` pages on from there. To find where something is in a large file, code_search is cheaper than paging through it. For image files (JPEG, PNG, GIF, WebP), returns the image for visual analysis. For audio files (MP3, WAV, OGG, FLAC, AAC, M4A), returns the audio for listening. Use host_file_read for files on your guardian's device instead.",
|
|
65
65
|
category: "filesystem",
|
|
66
66
|
executionTarget: "sandbox",
|
|
67
67
|
defaultRiskLevel: RiskLevel.Low,
|
|
@@ -53,7 +53,7 @@ export const hostFileReadInputSchema = z.looseObject({
|
|
|
53
53
|
max_chars: z
|
|
54
54
|
.number()
|
|
55
55
|
.describe(
|
|
56
|
-
"Maximum number of characters to read. Defaults to
|
|
56
|
+
"Maximum number of characters to read. Defaults to 100000, which is also the ceiling. Text files only.",
|
|
57
57
|
)
|
|
58
58
|
.optional()
|
|
59
59
|
.catch(undefined),
|
|
@@ -69,7 +69,7 @@ export const hostFileReadInputSchema = z.looseObject({
|
|
|
69
69
|
export const hostFileReadTool = {
|
|
70
70
|
name: "host_file_read",
|
|
71
71
|
description:
|
|
72
|
-
"Read the contents of a file on your guardian's device, including images (JPEG, PNG, GIF, WebP) and audio (MP3, WAV, OGG, FLAC, AAC, M4A). Text reads return the first
|
|
72
|
+
"Read the contents of a file on your guardian's device, including images (JPEG, PNG, GIF, WebP) and audio (MP3, WAV, OGG, FLAC, AAC, M4A). Text reads return the first 100000 characters unless you pass `max_chars`; when a read stops short the result says so, and `start_index` pages on from there. For files on your own machine, use file_read instead.",
|
|
73
73
|
category: "host-filesystem",
|
|
74
74
|
executionTarget: "host",
|
|
75
75
|
defaultRiskLevel: RiskLevel.Medium,
|
|
@@ -104,6 +104,10 @@ mock.module("../../daemon/host-bash-proxy.js", () => ({
|
|
|
104
104
|
// Import under test — MUST come after mock.module calls.
|
|
105
105
|
// ---------------------------------------------------------------------------
|
|
106
106
|
|
|
107
|
+
import {
|
|
108
|
+
MAX_OUTPUT_LENGTH,
|
|
109
|
+
OUTPUT_TRUNCATED_TAG,
|
|
110
|
+
} from "../shared/shell-output.js";
|
|
107
111
|
import type { ToolContext, ToolExecutionResult } from "../types.js";
|
|
108
112
|
import { hostShellTool } from "./host-shell.js";
|
|
109
113
|
|
|
@@ -362,4 +366,24 @@ describe("host_bash background lifecycle events — direct spawn path", () => {
|
|
|
362
366
|
expect(completed[0]!.output).toContain("cancelled");
|
|
363
367
|
expect(completed[0]!.output).not.toContain("failed");
|
|
364
368
|
});
|
|
369
|
+
|
|
370
|
+
test("drops stdout past the cap without a recovery file", async () => {
|
|
371
|
+
await hostShellTool.execute(
|
|
372
|
+
{ command: "yes", background: true },
|
|
373
|
+
makeContext(),
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
latestChild!.stdout.emit("data", Buffer.alloc(80_000, 0x78));
|
|
377
|
+
latestChild!.stdout.emit("data", Buffer.alloc(80_000, 0x79));
|
|
378
|
+
latestChild!.emit("close", 0);
|
|
379
|
+
await flush();
|
|
380
|
+
|
|
381
|
+
const completed = completedEvents();
|
|
382
|
+
expect(completed).toHaveLength(1);
|
|
383
|
+
const output = String(completed[0]!.output);
|
|
384
|
+
expect(output).toContain(OUTPUT_TRUNCATED_TAG);
|
|
385
|
+
expect(output).not.toContain("file=");
|
|
386
|
+
expect(output.length).toBeLessThan(MAX_OUTPUT_LENGTH + 80);
|
|
387
|
+
expect(output).not.toContain("y");
|
|
388
|
+
});
|
|
365
389
|
});
|
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
removeBackgroundTool,
|
|
41
41
|
} from "../background-tool-registry.js";
|
|
42
42
|
import {
|
|
43
|
-
|
|
43
|
+
attachBoundedStdio,
|
|
44
44
|
MAX_OUTPUT_LENGTH,
|
|
45
45
|
} from "../shared/shell-output.js";
|
|
46
46
|
import {
|
|
@@ -336,7 +336,7 @@ export const hostShellTool = {
|
|
|
336
336
|
untrustedOutput: {
|
|
337
337
|
content: output || "(no output)",
|
|
338
338
|
source: "tool_result",
|
|
339
|
-
//
|
|
339
|
+
// Bounded output plus the truncation marker (see shell.ts).
|
|
340
340
|
maxChars: MAX_OUTPUT_LENGTH * 2,
|
|
341
341
|
},
|
|
342
342
|
});
|
|
@@ -472,10 +472,10 @@ export const hostShellTool = {
|
|
|
472
472
|
env: hostEnv,
|
|
473
473
|
stdio: ["ignore", "pipe", "pipe"],
|
|
474
474
|
detached: true,
|
|
475
|
+
windowsHide: true,
|
|
475
476
|
});
|
|
476
477
|
|
|
477
|
-
const
|
|
478
|
-
const stderrChunks: Buffer[] = [];
|
|
478
|
+
const collector = attachBoundedStdio(child);
|
|
479
479
|
let timedOut = false;
|
|
480
480
|
// Set when cancelled via the registry's cancel callback so the completion
|
|
481
481
|
// event reports "cancelled" rather than "failed".
|
|
@@ -488,9 +488,6 @@ export const hostShellTool = {
|
|
|
488
488
|
killTree();
|
|
489
489
|
}, timeoutMs);
|
|
490
490
|
|
|
491
|
-
child.stdout.on("data", (data: Buffer) => stdoutChunks.push(data));
|
|
492
|
-
child.stderr.on("data", (data: Buffer) => stderrChunks.push(data));
|
|
493
|
-
|
|
494
491
|
// Guard against double-wake: when spawn fails (e.g. invalid cwd),
|
|
495
492
|
// Node emits both 'error' and 'close' for the same child process.
|
|
496
493
|
// Only the first handler to fire should wake the agent.
|
|
@@ -502,15 +499,7 @@ export const hostShellTool = {
|
|
|
502
499
|
}
|
|
503
500
|
completed = true;
|
|
504
501
|
clearTimeout(timer);
|
|
505
|
-
const
|
|
506
|
-
const stderr = Buffer.concat(stderrChunks).toString();
|
|
507
|
-
const result = formatShellOutput(
|
|
508
|
-
stdout,
|
|
509
|
-
stderr,
|
|
510
|
-
code,
|
|
511
|
-
timedOut,
|
|
512
|
-
timeoutSec,
|
|
513
|
-
);
|
|
502
|
+
const result = collector.format(code, timedOut, timeoutSec);
|
|
514
503
|
// Cancel takes precedence over the SIGKILL-induced error result.
|
|
515
504
|
const status = aborted
|
|
516
505
|
? "cancelled"
|
|
@@ -563,7 +552,7 @@ export const hostShellTool = {
|
|
|
563
552
|
untrustedOutput: {
|
|
564
553
|
content: output || "(no output)",
|
|
565
554
|
source: "tool_result",
|
|
566
|
-
//
|
|
555
|
+
// Bounded output plus the truncation marker (see shell.ts).
|
|
567
556
|
maxChars: MAX_OUTPUT_LENGTH * 2,
|
|
568
557
|
},
|
|
569
558
|
});
|
|
@@ -649,8 +638,6 @@ export const hostShellTool = {
|
|
|
649
638
|
}
|
|
650
639
|
|
|
651
640
|
return new Promise<ToolExecutionResult>((resolve) => {
|
|
652
|
-
const stdoutChunks: Buffer[] = [];
|
|
653
|
-
const stderrChunks: Buffer[] = [];
|
|
654
641
|
let timedOut = false;
|
|
655
642
|
|
|
656
643
|
const wrapped = buildShellInvocation(command);
|
|
@@ -659,6 +646,10 @@ export const hostShellTool = {
|
|
|
659
646
|
env: hostEnv,
|
|
660
647
|
stdio: ["ignore", "pipe", "pipe"],
|
|
661
648
|
detached: true,
|
|
649
|
+
windowsHide: true,
|
|
650
|
+
});
|
|
651
|
+
const collector = attachBoundedStdio(child, {
|
|
652
|
+
onOutput: context.onOutput,
|
|
662
653
|
});
|
|
663
654
|
|
|
664
655
|
const killTree = () => terminateProcessTree(child);
|
|
@@ -678,29 +669,11 @@ export const hostShellTool = {
|
|
|
678
669
|
}
|
|
679
670
|
}
|
|
680
671
|
|
|
681
|
-
child.stdout.on("data", (data: Buffer) => {
|
|
682
|
-
stdoutChunks.push(data);
|
|
683
|
-
context.onOutput?.(data.toString());
|
|
684
|
-
});
|
|
685
|
-
|
|
686
|
-
child.stderr.on("data", (data: Buffer) => {
|
|
687
|
-
stderrChunks.push(data);
|
|
688
|
-
context.onOutput?.(data.toString());
|
|
689
|
-
});
|
|
690
|
-
|
|
691
672
|
child.on("close", (code) => {
|
|
692
673
|
clearTimeout(timer);
|
|
693
674
|
context.signal?.removeEventListener("abort", onAbort);
|
|
694
675
|
|
|
695
|
-
const
|
|
696
|
-
const stderr = Buffer.concat(stderrChunks).toString();
|
|
697
|
-
const result = formatShellOutput(
|
|
698
|
-
stdout,
|
|
699
|
-
stderr,
|
|
700
|
-
code,
|
|
701
|
-
timedOut,
|
|
702
|
-
timeoutSec,
|
|
703
|
-
);
|
|
676
|
+
const result = collector.format(code, timedOut, timeoutSec);
|
|
704
677
|
|
|
705
678
|
resolve({
|
|
706
679
|
content: result.content,
|
|
@@ -78,12 +78,15 @@ function pathError(
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
/**
|
|
81
|
-
* Characters returned by a read that names no `max_chars
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
81
|
+
* Characters returned by a read that names no `max_chars`, and the ceiling for
|
|
82
|
+
* one that does. File reads are exempt from the result-time spool pass
|
|
83
|
+
* (`RESULT_TIME_SPOOL_EXEMPT_TOOLS` in `context/tool-result-spool.ts`), so the
|
|
84
|
+
* full window reaches the model for the turn that read it; the post-turn pass
|
|
85
|
+
* compacts oversized results to recoverable stubs at turn end, and the
|
|
86
|
+
* `tool-result-truncate` plugin bounds any single result against the model's
|
|
87
|
+
* context window.
|
|
85
88
|
*/
|
|
86
|
-
export const READ_CHAR_BUDGET =
|
|
89
|
+
export const READ_CHAR_BUDGET = 100_000;
|
|
87
90
|
|
|
88
91
|
/**
|
|
89
92
|
* Trailing marker appended when a read stops short of the end of the file. A
|
|
@@ -169,8 +172,8 @@ export class FileSystemOps {
|
|
|
169
172
|
try {
|
|
170
173
|
const raw = await readFile(filePath, "utf-8");
|
|
171
174
|
|
|
172
|
-
// A ceiling, not just a default:
|
|
173
|
-
//
|
|
175
|
+
// A ceiling, not just a default: an uncapped `max_chars` would put an
|
|
176
|
+
// arbitrarily large file inline in a single result.
|
|
174
177
|
const maxChars = Math.min(
|
|
175
178
|
READ_CHAR_BUDGET,
|
|
176
179
|
Math.max(0, input.maxChars ?? READ_CHAR_BUDGET),
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
|
+
import { describe, expect, test } from "bun:test";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
attachBoundedStdio,
|
|
6
|
+
BoundedStdioCollector,
|
|
7
|
+
formatShellOutput,
|
|
8
|
+
MAX_OUTPUT_LENGTH,
|
|
9
|
+
OUTPUT_TRUNCATED_TAG,
|
|
10
|
+
} from "./shell-output.js";
|
|
11
|
+
|
|
12
|
+
describe("BoundedStdioCollector", () => {
|
|
13
|
+
test("keeps output under the cap", () => {
|
|
14
|
+
const collector = new BoundedStdioCollector();
|
|
15
|
+
collector.consume("stdout", Buffer.from("hello"));
|
|
16
|
+
const result = collector.format(0, false, 120);
|
|
17
|
+
expect(result.content).toBe("hello");
|
|
18
|
+
expect(collector.didTruncate).toBe(false);
|
|
19
|
+
expect(collector.keptByteLength).toBe(5);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("caps a single oversized chunk without retaining the tail", () => {
|
|
23
|
+
const collector = new BoundedStdioCollector();
|
|
24
|
+
const huge = Buffer.alloc(MAX_OUTPUT_LENGTH + 50_000, 0x78);
|
|
25
|
+
collector.consume("stdout", huge);
|
|
26
|
+
huge.fill(0x79);
|
|
27
|
+
expect(collector.keptByteLength).toBe(MAX_OUTPUT_LENGTH);
|
|
28
|
+
expect(collector.didTruncate).toBe(true);
|
|
29
|
+
const result = collector.format(0, false, 120);
|
|
30
|
+
expect(result.content).toContain(OUTPUT_TRUNCATED_TAG);
|
|
31
|
+
expect(result.content).not.toContain("file=");
|
|
32
|
+
expect(result.content.startsWith("x".repeat(MAX_OUTPUT_LENGTH))).toBe(true);
|
|
33
|
+
expect(result.content.length).toBeLessThan(MAX_OUTPUT_LENGTH + 80);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("shares the budget across stdout and stderr", () => {
|
|
37
|
+
const collector = new BoundedStdioCollector();
|
|
38
|
+
collector.consume("stdout", Buffer.alloc(MAX_OUTPUT_LENGTH - 10, 0x61));
|
|
39
|
+
collector.consume("stderr", Buffer.alloc(100, 0x62));
|
|
40
|
+
expect(collector.keptByteLength).toBe(MAX_OUTPUT_LENGTH);
|
|
41
|
+
expect(collector.didTruncate).toBe(true);
|
|
42
|
+
const result = collector.format(0, false, 120);
|
|
43
|
+
expect(result.content).toContain(OUTPUT_TRUNCATED_TAG);
|
|
44
|
+
expect(result.content.includes("b")).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("ignores chunks after the cap", () => {
|
|
48
|
+
const collector = new BoundedStdioCollector();
|
|
49
|
+
collector.consume("stdout", Buffer.alloc(MAX_OUTPUT_LENGTH, 0x78));
|
|
50
|
+
const afterCap = collector.keptByteLength;
|
|
51
|
+
collector.consume("stdout", Buffer.alloc(10_000, 0x79));
|
|
52
|
+
collector.consume("stderr", Buffer.alloc(10_000, 0x7a));
|
|
53
|
+
expect(collector.keptByteLength).toBe(afterCap);
|
|
54
|
+
const result = collector.format(0, false, 120);
|
|
55
|
+
expect(result.content).not.toContain("y");
|
|
56
|
+
expect(result.content).not.toContain("z");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("does not call onOutput for discarded bytes", () => {
|
|
60
|
+
const seen: string[] = [];
|
|
61
|
+
const collector = new BoundedStdioCollector();
|
|
62
|
+
collector.consume("stdout", Buffer.from("abc"), (text) => seen.push(text));
|
|
63
|
+
collector.consume("stdout", Buffer.alloc(MAX_OUTPUT_LENGTH, 0x78), (text) =>
|
|
64
|
+
seen.push(text),
|
|
65
|
+
);
|
|
66
|
+
collector.consume("stdout", Buffer.from("TAIL"), (text) => seen.push(text));
|
|
67
|
+
const forwarded = seen.join("");
|
|
68
|
+
expect(forwarded).not.toContain("TAIL");
|
|
69
|
+
expect(Buffer.byteLength(forwarded)).toBe(MAX_OUTPUT_LENGTH);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe("attachBoundedStdio", () => {
|
|
74
|
+
test("drains further data events after the cap without growing kept bytes", () => {
|
|
75
|
+
const child = {
|
|
76
|
+
stdout: new EventEmitter(),
|
|
77
|
+
stderr: new EventEmitter(),
|
|
78
|
+
};
|
|
79
|
+
const collector = attachBoundedStdio(child);
|
|
80
|
+
child.stdout.emit("data", Buffer.alloc(MAX_OUTPUT_LENGTH + 1000, 0x78));
|
|
81
|
+
child.stdout.emit("data", Buffer.alloc(20_000, 0x79));
|
|
82
|
+
child.stderr.emit("data", Buffer.alloc(20_000, 0x7a));
|
|
83
|
+
expect(collector.keptByteLength).toBe(MAX_OUTPUT_LENGTH);
|
|
84
|
+
expect(collector.didTruncate).toBe(true);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe("formatShellOutput truncation", () => {
|
|
89
|
+
test("truncates an already-materialized oversized string without a file path", () => {
|
|
90
|
+
const longOutput = "x".repeat(30_000);
|
|
91
|
+
const result = formatShellOutput(longOutput, "", 0, false, 120);
|
|
92
|
+
expect(result.content).toContain(OUTPUT_TRUNCATED_TAG);
|
|
93
|
+
expect(result.content).not.toContain("file=");
|
|
94
|
+
expect(result.content.length).toBeLessThan(MAX_OUTPUT_LENGTH + 80);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("honors an explicit truncated flag at the exact cap", () => {
|
|
98
|
+
const exact = "x".repeat(MAX_OUTPUT_LENGTH);
|
|
99
|
+
const result = formatShellOutput(exact, "", 0, false, 120, {
|
|
100
|
+
truncated: true,
|
|
101
|
+
});
|
|
102
|
+
expect(result.content).toContain(OUTPUT_TRUNCATED_TAG);
|
|
103
|
+
});
|
|
104
|
+
});
|