@vellumai/assistant 0.11.6 → 0.11.7-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 +31 -19
- package/Dockerfile +1 -0
- package/docs/architecture/security.md +3 -3
- package/docs/flux-turn-detection-spike.md +8 -7
- package/docs/runbook-trusted-contacts.md +14 -0
- package/docs/stt-provider-onboarding.md +8 -4
- package/knip.json +1 -0
- package/node_modules/@vellumai/avatar-manifest/package.json +18 -0
- package/node_modules/@vellumai/avatar-manifest/src/__tests__/manifest.test.ts +173 -0
- package/node_modules/@vellumai/avatar-manifest/src/__tests__/read.test.ts +114 -0
- package/node_modules/@vellumai/avatar-manifest/src/index.ts +29 -0
- package/node_modules/@vellumai/avatar-manifest/src/layout.ts +18 -0
- package/node_modules/@vellumai/avatar-manifest/src/manifest.ts +156 -0
- package/node_modules/@vellumai/avatar-manifest/src/read.ts +109 -0
- package/node_modules/@vellumai/avatar-manifest/tsconfig.json +20 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +37 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/remote-web-pairing.test.ts +389 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/ingress.ts +22 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +508 -18
- package/node_modules/@vellumai/environments/package.json +2 -1
- package/node_modules/@vellumai/environments/src/shell.test.ts +56 -0
- package/node_modules/@vellumai/environments/src/shell.ts +65 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +37 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/remote-web-pairing.test.ts +389 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/ingress.ts +22 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +508 -18
- package/node_modules/@vellumai/gateway-client/src/__tests__/contact-read-contracts.test.ts +32 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/gateway-client.test.ts +1 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/inbound-event-kind.test.ts +82 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/plugin-admission-denied-contract.test.ts +56 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +18 -0
- package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +5 -11
- package/node_modules/@vellumai/gateway-client/src/channel-permission-contract.ts +3 -1
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +9 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +4 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-event-kind.ts +75 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +17 -0
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +8 -0
- package/node_modules/@vellumai/gateway-client/src/plugin-admission-denied-contract.ts +47 -0
- package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +11 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +37 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/remote-web-pairing.test.ts +389 -0
- package/node_modules/@vellumai/service-contracts/src/ingress.ts +22 -0
- package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +508 -18
- package/openapi.yaml +419 -55
- package/package.json +4 -1
- package/src/__tests__/approval-card-edit-inbound-handoff.test.ts +199 -0
- package/src/__tests__/approval-card-edit.test.ts +153 -0
- package/src/__tests__/attachments-store-heic-normalize.test.ts +1 -1
- package/src/__tests__/available-skills.test.ts +54 -0
- package/src/__tests__/avatar-identity-sync.test.ts +5 -1
- package/src/__tests__/catalog-install-normalize.test.ts +9 -1
- package/src/__tests__/channel-policy.test.ts +1 -3
- package/src/__tests__/channel-readiness-discord.test.ts +9 -70
- package/src/__tests__/compactor-call-site-logging.test.ts +61 -0
- package/src/__tests__/config-loader-backfill.test.ts +69 -0
- package/src/__tests__/config-schema.test.ts +2 -3
- package/src/__tests__/conversation-agent-loop.test.ts +238 -0
- package/src/__tests__/conversation-attachments.test.ts +0 -1
- package/src/__tests__/conversation-error.test.ts +44 -0
- package/src/__tests__/conversation-evictor.test.ts +19 -0
- package/src/__tests__/conversation-fork-referential.test.ts +73 -63
- package/src/__tests__/conversation-fork-retrospective.test.ts +99 -74
- package/src/__tests__/conversation-runtime-assembly.test.ts +23 -0
- package/src/__tests__/conversation-slash.test.ts +9 -0
- package/src/__tests__/conversation-title-service.test.ts +97 -0
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +16 -0
- package/src/__tests__/credential-broker-server-use.test.ts +0 -218
- package/src/__tests__/credential-broker.test.ts +43 -420
- package/src/__tests__/credential-routes.test.ts +17 -1
- package/src/__tests__/credential-security-invariants.test.ts +8 -9
- package/src/__tests__/default-profile-catalog-fallback.test.ts +289 -0
- package/src/__tests__/discord-callback-round-trip.test.ts +93 -0
- package/src/__tests__/discord-channel-config.test.ts +237 -0
- package/src/__tests__/evict-conversations-for-reload.test.ts +51 -1
- package/src/__tests__/fallback-breaker.test.ts +1335 -0
- package/src/__tests__/gateway-threshold-reader-mock.ts +6 -0
- package/src/__tests__/guardian-card-withdrawal.test.ts +30 -0
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -2
- package/src/__tests__/image-conversion.test.ts +38 -4
- package/src/__tests__/install-skill-routing.test.ts +25 -0
- package/src/__tests__/llm-resolver.test.ts +74 -0
- package/src/__tests__/llm-schema-fallback-profile.test.ts +359 -0
- package/src/__tests__/managed-fallback-dispatch.test.ts +430 -0
- package/src/__tests__/managed-profile-guard.test.ts +28 -0
- package/src/__tests__/managed-speech-defaults.test.ts +196 -1
- package/src/__tests__/mcp-cli.test.ts +23 -1
- package/src/__tests__/mcp-tool-annotations-risk.test.ts +43 -34
- package/src/__tests__/media-stream-stt-session.test.ts +2 -0
- package/src/__tests__/notification-decision-fallback.test.ts +106 -0
- package/src/__tests__/notification-discord-adapter.test.ts +162 -0
- package/src/__tests__/persist-unsendable-image-downscale.test.ts +6 -8
- package/src/__tests__/persist-unsendable-image.test.ts +3 -3
- package/src/__tests__/platform.test.ts +19 -1
- package/src/__tests__/plugin-import-boundary-guard.test.ts +5 -1
- package/src/__tests__/provider-usage-tracking.test.ts +56 -0
- package/src/__tests__/require-fresh-approval.test.ts +5 -2
- package/src/__tests__/retry-fallback-escalation.test.ts +1093 -0
- package/src/__tests__/schedule-retry.test.ts +183 -4
- package/src/__tests__/scheduler-reuse-conversation.test.ts +6 -6
- package/src/__tests__/secret-routes-platform-proxy.test.ts +55 -0
- package/src/__tests__/skill-load-tool.test.ts +34 -0
- package/src/__tests__/skill-tool-factory.test.ts +6 -1
- package/src/__tests__/skill-tool-manifest.test.ts +20 -0
- package/src/__tests__/skills.test.ts +25 -0
- package/src/__tests__/subagent-manager-notify.test.ts +38 -0
- package/src/__tests__/terminal-tools.test.ts +2 -0
- package/src/__tests__/tool-approval-handler.test.ts +146 -2
- package/src/__tests__/tool-approval-seed-content-blocks.test.ts +108 -2
- package/src/__tests__/tool-audit.test.ts +111 -40
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +228 -18
- package/src/__tests__/ui-shape-teaching.test.ts +1 -1
- package/src/__tests__/verification-control-plane-policy.test.ts +6 -2
- package/src/__tests__/voice-config-update.test.ts +43 -0
- package/src/__tests__/workspace-migration-147-rename-colliding-backup-profile-names.test.ts +480 -0
- package/src/__tests__/workspace-migration-148-strip-unsupported-fallback-profiles.test.ts +167 -0
- package/src/__tests__/workspace-migration-149-repoint-backup-profile-selections.test.ts +144 -0
- package/src/agent/loop.ts +31 -2
- package/src/approvals/guardian-card-withdrawal.ts +22 -0
- package/src/avatar/__tests__/ensure-raster.test.ts +291 -0
- package/src/avatar/avatar-manifest.ts +48 -100
- package/src/avatar/avatar-store.ts +7 -7
- package/src/avatar/ensure-raster.ts +146 -0
- package/src/avatar/resvg-lazy.test.ts +0 -1
- package/src/avatar/traits-png-sync.ts +9 -10
- package/src/background-wake/wake-intent-hooks.test.ts +0 -1
- package/src/backup/__tests__/paths.test.ts +6 -2
- package/src/backup/paths.ts +6 -4
- package/src/bundler/app-compiler.ts +2 -2
- package/src/bundler/compiler-tools.test.ts +36 -0
- package/src/bundler/compiler-tools.ts +54 -18
- package/src/calls/__tests__/telephony-synthesis-language.test.ts +81 -0
- package/src/calls/__tests__/voice-session-bridge.test.ts +20 -0
- package/src/calls/media-stream-stt-session.ts +2 -1
- package/src/calls/telephony-synthesis-language.ts +13 -5
- package/src/calls/voice-session-bridge.ts +6 -4
- package/src/channels/config.ts +23 -12
- package/src/cli/AGENTS.md +3 -1
- package/src/cli/commands/backup.help.ts +10 -13
- package/src/cli/commands/contacts.help.ts +16 -4
- package/src/cli/commands/contacts.ts +5 -1
- package/src/cli/commands/credentials.help.ts +1 -1
- package/src/cli/commands/inference-providers.ts +18 -53
- package/src/cli/commands/mcp.help.ts +6 -5
- package/src/cli/commands/mcp.ts +1 -1
- package/src/cli/commands/memory/memory-retrospective.ts +1 -0
- package/src/cli/commands/stt.help.ts +2 -2
- package/src/config/__tests__/backup-schema.test.ts +1 -1
- package/src/config/__tests__/default-profile-catalog.test.ts +83 -2
- package/src/config/__tests__/loader-sparse-array-cleanup.test.ts +1 -1
- package/src/config/__tests__/memory-retrospective-schema.test.ts +12 -15
- package/src/config/bundled-skills/computer-use/SKILL.md +6 -6
- package/src/config/bundled-skills/computer-use/TOOLS.json +9 -6
- package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +3 -1
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +1 -1
- package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +16 -15
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +2 -1
- package/src/config/bundled-skills/settings/TOOLS.json +11 -5
- package/src/config/bundled-skills/settings/tools/open-system-settings.test.ts +85 -0
- package/src/config/bundled-skills/settings/tools/open-system-settings.ts +33 -8
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +54 -3
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +1 -1
- package/src/config/default-profile-catalog.ts +195 -10
- package/src/config/default-profile-names.ts +41 -0
- package/src/config/feature-flag-registry.json +18 -2
- package/src/config/inference-profile-validation.ts +11 -2
- package/src/config/llm-resolver.ts +25 -0
- package/src/config/loader.ts +3 -0
- package/src/config/managed-speech-defaults.ts +160 -10
- package/src/config/schema.ts +2 -0
- package/src/config/schemas/__tests__/live-voice.test.ts +7 -6
- package/src/config/schemas/__tests__/stt.test.ts +94 -0
- package/src/config/schemas/backup.ts +1 -1
- package/src/config/schemas/channels.ts +32 -0
- package/src/config/schemas/live-voice.ts +13 -4
- package/src/config/schemas/llm.ts +259 -11
- package/src/config/schemas/mcp.ts +10 -2
- package/src/config/schemas/memory-retrospective.ts +1 -19
- package/src/config/schemas/services.ts +24 -0
- package/src/config/schemas/stt.ts +128 -5
- package/src/config/seed-inference-profiles.ts +32 -3
- package/src/config/skill-state.ts +5 -0
- package/src/config/skills.ts +21 -0
- package/src/contacts/notify-contacts-changed.ts +5 -2
- package/src/context/compactor.ts +74 -1
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +138 -1
- package/src/daemon/config-watcher.ts +2 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +27 -0
- package/src/daemon/conversation-agent-loop.ts +86 -10
- package/src/daemon/conversation-error.ts +25 -0
- package/src/daemon/conversation-evictor.ts +2 -4
- package/src/daemon/conversation-runtime-assembly.ts +19 -7
- package/src/daemon/conversation-skill-tools.ts +5 -1
- package/src/daemon/conversation-slash.ts +2 -2
- package/src/daemon/conversation-store.ts +8 -1
- package/src/daemon/conversation-tool-setup.ts +37 -0
- package/src/daemon/conversation.ts +0 -4
- package/src/daemon/handlers/channel-config-result.ts +30 -0
- package/src/daemon/handlers/config-discord-channel.ts +301 -0
- package/src/daemon/handlers/config-ingress.ts +9 -0
- package/src/daemon/handlers/config-slack-channel.ts +13 -15
- package/src/daemon/handlers/config-telegram.ts +2 -5
- package/src/daemon/handlers/conversations.ts +0 -2
- package/src/daemon/handlers/skills.ts +25 -7
- package/src/daemon/lifecycle.ts +3 -1
- package/src/daemon/message-types/messages.ts +0 -2
- package/src/daemon/tool-setup-types.ts +5 -3
- package/src/daemon/trust-context-types.test.ts +33 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -1
- package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +58 -4
- package/src/live-voice/__tests__/live-voice-managed-speech-fallback.test.ts +8 -3
- package/src/live-voice/__tests__/live-voice-session-manager.test.ts +73 -11
- package/src/live-voice/__tests__/live-voice-session-preflight.test.ts +148 -2
- package/src/live-voice/__tests__/live-voice-stt.test.ts +3 -0
- package/src/live-voice/__tests__/live-voice-text-turn.test.ts +391 -0
- package/src/live-voice/__tests__/protocol.test.ts +224 -0
- package/src/live-voice/live-voice-credential-preflight.ts +5 -1
- package/src/live-voice/live-voice-session-manager.ts +39 -2
- package/src/live-voice/live-voice-session.ts +213 -8
- package/src/live-voice/protocol.ts +187 -1
- package/src/messaging/providers/discord/api.ts +2 -1
- package/src/messaging/providers/discord/withdraw.ts +62 -0
- package/src/monitoring/__tests__/file-descriptors.test.ts +32 -0
- package/src/monitoring/__tests__/process-memory.test.ts +34 -1
- package/src/monitoring/db-integrity-sample.ts +1 -0
- package/src/monitoring/file-descriptors.ts +39 -6
- package/src/monitoring/process-memory.ts +37 -5
- package/src/monitoring/resource-sampler.ts +25 -8
- package/src/notifications/__tests__/activity-failed-dedupe.test.ts +161 -0
- package/src/notifications/__tests__/background-failure-signal.test.ts +107 -0
- package/src/notifications/__tests__/connected-channels.test.ts +12 -0
- package/src/notifications/__tests__/copy-composer.test.ts +83 -0
- package/src/notifications/__tests__/destination-resolver.test.ts +38 -0
- package/src/notifications/activity-failed-dedupe.ts +95 -0
- package/src/notifications/adapters/discord.ts +132 -0
- package/src/notifications/adapters/shared.ts +16 -0
- package/src/notifications/adapters/slack.ts +6 -1
- package/src/notifications/adapters/telegram.ts +5 -8
- package/src/notifications/approval-card-data.ts +94 -1
- package/src/notifications/background-failure-signal.ts +116 -0
- package/src/notifications/broadcaster.ts +3 -9
- package/src/notifications/copy-composer.ts +41 -8
- package/src/notifications/decision-engine.ts +55 -0
- package/src/notifications/destination-resolver.ts +45 -5
- package/src/notifications/emit-signal.ts +19 -5
- package/src/notifications/guardian-question-mode.ts +57 -0
- package/src/notifications/types.ts +2 -8
- package/src/oauth/__tests__/seed-providers-managed.test.ts +18 -0
- package/src/oauth/seed-providers.ts +261 -0
- package/src/permissions/checker.test.ts +21 -0
- package/src/permissions/checker.ts +6 -0
- package/src/permissions/gateway-threshold-reader.test.ts +184 -0
- package/src/permissions/gateway-threshold-reader.ts +144 -10
- package/src/permissions/prompter.ts +0 -6
- package/src/permissions/types.ts +5 -4
- package/src/persistence/__tests__/bulk-write-gate.test.ts +0 -38
- package/src/persistence/bulk-write-gate.ts +9 -10
- package/src/persistence/conversation-crud.ts +85 -133
- package/src/persistence/conversation-title-service.ts +43 -29
- package/src/persistence/embeddings/embedding-local.ts +14 -85
- package/src/persistence/embeddings/embedding-runtime-manager.ts +1 -0
- package/src/persistence/lifecycle-events-store.test.ts +50 -0
- package/src/persistence/lifecycle-events-store.ts +75 -5
- package/src/platform/platform-patch-queue.test.ts +368 -0
- package/src/platform/platform-patch-queue.ts +190 -0
- package/src/platform/sync-avatar.test.ts +527 -0
- package/src/platform/sync-avatar.ts +209 -0
- package/src/platform/sync-identity.test.ts +75 -0
- package/src/platform/sync-identity.ts +17 -110
- package/src/plugin-api/model-profiles.test.ts +2 -2
- package/src/plugin-api/model-profiles.ts +7 -7
- package/src/plugin-api/transcription-session.ts +1 -1
- package/src/plugins/defaults/compaction/window-manager.ts +4 -0
- package/src/plugins/defaults/image-recovery/recover.ts +2 -2
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +133 -34
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +21 -31
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +13 -9
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +31 -4
- package/src/plugins/defaults/memory/memory-retrospective-constants.ts +0 -11
- package/src/plugins/defaults/memory/memory-retrospective-fork-boundary.ts +27 -13
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +183 -113
- package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +13 -13
- package/src/plugins/defaults/memory/src/memory-v2-routes.ts +2 -2
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +1 -1
- package/src/plugins/defaults/memory/substrate/consolidation-job.ts +4 -2
- package/src/plugins/defaults/memory/substrate/sweep-job.ts +9 -42
- package/src/plugins/defaults/memory/v2/rerank-local.ts +1 -0
- package/src/plugins/defaults/memory/worker.ts +7 -0
- package/src/providers/__tests__/retry-callsite.test.ts +55 -0
- package/src/providers/connection-resolution.ts +2 -17
- package/src/providers/fallback-breaker.ts +389 -0
- package/src/providers/inference/__tests__/adapter-factory-opencode.test.ts +92 -0
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +16 -0
- package/src/providers/inference/__tests__/base-url-security.test.ts +12 -0
- package/src/providers/inference/adapter-factory.ts +231 -3
- package/src/providers/inference/auth.ts +1 -0
- package/src/providers/inference/chatgpt-subscription-auth.ts +70 -0
- package/src/providers/inference/chatgpt-subscription-credentials.ts +84 -0
- package/src/providers/inference/resolve-auth.ts +1 -1
- package/src/providers/media-resolve.ts +2 -3
- package/src/providers/model-catalog.ts +75 -0
- package/src/providers/openai/__tests__/api-error-normalization.test.ts +22 -0
- package/src/providers/openai/__tests__/chat-template-flatten.test.ts +198 -0
- package/src/providers/openai/api-error-normalization.ts +13 -0
- package/src/providers/openai/chat-completions-provider.ts +84 -0
- package/src/providers/opencode/client.test.ts +150 -0
- package/src/providers/opencode/client.ts +61 -0
- package/src/providers/provider-resolvability.ts +20 -0
- package/src/providers/retry.ts +956 -68
- package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +42 -0
- package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +136 -0
- package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +120 -0
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +316 -12
- package/src/providers/speech-to-text/deepgram-flux-frames.ts +83 -3
- package/src/providers/speech-to-text/deepgram-flux-realtime.ts +132 -4
- package/src/providers/speech-to-text/provider-catalog.ts +191 -3
- package/src/providers/speech-to-text/resolve.ts +132 -24
- package/src/providers/speech-to-text/vellum-managed-flux-realtime.ts +296 -0
- package/src/providers/types.ts +39 -0
- package/src/providers/usage-tracking.ts +12 -1
- package/src/runtime/AGENTS.md +1 -2
- package/src/runtime/__tests__/agent-wake.test.ts +105 -3
- package/src/runtime/__tests__/background-job-runner.test.ts +89 -3
- package/src/runtime/agent-wake.ts +44 -5
- package/src/runtime/background-job-runner.ts +68 -50
- package/src/runtime/channel-readiness-service.ts +0 -39
- package/src/runtime/http-server.ts +12 -4
- package/src/runtime/routes/__tests__/avatar-state-routes.test.ts +18 -18
- package/src/runtime/routes/__tests__/backup-routes.test.ts +9 -5
- package/src/runtime/routes/__tests__/chatgpt-subscription-auth-routes.test.ts +372 -0
- package/src/runtime/routes/__tests__/contact-routes.test.ts +7 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +96 -0
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +37 -0
- package/src/runtime/routes/__tests__/ingress-status-routes.test.ts +2 -1
- package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +87 -0
- package/src/runtime/routes/acp-claude-auth-routes.ts +25 -54
- package/src/runtime/routes/approval-routes.ts +0 -12
- package/src/runtime/routes/avatar-routes.ts +17 -30
- package/src/runtime/routes/backup-routes.ts +2 -2
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +296 -113
- package/src/runtime/routes/contact-prompt-routes.ts +65 -6
- package/src/runtime/routes/contact-routes.ts +20 -15
- package/src/runtime/routes/conversation-query-routes.ts +98 -10
- package/src/runtime/routes/conversation-routes.ts +2 -2
- package/src/runtime/routes/credential-routes.ts +2 -1
- package/src/runtime/routes/guardian-approval-interception.ts +12 -9
- package/src/runtime/routes/inbound-message-handler.ts +21 -9
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +18 -13
- package/src/runtime/routes/inbound-stages/admission-policy.ts +2 -2
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +9 -8
- package/src/runtime/routes/inbound-stages/transcribe-audio.ts +1 -1
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/inference-profile-session-handler.ts +2 -2
- package/src/runtime/routes/inference-profiles-routes.ts +12 -7
- package/src/runtime/routes/inference-provider-connection-routes.ts +2 -2
- package/src/runtime/routes/inference-send-routes.ts +2 -2
- package/src/runtime/routes/ingress-status-routes.ts +2 -2
- package/src/runtime/routes/integrations/discord.ts +119 -0
- package/src/runtime/routes/llm-call-sites-routes.ts +2 -2
- package/src/runtime/routes/mcp-auth-routes.ts +12 -6
- package/src/runtime/routes/oauth-pending-flows.ts +136 -0
- package/src/runtime/routes/secret-routes.ts +7 -0
- package/src/runtime/routes/stt-routes.ts +38 -6
- package/src/runtime/routes/wake-conversation-routes.ts +8 -1
- package/src/runtime/routes/watch-routes.ts +9 -5
- package/src/runtime/sync/resource-sync-events.ts +2 -0
- package/src/runtime/trust-verdict-consumer.ts +3 -3
- package/src/schedule/__tests__/worker-feature-flags.test.ts +58 -0
- package/src/schedule/retry-policy.ts +8 -8
- package/src/schedule/run-script.ts +14 -23
- package/src/schedule/schedule-recovery.ts +0 -1
- package/src/schedule/scheduler.ts +89 -51
- package/src/schedule/worker.ts +57 -0
- package/src/security/__tests__/openai-device-auth.test.ts +355 -0
- package/src/security/oauth2.ts +7 -0
- package/src/security/openai-device-auth.ts +364 -0
- package/src/skills/available-skills.ts +10 -1
- package/src/skills/catalog-install.ts +19 -1
- package/src/skills/inline-command-runner.ts +6 -2
- package/src/skills/platform-compatibility.test.ts +49 -0
- package/src/skills/platform-compatibility.ts +65 -0
- package/src/skills/tool-manifest.ts +20 -0
- package/src/stt/__tests__/roles.test.ts +152 -0
- package/src/stt/daemon-batch-transcriber.ts +4 -1
- package/src/stt/roles.ts +180 -0
- package/src/stt/stt-stream-session.ts +3 -2
- package/src/stt/types.ts +33 -3
- package/src/subagent/manager.ts +12 -0
- package/src/telemetry/__tests__/live-voice-funnel.test.ts +34 -0
- package/src/telemetry/live-voice-funnel.ts +12 -2
- package/src/telemetry/telemetry-wire.generated.ts +6 -0
- package/src/telemetry/tool-audit.ts +83 -30
- package/src/telemetry/tool-executed-events-store.test.ts +0 -1
- package/src/telemetry/turn-outcome.ts +75 -4
- package/src/tools/client-os.ts +115 -0
- package/src/tools/computer-use/definitions.ts +11 -7
- package/src/tools/computer-use/skill-proxy-bridge.ts +2 -2
- package/src/tools/credentials/broker-types.ts +0 -71
- package/src/tools/credentials/broker.ts +6 -201
- package/src/tools/executor.ts +2 -8
- package/src/tools/host-shell.test.ts +75 -0
- package/src/tools/host-shell.ts +24 -0
- package/src/tools/host-terminal/host-shell.ts +15 -44
- package/src/tools/mcp/mcp-tool-factory.ts +44 -9
- package/src/tools/permission-checker.ts +73 -21
- package/src/tools/policy-context.ts +1 -0
- package/src/tools/shared/filesystem/image-read.ts +1 -1
- package/src/tools/skills/find-similar-skills.ts +10 -2
- package/src/tools/skills/load.ts +41 -6
- package/src/tools/skills/sandbox-runner.ts +8 -16
- package/src/tools/skills/skill-tool-factory.ts +1 -0
- package/src/tools/terminal/__tests__/safe-env.test.ts +64 -1
- package/src/tools/terminal/safe-env.ts +39 -9
- package/src/tools/terminal/shell.ts +12 -31
- package/src/tools/tool-approval-handler.ts +64 -10
- package/src/tools/tool-types.ts +10 -0
- package/src/tools/types.ts +19 -3
- package/src/tools/ui-surface/surface-shape-docs.ts +4 -3
- package/src/tools/workflows/manage-workflows.ts +2 -2
- package/src/tools/workflows/run-workflow.test.ts +2 -1
- package/src/util/__tests__/file-use.test.ts +61 -0
- package/src/util/__tests__/process-table.test.ts +66 -0
- package/src/util/__tests__/process-tree.test.ts +46 -0
- package/src/util/errors.ts +1 -0
- package/src/util/file-use.ts +91 -0
- package/src/util/host-process.test.ts +17 -0
- package/src/util/host-process.ts +75 -0
- package/src/util/image-conversion.ts +39 -7
- package/src/util/platform.ts +32 -13
- package/src/util/process-table.ts +249 -0
- package/src/util/process-tree.ts +85 -129
- package/src/util/provider-error-patterns.ts +20 -0
- package/src/util/spawn.ts +9 -2
- package/src/workspace/git-service.ts +4 -14
- package/src/workspace/migrations/147-rename-colliding-backup-profile-names.ts +360 -0
- package/src/workspace/migrations/148-strip-unsupported-fallback-profiles.ts +117 -0
- package/src/workspace/migrations/149-repoint-backup-profile-selections.ts +204 -0
- package/src/workspace/migrations/150-stt-flux-provider-to-model-family.ts +139 -0
- package/src/workspace/migrations/__tests__/150-stt-flux-provider-to-model-family.test.ts +160 -0
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/__tests__/permission-types.test.ts +0 -17
- package/src/channels/gateway-discord-admission.ts +0 -32
- package/src/persistence/fork-message-copy.ts +0 -239
- package/src/plugins/defaults/memory/__tests__/fork-message-copy.test.ts +0 -221
- package/src/security/__tests__/oauth2-device-code.test.ts +0 -431
- package/src/security/oauth2-device-code.ts +0 -312
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* JPEG conversion for images, backed by macOS `sips
|
|
2
|
+
* JPEG conversion for images, backed by sharp with a macOS `sips` fallback.
|
|
3
3
|
*
|
|
4
4
|
* Two consumers share this converter:
|
|
5
5
|
* - transport optimization (`agent/image-optimize.ts`) downscales large
|
|
@@ -7,10 +7,8 @@
|
|
|
7
7
|
* - storage normalization (attachment ingress + history hydration) converts
|
|
8
8
|
* HEIF/HEIC — which Chromium-based clients cannot decode — to JPEG.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* cached on disk keyed by content hash + conversion options, so repeated
|
|
13
|
-
* conversions of the same image (or daemon restarts) skip the sips call.
|
|
10
|
+
* Results are cached on disk keyed by content hash + conversion options, so
|
|
11
|
+
* repeated conversions of the same image skip the encoder call.
|
|
14
12
|
*/
|
|
15
13
|
|
|
16
14
|
import { createHash } from "node:crypto";
|
|
@@ -291,9 +289,41 @@ async function runSips(
|
|
|
291
289
|
}
|
|
292
290
|
}
|
|
293
291
|
|
|
292
|
+
async function runSharp(
|
|
293
|
+
inputBytes: Uint8Array,
|
|
294
|
+
options: ConvertToJpegOptions,
|
|
295
|
+
): Promise<Buffer | null> {
|
|
296
|
+
try {
|
|
297
|
+
const { default: sharp } = await import("sharp");
|
|
298
|
+
let pipeline = sharp(inputBytes, { failOn: "error" }).autoOrient();
|
|
299
|
+
if (options.resizeToPx != null) {
|
|
300
|
+
pipeline = pipeline.resize(
|
|
301
|
+
options.resizeToPx.width,
|
|
302
|
+
options.resizeToPx.height,
|
|
303
|
+
{ fit: "fill" },
|
|
304
|
+
);
|
|
305
|
+
} else if (options.maxDimensionPx != null) {
|
|
306
|
+
pipeline = pipeline.resize(
|
|
307
|
+
options.maxDimensionPx,
|
|
308
|
+
options.maxDimensionPx,
|
|
309
|
+
{
|
|
310
|
+
fit: "inside",
|
|
311
|
+
withoutEnlargement: true,
|
|
312
|
+
},
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
const out = await pipeline
|
|
316
|
+
.jpeg({ quality: options.quality ?? DEFAULT_JPEG_QUALITY })
|
|
317
|
+
.toBuffer();
|
|
318
|
+
return isCompleteJpeg(out) ? out : null;
|
|
319
|
+
} catch {
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
294
324
|
/**
|
|
295
325
|
* Convert an image to JPEG, optionally downscaling. Returns null when
|
|
296
|
-
* conversion is unavailable
|
|
326
|
+
* conversion is unavailable or fails; callers keep the original.
|
|
297
327
|
*/
|
|
298
328
|
export async function convertImageToJpeg(
|
|
299
329
|
bytes: Uint8Array,
|
|
@@ -313,7 +343,9 @@ export async function convertImageToJpeg(
|
|
|
313
343
|
return cached;
|
|
314
344
|
}
|
|
315
345
|
|
|
316
|
-
const converted =
|
|
346
|
+
const converted =
|
|
347
|
+
(await runSharp(bytes, options)) ??
|
|
348
|
+
(process.platform === "darwin" ? await runSips(bytes, options) : null);
|
|
317
349
|
if (!converted) {
|
|
318
350
|
return null;
|
|
319
351
|
}
|
package/src/util/platform.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { chmodSync, existsSync, mkdirSync, realpathSync } from "node:fs";
|
|
2
2
|
import { homedir, tmpdir } from "node:os";
|
|
3
|
-
import { basename, dirname, join, sep } from "node:path";
|
|
3
|
+
import { basename, dirname, isAbsolute, join, relative, sep } from "node:path";
|
|
4
4
|
|
|
5
|
+
import {
|
|
6
|
+
AVATAR_IMAGE_FILENAME,
|
|
7
|
+
AVATAR_MANIFEST_FILENAME,
|
|
8
|
+
resolveAvatarDir,
|
|
9
|
+
} from "@vellumai/avatar-manifest";
|
|
5
10
|
import { SEEDS } from "@vellumai/environments";
|
|
6
11
|
|
|
7
12
|
import { getWorkspaceDirOverride } from "../config/env-registry.js";
|
|
@@ -160,20 +165,14 @@ export function getSoundsDir(): string {
|
|
|
160
165
|
|
|
161
166
|
/** Returns the avatar directory ($VELLUM_WORKSPACE_DIR/data/avatar). */
|
|
162
167
|
export function getAvatarDir(): string {
|
|
163
|
-
return
|
|
168
|
+
return resolveAvatarDir(getWorkspaceDir());
|
|
164
169
|
}
|
|
165
170
|
|
|
166
|
-
/** Canonical filename for the custom avatar PNG. */
|
|
167
|
-
export const AVATAR_IMAGE_FILENAME = "avatar-image.png";
|
|
168
|
-
|
|
169
171
|
/** Returns the canonical avatar image path ($VELLUM_WORKSPACE_DIR/data/avatar/avatar-image.png). */
|
|
170
172
|
export function getAvatarImagePath(): string {
|
|
171
173
|
return join(getAvatarDir(), AVATAR_IMAGE_FILENAME);
|
|
172
174
|
}
|
|
173
175
|
|
|
174
|
-
/** Canonical filename for the avatar state manifest. */
|
|
175
|
-
export const AVATAR_MANIFEST_FILENAME = "avatar.json";
|
|
176
|
-
|
|
177
176
|
/** Returns the canonical avatar manifest path ($VELLUM_WORKSPACE_DIR/data/avatar/avatar.json). */
|
|
178
177
|
export function getAvatarManifestPath(): string {
|
|
179
178
|
return join(getAvatarDir(), AVATAR_MANIFEST_FILENAME);
|
|
@@ -442,12 +441,32 @@ export function getWorkspaceDir(): string {
|
|
|
442
441
|
* /data/.vellum/workspace → /data/.vellum/workspace
|
|
443
442
|
*/
|
|
444
443
|
export function getWorkspaceDirDisplay(): string {
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
444
|
+
return formatHomeRelativePath(getWorkspaceDir(), homedir());
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
interface PathOperations {
|
|
448
|
+
isAbsolute(path: string): boolean;
|
|
449
|
+
relative(from: string, to: string): string;
|
|
450
|
+
sep: string;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
export function formatHomeRelativePath(
|
|
454
|
+
absolutePath: string,
|
|
455
|
+
homePath: string,
|
|
456
|
+
pathOperations: PathOperations = { isAbsolute, relative, sep },
|
|
457
|
+
): string {
|
|
458
|
+
const relativeToHome = pathOperations.relative(homePath, absolutePath);
|
|
459
|
+
if (relativeToHome === "") {
|
|
460
|
+
return "~";
|
|
461
|
+
}
|
|
462
|
+
if (
|
|
463
|
+
relativeToHome !== ".." &&
|
|
464
|
+
!relativeToHome.startsWith(`..${pathOperations.sep}`) &&
|
|
465
|
+
!pathOperations.isAbsolute(relativeToHome)
|
|
466
|
+
) {
|
|
467
|
+
return `~${pathOperations.sep}${relativeToHome}`;
|
|
449
468
|
}
|
|
450
|
-
return
|
|
469
|
+
return absolutePath;
|
|
451
470
|
}
|
|
452
471
|
|
|
453
472
|
/** Returns $VELLUM_WORKSPACE_DIR/config.json */
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
|
|
5
|
+
const execFileAsync = promisify(execFile);
|
|
6
|
+
|
|
7
|
+
const PS_PROCESS_TABLE_COMMAND = [
|
|
8
|
+
"ps",
|
|
9
|
+
"-A",
|
|
10
|
+
"-ww",
|
|
11
|
+
"-o",
|
|
12
|
+
"pid=,ppid=,command=",
|
|
13
|
+
];
|
|
14
|
+
const WINDOWS_PROCESS_TABLE_COMMAND = [
|
|
15
|
+
"powershell.exe",
|
|
16
|
+
"-NoProfile",
|
|
17
|
+
"-NonInteractive",
|
|
18
|
+
"-Command",
|
|
19
|
+
"$OutputEncoding = [Console]::OutputEncoding = [Text.UTF8Encoding]::new(); Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId,CommandLine,Name,WorkingSetSize,HandleCount | ConvertTo-Json -Compress",
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
export interface ProcessTableRow {
|
|
23
|
+
pid: number;
|
|
24
|
+
ppid: number;
|
|
25
|
+
/** Raw command line. Callers must redact it before logging or persistence. */
|
|
26
|
+
command: string;
|
|
27
|
+
rssBytes?: number;
|
|
28
|
+
handleCount?: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ProcessTableOptions {
|
|
32
|
+
platform?: NodeJS.Platform;
|
|
33
|
+
processTable?: readonly ProcessTableRow[] | null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function getProcessTableRows(
|
|
37
|
+
options: ProcessTableOptions = {},
|
|
38
|
+
): readonly ProcessTableRow[] {
|
|
39
|
+
if (options.processTable !== undefined) {
|
|
40
|
+
return options.processTable ?? [];
|
|
41
|
+
}
|
|
42
|
+
return listProcessTable(options.platform);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface WindowsProcessRow {
|
|
46
|
+
ProcessId?: unknown;
|
|
47
|
+
ParentProcessId?: unknown;
|
|
48
|
+
CommandLine?: unknown;
|
|
49
|
+
Name?: unknown;
|
|
50
|
+
WorkingSetSize?: unknown;
|
|
51
|
+
HandleCount?: unknown;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function nonNegativeNumber(value: unknown): number | null {
|
|
55
|
+
if (typeof value !== "number" && typeof value !== "string") {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const parsed = Number(value);
|
|
59
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function parseProcStat(
|
|
63
|
+
content: string,
|
|
64
|
+
): { command: string; ppid: number } | null {
|
|
65
|
+
const lparen = content.indexOf("(");
|
|
66
|
+
const rparen = content.lastIndexOf(")");
|
|
67
|
+
if (lparen === -1 || rparen === -1 || rparen < lparen) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
const ppid = Number(content.slice(rparen + 2).split(" ")[1]);
|
|
71
|
+
if (!Number.isInteger(ppid)) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
return { command: content.slice(lparen + 1, rparen), ppid };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function listProcessesFromProc(): ProcessTableRow[] {
|
|
78
|
+
const rows: ProcessTableRow[] = [];
|
|
79
|
+
for (const entry of readdirSync("/proc")) {
|
|
80
|
+
const pid = Number(entry);
|
|
81
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
const stat = parseProcStat(readFileSync(`/proc/${pid}/stat`, "utf8"));
|
|
87
|
+
if (!stat) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const commandLine = readFileSync(`/proc/${pid}/cmdline`, "utf8")
|
|
91
|
+
.split("\0")
|
|
92
|
+
.filter(Boolean)
|
|
93
|
+
.join(" ");
|
|
94
|
+
rows.push({
|
|
95
|
+
pid,
|
|
96
|
+
ppid: stat.ppid,
|
|
97
|
+
command: commandLine || stat.command,
|
|
98
|
+
});
|
|
99
|
+
} catch {
|
|
100
|
+
// Processes can exit between reading the directory and their files.
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return rows;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function parsePsProcessTable(output: string): ProcessTableRow[] {
|
|
107
|
+
const rows: ProcessTableRow[] = [];
|
|
108
|
+
for (const line of output.split("\n")) {
|
|
109
|
+
const match = line.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/);
|
|
110
|
+
if (!match) {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
rows.push({
|
|
114
|
+
pid: Number(match[1]),
|
|
115
|
+
ppid: Number(match[2]),
|
|
116
|
+
command: match[3].trim(),
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
return rows;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function parseWindowsProcessTable(output: string): ProcessTableRow[] {
|
|
123
|
+
const decoded: unknown = JSON.parse(output.trim().replace(/^\uFEFF/, ""));
|
|
124
|
+
const values = Array.isArray(decoded) ? decoded : [decoded];
|
|
125
|
+
const rows: ProcessTableRow[] = [];
|
|
126
|
+
for (const value of values) {
|
|
127
|
+
if (!value || typeof value !== "object") {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const processRow = value as WindowsProcessRow;
|
|
131
|
+
const pid = Number(processRow.ProcessId);
|
|
132
|
+
const ppid = Number(processRow.ParentProcessId);
|
|
133
|
+
if (!Number.isInteger(pid) || pid <= 0 || !Number.isInteger(ppid)) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const commandLine =
|
|
137
|
+
typeof processRow.CommandLine === "string" ? processRow.CommandLine : "";
|
|
138
|
+
const name = typeof processRow.Name === "string" ? processRow.Name : "";
|
|
139
|
+
const rssBytes = nonNegativeNumber(processRow.WorkingSetSize);
|
|
140
|
+
const handleCount = nonNegativeNumber(processRow.HandleCount);
|
|
141
|
+
rows.push({
|
|
142
|
+
pid,
|
|
143
|
+
ppid,
|
|
144
|
+
command: commandLine || name,
|
|
145
|
+
...(rssBytes == null ? {} : { rssBytes }),
|
|
146
|
+
...(handleCount == null ? {} : { handleCount }),
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
return rows;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function runProcessTableCommand(
|
|
153
|
+
command: string[],
|
|
154
|
+
parser: (output: string) => ProcessTableRow[],
|
|
155
|
+
): ProcessTableRow[] {
|
|
156
|
+
const result = Bun.spawnSync({
|
|
157
|
+
cmd: command,
|
|
158
|
+
stdout: "pipe",
|
|
159
|
+
stderr: "ignore",
|
|
160
|
+
});
|
|
161
|
+
if (result.exitCode !== 0) {
|
|
162
|
+
throw new Error(
|
|
163
|
+
`Process enumeration failed with exit code ${result.exitCode}`,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
return parser(new TextDecoder().decode(result.stdout));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function runProcessTableCommandAsync(
|
|
170
|
+
command: string[],
|
|
171
|
+
parser: (output: string) => ProcessTableRow[],
|
|
172
|
+
): Promise<ProcessTableRow[]> {
|
|
173
|
+
const { stdout } = await execFileAsync(command[0], command.slice(1), {
|
|
174
|
+
encoding: "utf8",
|
|
175
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
176
|
+
});
|
|
177
|
+
return parser(stdout);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function listProcessesFromPs(): ProcessTableRow[] {
|
|
181
|
+
return runProcessTableCommand(PS_PROCESS_TABLE_COMMAND, parsePsProcessTable);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function listProcessesFromPowerShell(): ProcessTableRow[] {
|
|
185
|
+
return runProcessTableCommand(
|
|
186
|
+
WINDOWS_PROCESS_TABLE_COMMAND,
|
|
187
|
+
parseWindowsProcessTable,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function listProcessTable(
|
|
192
|
+
platform: NodeJS.Platform = process.platform,
|
|
193
|
+
): ProcessTableRow[] {
|
|
194
|
+
if (platform === "win32") {
|
|
195
|
+
return listProcessesFromPowerShell();
|
|
196
|
+
}
|
|
197
|
+
if (platform === "linux") {
|
|
198
|
+
try {
|
|
199
|
+
return listProcessesFromProc();
|
|
200
|
+
} catch {
|
|
201
|
+
return listProcessesFromPs();
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return listProcessesFromPs();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export async function listProcessTableAsync(
|
|
208
|
+
platform: NodeJS.Platform = process.platform,
|
|
209
|
+
): Promise<ProcessTableRow[]> {
|
|
210
|
+
if (platform === "win32") {
|
|
211
|
+
return runProcessTableCommandAsync(
|
|
212
|
+
WINDOWS_PROCESS_TABLE_COMMAND,
|
|
213
|
+
parseWindowsProcessTable,
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
if (platform === "linux") {
|
|
217
|
+
try {
|
|
218
|
+
return listProcessesFromProc();
|
|
219
|
+
} catch {
|
|
220
|
+
return runProcessTableCommandAsync(
|
|
221
|
+
PS_PROCESS_TABLE_COMMAND,
|
|
222
|
+
parsePsProcessTable,
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return runProcessTableCommandAsync(
|
|
227
|
+
PS_PROCESS_TABLE_COMMAND,
|
|
228
|
+
parsePsProcessTable,
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function readRawProcessCommand(pid: number): string | null {
|
|
233
|
+
if (process.platform === "linux") {
|
|
234
|
+
try {
|
|
235
|
+
return readFileSync(`/proc/${pid}/cmdline`, "utf8")
|
|
236
|
+
.split("\0")
|
|
237
|
+
.filter(Boolean)
|
|
238
|
+
.join(" ");
|
|
239
|
+
} catch {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
return listProcessTable().find((row) => row.pid === pid)?.command ?? null;
|
|
246
|
+
} catch {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
}
|
package/src/util/process-tree.ts
CHANGED
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
* process that is actually parented to it.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
import {
|
|
13
|
+
listProcessTableAsync,
|
|
14
|
+
type ProcessTableRow,
|
|
15
|
+
readRawProcessCommand,
|
|
16
|
+
} from "./process-table.js";
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Whether a process runs plugin-owned code or is the daemon itself / one of its
|
|
@@ -52,6 +52,12 @@ export interface ProcTreeNode {
|
|
|
52
52
|
children: ProcTreeNode[];
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
interface ProcessTreeBuilder {
|
|
56
|
+
build: (pid: number) => ProcTreeNode;
|
|
57
|
+
byPid: Map<number, ProcInfo>;
|
|
58
|
+
visited: Set<number>;
|
|
59
|
+
}
|
|
60
|
+
|
|
55
61
|
/** Interpreters whose script argument is more descriptive than argv[0]. */
|
|
56
62
|
const RUNTIMES = new Set([
|
|
57
63
|
"bun",
|
|
@@ -64,7 +70,14 @@ const RUNTIMES = new Set([
|
|
|
64
70
|
"env",
|
|
65
71
|
]);
|
|
66
72
|
|
|
67
|
-
const basename = (p: string): string => p.split(
|
|
73
|
+
const basename = (p: string): string => p.split(/[\\/]/).pop() || p;
|
|
74
|
+
|
|
75
|
+
function splitCommand(command: string): string[] {
|
|
76
|
+
return Array.from(
|
|
77
|
+
command.matchAll(/"([^"]*)"|'([^']*)'|(\S+)/g),
|
|
78
|
+
(match) => match[1] ?? match[2] ?? match[3],
|
|
79
|
+
);
|
|
80
|
+
}
|
|
68
81
|
|
|
69
82
|
/** Script extensions whose path we summarize as `<parent>-<file>`. */
|
|
70
83
|
const SCRIPT_EXT_RE = /\.(ts|js|mjs|cjs|py)$/;
|
|
@@ -76,7 +89,7 @@ const SCRIPT_EXT_RE = /\.(ts|js|mjs|cjs|py)$/;
|
|
|
76
89
|
* filename when the script sits at the filesystem root.
|
|
77
90
|
*/
|
|
78
91
|
function scriptName(scriptPath: string): string {
|
|
79
|
-
const parts = scriptPath.split(
|
|
92
|
+
const parts = scriptPath.split(/[\\/]/).filter(Boolean);
|
|
80
93
|
const file = parts[parts.length - 1].replace(SCRIPT_EXT_RE, "");
|
|
81
94
|
const parent = parts.length >= 2 ? parts[parts.length - 2] : "";
|
|
82
95
|
return parent ? `${parent}-${file}` : file;
|
|
@@ -93,13 +106,14 @@ function scriptName(scriptPath: string): string {
|
|
|
93
106
|
* (`/…/vellum-qdrant`) keep their bare executable name.
|
|
94
107
|
*/
|
|
95
108
|
export function deriveName(command: string): string {
|
|
96
|
-
const tokens = command.trim()
|
|
109
|
+
const tokens = splitCommand(command.trim());
|
|
97
110
|
if (tokens.length === 0) {
|
|
98
111
|
return "(unknown)";
|
|
99
112
|
}
|
|
100
113
|
|
|
101
114
|
const argv0 = basename(tokens[0]);
|
|
102
|
-
|
|
115
|
+
const runtimeName = argv0.replace(/\.exe$/i, "").toLowerCase();
|
|
116
|
+
if (RUNTIMES.has(runtimeName)) {
|
|
103
117
|
const args = tokens.slice(1);
|
|
104
118
|
const script = args.find((t) => SCRIPT_EXT_RE.test(t));
|
|
105
119
|
if (script) {
|
|
@@ -109,7 +123,7 @@ export function deriveName(command: string): string {
|
|
|
109
123
|
// "what was run" rather than an opaque `bun`. Flags are dropped as noise.
|
|
110
124
|
const meaningful = args.filter((t) => !t.startsWith("-"));
|
|
111
125
|
if (meaningful.length > 0) {
|
|
112
|
-
return `${
|
|
126
|
+
return `${runtimeName} ${meaningful.join(" ")}`;
|
|
113
127
|
}
|
|
114
128
|
}
|
|
115
129
|
return argv0;
|
|
@@ -126,12 +140,8 @@ export function deriveName(command: string): string {
|
|
|
126
140
|
* {@link deriveName} descriptor.
|
|
127
141
|
*/
|
|
128
142
|
export function readProcessCommand(pid: number): string | null {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
return deriveName(raw.split("\0").filter(Boolean).join(" "));
|
|
132
|
-
} catch {
|
|
133
|
-
return null;
|
|
134
|
-
}
|
|
143
|
+
const command = readRawProcessCommand(pid);
|
|
144
|
+
return command == null ? null : deriveName(command);
|
|
135
145
|
}
|
|
136
146
|
|
|
137
147
|
/**
|
|
@@ -143,7 +153,8 @@ export function readProcessCommand(pid: number): string | null {
|
|
|
143
153
|
* (`.../plugins/defaults/<name>/`). The `plugins-data/` state directory is
|
|
144
154
|
* deliberately not matched — the slash after `plugins` excludes it.
|
|
145
155
|
*/
|
|
146
|
-
const PLUGIN_PATH_RE =
|
|
156
|
+
const PLUGIN_PATH_RE =
|
|
157
|
+
/(?:^|[\\/])plugins[\\/]([^\\/\s]+)(?:[\\/]([^\\/\s]+))?/;
|
|
147
158
|
|
|
148
159
|
/**
|
|
149
160
|
* Classify whether a raw command line belongs to plugin-owned code, and if so
|
|
@@ -169,122 +180,23 @@ export function deriveOrigin(command: string): ProcessOrigin {
|
|
|
169
180
|
return `plugin:${name}`;
|
|
170
181
|
}
|
|
171
182
|
|
|
172
|
-
/**
|
|
173
|
-
* Parse a `/proc/<pid>/stat` line into its leading fields. `comm` (the
|
|
174
|
-
* executable name) may itself contain spaces and parentheses, so the fixed
|
|
175
|
-
* fields are read relative to the final `)` rather than by naive splitting.
|
|
176
|
-
* Returns null if the line is malformed.
|
|
177
|
-
*/
|
|
178
|
-
function parseProcStat(content: string): { comm: string; ppid: number } | null {
|
|
179
|
-
const lparen = content.indexOf("(");
|
|
180
|
-
const rparen = content.lastIndexOf(")");
|
|
181
|
-
if (lparen === -1 || rparen === -1 || rparen < lparen) {
|
|
182
|
-
return null;
|
|
183
|
-
}
|
|
184
|
-
const comm = content.slice(lparen + 1, rparen);
|
|
185
|
-
// After ")" come: " <state> <ppid> …" — split the remainder on spaces.
|
|
186
|
-
const rest = content.slice(rparen + 2).split(" ");
|
|
187
|
-
const ppid = Number(rest[1]);
|
|
188
|
-
if (!Number.isInteger(ppid)) {
|
|
189
|
-
return null;
|
|
190
|
-
}
|
|
191
|
-
return { comm, ppid };
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/** Read the live process table from Linux `/proc`. Throws if `/proc` is absent. */
|
|
195
|
-
function listProcessesFromProc(): ProcInfo[] {
|
|
196
|
-
const entries = readdirSync("/proc");
|
|
197
|
-
const procs: ProcInfo[] = [];
|
|
198
|
-
for (const entry of entries) {
|
|
199
|
-
const pid = Number(entry);
|
|
200
|
-
if (!Number.isInteger(pid) || pid <= 0) {
|
|
201
|
-
continue;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
let ppid: number;
|
|
205
|
-
let comm: string;
|
|
206
|
-
try {
|
|
207
|
-
const parsed = parseProcStat(readFileSync(`/proc/${pid}/stat`, "utf8"));
|
|
208
|
-
if (!parsed) {
|
|
209
|
-
continue;
|
|
210
|
-
}
|
|
211
|
-
({ ppid, comm } = parsed);
|
|
212
|
-
} catch {
|
|
213
|
-
// Process exited between readdir and read — skip.
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// `/proc/<pid>/cmdline` is NUL-delimited and empty for kernel threads.
|
|
218
|
-
// Redact the raw command line via deriveName to strip secrets (tokens,
|
|
219
|
-
// API keys, database URLs) that are commonly passed as process arguments.
|
|
220
|
-
// The raw command line is read here but never stored — only the derived
|
|
221
|
-
// safe descriptor is kept.
|
|
222
|
-
let command = comm;
|
|
223
|
-
// Origin is derived from the raw command path (which carries the plugin
|
|
224
|
-
// directory), so it is classified before deriveName redacts the path away.
|
|
225
|
-
let origin: ProcessOrigin = "workspace";
|
|
226
|
-
try {
|
|
227
|
-
const raw = readFileSync(`/proc/${pid}/cmdline`, "utf8");
|
|
228
|
-
const joined = raw.split("\0").filter(Boolean).join(" ");
|
|
229
|
-
if (joined) {
|
|
230
|
-
command = deriveName(joined);
|
|
231
|
-
origin = deriveOrigin(joined);
|
|
232
|
-
}
|
|
233
|
-
} catch {
|
|
234
|
-
// Fall back to comm.
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
procs.push({ pid, ppid, command, origin });
|
|
238
|
-
}
|
|
239
|
-
return procs;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/** Read the live process table via the `ps` command (macOS / no `/proc`). */
|
|
243
|
-
async function listProcessesFromPs(): Promise<ProcInfo[]> {
|
|
244
|
-
const { stdout } = await execFileAsync(
|
|
245
|
-
"ps",
|
|
246
|
-
["-A", "-o", "pid=,ppid=,command="],
|
|
247
|
-
{ maxBuffer: 8 * 1024 * 1024 },
|
|
248
|
-
);
|
|
249
|
-
|
|
250
|
-
const procs: ProcInfo[] = [];
|
|
251
|
-
for (const line of stdout.split("\n")) {
|
|
252
|
-
const m = line.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/);
|
|
253
|
-
if (!m) {
|
|
254
|
-
continue;
|
|
255
|
-
}
|
|
256
|
-
const raw = m[3].trim();
|
|
257
|
-
procs.push({
|
|
258
|
-
pid: Number(m[1]),
|
|
259
|
-
ppid: Number(m[2]),
|
|
260
|
-
command: deriveName(raw),
|
|
261
|
-
origin: deriveOrigin(raw),
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
return procs;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
183
|
/**
|
|
268
184
|
* Enumerate the live process table as `(pid, ppid, command)` rows. Prefers
|
|
269
|
-
* Linux `/proc
|
|
185
|
+
* Linux `/proc`, uses `ps` on macOS, and queries Win32_Process on Windows.
|
|
270
186
|
*/
|
|
271
|
-
export async function listProcesses(
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
187
|
+
export async function listProcesses(
|
|
188
|
+
rows?: ProcessTableRow[],
|
|
189
|
+
): Promise<ProcInfo[]> {
|
|
190
|
+
const processRows = rows ?? (await listProcessTableAsync());
|
|
191
|
+
return processRows.map(({ pid, ppid, command }) => ({
|
|
192
|
+
pid,
|
|
193
|
+
ppid,
|
|
194
|
+
command: deriveName(command),
|
|
195
|
+
origin: deriveOrigin(command),
|
|
196
|
+
}));
|
|
277
197
|
}
|
|
278
198
|
|
|
279
|
-
|
|
280
|
-
* Build the process subtree rooted at `rootPid` from a flat process list.
|
|
281
|
-
* Children are ordered by PID. Self-references and already-visited PIDs are
|
|
282
|
-
* skipped so a malformed table cannot produce an infinite tree.
|
|
283
|
-
*/
|
|
284
|
-
export function buildProcessTree(
|
|
285
|
-
procs: ProcInfo[],
|
|
286
|
-
rootPid: number,
|
|
287
|
-
): ProcTreeNode {
|
|
199
|
+
function createProcessTreeBuilder(procs: ProcInfo[]): ProcessTreeBuilder {
|
|
288
200
|
const byPid = new Map<number, ProcInfo>();
|
|
289
201
|
const childrenOf = new Map<number, number[]>();
|
|
290
202
|
for (const p of procs) {
|
|
@@ -316,5 +228,49 @@ export function buildProcessTree(
|
|
|
316
228
|
};
|
|
317
229
|
};
|
|
318
230
|
|
|
319
|
-
return build
|
|
231
|
+
return { build, byPid, visited };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Build the process subtree rooted at `rootPid` from a flat process list.
|
|
236
|
+
* Children are ordered by PID. Self-references and already-visited PIDs are
|
|
237
|
+
* skipped so a malformed table cannot produce an infinite tree.
|
|
238
|
+
*/
|
|
239
|
+
export function buildProcessTree(
|
|
240
|
+
procs: ProcInfo[],
|
|
241
|
+
rootPid: number,
|
|
242
|
+
): ProcTreeNode {
|
|
243
|
+
return createProcessTreeBuilder(procs).build(rootPid);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** Build a synthetic system root containing every enumerated process once. */
|
|
247
|
+
export function buildSystemProcessTree(procs: ProcInfo[]): ProcTreeNode {
|
|
248
|
+
const { build, byPid, visited } = createProcessTreeBuilder(procs);
|
|
249
|
+
const children: ProcTreeNode[] = [];
|
|
250
|
+
const orderedProcesses = [...byPid.values()].sort((a, b) => a.pid - b.pid);
|
|
251
|
+
|
|
252
|
+
for (const processInfo of orderedProcesses) {
|
|
253
|
+
if (
|
|
254
|
+
processInfo.ppid !== processInfo.pid &&
|
|
255
|
+
!byPid.has(processInfo.ppid) &&
|
|
256
|
+
!visited.has(processInfo.pid)
|
|
257
|
+
) {
|
|
258
|
+
children.push(build(processInfo.pid));
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Malformed process tables can contain parent cycles with no natural root.
|
|
263
|
+
for (const processInfo of orderedProcesses) {
|
|
264
|
+
if (!visited.has(processInfo.pid)) {
|
|
265
|
+
children.push(build(processInfo.pid));
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
pid: 0,
|
|
271
|
+
name: "system",
|
|
272
|
+
command: "",
|
|
273
|
+
origin: "workspace",
|
|
274
|
+
children,
|
|
275
|
+
};
|
|
320
276
|
}
|