@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,6 +1,12 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
classifyOrphanAfterWait,
|
|
5
|
+
decideWorkerSlot,
|
|
6
|
+
resolveWorkerCommand,
|
|
7
|
+
workerKindSignature,
|
|
8
|
+
type WorkerProcessStatus,
|
|
9
|
+
} from "../worker-process.js";
|
|
4
10
|
|
|
5
11
|
const entry = new URL("file:///source/monitoring/worker.ts");
|
|
6
12
|
|
|
@@ -35,3 +41,242 @@ describe("resolveWorkerCommand", () => {
|
|
|
35
41
|
).toEqual(["bun", "--smol", "run", "/source/monitoring/worker.ts"]);
|
|
36
42
|
});
|
|
37
43
|
});
|
|
44
|
+
|
|
45
|
+
describe("workerKindSignature", () => {
|
|
46
|
+
const schedule = new URL(
|
|
47
|
+
"file:////app/runtime/0.11.7/src/schedule/worker.ts",
|
|
48
|
+
);
|
|
49
|
+
const source = {
|
|
50
|
+
platform: "darwin" as const,
|
|
51
|
+
execPath: "/runtime/bun",
|
|
52
|
+
executableExists: () => false,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
test("is the same for a worker of this kind from any install", () => {
|
|
56
|
+
const previous = new URL(
|
|
57
|
+
"file:////app/runtime/0.10.11/src/schedule/worker.ts",
|
|
58
|
+
);
|
|
59
|
+
expect(workerKindSignature(schedule, "schedule", source)).toEqual(
|
|
60
|
+
workerKindSignature(previous, "schedule", source),
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("distinguishes one worker kind from another", () => {
|
|
65
|
+
const monitoring = new URL(
|
|
66
|
+
"file:////app/runtime/0.11.7/src/monitoring/worker.ts",
|
|
67
|
+
);
|
|
68
|
+
expect(workerKindSignature(schedule, "schedule", source)).not.toEqual(
|
|
69
|
+
workerKindSignature(monitoring, "monitoring", source),
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// The signature only guards against signalling a recycled PID, so it has to
|
|
74
|
+
// be specific enough that an unrelated program running its own worker.ts
|
|
75
|
+
// never matches.
|
|
76
|
+
const matches = (command: string, signature: readonly string[]): boolean =>
|
|
77
|
+
signature.every((part) => command.includes(part));
|
|
78
|
+
|
|
79
|
+
test("does not match an unrelated program running some other worker.ts", () => {
|
|
80
|
+
const signature = workerKindSignature(schedule, "schedule", source);
|
|
81
|
+
expect(matches("bun run /home/dev/side-project/worker.ts", signature)).toBe(
|
|
82
|
+
false,
|
|
83
|
+
);
|
|
84
|
+
expect(matches("node /srv/queue/src/jobs/worker.ts", signature)).toBe(
|
|
85
|
+
false,
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("matches the same worker from a previous install", () => {
|
|
90
|
+
const signature = workerKindSignature(schedule, "schedule", source);
|
|
91
|
+
expect(
|
|
92
|
+
matches(
|
|
93
|
+
"bun --smol run /app/runtime/0.10.11/src/schedule/worker.ts",
|
|
94
|
+
signature,
|
|
95
|
+
),
|
|
96
|
+
).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const packaged = {
|
|
100
|
+
platform: "win32" as const,
|
|
101
|
+
execPath: "/runtime/vellum-daemon.exe",
|
|
102
|
+
executableExists: () => true,
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// Every packaged worker runs one executable and is told apart by the
|
|
106
|
+
// subcommand, so the executable alone must not identify a worker: one
|
|
107
|
+
// worker's slot would otherwise reclaim another's process.
|
|
108
|
+
test("distinguishes packaged worker kinds sharing one executable", () => {
|
|
109
|
+
const scheduleSig = workerKindSignature(schedule, "schedule", packaged);
|
|
110
|
+
expect(matches('"C:/App/vellum-worker.exe" schedule', scheduleSig)).toBe(
|
|
111
|
+
true,
|
|
112
|
+
);
|
|
113
|
+
expect(matches('"C:/App/vellum-worker.exe" monitoring', scheduleSig)).toBe(
|
|
114
|
+
false,
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("matches a packaged worker from a previous install", () => {
|
|
119
|
+
const scheduleSig = workerKindSignature(schedule, "schedule", packaged);
|
|
120
|
+
expect(matches('"C:/Prev/vellum-worker.exe" schedule', scheduleSig)).toBe(
|
|
121
|
+
true,
|
|
122
|
+
);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Every branch that can reach a kill. `reclaim` is the only decision that
|
|
127
|
+
// signals a process, so each test below is really asking: could this input
|
|
128
|
+
// have killed something it should not have?
|
|
129
|
+
describe("decideWorkerSlot", () => {
|
|
130
|
+
const DAEMON = 900;
|
|
131
|
+
const WORKER = 4242;
|
|
132
|
+
const signature = ["src/schedule/worker.ts"];
|
|
133
|
+
const running = { status: "running" as const, pid: WORKER };
|
|
134
|
+
const ours = `bun --smol run /app/runtime/0.11.7/src/schedule/worker.ts`;
|
|
135
|
+
const previous = `bun --smol run /app/runtime/0.10.11/src/schedule/worker.ts`;
|
|
136
|
+
const alive = () => true;
|
|
137
|
+
const dead = () => false;
|
|
138
|
+
|
|
139
|
+
const decide = (
|
|
140
|
+
row: { pid: number; ppid: number; command: string } | null,
|
|
141
|
+
isOwnerAlive = alive,
|
|
142
|
+
pid1OwnsWorkers = false,
|
|
143
|
+
status: WorkerProcessStatus = running,
|
|
144
|
+
) =>
|
|
145
|
+
decideWorkerSlot(
|
|
146
|
+
status,
|
|
147
|
+
row,
|
|
148
|
+
signature,
|
|
149
|
+
DAEMON,
|
|
150
|
+
isOwnerAlive,
|
|
151
|
+
pid1OwnsWorkers,
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
test("spawns when the PID file names nothing running", () => {
|
|
155
|
+
expect(decide(null, alive, false, { status: "not_running" })).toEqual({
|
|
156
|
+
action: "spawn",
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("adopts this daemon's own worker", () => {
|
|
161
|
+
expect(decide({ pid: WORKER, ppid: DAEMON, command: ours })).toEqual({
|
|
162
|
+
action: "adopt",
|
|
163
|
+
pid: WORKER,
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("reclaims a worker reparented to init after its daemon died", () => {
|
|
168
|
+
expect(decide({ pid: WORKER, ppid: 1, command: previous })).toEqual({
|
|
169
|
+
action: "reclaim",
|
|
170
|
+
pid: WORKER,
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("reclaims a worker whose owner is gone", () => {
|
|
175
|
+
expect(decide({ pid: WORKER, ppid: 777, command: previous }, dead)).toEqual(
|
|
176
|
+
{
|
|
177
|
+
action: "reclaim",
|
|
178
|
+
pid: WORKER,
|
|
179
|
+
},
|
|
180
|
+
);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// `isOwnerAlive` is "is the owner a daemon", not "does that PID exist". These
|
|
184
|
+
// workers have exactly one legitimate owner, so a recycled owner PID must not
|
|
185
|
+
// keep a stranded worker looking owned.
|
|
186
|
+
test("adopts a worker a live daemon owns", () => {
|
|
187
|
+
expect(decide({ pid: WORKER, ppid: 777, command: ours }, alive)).toEqual({
|
|
188
|
+
action: "adopt",
|
|
189
|
+
pid: WORKER,
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test("reclaims a worker whose owner PID was recycled to a non-daemon", () => {
|
|
194
|
+
const ownerIsNotADaemon = () => false;
|
|
195
|
+
expect(
|
|
196
|
+
decide({ pid: WORKER, ppid: 777, command: previous }, ownerIsNotADaemon),
|
|
197
|
+
).toEqual({ action: "reclaim", pid: WORKER });
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// In a container the daemon is PID 1, so a worker parented to 1 is a live
|
|
201
|
+
// sibling's, not an orphan.
|
|
202
|
+
test("adopts a PID-1 child when PID 1 is the daemon", () => {
|
|
203
|
+
expect(
|
|
204
|
+
decide({ pid: WORKER, ppid: 1, command: ours }, alive, true),
|
|
205
|
+
).toEqual({ action: "adopt", pid: WORKER });
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// The cases below are the ones that must never reach a kill.
|
|
209
|
+
test("never signals an unrelated process on a recycled PID", () => {
|
|
210
|
+
expect(
|
|
211
|
+
decide({ pid: WORKER, ppid: 1, command: "/usr/bin/postgres -D /data" }),
|
|
212
|
+
).toEqual({ action: "adopt", pid: WORKER });
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test("never signals another project's worker.ts on a recycled PID", () => {
|
|
216
|
+
expect(
|
|
217
|
+
decide({
|
|
218
|
+
pid: WORKER,
|
|
219
|
+
ppid: 1,
|
|
220
|
+
command: "bun run /home/dev/side-project/worker.ts",
|
|
221
|
+
}),
|
|
222
|
+
).toEqual({ action: "adopt", pid: WORKER });
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test("never signals a different worker kind holding this slot", () => {
|
|
226
|
+
expect(
|
|
227
|
+
decide({
|
|
228
|
+
pid: WORKER,
|
|
229
|
+
ppid: 1,
|
|
230
|
+
command: "bun --smol run /app/runtime/0.10.11/src/monitoring/worker.ts",
|
|
231
|
+
}),
|
|
232
|
+
).toEqual({ action: "adopt", pid: WORKER });
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test("never signals when the process table could not be read", () => {
|
|
236
|
+
expect(decide(null)).toEqual({ action: "adopt", pid: WORKER });
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// Escalation happens after an awaited SIGTERM grace, so the PID alone cannot
|
|
241
|
+
// carry identity across it: the orphan may exit mid-wait and the OS may hand
|
|
242
|
+
// its PID to a stranger, or the process table may simply fail to answer.
|
|
243
|
+
// Only a positive re-match escalates, and only a proven "gone" releases the
|
|
244
|
+
// slot; a live PID whose identity cannot be read resolves to reuse, because
|
|
245
|
+
// releasing it would spawn a second worker next to a live one.
|
|
246
|
+
describe("classifyOrphanAfterWait", () => {
|
|
247
|
+
const signature = ["src/schedule/worker.ts"];
|
|
248
|
+
const ours = "bun --smol run /app/runtime/0.10.11/src/schedule/worker.ts";
|
|
249
|
+
|
|
250
|
+
test("dead PID: gone, slot may be released", () => {
|
|
251
|
+
expect(classifyOrphanAfterWait(false, null, signature)).toBe("gone");
|
|
252
|
+
expect(classifyOrphanAfterWait(false, ours, signature)).toBe("gone");
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test("still this worker: escalation proceeds", () => {
|
|
256
|
+
expect(classifyOrphanAfterWait(true, ours, signature)).toBe("orphan");
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
test("PID reused by a stranger: orphan exited, never SIGKILL", () => {
|
|
260
|
+
expect(
|
|
261
|
+
classifyOrphanAfterWait(true, "/usr/bin/postgres -D /data", signature),
|
|
262
|
+
).toBe("gone");
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
test("PID reused by a different worker kind: never SIGKILL", () => {
|
|
266
|
+
expect(
|
|
267
|
+
classifyOrphanAfterWait(
|
|
268
|
+
true,
|
|
269
|
+
"bun --smol run /app/runtime/0.10.11/src/monitoring/worker.ts",
|
|
270
|
+
signature,
|
|
271
|
+
),
|
|
272
|
+
).toBe("gone");
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// The fail-safe vex pinned: a live PID with an unreadable command line is
|
|
276
|
+
// neither killable nor releasable.
|
|
277
|
+
test("alive but command unreadable: identity-unreadable, reuse the slot", () => {
|
|
278
|
+
expect(classifyOrphanAfterWait(true, null, signature)).toBe(
|
|
279
|
+
"identity-unreadable",
|
|
280
|
+
);
|
|
281
|
+
});
|
|
282
|
+
});
|
package/src/util/bun-runtime.ts
CHANGED
|
@@ -22,7 +22,7 @@ import { arch, homedir, platform } from "node:os";
|
|
|
22
22
|
import { basename, join } from "node:path";
|
|
23
23
|
|
|
24
24
|
import { getLogger } from "./logger.js";
|
|
25
|
-
import { getBinDir } from "./platform.js";
|
|
25
|
+
import { getBinDir, isWindows } from "./platform.js";
|
|
26
26
|
|
|
27
27
|
const log = getLogger("bun-runtime");
|
|
28
28
|
|
|
@@ -81,19 +81,31 @@ export function findBun(): string | undefined {
|
|
|
81
81
|
return process.execPath;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
const exe = isWindows() ? "bun.exe" : "bun";
|
|
85
|
+
|
|
84
86
|
// 2. Previously downloaded copy
|
|
85
|
-
const downloaded = join(getBinDir(),
|
|
87
|
+
const downloaded = join(getBinDir(), exe);
|
|
86
88
|
if (existsSync(downloaded)) {
|
|
87
89
|
return downloaded;
|
|
88
90
|
}
|
|
89
91
|
|
|
90
92
|
// 3. Common install locations
|
|
91
93
|
const home = homedir();
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
const candidates = isWindows()
|
|
95
|
+
? [
|
|
96
|
+
join(home, ".bun", "bin", exe),
|
|
97
|
+
join(
|
|
98
|
+
process.env.LOCALAPPDATA ?? join(home, "AppData", "Local"),
|
|
99
|
+
"bun",
|
|
100
|
+
exe,
|
|
101
|
+
),
|
|
102
|
+
]
|
|
103
|
+
: [
|
|
104
|
+
join(home, ".bun", "bin", exe),
|
|
105
|
+
"/opt/homebrew/bin/bun",
|
|
106
|
+
"/usr/local/bin/bun",
|
|
107
|
+
];
|
|
108
|
+
for (const p of candidates) {
|
|
97
109
|
if (existsSync(p)) {
|
|
98
110
|
return p;
|
|
99
111
|
}
|
|
@@ -108,13 +120,20 @@ export function findBun(): string | undefined {
|
|
|
108
120
|
return undefined;
|
|
109
121
|
}
|
|
110
122
|
|
|
123
|
+
/** Escape a string for a PowerShell single-quoted literal. */
|
|
124
|
+
function psQuote(value: string): string {
|
|
125
|
+
return value.replace(/'/g, "''");
|
|
126
|
+
}
|
|
127
|
+
|
|
111
128
|
/**
|
|
112
129
|
* Download a pinned bun release into the given directory.
|
|
113
130
|
* Returns the absolute path to the downloaded binary.
|
|
114
131
|
*/
|
|
115
132
|
async function downloadBun(installDir: string): Promise<string> {
|
|
116
|
-
const
|
|
117
|
-
const
|
|
133
|
+
const win = isWindows();
|
|
134
|
+
const os = win ? "windows" : platform();
|
|
135
|
+
// Bun ships no Windows ARM64 build; the x64 one runs under emulation.
|
|
136
|
+
const cpu = win ? "x64" : arch() === "arm64" ? "aarch64" : arch();
|
|
118
137
|
const target = `${os}-${cpu}`;
|
|
119
138
|
const url = `https://github.com/oven-sh/bun/releases/download/bun-v${BUN_VERSION}/bun-${target}.zip`;
|
|
120
139
|
|
|
@@ -135,9 +154,18 @@ async function downloadBun(installDir: string): Promise<string> {
|
|
|
135
154
|
|
|
136
155
|
try {
|
|
137
156
|
const proc = Bun.spawn({
|
|
138
|
-
cmd:
|
|
157
|
+
cmd: win
|
|
158
|
+
? [
|
|
159
|
+
"powershell.exe",
|
|
160
|
+
"-NoProfile",
|
|
161
|
+
"-NonInteractive",
|
|
162
|
+
"-Command",
|
|
163
|
+
`Expand-Archive -LiteralPath '${psQuote(tmpZip)}' -DestinationPath '${psQuote(installDir)}' -Force`,
|
|
164
|
+
]
|
|
165
|
+
: ["unzip", "-o", tmpZip, "-d", installDir],
|
|
139
166
|
stdout: "ignore",
|
|
140
167
|
stderr: "pipe",
|
|
168
|
+
windowsHide: true,
|
|
141
169
|
});
|
|
142
170
|
await proc.exited;
|
|
143
171
|
if (proc.exitCode !== 0) {
|
|
@@ -146,8 +174,9 @@ async function downloadBun(installDir: string): Promise<string> {
|
|
|
146
174
|
}
|
|
147
175
|
|
|
148
176
|
// Move binary from bun-{target}/bun to installDir/bun
|
|
149
|
-
const
|
|
150
|
-
const
|
|
177
|
+
const exe = win ? "bun.exe" : "bun";
|
|
178
|
+
const extractedBun = join(installDir, `bun-${target}`, exe);
|
|
179
|
+
const finalPath = join(installDir, exe);
|
|
151
180
|
if (existsSync(extractedBun)) {
|
|
152
181
|
// Remove old binary first to avoid "Text file busy" on some systems
|
|
153
182
|
if (existsSync(finalPath)) {
|
|
@@ -164,7 +193,9 @@ async function downloadBun(installDir: string): Promise<string> {
|
|
|
164
193
|
);
|
|
165
194
|
}
|
|
166
195
|
|
|
167
|
-
|
|
196
|
+
if (!win) {
|
|
197
|
+
chmodSync(finalPath, 0o755);
|
|
198
|
+
}
|
|
168
199
|
|
|
169
200
|
log.info({ path: finalPath }, "Bun binary downloaded successfully");
|
|
170
201
|
return finalPath;
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
|
|
4
|
+
export function isBunVirtualPath(value: string): boolean {
|
|
5
|
+
const normalized = value.replaceAll("\\", "/");
|
|
6
|
+
return (
|
|
7
|
+
normalized.startsWith("/$bunfs/") ||
|
|
8
|
+
/^[a-z]:\/~bun(?:\/|$)/i.test(normalized)
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
|
|
4
12
|
/**
|
|
5
13
|
* Resolve the path to a bundled asset directory, handling compiled Bun binaries
|
|
6
14
|
* where `import.meta.dirname` points to the `/$bunfs/` virtual filesystem and
|
|
@@ -22,7 +30,7 @@ export function resolveBundledDir(
|
|
|
22
30
|
relativePath: string,
|
|
23
31
|
bundleName: string,
|
|
24
32
|
): string {
|
|
25
|
-
if (callerDir
|
|
33
|
+
if (isBunVirtualPath(callerDir)) {
|
|
26
34
|
const execDir = dirname(process.execPath);
|
|
27
35
|
// macOS .app bundle: binary in Contents/MacOS/, resources in Contents/Resources/
|
|
28
36
|
const resourcesPath = join(execDir, "..", "Resources", bundleName);
|
package/src/util/disk-usage.ts
CHANGED
|
@@ -30,6 +30,7 @@ async function getDirectorySizeBytes(paths: string[]): Promise<number | null> {
|
|
|
30
30
|
}
|
|
31
31
|
const { stdout } = await execFileAsync("du", ["-sb", ...existing], {
|
|
32
32
|
timeout: 30_000,
|
|
33
|
+
windowsHide: true,
|
|
33
34
|
});
|
|
34
35
|
let total = 0;
|
|
35
36
|
for (const line of stdout.trim().split("\n")) {
|
package/src/util/file-use.ts
CHANGED
|
@@ -48,7 +48,10 @@ type ExecFileRunner = (
|
|
|
48
48
|
) => Promise<ExecFileResult>;
|
|
49
49
|
|
|
50
50
|
const runExecFile: ExecFileRunner = async (command, args, options) => {
|
|
51
|
-
const { stdout } = await execFileAsync(command, args,
|
|
51
|
+
const { stdout } = await execFileAsync(command, args, {
|
|
52
|
+
...options,
|
|
53
|
+
windowsHide: true,
|
|
54
|
+
});
|
|
52
55
|
return { stdout };
|
|
53
56
|
};
|
|
54
57
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Timings for the forms the guardian fills in their app, shared by the CLI
|
|
3
|
+
* that waits and the registry that holds the form open.
|
|
4
|
+
*
|
|
5
|
+
* One budget split across a socket and a timer, so the numbers live together
|
|
6
|
+
* and the caller's wait is derived from the deadlines rather than written down
|
|
7
|
+
* beside them. The invariant: the caller outlasts the form. A caller that
|
|
8
|
+
* gives up first reports a failure while the write it was waiting on goes on
|
|
9
|
+
* to commit.
|
|
10
|
+
*
|
|
11
|
+
* Under `util/` because the CLI hoists it: constants and one pure function,
|
|
12
|
+
* with no daemon runtime graph behind it (`cli/no-daemon-internals`).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** Default time a form stays open for an answer (5 min). */
|
|
16
|
+
export const GUARDIAN_FORM_DEFAULT_TIMEOUT_MS = 300_000;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Ceiling on a caller-supplied wait (1 hour), so a bad value cannot park a
|
|
20
|
+
* pending form indefinitely.
|
|
21
|
+
*/
|
|
22
|
+
export const GUARDIAN_FORM_MAX_TIMEOUT_MS = 3_600_000;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* How long a claimed form is held while its write settles (3 min).
|
|
26
|
+
*
|
|
27
|
+
* A claim means somebody answered, so the open-for-answers deadline stops
|
|
28
|
+
* governing and this one takes over. It covers the gateway's whole write
|
|
29
|
+
* rather than one call of it: its IPC calls bound at 30s each and a delete
|
|
30
|
+
* makes three in sequence (mirror probe, mirror delete, then the resolve
|
|
31
|
+
* back).
|
|
32
|
+
*/
|
|
33
|
+
export const GUARDIAN_FORM_SETTLE_MS = 180_000;
|
|
34
|
+
|
|
35
|
+
/** Slack on top of the deadlines, so the socket is never the first to give up. */
|
|
36
|
+
const GUARDIAN_FORM_TRANSPORT_BUFFER_MS = 10_000;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* How long a caller waits on a form that stays open for `timeoutMs`.
|
|
40
|
+
*
|
|
41
|
+
* Covers the answer window plus the settle window, since an answer landing at
|
|
42
|
+
* the deadline starts the settle window from there. A caller that gives up
|
|
43
|
+
* first reports a failure while the write proceeds, which for a delete means a
|
|
44
|
+
* contact removed against a command that reported nothing happened.
|
|
45
|
+
*/
|
|
46
|
+
export function guardianFormCallBudgetMs(timeoutMs: number): number {
|
|
47
|
+
return (
|
|
48
|
+
timeoutMs + GUARDIAN_FORM_SETTLE_MS + GUARDIAN_FORM_TRANSPORT_BUFFER_MS
|
|
49
|
+
);
|
|
50
|
+
}
|
|
@@ -2,7 +2,9 @@ import { describe, expect, test } from "bun:test";
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
findContentTypeHeader,
|
|
5
|
+
isBinaryOAuthContentType,
|
|
5
6
|
isJsonContentType,
|
|
7
|
+
parseRequestBodyBytes,
|
|
6
8
|
parseRequestBodyData,
|
|
7
9
|
} from "./oauth-request-body.js";
|
|
8
10
|
|
|
@@ -72,3 +74,60 @@ describe("parseRequestBodyData", () => {
|
|
|
72
74
|
expect(parseRequestBodyData('"hello"', undefined)).toBe('"hello"');
|
|
73
75
|
});
|
|
74
76
|
});
|
|
77
|
+
|
|
78
|
+
describe("isBinaryOAuthContentType", () => {
|
|
79
|
+
test("recognizes PDF, octet-stream, and image types", () => {
|
|
80
|
+
expect(isBinaryOAuthContentType("application/pdf")).toBe(true);
|
|
81
|
+
expect(isBinaryOAuthContentType("application/octet-stream")).toBe(true);
|
|
82
|
+
expect(isBinaryOAuthContentType("image/png; charset=binary")).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("rejects JSON, multipart, and form types", () => {
|
|
86
|
+
expect(isBinaryOAuthContentType("application/json")).toBe(false);
|
|
87
|
+
expect(isBinaryOAuthContentType("multipart/related; boundary=b")).toBe(
|
|
88
|
+
false,
|
|
89
|
+
);
|
|
90
|
+
expect(isBinaryOAuthContentType("application/x-www-form-urlencoded")).toBe(
|
|
91
|
+
false,
|
|
92
|
+
);
|
|
93
|
+
expect(isBinaryOAuthContentType(undefined)).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe("parseRequestBodyBytes", () => {
|
|
98
|
+
const png = Buffer.from([
|
|
99
|
+
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0xff, 0xfe,
|
|
100
|
+
]);
|
|
101
|
+
|
|
102
|
+
test("keeps invalid UTF-8 as a Buffer", () => {
|
|
103
|
+
const parsed = parseRequestBodyBytes(png, "multipart/related; boundary=b");
|
|
104
|
+
expect(Buffer.isBuffer(parsed)).toBe(true);
|
|
105
|
+
expect(Buffer.from(parsed as Uint8Array).equals(png)).toBe(true);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("keeps a PDF Content-Type as a Buffer even when the bytes are ASCII", () => {
|
|
109
|
+
const asciiPdf = Buffer.from("%PDF-1.4 simple", "ascii");
|
|
110
|
+
const parsed = parseRequestBodyBytes(asciiPdf, "application/pdf");
|
|
111
|
+
expect(Buffer.isBuffer(parsed)).toBe(true);
|
|
112
|
+
expect(Buffer.from(parsed as Uint8Array).equals(asciiPdf)).toBe(true);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("keeps valid UTF-8 multipart as the exact string", () => {
|
|
116
|
+
const multipart = "--b\r\nContent-Type: text/csv\r\n\r\na,b\r\n--b--\r\n";
|
|
117
|
+
expect(
|
|
118
|
+
parseRequestBodyBytes(
|
|
119
|
+
Buffer.from(multipart, "utf-8"),
|
|
120
|
+
"multipart/related; boundary=b",
|
|
121
|
+
),
|
|
122
|
+
).toBe(multipart);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("parses UTF-8 JSON into an object", () => {
|
|
126
|
+
expect(
|
|
127
|
+
parseRequestBodyBytes(
|
|
128
|
+
Buffer.from('{"name":"Sheet"}', "utf-8"),
|
|
129
|
+
"application/json",
|
|
130
|
+
),
|
|
131
|
+
).toEqual({ name: "Sheet" });
|
|
132
|
+
});
|
|
133
|
+
});
|
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared helpers for deciding how an outbound OAuth request body is encoded.
|
|
3
3
|
*
|
|
4
|
-
* A body is
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* A body is structured (an object or array, serialized as JSON), raw text
|
|
5
|
+
* (a string forwarded to the provider as UTF-8), or raw bytes (a Buffer
|
|
6
|
+
* forwarded to the provider byte-for-byte). The Content-Type the caller
|
|
7
|
+
* supplied decides which one a string body is: multipart, XML, form-encoded,
|
|
8
|
+
* and CSV payloads must survive intact, while JSON text is already in its
|
|
9
|
+
* wire form and must not be re-encoded. Files that are not valid UTF-8, or
|
|
10
|
+
* that carry a binary Content-Type, stay as bytes.
|
|
9
11
|
*/
|
|
10
12
|
|
|
13
|
+
const BINARY_MEDIA_TYPES = new Set([
|
|
14
|
+
"application/octet-stream",
|
|
15
|
+
"application/pdf",
|
|
16
|
+
"application/zip",
|
|
17
|
+
"application/gzip",
|
|
18
|
+
"application/x-gzip",
|
|
19
|
+
]);
|
|
20
|
+
|
|
11
21
|
/** True when a Content-Type header names a JSON media type. */
|
|
12
22
|
export function isJsonContentType(
|
|
13
23
|
contentType: string | undefined | null,
|
|
@@ -58,3 +68,44 @@ export function parseRequestBodyData(
|
|
|
58
68
|
// body once unquoted, so the original quoted text is kept as the wire form.
|
|
59
69
|
return typeof parsed === "string" ? raw : parsed;
|
|
60
70
|
}
|
|
71
|
+
|
|
72
|
+
/** True when a Content-Type names a binary media type (PDF, image, zip). */
|
|
73
|
+
export function isBinaryOAuthContentType(
|
|
74
|
+
contentType: string | undefined | null,
|
|
75
|
+
): boolean {
|
|
76
|
+
if (!contentType) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
const mediaType = contentType.split(";")[0]?.trim().toLowerCase() ?? "";
|
|
80
|
+
if (
|
|
81
|
+
mediaType.startsWith("image/") ||
|
|
82
|
+
mediaType.startsWith("audio/") ||
|
|
83
|
+
mediaType.startsWith("video/")
|
|
84
|
+
) {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
return BINARY_MEDIA_TYPES.has(mediaType);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Decide how file or stdin bytes reach the provider.
|
|
92
|
+
*
|
|
93
|
+
* Binary Content-Types and payloads that are not valid UTF-8 stay as a
|
|
94
|
+
* Buffer so the original bytes survive the JSON proxy envelope. Valid UTF-8
|
|
95
|
+
* follows {@link parseRequestBodyData}.
|
|
96
|
+
*/
|
|
97
|
+
export function parseRequestBodyBytes(
|
|
98
|
+
raw: Uint8Array,
|
|
99
|
+
contentType: string | undefined,
|
|
100
|
+
): unknown {
|
|
101
|
+
if (isBinaryOAuthContentType(contentType)) {
|
|
102
|
+
return Buffer.from(raw);
|
|
103
|
+
}
|
|
104
|
+
let utf8: string;
|
|
105
|
+
try {
|
|
106
|
+
utf8 = new TextDecoder("utf-8", { fatal: true }).decode(raw);
|
|
107
|
+
} catch {
|
|
108
|
+
return Buffer.from(raw);
|
|
109
|
+
}
|
|
110
|
+
return parseRequestBodyData(utf8, contentType);
|
|
111
|
+
}
|