@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
package/src/util/platform.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { chmodSync, existsSync, mkdirSync
|
|
2
|
-
import { homedir
|
|
3
|
-
import {
|
|
1
|
+
import { chmodSync, existsSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { delimiter, dirname, isAbsolute, join, relative, sep } from "node:path";
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
AVATAR_IMAGE_FILENAME,
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
resolveAvatarDir,
|
|
9
9
|
} from "@vellumai/avatar-manifest";
|
|
10
10
|
import { SEEDS } from "@vellumai/environments";
|
|
11
|
+
import { assertTestPathIsEphemeral as assertEphemeralInTests } from "@vellumai/environments/test-path-guard";
|
|
11
12
|
|
|
12
13
|
import { getWorkspaceDirOverride } from "../config/env-registry.js";
|
|
13
14
|
|
|
@@ -54,6 +55,62 @@ export function isWindows(): boolean {
|
|
|
54
55
|
return process.platform === "win32";
|
|
55
56
|
}
|
|
56
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Per-user application data root: `~/Library/Application Support` on macOS,
|
|
60
|
+
* `%APPDATA%` on Windows, `$XDG_DATA_HOME` (default `~/.local/share`) elsewhere.
|
|
61
|
+
*/
|
|
62
|
+
export function getUserAppDataDir(): string {
|
|
63
|
+
if (isMacOS()) {
|
|
64
|
+
return join(homedir(), "Library", "Application Support");
|
|
65
|
+
}
|
|
66
|
+
if (isWindows()) {
|
|
67
|
+
return process.env.APPDATA ?? join(homedir(), "AppData", "Roaming");
|
|
68
|
+
}
|
|
69
|
+
return process.env.XDG_DATA_HOME ?? join(homedir(), ".local", "share");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Extra directories to prepend to PATH so daemon-spawned tools are found
|
|
74
|
+
* even when launched from a minimal environment (macOS .app bundle). Empty
|
|
75
|
+
* on Windows, where PATH already carries the installer-managed entries.
|
|
76
|
+
*/
|
|
77
|
+
export function getExtraToolPathDirs(): string[] {
|
|
78
|
+
if (isWindows()) {
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
return [
|
|
82
|
+
"/opt/homebrew/bin",
|
|
83
|
+
"/usr/local/bin",
|
|
84
|
+
join(homedir(), ".local", "bin"),
|
|
85
|
+
];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Add missing `dirs` to the PATH entry of `env` in place, using the platform
|
|
90
|
+
* delimiter. Windows keys the variable as `Path`, so the existing key is
|
|
91
|
+
* matched case-insensitively rather than assuming `PATH`.
|
|
92
|
+
*/
|
|
93
|
+
export function addToPathEnv(
|
|
94
|
+
env: Record<string, string | undefined>,
|
|
95
|
+
dirs: string[],
|
|
96
|
+
position: "front" | "back" = "front",
|
|
97
|
+
): void {
|
|
98
|
+
if (dirs.length === 0) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const key =
|
|
102
|
+
Object.keys(env).find((k) => k.toUpperCase() === "PATH") ?? "PATH";
|
|
103
|
+
const entries = (env[key] ?? "").split(delimiter).filter(Boolean);
|
|
104
|
+
const missing = dirs.filter((d) => !entries.includes(d));
|
|
105
|
+
if (missing.length === 0) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
env[key] =
|
|
109
|
+
position === "front"
|
|
110
|
+
? [...missing, ...entries].join(delimiter)
|
|
111
|
+
: [...entries, ...missing].join(delimiter);
|
|
112
|
+
}
|
|
113
|
+
|
|
57
114
|
/**
|
|
58
115
|
* Returns the raw platform string from Node.js (e.g. 'darwin', 'linux', 'win32').
|
|
59
116
|
* Prefer this over accessing process.platform directly so all platform
|
|
@@ -341,78 +398,17 @@ export function getProcPidPath(name: string): string {
|
|
|
341
398
|
//
|
|
342
399
|
// A test process must never resolve the workspace (or the vellum root) to a
|
|
343
400
|
// real, non-temp directory: production code exercised by a test would then
|
|
344
|
-
// read and destructively write live state. The
|
|
345
|
-
//
|
|
346
|
-
//
|
|
347
|
-
//
|
|
348
|
-
// inherits the ambient VELLUM_WORKSPACE_DIR. The containment assertion
|
|
349
|
-
// therefore lives here, in production code, where it fires no matter how the
|
|
350
|
-
// test process was launched.
|
|
351
|
-
//
|
|
352
|
-
// Containment logic mirrors src/__tests__/assert-not-live-db.ts, which cannot
|
|
353
|
-
// be imported here (production code must not depend on test machinery).
|
|
354
|
-
|
|
355
|
-
/** Lazily computed: is this process a `bun test` run? */
|
|
356
|
-
let isTestProcess: boolean | undefined;
|
|
357
|
-
|
|
358
|
-
/**
|
|
359
|
-
* Resolve symlinks in the deepest existing ancestor of `p`, then re-append
|
|
360
|
-
* the not-yet-created tail. This keeps the containment check honest both for
|
|
361
|
-
* paths under a symlinked temp root (macOS /var/folders) and for symlinks
|
|
362
|
-
* that point outside it, whether or not the leaf exists yet.
|
|
363
|
-
*/
|
|
364
|
-
function canonicalizeForWorkspaceGuard(p: string): string {
|
|
365
|
-
let cur = p;
|
|
366
|
-
const tail: string[] = [];
|
|
367
|
-
for (;;) {
|
|
368
|
-
try {
|
|
369
|
-
return join(realpathSync(cur), ...tail);
|
|
370
|
-
} catch {
|
|
371
|
-
const parent = dirname(cur);
|
|
372
|
-
if (parent === cur) {
|
|
373
|
-
return p;
|
|
374
|
-
}
|
|
375
|
-
tail.unshift(basename(cur));
|
|
376
|
-
cur = parent;
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
}
|
|
401
|
+
// read and destructively write live state. The containment assertion is
|
|
402
|
+
// shared with the gateway via @vellumai/environments/test-path-guard;
|
|
403
|
+
// src/__tests__/assert-not-live-db.ts keeps its own containment check
|
|
404
|
+
// because test machinery must not import production dependencies.
|
|
380
405
|
|
|
381
406
|
function assertTestPathIsEphemeral(dir: string): void {
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
(typeof Bun !== "undefined" &&
|
|
388
|
-
/\.(test|spec)\.[cm]?[jt]sx?$/.test(Bun.main));
|
|
389
|
-
if (!isTestProcess) {
|
|
390
|
-
return;
|
|
391
|
-
}
|
|
392
|
-
// Escape hatch for the rare intentional run against a real workspace,
|
|
393
|
-
// shared with assertTestDbIsIsolated() in persistence/db-connection.ts.
|
|
394
|
-
// Deliberately NOT added to tools/terminal/safe-env.ts: a daemon-level
|
|
395
|
-
// opt-out must not propagate into agent-spawned shells and disarm the
|
|
396
|
-
// guard for tests run from there.
|
|
397
|
-
if (process.env.VELLUM_ALLOW_REAL_WORKSPACE_IN_TESTS === "1") {
|
|
398
|
-
return;
|
|
399
|
-
}
|
|
400
|
-
const tmpRoot = canonicalizeForWorkspaceGuard(tmpdir());
|
|
401
|
-
const resolved = canonicalizeForWorkspaceGuard(dir);
|
|
402
|
-
if (resolved !== tmpRoot && !resolved.startsWith(tmpRoot + sep)) {
|
|
403
|
-
throw new Error(
|
|
404
|
-
[
|
|
405
|
-
`Refusing to use ${dir} (resolves to ${resolved}) in a test process: it is not under the temp directory (${tmpRoot}).`,
|
|
406
|
-
"",
|
|
407
|
-
"Tests must only touch an ephemeral workspace; a real one would expose",
|
|
408
|
-
"live assistant state to destructive test fixtures. This usually means",
|
|
409
|
-
"`bun test` ran from a cwd without the repo bunfig.toml, so the test",
|
|
410
|
-
"preload that redirects VELLUM_WORKSPACE_DIR to a tmpdir never loaded.",
|
|
411
|
-
"Run tests from the assistant package root, or set",
|
|
412
|
-
"VELLUM_ALLOW_REAL_WORKSPACE_IN_TESTS=1 to bypass deliberately.",
|
|
413
|
-
].join("\n"),
|
|
414
|
-
);
|
|
415
|
-
}
|
|
407
|
+
assertEphemeralInTests(dir, {
|
|
408
|
+
// Shared with assertTestDbIsIsolated() in persistence/db-connection.ts.
|
|
409
|
+
allowEnvVar: "VELLUM_ALLOW_REAL_WORKSPACE_IN_TESTS",
|
|
410
|
+
runHint: "Run tests from the assistant package root.",
|
|
411
|
+
});
|
|
416
412
|
}
|
|
417
413
|
|
|
418
414
|
/**
|
|
@@ -157,6 +157,7 @@ function runProcessTableCommand(
|
|
|
157
157
|
cmd: command,
|
|
158
158
|
stdout: "pipe",
|
|
159
159
|
stderr: "ignore",
|
|
160
|
+
windowsHide: true,
|
|
160
161
|
});
|
|
161
162
|
if (result.exitCode !== 0) {
|
|
162
163
|
throw new Error(
|
|
@@ -173,6 +174,7 @@ async function runProcessTableCommandAsync(
|
|
|
173
174
|
const { stdout } = await execFileAsync(command[0], command.slice(1), {
|
|
174
175
|
encoding: "utf8",
|
|
175
176
|
maxBuffer: 8 * 1024 * 1024,
|
|
177
|
+
windowsHide: true,
|
|
176
178
|
});
|
|
177
179
|
return parser(stdout);
|
|
178
180
|
}
|
|
@@ -229,6 +231,19 @@ export async function listProcessTableAsync(
|
|
|
229
231
|
);
|
|
230
232
|
}
|
|
231
233
|
|
|
234
|
+
/**
|
|
235
|
+
* The process-table row for `pid`, or null when the process is gone or the
|
|
236
|
+
* table cannot be read. Callers get `ppid` alongside the command line, so
|
|
237
|
+
* ownership and identity can be decided from a single snapshot.
|
|
238
|
+
*/
|
|
239
|
+
function findProcessRow(pid: number): ProcessTableRow | null {
|
|
240
|
+
try {
|
|
241
|
+
return listProcessTable().find((row) => row.pid === pid) ?? null;
|
|
242
|
+
} catch {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
232
247
|
export function readRawProcessCommand(pid: number): string | null {
|
|
233
248
|
if (process.platform === "linux") {
|
|
234
249
|
try {
|
|
@@ -241,9 +256,5 @@ export function readRawProcessCommand(pid: number): string | null {
|
|
|
241
256
|
}
|
|
242
257
|
}
|
|
243
258
|
|
|
244
|
-
|
|
245
|
-
return listProcessTable().find((row) => row.pid === pid)?.command ?? null;
|
|
246
|
-
} catch {
|
|
247
|
-
return null;
|
|
248
|
-
}
|
|
259
|
+
return findProcessRow(pid)?.command ?? null;
|
|
249
260
|
}
|
package/src/util/read-stdin.ts
CHANGED
|
@@ -25,3 +25,8 @@ export const STDIN_FD = 0;
|
|
|
25
25
|
export function readStdinSync(encoding: BufferEncoding = "utf-8"): string {
|
|
26
26
|
return readFileSync(STDIN_FD, encoding);
|
|
27
27
|
}
|
|
28
|
+
|
|
29
|
+
/** Read all data currently available on stdin as raw bytes. */
|
|
30
|
+
export function readStdinBytesSync(): Buffer {
|
|
31
|
+
return readFileSync(STDIN_FD);
|
|
32
|
+
}
|
package/src/util/spawn.ts
CHANGED
|
@@ -21,6 +21,10 @@
|
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
23
|
import { existsSync } from "node:fs";
|
|
24
|
+
import { homedir } from "node:os";
|
|
25
|
+
import { join } from "node:path";
|
|
26
|
+
|
|
27
|
+
import { isWindows } from "./platform.js";
|
|
24
28
|
|
|
25
29
|
let cachedSqlite3Path: string | undefined | null;
|
|
26
30
|
|
|
@@ -37,11 +41,27 @@ export function findSqlite3(): string | undefined {
|
|
|
37
41
|
// 1. Common install locations. We check these before `Bun.which` so we
|
|
38
42
|
// pick a deterministic, predictable binary even when PATH is
|
|
39
43
|
// unusual (e.g. a daemon launched with a stripped env).
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
const candidates = isWindows()
|
|
45
|
+
? [
|
|
46
|
+
join(
|
|
47
|
+
process.env.LOCALAPPDATA ?? join(homedir(), "AppData", "Local"),
|
|
48
|
+
"Programs",
|
|
49
|
+
"sqlite",
|
|
50
|
+
"sqlite3.exe",
|
|
51
|
+
),
|
|
52
|
+
join(
|
|
53
|
+
process.env.ProgramFiles ?? "C:\\Program Files",
|
|
54
|
+
"SQLite",
|
|
55
|
+
"sqlite3.exe",
|
|
56
|
+
),
|
|
57
|
+
"C:\\sqlite\\sqlite3.exe",
|
|
58
|
+
]
|
|
59
|
+
: [
|
|
60
|
+
"/usr/bin/sqlite3",
|
|
61
|
+
"/usr/local/bin/sqlite3",
|
|
62
|
+
"/opt/homebrew/bin/sqlite3",
|
|
63
|
+
];
|
|
64
|
+
for (const p of candidates) {
|
|
45
65
|
if (existsSync(p)) {
|
|
46
66
|
cachedSqlite3Path = p;
|
|
47
67
|
return p;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process-tree ownership for background worker processes.
|
|
3
|
+
*
|
|
4
|
+
* A worker's parent IS its owner. The process table is therefore the
|
|
5
|
+
* authoritative answer to "whose worker is this": unlike a PID file it
|
|
6
|
+
* survives a crash, and a second owner cannot overwrite it.
|
|
7
|
+
*
|
|
8
|
+
* Nothing here is specific to one worker kind. `listWorkerProcesses` matches
|
|
9
|
+
* on the absolute worker path the caller passes, and `classifyWorkerOwnership`
|
|
10
|
+
* takes injected liveness and PID-1 predicates, so any worker whose spawner
|
|
11
|
+
* knows its own PID can use both.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { listProcessTable, readRawProcessCommand } from "./process-table.js";
|
|
15
|
+
|
|
16
|
+
export interface WorkerProcess {
|
|
17
|
+
pid: number;
|
|
18
|
+
ppid: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* What this process may do with a worker it found in the process table.
|
|
23
|
+
*
|
|
24
|
+
* - `reclaim`: parented to us, a worker we started and lost track of. Reaping
|
|
25
|
+
* it is what enforces one live worker per owning process.
|
|
26
|
+
* - `orphan`: reparented to init, or its owner is gone. Nobody else will ever
|
|
27
|
+
* clean it up.
|
|
28
|
+
* - `foreign`: owned by another live process. The memory-worker process runs
|
|
29
|
+
* its own backend against this same workspace and is entitled to its own
|
|
30
|
+
* worker; signalling it is what made the two replace each other in a loop.
|
|
31
|
+
*
|
|
32
|
+
* A parent of PID 1 is ambiguous, and resolving it wrongly in either direction
|
|
33
|
+
* costs something real. `docker-entrypoint.sh` execs the daemon, so PID 1 can
|
|
34
|
+
* BE the daemon and its child a healthy sibling's worker. Under `docker run
|
|
35
|
+
* --init`, and on any host, PID 1 is an init process instead, so the same
|
|
36
|
+
* parentage means the owner died and the worker needs reclaiming. Callers
|
|
37
|
+
* therefore pass what PID 1 actually is rather than inferring it from whether
|
|
38
|
+
* the deployment is containerized, which is true in both container shapes.
|
|
39
|
+
*/
|
|
40
|
+
export type WorkerOwnership = "reclaim" | "orphan" | "foreign";
|
|
41
|
+
|
|
42
|
+
export function classifyWorkerOwnership(
|
|
43
|
+
worker: WorkerProcess,
|
|
44
|
+
selfPid: number,
|
|
45
|
+
isOwnerAlive: (pid: number) => boolean,
|
|
46
|
+
pid1OwnsWorkers: boolean,
|
|
47
|
+
): WorkerOwnership {
|
|
48
|
+
if (worker.ppid === selfPid) {
|
|
49
|
+
return "reclaim";
|
|
50
|
+
}
|
|
51
|
+
if (worker.ppid <= 1) {
|
|
52
|
+
return pid1OwnsWorkers ? "foreign" : "orphan";
|
|
53
|
+
}
|
|
54
|
+
if (!isOwnerAlive(worker.ppid)) {
|
|
55
|
+
return "orphan";
|
|
56
|
+
}
|
|
57
|
+
return "foreign";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** The packaged daemon binary, with or without a Windows extension. */
|
|
61
|
+
const PACKAGED_DAEMON_ARGV0 = /^vellum-daemon(\.exe)?$/i;
|
|
62
|
+
|
|
63
|
+
/** The runtime the source daemon is launched under. */
|
|
64
|
+
const BUN_ARGV0 = /^bun(\.exe)?$/i;
|
|
65
|
+
|
|
66
|
+
/** The daemon entry script, as a whole trailing path segment pair. */
|
|
67
|
+
const DAEMON_ENTRY_ARG = /(^|\/)daemon\/main\.(ts|js)$/;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Split a command line into argv, keeping quoted paths whole.
|
|
71
|
+
*
|
|
72
|
+
* Splitting on whitespace first would shred the ordinary Windows install path
|
|
73
|
+
* `"C:\\Program Files\\Vellum\\...\\vellum-daemon.exe"` into `"C:\\Program` and
|
|
74
|
+
* make the packaged daemon unrecognisable, which is the very install this
|
|
75
|
+
* recovery path exists for. Separators are normalised per token so a caller
|
|
76
|
+
* can take a basename either way.
|
|
77
|
+
*/
|
|
78
|
+
function tokenizeCommandLine(command: string): string[] {
|
|
79
|
+
const tokens: string[] = [];
|
|
80
|
+
let current = "";
|
|
81
|
+
let quote: string | null = null;
|
|
82
|
+
let started = false;
|
|
83
|
+
|
|
84
|
+
for (const ch of command) {
|
|
85
|
+
if (quote !== null) {
|
|
86
|
+
if (ch === quote) {
|
|
87
|
+
quote = null;
|
|
88
|
+
} else {
|
|
89
|
+
current += ch;
|
|
90
|
+
}
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (ch === '"' || ch === "'") {
|
|
94
|
+
quote = ch;
|
|
95
|
+
started = true;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (/\s/.test(ch)) {
|
|
99
|
+
if (started) {
|
|
100
|
+
tokens.push(current);
|
|
101
|
+
current = "";
|
|
102
|
+
started = false;
|
|
103
|
+
}
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
current += ch;
|
|
107
|
+
started = true;
|
|
108
|
+
}
|
|
109
|
+
if (started) {
|
|
110
|
+
tokens.push(current);
|
|
111
|
+
}
|
|
112
|
+
return tokens.map((token) => token.replaceAll("\\", "/"));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Whether a command line is an assistant daemon, judged by argv shape rather
|
|
117
|
+
* than by substring.
|
|
118
|
+
*
|
|
119
|
+
* Two accepted shapes: the packaged binary as argv0 (which also covers the
|
|
120
|
+
* source daemon once `process.title` rewrites its argv), or bun as argv0 with
|
|
121
|
+
* the daemon entry script among its arguments.
|
|
122
|
+
*
|
|
123
|
+
* The shape matters because this authorises an irreversible signal. A
|
|
124
|
+
* substring test would accept any command line that merely mentions a
|
|
125
|
+
* `daemon/main` path, so an unrelated service, an editor, or a test runner
|
|
126
|
+
* holding a recycled PID would qualify. `cli/src/lib/orphan-detection.test.ts`
|
|
127
|
+
* already pins that exact collision (`node /opt/unrelated-service/daemon/main.ts`)
|
|
128
|
+
* as something we must not claim.
|
|
129
|
+
*/
|
|
130
|
+
export function isDaemonCommand(command: string | null): boolean {
|
|
131
|
+
if (command == null) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
const tokens = tokenizeCommandLine(command);
|
|
135
|
+
const argv0 = tokens[0]?.split("/").pop() ?? "";
|
|
136
|
+
if (PACKAGED_DAEMON_ARGV0.test(argv0)) {
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
if (!BUN_ARGV0.test(argv0)) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
return tokens.slice(1).some((token) => DAEMON_ENTRY_ARG.test(token));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Whether PID 1 is an assistant daemon rather than an init process.
|
|
147
|
+
*
|
|
148
|
+
* True only where the daemon was exec'd as PID 1 (`docker-entrypoint.sh`),
|
|
149
|
+
* which is what makes a worker parented to 1 a live sibling's rather than an
|
|
150
|
+
* orphan. Under `docker run --init` PID 1 is docker-init and under launchd or
|
|
151
|
+
* systemd it is the system init, so both answer false and PID-1 orphans stay
|
|
152
|
+
* reclaimable. Unreadable means false, which keeps the reclaiming behaviour.
|
|
153
|
+
*
|
|
154
|
+
* Callers that already hold a process-table snapshot pass PID 1's command line
|
|
155
|
+
* so this does not read the table a second time.
|
|
156
|
+
*/
|
|
157
|
+
export function pid1OwnsWorkers(pid1Command?: string | null): boolean {
|
|
158
|
+
if (process.pid === 1) {
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
return isDaemonCommand(
|
|
162
|
+
pid1Command === undefined ? readRawProcessCommand(1) : pid1Command,
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Live worker processes matching `workerPath`, as `(pid, ppid)` pairs.
|
|
168
|
+
*
|
|
169
|
+
* `workerPath` is an absolute path unique to one worker of one install, so a
|
|
170
|
+
* sibling instance's workers never match. `model`, when given, must appear as
|
|
171
|
+
* a whole argv token rather than a substring, so a backend for `bge-small`
|
|
172
|
+
* cannot reclaim a live `bge-small-v2` worker.
|
|
173
|
+
*
|
|
174
|
+
* Raw command lines are read for matching only, never stored or logged:
|
|
175
|
+
* process arguments can carry secrets (see the redaction note in
|
|
176
|
+
* `util/process-tree.ts`).
|
|
177
|
+
*/
|
|
178
|
+
export function listWorkerProcesses(
|
|
179
|
+
workerPath: string,
|
|
180
|
+
model?: string,
|
|
181
|
+
): WorkerProcess[] {
|
|
182
|
+
try {
|
|
183
|
+
return listProcessTable()
|
|
184
|
+
.filter(
|
|
185
|
+
(row) =>
|
|
186
|
+
row.command.includes(workerPath) &&
|
|
187
|
+
(!model || row.command.split(/\s+/).includes(model)),
|
|
188
|
+
)
|
|
189
|
+
.map(({ pid, ppid }) => ({ pid, ppid }));
|
|
190
|
+
} catch {
|
|
191
|
+
return [];
|
|
192
|
+
}
|
|
193
|
+
}
|