@vellumai/assistant 0.11.2 → 0.11.3-staging.2
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 +58 -3
- package/docs/trusted-contact-access.md +9 -5
- package/docs/vellum-doctor.md +195 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
- package/node_modules/@vellumai/ces-client/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-client/src/rpc-client.ts +19 -2
- package/node_modules/@vellumai/environments/package.json +2 -1
- package/node_modules/@vellumai/environments/src/__tests__/cloud-assistant-hub-url.test.ts +38 -0
- package/node_modules/@vellumai/environments/src/__tests__/install-layout.test.ts +98 -0
- package/node_modules/@vellumai/environments/src/__tests__/package-boundary.test.ts +5 -5
- package/node_modules/@vellumai/environments/src/index.ts +17 -6
- package/node_modules/@vellumai/environments/src/install-layout.ts +49 -0
- package/node_modules/@vellumai/environments/src/seeds.ts +29 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +4 -0
- package/node_modules/@vellumai/gateway-client/src/verification-session-contract.ts +91 -6
- package/node_modules/@vellumai/ipc-server-utils/src/endpoint.test.ts +36 -0
- package/node_modules/@vellumai/ipc-server-utils/src/endpoint.ts +142 -0
- package/node_modules/@vellumai/ipc-server-utils/src/index.ts +2 -0
- package/node_modules/@vellumai/ipc-server-utils/src/listen-options.ts +3 -0
- package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.test.ts +15 -1
- package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.ts +17 -2
- package/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
- package/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
- package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
- package/node_modules/@vellumai/slack-text/src/index.ts +16 -2
- package/openapi.yaml +589 -30
- package/package.json +1 -1
- package/src/__tests__/agent-image-optimize.test.ts +4 -4
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +113 -66
- package/src/__tests__/agent-loop-override-profile.test.ts +38 -48
- package/src/__tests__/agent-loop-subagent-billing-headers.test.ts +166 -0
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +1 -0
- package/src/__tests__/agent-wake-override-profile.test.ts +1 -0
- package/src/__tests__/answered-question-persistence.test.ts +240 -0
- package/src/__tests__/anthropic-provider.test.ts +165 -30
- package/src/__tests__/app-control-flow.test.ts +9 -8
- package/src/__tests__/assistant-event-hub.test.ts +48 -0
- package/src/__tests__/attachments-store-heic-normalize.test.ts +16 -12
- package/src/__tests__/attachments-store.test.ts +130 -68
- package/src/__tests__/attachments.test.ts +36 -36
- package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
- package/src/__tests__/call-start-guardian-guard.test.ts +18 -4
- package/src/__tests__/canned-reply-release.test.ts +86 -0
- package/src/__tests__/channel-guardian.test.ts +59 -29
- package/src/__tests__/channel-readiness-telegram-remote.test.ts +154 -0
- package/src/__tests__/channel-setup-panel-ack.test.ts +6 -5
- package/src/__tests__/client-os-metadata-persistence.test.ts +103 -4
- package/src/__tests__/compactor-image-manifest-trust.test.ts +1 -1
- package/src/__tests__/consult-deadline.test.ts +65 -0
- package/src/__tests__/conversation-agent-loop.test.ts +438 -2
- package/src/__tests__/conversation-disk-view-integration.test.ts +5 -1
- package/src/__tests__/conversation-disk-view.test.ts +4 -4
- package/src/__tests__/conversation-error.test.ts +22 -3
- package/src/__tests__/conversation-fork-crud.test.ts +5 -1
- package/src/__tests__/conversation-fork-referential.test.ts +352 -0
- package/src/__tests__/conversation-fork-retrospective.test.ts +5 -1
- package/src/__tests__/conversation-group-tools.test.ts +32 -2
- package/src/__tests__/conversation-inference-profile-route.test.ts +27 -1
- package/src/__tests__/conversation-initial-prompt.test.ts +51 -22
- package/src/__tests__/conversation-key-store-bootstrap-cleanup.test.ts +119 -0
- package/src/__tests__/conversation-key-store-origin.test.ts +105 -0
- package/src/__tests__/conversation-list-recency-ordering.test.ts +104 -0
- package/src/__tests__/conversation-origin-at-creation.test.ts +153 -0
- package/src/__tests__/conversation-origin-channel-filter.test.ts +95 -0
- package/src/__tests__/conversation-placement-promotion.test.ts +172 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +123 -0
- package/src/__tests__/conversation-queue.test.ts +433 -1
- package/src/__tests__/conversation-retry-route.test.ts +3 -1
- package/src/__tests__/conversation-routes-hidden-queue.test.ts +64 -1
- package/src/__tests__/conversation-store-ephemeral.test.ts +4 -0
- package/src/__tests__/conversation-store.test.ts +26 -10
- package/src/__tests__/conversation-summarize-route.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +15 -6
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -5
- package/src/__tests__/conversation-surfaces-app-control.test.ts +12 -11
- package/src/__tests__/conversation-surfaces-app-open.test.ts +11 -5
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +5 -4
- package/src/__tests__/conversation-surfaces-history-restored-completion.test.ts +967 -0
- package/src/__tests__/conversation-surfaces-persisted-info.test.ts +312 -0
- package/src/__tests__/conversation-surfaces-queued-emit.test.ts +6 -5
- package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +12 -8
- package/src/__tests__/conversation-surfaces-standalone.test.ts +21 -11
- package/src/__tests__/conversation-surfaces-state-update.test.ts +7 -11
- package/src/__tests__/conversation-surfaces-table-action.test.ts +7 -6
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +11 -6
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +9 -6
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +70 -7
- package/src/__tests__/conversation-wait-for-idle.test.ts +6 -11
- package/src/__tests__/credential-prompt-route.test.ts +57 -7
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/cu-unified-flow.test.ts +89 -28
- package/src/__tests__/custom-group-listing-visibility.test.ts +1 -1
- package/src/__tests__/db-schedule-syntax-migration.test.ts +4 -1
- package/src/__tests__/discord-access-request-privacy.test.ts +366 -0
- package/src/__tests__/discord-requester-notice-privacy.test.ts +87 -0
- package/src/__tests__/disk-pressure-guard.test.ts +55 -55
- package/src/__tests__/disk-pressure-routes.test.ts +10 -10
- package/src/__tests__/disk-pressure-tools.test.ts +2 -2
- package/src/__tests__/disk-usage.test.ts +35 -20
- package/src/__tests__/document-sync-tags.test.ts +379 -0
- package/src/__tests__/document-update-default-surface.test.ts +28 -0
- package/src/__tests__/dynamic-skill-background-guard.test.ts +2 -2
- package/src/__tests__/emit-signal-routing-intent.test.ts +3 -4
- package/src/__tests__/ensure-conversation-exists.test.ts +8 -5
- package/src/__tests__/events-dev-bypass-actor.test.ts +183 -1
- package/src/__tests__/evict-conversations-for-reload.test.ts +90 -0
- package/src/__tests__/file-list-tool.test.ts +12 -10
- package/src/__tests__/file-ops-service.test.ts +151 -44
- package/src/__tests__/file-read-container-boundary.test.ts +137 -0
- package/src/__tests__/file-read-tool.test.ts +21 -9
- package/src/__tests__/file-url-path-guard.test.ts +83 -0
- package/src/__tests__/filesystem-tools.test.ts +82 -66
- package/src/__tests__/guardian-outbound-http.test.ts +8 -8
- package/src/__tests__/helpers/gateway-verification-sessions-stub.ts +14 -1
- package/src/__tests__/helpers/hub-clients.ts +42 -0
- package/src/__tests__/helpers/mock-conversation.ts +46 -0
- package/src/__tests__/helpers/verification-sessions-ipc-sim.ts +61 -57
- package/src/__tests__/history-repair.test.ts +112 -12
- package/src/__tests__/host-bash-proxy.test.ts +10 -5
- package/src/__tests__/host-bash-routes.test.ts +67 -0
- package/src/__tests__/host-browser-proxy.test.ts +2 -2
- package/src/__tests__/host-cu-proxy.test.ts +3 -7
- package/src/__tests__/host-file-proxy.test.ts +3 -7
- package/src/__tests__/host-file-read-tool.test.ts +5 -1
- package/src/__tests__/host-transfer-proxy-targeted.test.ts +6 -4
- package/src/__tests__/http-conversation-lineage.test.ts +2 -5
- package/src/__tests__/http-user-message-parity.test.ts +44 -0
- package/src/__tests__/identity-routes.test.ts +13 -13
- package/src/__tests__/image-conversion.test.ts +29 -26
- package/src/__tests__/image-source-path-reinject.test.ts +2 -2
- package/src/__tests__/inference-profile-session-handler.test.ts +95 -0
- package/src/__tests__/inference-profile-session-ipc.test.ts +25 -0
- package/src/__tests__/introduction-card-resolver.test.ts +6 -1
- package/src/__tests__/list-messages-attachments.test.ts +56 -32
- package/src/__tests__/list-messages-background-tool-completion.test.ts +4 -4
- package/src/__tests__/list-messages-client-message-id.test.ts +2 -2
- package/src/__tests__/list-messages-hidden-metadata.test.ts +16 -16
- package/src/__tests__/list-messages-page-latest.test.ts +61 -61
- package/src/__tests__/list-messages-provider-error.test.ts +6 -6
- package/src/__tests__/list-messages-queued.test.ts +49 -6
- package/src/__tests__/list-messages-system-card.test.ts +4 -4
- package/src/__tests__/list-messages-tool-merge.test.ts +9 -9
- package/src/__tests__/llm-resolver-override-or-default.test.ts +141 -0
- package/src/__tests__/llm-usage-store.test.ts +55 -0
- package/src/__tests__/manual-token-reconciliation.test.ts +45 -0
- package/src/__tests__/mcp-tool-annotations-risk.test.ts +133 -0
- package/src/__tests__/media-stream-server-integration.test.ts +36 -0
- package/src/__tests__/migration-export-http.test.ts +1 -1
- package/src/__tests__/migration-import-commit-http.test.ts +1 -1
- package/src/__tests__/migration-import-preflight-http.test.ts +1 -1
- package/src/__tests__/migration-validate-http.test.ts +1 -1
- package/src/__tests__/mtime-cache.test.ts +65 -3
- package/src/__tests__/notification-platform-adapter.test.ts +104 -0
- package/src/__tests__/notification-schedule-notify-dedup.test.ts +1 -1
- package/src/__tests__/oauth-provider-profiles.test.ts +4 -2
- package/src/__tests__/oauth-provider-seed-logos.test.ts +65 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +17 -0
- package/src/__tests__/oauth-providers-routes.test.ts +1 -0
- package/src/__tests__/openai-responses-prompt-cache.test.ts +5 -2
- package/src/__tests__/path-policy.test.ts +164 -1
- package/src/__tests__/persist-media-references.test.ts +2 -2
- package/src/__tests__/persist-unsendable-image-downscale.test.ts +5 -5
- package/src/__tests__/persist-unsendable-image.test.ts +23 -23
- package/src/__tests__/persona-resolver.test.ts +154 -0
- package/src/__tests__/plugin-api-webhook-url.test.ts +170 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +6 -0
- package/src/__tests__/processing-flag-persist-failure.test.ts +15 -10
- package/src/__tests__/profile-availability-incomplete.test.ts +113 -0
- package/src/__tests__/profiler-routes.test.ts +13 -13
- package/src/__tests__/prompt-cache-cross-turn-stability.test.ts +593 -0
- package/src/__tests__/proxy-approval-callback.test.ts +3 -3
- package/src/__tests__/queued-message-cancel-and-steer.test.ts +189 -0
- package/src/__tests__/queued-message-steer-actor-scoping.test.ts +164 -0
- package/src/__tests__/resolve-trust-class.test.ts +20 -0
- package/src/__tests__/run-conversation-turn-persistence.test.ts +1 -1
- package/src/__tests__/run-due-schedules.test.ts +174 -0
- package/src/__tests__/runtime-attachment-metadata.test.ts +15 -7
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +166 -0
- package/src/__tests__/schedule-routes.test.ts +730 -8
- package/src/__tests__/schedule-store.test.ts +540 -0
- package/src/__tests__/schedule-tools.test.ts +103 -0
- package/src/__tests__/scheduler-wake.test.ts +5 -0
- package/src/__tests__/secret-routes-platform-proxy.test.ts +29 -1
- package/src/__tests__/secure-keys.test.ts +91 -0
- package/src/__tests__/server-history-render.test.ts +97 -7
- package/src/__tests__/size-guard.test.ts +8 -8
- package/src/__tests__/skill-docs-sync-guard.test.ts +3 -2
- package/src/__tests__/skill-tool-factory.test.ts +247 -0
- package/src/__tests__/skills-install-staging.test.ts +15 -6
- package/src/__tests__/slack-edit-ordering-characterization.test.ts +103 -0
- package/src/__tests__/slack-inbound-verification.test.ts +4 -1
- package/src/__tests__/slack-mention-binding-invariance.test.ts +103 -0
- package/src/__tests__/slack-mention-provider-leak-audit.test.ts +135 -0
- package/src/__tests__/starter-task-flow.test.ts +5 -4
- package/src/__tests__/steer-on-enqueue-question.test.ts +3 -2
- package/src/__tests__/stt-language-catalog-parity.test.ts +38 -0
- package/src/__tests__/subagent-allowlist-validation.test.ts +26 -4
- package/src/__tests__/subagent-cron-run-attribution.test.ts +301 -0
- package/src/__tests__/subagent-disposal.test.ts +10 -0
- package/src/__tests__/subagent-fork-notifications.test.ts +10 -0
- package/src/__tests__/subagent-fork-prompt-role.test.ts +85 -11
- package/src/__tests__/subagent-fork-spawn.test.ts +15 -7
- package/src/__tests__/subagent-manager-notify.test.ts +148 -0
- package/src/__tests__/subagent-notify-parent.test.ts +1 -1
- package/src/__tests__/subagent-persistence.test.ts +141 -0
- package/src/__tests__/subagent-role-registry.test.ts +172 -63
- package/src/__tests__/subagent-role-resolution.test.ts +88 -0
- package/src/__tests__/subagent-spawn-and-await.test.ts +48 -0
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +111 -10
- package/src/__tests__/subagent-terminal-message.test.ts +88 -1
- package/src/__tests__/subagent-tool-gate-mode.test.ts +225 -10
- package/src/__tests__/subagent-tools.test.ts +1901 -108
- package/src/__tests__/surface-completion-compaction-boundary.test.ts +124 -0
- package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +298 -0
- package/src/__tests__/system-prompt.test.ts +82 -7
- package/src/__tests__/tool-grant-request-escalation.test.ts +40 -1
- package/src/__tests__/tool-result-spool.test.ts +74 -18
- package/src/__tests__/tool-side-effects-documents.test.ts +174 -0
- package/src/__tests__/tool-side-effects-hook-failures.test.ts +129 -0
- package/src/__tests__/tools-audio-read.test.ts +10 -10
- package/src/__tests__/tools-get-route.test.ts +85 -12
- package/src/__tests__/tools-list-cli.test.ts +171 -0
- package/src/__tests__/top-level-renderer.test.ts +54 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +68 -1
- package/src/__tests__/trusted-contact-multichannel.test.ts +4 -1
- package/src/__tests__/turn-tail-chain.test.ts +135 -0
- package/src/__tests__/ui-choice-copy-surfaces.test.ts +6 -4
- package/src/__tests__/ui-visual-surface.test.ts +5 -4
- package/src/__tests__/ui-work-result-surface.test.ts +5 -4
- package/src/__tests__/unified-turn-context-visible-app.test.ts +7 -8
- package/src/__tests__/usage-routes.test.ts +62 -0
- package/src/__tests__/verification-outbound-delivery.test.ts +176 -0
- package/src/__tests__/visible-app-context.test.ts +20 -4
- package/src/__tests__/web-search-history.test.ts +96 -0
- package/src/__tests__/workspace-migration-139-clear-renamed-cost-profile-label.test.ts +6 -6
- package/src/__tests__/workspace-migration-140-repair-seed-pinned-memory-v3-live.test.ts +330 -0
- package/src/acp/failure-error.ts +3 -5
- package/src/agent/attachments.ts +26 -24
- package/src/agent/history-repair/history-repair.ts +27 -26
- package/src/agent/image-optimize.ts +6 -6
- package/src/agent/loop.ts +40 -22
- package/src/agent/message-types.ts +3 -3
- package/src/api/constants/document-tools.ts +55 -0
- package/src/api/events/question-answered.ts +53 -0
- package/src/api/events/tool-result.ts +7 -0
- package/src/api/index.ts +13 -0
- package/src/api/responses/conversation-message.ts +11 -0
- package/src/api/surfaces-oauth-connect.test.ts +96 -0
- package/src/api/surfaces.ts +36 -8
- package/src/approvals/guardian-channel-delivery.ts +83 -0
- package/src/approvals/guardian-expiry-notifier.ts +13 -11
- package/src/approvals/guardian-request-resolvers.ts +222 -135
- package/src/calls/__tests__/voice-session-bridge.test.ts +50 -0
- package/src/calls/media-stream-server.ts +9 -1
- package/src/calls/voice-session-bridge.ts +38 -0
- package/src/channels/__tests__/gateway-verification-sessions.test.ts +3 -3
- package/src/channels/config.ts +7 -2
- package/src/channels/gateway-verification-sessions.ts +7 -2
- package/src/channels/types.ts +7 -1
- package/src/cli/commands/__tests__/cache.test.ts +7 -4
- package/src/cli/commands/__tests__/cli-test-harness.ts +82 -0
- package/src/cli/commands/__tests__/conversations-wake.test.ts +51 -6
- package/src/cli/commands/__tests__/inference-models.test.ts +14 -0
- package/src/cli/commands/__tests__/inference-profiles.test.ts +44 -1
- package/src/cli/commands/__tests__/inference-providers.test.ts +7 -0
- package/src/cli/commands/__tests__/plugins.test.ts +692 -0
- package/src/cli/commands/__tests__/schedules.test.ts +136 -37
- package/src/cli/commands/conversations.ts +7 -3
- package/src/cli/commands/credentials.help.ts +7 -0
- package/src/cli/commands/inference-profiles.ts +9 -3
- package/src/cli/commands/inference.help.ts +16 -2
- package/src/cli/commands/mcp.help.ts +4 -2
- package/src/cli/commands/memory/memory-retrospective.ts +18 -0
- package/src/cli/commands/notifications.ts +3 -6
- package/src/cli/commands/oauth/connect-surface-guidance.test.ts +16 -0
- package/src/cli/commands/oauth/connect-surface-guidance.ts +25 -7
- package/src/cli/commands/oauth/connect.test.ts +26 -1
- package/src/cli/commands/oauth/connect.ts +4 -1
- package/src/cli/commands/oauth/index.help.ts +3 -1
- package/src/cli/commands/oauth/providers.ts +3 -0
- package/src/cli/commands/plugins.help.ts +25 -2
- package/src/cli/commands/plugins.ts +289 -10
- package/src/cli/commands/schedules.help.ts +14 -12
- package/src/cli/commands/schedules.ts +19 -5
- package/src/cli/commands/tools.help.ts +4 -2
- package/src/cli/commands/tools.ts +38 -0
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +1 -0
- package/src/cli/lib/__tests__/install-from-github.test.ts +79 -0
- package/src/cli/lib/__tests__/install-from-platform.test.ts +47 -1
- package/src/cli/lib/__tests__/plugin-surfaces.test.ts +86 -7
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +122 -0
- package/src/cli/lib/install-from-github.ts +61 -0
- package/src/cli/lib/install-from-platform.ts +13 -2
- package/src/cli/lib/plugin-surfaces.ts +124 -4
- package/src/cli/lib/upgrade-plugin.ts +18 -0
- package/src/cli.ts +1 -1
- package/src/config/__tests__/deployment-context-defaults.test.ts +73 -0
- package/src/config/__tests__/memory-retrospective-schema.test.ts +40 -0
- package/src/config/__tests__/plugin-updates-schema.test.ts +43 -0
- package/src/config/__tests__/profile-tool-support.test.ts +131 -0
- package/src/config/bundled-skills/AGENTS.md +1 -1
- package/src/config/bundled-skills/app-builder/SKILL.md +1 -1
- package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +3 -3
- package/src/config/bundled-skills/schedule/SKILL.md +5 -1
- package/src/config/bundled-skills/schedule/TOOLS.json +2 -2
- package/src/config/bundled-skills/settings/TOOLS.json +2 -2
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +1 -0
- package/src/config/bundled-skills/subagent/SKILL.md +53 -22
- package/src/config/bundled-skills/subagent/TOOLS.json +14 -13
- package/src/config/bundled-skills/visualize/SKILL.md +1 -1
- package/src/config/default-profile-catalog.ts +6 -2
- package/src/config/feature-flag-registry.json +25 -17
- package/src/config/llm-resolver.ts +45 -15
- package/src/config/loader.ts +70 -1
- package/src/config/profile-tool-support.ts +110 -0
- package/src/config/sanitize-for-transfer.ts +11 -0
- package/src/config/schema.ts +4 -0
- package/src/config/schemas/__tests__/stt.test.ts +20 -5
- package/src/config/schemas/channels.ts +13 -0
- package/src/config/schemas/memory-retrospective.ts +25 -0
- package/src/config/schemas/plugin-updates.ts +55 -0
- package/src/config/schemas/services.ts +8 -4
- package/src/config/schemas/stt.ts +17 -5
- package/src/config/schemas/timeouts.ts +1 -1
- package/src/contacts/__tests__/guardian-delivery-reader.test.ts +16 -0
- package/src/contacts/guardian-delivery-reader.ts +29 -2
- package/src/context/compactor.ts +4 -4
- package/src/context/post-turn-tool-result-truncation.ts +32 -16
- package/src/context/strip-injections.ts +42 -0
- package/src/context/tool-result-spool.ts +32 -18
- package/src/credential-execution/ces-runtime.ts +3 -0
- package/src/credential-execution/client.ts +22 -1
- package/src/credential-execution/process-manager.test.ts +99 -0
- package/src/credential-execution/process-manager.ts +62 -16
- package/src/daemon/__tests__/abort-null-controller.test.ts +141 -5
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +8 -12
- package/src/daemon/__tests__/install-assistant-command.test.ts +234 -0
- package/src/daemon/__tests__/turn-tail-assistant-reply-notify.test.ts +2 -4
- package/src/daemon/__tests__/turn-tail-deleted-conversation.test.ts +20 -29
- package/src/daemon/channel-ui-capability.ts +4 -4
- package/src/daemon/conversation-agent-loop-handlers.ts +117 -61
- package/src/daemon/conversation-agent-loop.ts +318 -148
- package/src/daemon/conversation-attachments.ts +1 -1
- package/src/daemon/conversation-error.ts +8 -1
- package/src/daemon/conversation-initial-prompt.ts +16 -38
- package/src/daemon/conversation-lifecycle.ts +154 -32
- package/src/daemon/conversation-messaging.ts +106 -43
- package/src/daemon/conversation-process.ts +131 -33
- package/src/daemon/conversation-queue-manager.ts +10 -0
- package/src/daemon/conversation-runtime-assembly.ts +9 -4
- package/src/daemon/conversation-store.ts +52 -3
- package/src/daemon/conversation-surface-state.ts +9 -0
- package/src/daemon/conversation-surfaces.ts +403 -210
- package/src/daemon/conversation-tool-setup.ts +56 -7
- package/src/daemon/conversation-turn-finalize.ts +134 -89
- package/src/daemon/conversation.ts +111 -27
- package/src/daemon/disk-pressure-guard-lifecycle.ts +2 -2
- package/src/daemon/disk-pressure-guard.ts +3 -3
- package/src/daemon/doordash-steps.ts +5 -5
- package/src/daemon/external-plugins-bootstrap.ts +7 -1
- package/src/daemon/handlers/config-channels.ts +48 -5
- package/src/daemon/handlers/conversation-history.ts +1 -1
- package/src/daemon/handlers/conversations.ts +62 -17
- package/src/daemon/handlers/shared.ts +12 -0
- package/src/daemon/handlers/skills.ts +11 -1
- package/src/daemon/identity-helpers.ts +38 -10
- package/src/daemon/install-assistant-command.ts +322 -0
- package/src/daemon/lifecycle.ts +14 -3
- package/src/daemon/message-types/conversations.ts +6 -7
- package/src/daemon/message-types/sync.ts +1 -0
- package/src/daemon/persist-media-references.ts +23 -14
- package/src/daemon/process-message.ts +6 -6
- package/src/daemon/tool-setup-types.ts +19 -122
- package/src/daemon/tool-side-effects.ts +17 -94
- package/src/daemon/trust-context-types.ts +39 -0
- package/src/daemon/trust-context.ts +14 -6
- package/src/daemon/turn-tail-chain.ts +73 -0
- package/src/daemon/web-search-history.ts +47 -33
- package/src/heartbeat/heartbeat-service.ts +6 -6
- package/src/ipc/__tests__/socket-path.test.ts +21 -0
- package/src/ipc/assistant-server.ts +17 -10
- package/src/ipc/cli-client.ts +10 -1
- package/src/ipc/routes/__tests__/documents-sync-ipc-routes.test.ts +56 -0
- package/src/ipc/routes/documents-sync-ipc-routes.ts +42 -0
- package/src/ipc/socket-cleanup.ts +9 -7
- package/src/ipc/socket-path.ts +4 -96
- package/src/live-voice/__tests__/live-voice-archive.test.ts +16 -16
- package/src/live-voice/__tests__/live-voice-attach-image.test.ts +209 -0
- package/src/live-voice/__tests__/live-voice-connection.test.ts +20 -0
- package/src/live-voice/__tests__/live-voice-integration.test.ts +7 -5
- package/src/live-voice/__tests__/live-voice-session-telemetry.test.ts +265 -0
- package/src/live-voice/__tests__/live-voice-stt.test.ts +6 -1
- package/src/live-voice/__tests__/live-voice-vad.test.ts +35 -15
- package/src/live-voice/__tests__/protocol.test.ts +52 -0
- package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +3 -0
- package/src/live-voice/live-voice-archive.ts +13 -13
- package/src/live-voice/live-voice-connection.ts +7 -1
- package/src/live-voice/live-voice-photo.ts +169 -0
- package/src/live-voice/live-voice-session.ts +108 -4
- package/src/live-voice/protocol.ts +89 -1
- package/src/mcp/client.ts +10 -0
- package/src/messaging/providers/__tests__/callback-routing.test.ts +5 -1
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +66 -4
- package/src/messaging/providers/callback-routing.ts +1 -0
- package/src/messaging/providers/discord/api.ts +312 -0
- package/src/messaging/providers/discord/dm-delivery.test.ts +204 -0
- package/src/messaging/providers/discord/render.test.ts +204 -0
- package/src/messaging/providers/discord/render.ts +195 -0
- package/src/messaging/providers/discord/send.test.ts +179 -0
- package/src/messaging/providers/discord/send.ts +212 -0
- package/src/messaging/providers/discord/transport.ts +81 -0
- package/src/messaging/providers/index.ts +3 -1
- package/src/messaging/providers/retry-policy.test.ts +247 -0
- package/src/messaging/providers/retry-policy.ts +206 -0
- package/src/messaging/providers/slack/mention-source.test.ts +285 -0
- package/src/messaging/providers/slack/mention-source.ts +287 -0
- package/src/messaging/providers/telegram-bot/api.ts +47 -130
- package/src/messaging/providers/whatsapp/api.ts +47 -136
- package/src/monitoring/__tests__/db-integrity-sample.test.ts +4 -1
- package/src/monitoring/__tests__/plugin-auto-update.test.ts +332 -0
- package/src/monitoring/db-integrity-sample.ts +6 -1
- package/src/monitoring/plugin-auto-update.ts +359 -0
- package/src/monitoring/resource-sampler.ts +21 -5
- package/src/monitoring/worker.ts +13 -0
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +375 -2
- package/src/notifications/__tests__/broadcaster.test.ts +37 -1
- package/src/notifications/__tests__/copy-composer.test.ts +165 -0
- package/src/notifications/__tests__/emit-signal-pipeline-failure.test.ts +205 -0
- package/src/notifications/__tests__/notification-utils.test.ts +249 -0
- package/src/notifications/adapters/platform.ts +31 -2
- package/src/notifications/assistant-reply-producer.ts +79 -32
- package/src/notifications/broadcaster.ts +30 -10
- package/src/notifications/copy-composer.ts +69 -0
- package/src/notifications/emit-signal.ts +82 -15
- package/src/notifications/events-store.ts +10 -2
- package/src/notifications/home-feed-side-effect.ts +6 -3
- package/src/notifications/notification-utils.ts +173 -5
- package/src/notifications/signal.ts +12 -0
- package/src/oauth/AGENTS.md +4 -2
- package/src/oauth/byo-connection.ts +6 -1
- package/src/oauth/credential-token-resolver.ts +3 -10
- package/src/oauth/manual-token-connection.ts +29 -64
- package/src/oauth/manual-token-providers.ts +62 -0
- package/src/oauth/provider-serializer.ts +13 -0
- package/src/oauth/seed-providers.ts +62 -11
- package/src/onboarding/onboarding-events-store.ts +91 -1
- package/src/permissions/question-prompter.test.ts +4 -0
- package/src/permissions/question-prompter.ts +21 -4
- package/src/persistence/__tests__/conversation-lineage.test.ts +248 -0
- package/src/persistence/attachments-store.ts +15 -15
- package/src/persistence/conversation-attention-store.ts +30 -10
- package/src/persistence/conversation-bootstrap.ts +17 -0
- package/src/persistence/conversation-crud.ts +363 -97
- package/src/persistence/conversation-group-migration.ts +1 -1
- package/src/persistence/conversation-key-store.ts +38 -10
- package/src/persistence/conversation-lineage.ts +226 -0
- package/src/persistence/conversation-plugin-facade.ts +28 -3
- package/src/persistence/conversation-queries.ts +207 -33
- package/src/persistence/conversation-types.ts +107 -1
- package/src/persistence/jobs-store.ts +32 -8
- package/src/persistence/llm-usage-store.ts +22 -0
- package/src/persistence/migrations/361-normalize-managed-connection-rows.test.ts +168 -0
- package/src/persistence/migrations/361-normalize-managed-connection-rows.ts +76 -0
- package/src/persistence/migrations/362-add-conversation-subagent-kind.test.ts +187 -0
- package/src/persistence/migrations/362-add-conversation-subagent-kind.ts +93 -0
- package/src/persistence/migrations/363-backfill-schedule-inference-profile.test.ts +229 -0
- package/src/persistence/migrations/363-backfill-schedule-inference-profile.ts +127 -0
- package/src/persistence/migrations/364-add-schedule-source-key.test.ts +120 -0
- package/src/persistence/migrations/364-add-schedule-source-key.ts +43 -0
- package/src/persistence/migrations/365-add-conversation-fork-strategy.ts +27 -0
- package/src/persistence/schema/conversations.ts +32 -0
- package/src/persistence/schema/infrastructure.ts +3 -0
- package/src/persistence/steps.ts +30 -0
- package/src/persistence/subagent-store.ts +148 -6
- package/src/platform/managed-speech.test.ts +20 -0
- package/src/platform/managed-speech.ts +9 -0
- package/src/plugin-api/index.ts +8 -0
- package/src/plugin-api/webhook-url.ts +151 -0
- package/src/plugins/__tests__/installed-plugin-dirs.test.ts +82 -0
- package/src/plugins/__tests__/source-fingerprint.test.ts +5 -1
- package/src/plugins/collect-source-versions.ts +4 -27
- package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +5 -5
- package/src/plugins/defaults/exploration-drift/package.json +1 -1
- package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +4 -2
- package/src/plugins/defaults/image-recovery/recover.ts +67 -57
- package/src/plugins/defaults/index.ts +1 -1
- package/src/plugins/defaults/memory/__tests__/conversation-queries.test.ts +64 -18
- package/src/plugins/defaults/memory/__tests__/jobs-worker-outcome-contract.test.ts +242 -0
- package/src/plugins/defaults/memory/__tests__/jobs-worker-retrospective-sweep.test.ts +24 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +48 -3
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +511 -19
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +39 -3
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +493 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +5 -4
- package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +5 -1
- package/src/plugins/defaults/memory/job-handlers.ts +87 -5
- package/src/plugins/defaults/memory/jobs-worker.ts +60 -6
- package/src/plugins/defaults/memory/memory-retrospective-constants.ts +11 -0
- package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +29 -0
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +342 -60
- package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +23 -3
- package/src/plugins/defaults/memory/substrate/consolidation-lock.ts +9 -4
- package/src/plugins/defaults/memory/substrate/sweep-job.ts +7 -0
- package/src/plugins/defaults/memory/v1/graph/injection.ts +1 -1
- package/src/plugins/defaults/memory/v2/router.ts +8 -0
- package/src/plugins/defaults/turn-context/unified-turn-context.ts +1 -2
- package/src/plugins/external-plugin-loader.ts +17 -8
- package/src/plugins/installed-plugin-dirs.ts +101 -0
- package/src/plugins/mtime-cache.ts +34 -22
- package/src/plugins/types.ts +69 -8
- package/src/prompts/persona-resolver.ts +6 -2
- package/src/prompts/system-prompt.ts +72 -17
- package/src/providers/__tests__/inference.test.ts +40 -0
- package/src/providers/__tests__/preflight-resolved-config.test.ts +58 -1
- package/src/providers/__tests__/retry-callsite.test.ts +67 -0
- package/src/providers/__tests__/vellum-connection-routing.test.ts +42 -11
- package/src/providers/anthropic/client.ts +112 -95
- package/src/providers/connection-resolution.ts +35 -1
- package/src/providers/gemini/client.ts +5 -5
- package/src/providers/inference/adapter-factory.ts +16 -10
- package/src/providers/inference/auth.ts +16 -0
- package/src/providers/inference/connection-availability.ts +164 -12
- package/src/providers/inference/connections.ts +25 -1
- package/src/providers/media-resolve.ts +21 -11
- package/src/providers/model-catalog.ts +15 -0
- package/src/providers/openai/chat-completions-provider.ts +4 -4
- package/src/providers/openai/responses-provider.ts +12 -25
- package/src/providers/registry.ts +9 -4
- package/src/providers/retry.ts +25 -0
- package/src/providers/server-tool-pairing.ts +134 -0
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +187 -6
- package/src/providers/speech-to-text/deepgram.ts +1 -0
- package/src/providers/speech-to-text/resolve.ts +80 -10
- package/src/providers/speech-to-text/vellum-managed.ts +7 -1
- package/src/providers/speech-to-text/vellum-speech-relay-connection.ts +3 -1
- package/src/providers/types.ts +26 -6
- package/src/providers/vellum-model-routing.ts +6 -7
- package/src/runtime/AGENTS.md +2 -0
- package/src/runtime/__tests__/agent-wake.test.ts +16 -16
- package/src/runtime/__tests__/background-job-runner.test.ts +0 -144
- package/src/runtime/__tests__/desktop-presence.test.ts +162 -0
- package/src/runtime/agent-wake.ts +63 -24
- package/src/runtime/assistant-event-hub.ts +87 -12
- package/src/runtime/auth/__tests__/guard-tests.test.ts +2 -0
- package/src/runtime/auth/__tests__/scopes.test.ts +20 -0
- package/src/runtime/auth/same-actor.ts +56 -8
- package/src/runtime/auth/scopes.ts +12 -0
- package/src/runtime/auth/types.ts +2 -0
- package/src/runtime/background-job-runner.ts +5 -27
- package/src/runtime/channel-readiness-service.ts +59 -3
- package/src/runtime/channel-readiness-types.ts +18 -0
- package/src/runtime/desktop-presence.ts +69 -0
- package/src/runtime/http-server.ts +12 -0
- package/src/runtime/local-actor-identity.ts +11 -9
- package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -0
- package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/client-routes.test.ts +186 -14
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +547 -1
- package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +162 -7
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +14 -10
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +340 -3
- package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +28 -1
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +173 -1
- package/src/runtime/routes/__tests__/question-routes.test.ts +64 -0
- package/src/runtime/routes/__tests__/retrospective-routes.test.ts +20 -0
- package/src/runtime/routes/__tests__/skill-history-route.test.ts +116 -0
- package/src/runtime/routes/__tests__/wake-conversation-routes.test.ts +60 -2
- package/src/runtime/routes/archive-utils.ts +16 -12
- package/src/runtime/routes/attachment-routes.ts +13 -6
- package/src/runtime/routes/canned-reply-release.ts +54 -0
- package/src/runtime/routes/channel-verification-routes.ts +2 -27
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +5 -0
- package/src/runtime/routes/client-routes.ts +114 -2
- package/src/runtime/routes/conversation-cli-routes.ts +10 -12
- package/src/runtime/routes/conversation-list-routes.ts +59 -10
- package/src/runtime/routes/conversation-management-routes.ts +47 -18
- package/src/runtime/routes/conversation-query-routes.ts +32 -53
- package/src/runtime/routes/conversation-routes.ts +360 -293
- package/src/runtime/routes/credential-prompt-routes.ts +23 -2
- package/src/runtime/routes/debug-routes.ts +4 -1
- package/src/runtime/routes/default-provider-routes.ts +7 -11
- package/src/runtime/routes/documents-routes.ts +44 -1
- package/src/runtime/routes/events-routes.ts +28 -1
- package/src/runtime/routes/host-app-control-routes.ts +2 -0
- package/src/runtime/routes/host-bash-routes.ts +2 -0
- package/src/runtime/routes/host-browser-routes.ts +2 -0
- package/src/runtime/routes/host-cu-routes.ts +2 -0
- package/src/runtime/routes/host-file-routes.ts +2 -0
- package/src/runtime/routes/host-transfer-routes.ts +4 -0
- package/src/runtime/routes/identity-routes.ts +6 -6
- package/src/runtime/routes/inbound-message-handler.ts +5 -5
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +7 -3
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +14 -0
- package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +2 -2
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +14 -11
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +14 -17
- package/src/runtime/routes/inference-profile-availability-guard.ts +171 -0
- package/src/runtime/routes/inference-profile-session-handler.ts +26 -2
- package/src/runtime/routes/inference-profile-session-routes.ts +2 -1
- package/src/runtime/routes/inference-profiles-routes.ts +152 -73
- package/src/runtime/routes/inference-provider-connection-routes.ts +0 -16
- package/src/runtime/routes/llm-call-sites-routes.ts +13 -0
- package/src/runtime/routes/log-export-routes.ts +1 -1
- package/src/runtime/routes/notification-routes.ts +0 -14
- package/src/runtime/routes/oauth-providers.ts +1 -0
- package/src/runtime/routes/plugins-routes.ts +106 -1
- package/src/runtime/routes/profiler-routes.ts +4 -2
- package/src/runtime/routes/question-routes.ts +28 -10
- package/src/runtime/routes/retrospective-routes.ts +9 -3
- package/src/runtime/routes/schedule-routes.ts +210 -12
- package/src/runtime/routes/secret-routes.ts +40 -23
- package/src/runtime/routes/settings-routes.ts +41 -13
- package/src/runtime/routes/skills-routes.ts +79 -0
- package/src/runtime/routes/ui-snapshot-routes.ts +1 -0
- package/src/runtime/routes/wake-conversation-routes.ts +23 -0
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +1 -0
- package/src/runtime/services/conversation-serializer.ts +24 -9
- package/src/runtime/sync/documents-sidecar-publish.test.ts +101 -0
- package/src/runtime/sync/resource-sync-events.ts +79 -1
- package/src/runtime/sync/worker-daemon-notify.test.ts +34 -0
- package/src/runtime/sync/worker-daemon-notify.ts +44 -6
- package/src/runtime/verification-outbound-actions.ts +250 -222
- package/src/runtime/verification-templates.ts +41 -30
- package/src/schedule/__tests__/plugin-schedule-declarations.test.ts +614 -0
- package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +1066 -0
- package/src/schedule/inference-profile.ts +64 -3
- package/src/schedule/plugin-schedule-declarations.ts +518 -0
- package/src/schedule/plugin-schedule-reconciler.ts +605 -0
- package/src/schedule/plugin-schedules-gate.ts +23 -0
- package/src/schedule/schedule-store.ts +520 -26
- package/src/schedule/scheduler.ts +38 -0
- package/src/security/credential-backend.ts +51 -4
- package/src/security/encrypted-store.ts +64 -42
- package/src/security/secure-keys.ts +55 -7
- package/src/skills/catalog-install.ts +20 -7
- package/src/skills/inline-command-runner.ts +2 -7
- package/src/skills/skill-history.test.ts +269 -0
- package/src/skills/skill-history.ts +250 -0
- package/src/skills/skillssh-registry.ts +1 -1
- package/src/stt/daemon-batch-transcriber.ts +8 -6
- package/src/subagent/__tests__/consult-prompt.test.ts +25 -0
- package/src/subagent/consult-prompt.ts +3 -1
- package/src/subagent/consult-transcript.ts +1 -1
- package/src/subagent/manager.ts +304 -44
- package/src/subagent/progress-events.ts +35 -0
- package/src/subagent/role-resolution.ts +88 -0
- package/src/subagent/types.ts +302 -67
- package/src/subagent/validate-allowlists.ts +3 -4
- package/src/telegram/__tests__/webhook-health.test.ts +73 -1
- package/src/telegram/webhook-health.ts +101 -3
- package/src/telemetry/__tests__/telemetry-event-fixtures.ts +2 -0
- package/src/telemetry/activation-funnel.ts +6 -1
- package/src/telemetry/live-voice-funnel.ts +75 -0
- package/src/telemetry/telemetry-event-sources.ts +6 -0
- package/src/telemetry/telemetry-wire-source.json +1 -1
- package/src/telemetry/telemetry-wire.generated.ts +2 -0
- package/src/telemetry/types.ts +23 -4
- package/src/telemetry/usage-telemetry-reporter.test.ts +4 -0
- package/src/tools/__tests__/subagent-workflow-tool-input-schemas.test.ts +1 -1
- package/src/tools/ask-question/ask-question-tool.test.ts +118 -3
- package/src/tools/ask-question/ask-question-tool.ts +56 -2
- package/src/tools/browser/browser-execution.ts +1 -1
- package/src/tools/calls/call-start.ts +8 -5
- package/src/tools/conversation-groups/move_to_group.ts +10 -12
- package/src/tools/document/document-tool.ts +11 -4
- package/src/tools/execution-timeout.ts +9 -2
- package/src/tools/executor.ts +7 -6
- package/src/tools/filesystem/edit.ts +1 -1
- package/src/tools/filesystem/list.ts +1 -1
- package/src/tools/filesystem/read.ts +7 -13
- package/src/tools/filesystem/search.ts +23 -21
- package/src/tools/filesystem/write.ts +4 -1
- package/src/tools/host-filesystem/edit.ts +7 -2
- package/src/tools/host-filesystem/read.ts +21 -8
- package/src/tools/host-filesystem/write.ts +10 -2
- package/src/tools/host-terminal/host-shell.ts +2 -2
- package/src/tools/mcp/mcp-tool-factory.ts +25 -1
- package/src/tools/permission-checker.ts +8 -3
- package/src/tools/schedule/delete.ts +13 -1
- package/src/tools/schedule/list.ts +14 -3
- package/src/tools/schedule/update.ts +56 -33
- package/src/tools/shared/filesystem/audio-read.ts +6 -4
- package/src/tools/shared/filesystem/file-ops-service.ts +176 -116
- package/src/tools/shared/filesystem/image-read.ts +12 -12
- package/src/tools/shared/filesystem/path-policy.ts +70 -5
- package/src/tools/shared/filesystem/size-guard.ts +6 -6
- package/src/tools/shared/input-misuse.ts +72 -0
- package/src/tools/skills/scaffold-managed.ts +82 -0
- package/src/tools/skills/skill-tool-factory.ts +14 -1
- package/src/tools/subagent/consult-deadline.ts +12 -7
- package/src/tools/subagent/message.ts +3 -1
- package/src/tools/subagent/read.ts +66 -4
- package/src/tools/subagent/spawn.ts +500 -78
- package/src/tools/tool-approval-handler.ts +45 -4
- package/src/tools/types.ts +22 -3
- package/src/tools/ui-surface/surface-shape-docs.ts +1 -1
- package/src/tools/ui-surface/visual-validation.ts +2 -0
- package/src/usage/__tests__/subagent-attribution.test.ts +109 -0
- package/src/usage/subagent-attribution.ts +108 -0
- package/src/util/__tests__/sqlite-retry.test.ts +112 -0
- package/src/util/abort-reasons.ts +16 -0
- package/src/util/ansi.ts +44 -0
- package/src/util/disk-usage.ts +17 -13
- package/src/util/image-conversion.ts +20 -13
- package/src/util/schedule-source-key.ts +23 -0
- package/src/util/worker-process.ts +7 -1
- package/src/workspace/git-service.ts +33 -9
- package/src/workspace/migrations/140-repair-seed-pinned-memory-v3-live.ts +260 -0
- package/src/workspace/migrations/141-stt-english-default-to-multilingual.ts +111 -0
- package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +126 -0
- package/src/workspace/migrations/registry.ts +4 -0
- package/src/workspace/top-level-renderer.ts +15 -2
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +0 -357
- package/src/daemon/install-symlink.ts +0 -207
- package/src/notifications/deferred-emit.ts +0 -147
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Guard against deriving a filesystem path from a `file:` URL's `.pathname`.
|
|
3
|
+
*
|
|
4
|
+
* `URL.pathname` is percent-encoded, so any path segment containing a space
|
|
5
|
+
* survives as `%20` and every downstream `existsSync` / spawn against it
|
|
6
|
+
* fails. This is not theoretical: macOS desktop installs live under
|
|
7
|
+
* `~/Library/Application Support/…`, so the encoded form broke every
|
|
8
|
+
* background worker spawn on that platform. `fileURLToPath()` decodes.
|
|
9
|
+
*
|
|
10
|
+
* Two shapes are checked:
|
|
11
|
+
* 1. `.pathname` read off a URL built from `import.meta.url`.
|
|
12
|
+
* 2. `.pathname` passed inside a `cmd: [...]` spawn array, the sink that
|
|
13
|
+
* turns an encoded path into a "Module not found" at runtime.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
17
|
+
import { join, relative } from "node:path";
|
|
18
|
+
import { describe, expect, test } from "bun:test";
|
|
19
|
+
|
|
20
|
+
const SRC_DIR = join(import.meta.dir, "..");
|
|
21
|
+
|
|
22
|
+
/** This guard's own file, which necessarily contains the forbidden patterns. */
|
|
23
|
+
const SELF = "__tests__/file-url-path-guard.test.ts";
|
|
24
|
+
|
|
25
|
+
/** `.pathname` read off a URL derived from `import.meta.url`. */
|
|
26
|
+
const IMPORT_META_URL_PATHNAME =
|
|
27
|
+
/import\.meta\.url[\s\S]{0,80}?\)\s*\.pathname/;
|
|
28
|
+
|
|
29
|
+
/** A `cmd: [...]` spawn array (bounded so it can't run away over a file). */
|
|
30
|
+
const SPAWN_CMD_ARRAY = /cmd:\s*\[[\s\S]{0,300}?\]/g;
|
|
31
|
+
|
|
32
|
+
function collectSourceFiles(rootDir: string): string[] {
|
|
33
|
+
const pending = [rootDir];
|
|
34
|
+
const files: string[] = [];
|
|
35
|
+
|
|
36
|
+
while (pending.length > 0) {
|
|
37
|
+
const current = pending.pop();
|
|
38
|
+
if (!current) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
42
|
+
const fullPath = join(current, entry.name);
|
|
43
|
+
if (entry.isDirectory()) {
|
|
44
|
+
if (entry.name !== "node_modules") {
|
|
45
|
+
pending.push(fullPath);
|
|
46
|
+
}
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (entry.isFile() && /\.tsx?$/.test(entry.name)) {
|
|
50
|
+
files.push(fullPath);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return files;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function offenders(predicate: (source: string) => boolean): string[] {
|
|
59
|
+
return collectSourceFiles(SRC_DIR)
|
|
60
|
+
.map((path) => ({ path, rel: relative(SRC_DIR, path) }))
|
|
61
|
+
.filter(({ rel }) => rel !== SELF)
|
|
62
|
+
.filter(({ path }) => predicate(readFileSync(path, "utf-8")))
|
|
63
|
+
.map(({ rel }) => rel)
|
|
64
|
+
.sort();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
describe("file URL → path guard", () => {
|
|
68
|
+
test("no path is derived from import.meta.url via .pathname", () => {
|
|
69
|
+
expect(
|
|
70
|
+
offenders((source) => IMPORT_META_URL_PATHNAME.test(source)),
|
|
71
|
+
).toEqual([]);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("no .pathname is passed as a spawn argument", () => {
|
|
75
|
+
expect(
|
|
76
|
+
offenders((source) =>
|
|
77
|
+
[...source.matchAll(SPAWN_CMD_ARRAY)].some((match) =>
|
|
78
|
+
match[0].includes(".pathname"),
|
|
79
|
+
),
|
|
80
|
+
),
|
|
81
|
+
).toEqual([]);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -49,7 +49,7 @@ function sandboxPolicyFor(boundary: string): PathPolicy {
|
|
|
49
49
|
// ===========================================================================
|
|
50
50
|
|
|
51
51
|
describe("FileSystemOps symlink handling", () => {
|
|
52
|
-
test("read blocks symlink pointing outside boundary", () => {
|
|
52
|
+
test("read blocks symlink pointing outside boundary", async () => {
|
|
53
53
|
const boundary = makeTempDir();
|
|
54
54
|
const outside = makeTempDir();
|
|
55
55
|
const outsideFile = join(outside, "secret.txt");
|
|
@@ -58,7 +58,7 @@ describe("FileSystemOps symlink handling", () => {
|
|
|
58
58
|
symlinkSync(outsideFile, join(boundary, "link.txt"));
|
|
59
59
|
const ops = new FileSystemOps(sandboxPolicyFor(boundary));
|
|
60
60
|
|
|
61
|
-
const result = ops.readFileSafe({ path: "link.txt" });
|
|
61
|
+
const result = await ops.readFileSafe({ path: "link.txt" });
|
|
62
62
|
expect(result.ok).toBe(false);
|
|
63
63
|
if (result.ok) {
|
|
64
64
|
return;
|
|
@@ -66,14 +66,14 @@ describe("FileSystemOps symlink handling", () => {
|
|
|
66
66
|
expect(result.error.code).toBe("PATH_OUT_OF_BOUNDS");
|
|
67
67
|
});
|
|
68
68
|
|
|
69
|
-
test("read allows symlink within boundary", () => {
|
|
69
|
+
test("read allows symlink within boundary", async () => {
|
|
70
70
|
const boundary = makeTempDir();
|
|
71
71
|
const realFile = join(boundary, "real.txt");
|
|
72
72
|
writeFileSync(realFile, "hello");
|
|
73
73
|
symlinkSync(realFile, join(boundary, "link.txt"));
|
|
74
74
|
|
|
75
75
|
const ops = new FileSystemOps(sandboxPolicyFor(boundary));
|
|
76
|
-
const result = ops.readFileSafe({ path: "link.txt" });
|
|
76
|
+
const result = await ops.readFileSafe({ path: "link.txt" });
|
|
77
77
|
expect(result.ok).toBe(true);
|
|
78
78
|
if (!result.ok) {
|
|
79
79
|
return;
|
|
@@ -81,13 +81,13 @@ describe("FileSystemOps symlink handling", () => {
|
|
|
81
81
|
expect(result.value.content).toContain("hello");
|
|
82
82
|
});
|
|
83
83
|
|
|
84
|
-
test("write blocks creating file under symlinked dir pointing outside", () => {
|
|
84
|
+
test("write blocks creating file under symlinked dir pointing outside", async () => {
|
|
85
85
|
const boundary = makeTempDir();
|
|
86
86
|
const outside = makeTempDir();
|
|
87
87
|
symlinkSync(outside, join(boundary, "link-dir"));
|
|
88
88
|
|
|
89
89
|
const ops = new FileSystemOps(sandboxPolicyFor(boundary));
|
|
90
|
-
const result = ops.writeFileSafe({
|
|
90
|
+
const result = await ops.writeFileSafe({
|
|
91
91
|
path: "link-dir/evil.txt",
|
|
92
92
|
content: "bad",
|
|
93
93
|
});
|
|
@@ -100,7 +100,7 @@ describe("FileSystemOps symlink handling", () => {
|
|
|
100
100
|
expect(existsSync(join(outside, "evil.txt"))).toBe(false);
|
|
101
101
|
});
|
|
102
102
|
|
|
103
|
-
test("edit blocks symlink pointing outside boundary", () => {
|
|
103
|
+
test("edit blocks symlink pointing outside boundary", async () => {
|
|
104
104
|
const boundary = makeTempDir();
|
|
105
105
|
const outside = makeTempDir();
|
|
106
106
|
const outsideFile = join(outside, "target.txt");
|
|
@@ -108,7 +108,7 @@ describe("FileSystemOps symlink handling", () => {
|
|
|
108
108
|
symlinkSync(outsideFile, join(boundary, "link.txt"));
|
|
109
109
|
|
|
110
110
|
const ops = new FileSystemOps(sandboxPolicyFor(boundary));
|
|
111
|
-
const result = ops.editFileSafe({
|
|
111
|
+
const result = await ops.editFileSafe({
|
|
112
112
|
path: "link.txt",
|
|
113
113
|
oldString: "original",
|
|
114
114
|
newString: "modified",
|
|
@@ -129,12 +129,12 @@ describe("FileSystemOps symlink handling", () => {
|
|
|
129
129
|
// ===========================================================================
|
|
130
130
|
|
|
131
131
|
describe("FileSystemOps read offset/limit edge cases", () => {
|
|
132
|
-
test("offset beyond file length returns empty content", () => {
|
|
132
|
+
test("offset beyond file length returns empty content", async () => {
|
|
133
133
|
const dir = makeTempDir();
|
|
134
134
|
writeFileSync(join(dir, "short.txt"), "a\nb\nc");
|
|
135
135
|
const ops = new FileSystemOps(sandboxPolicyFor(dir));
|
|
136
136
|
|
|
137
|
-
const result = ops.readFileSafe({ path: "short.txt", offset: 100 });
|
|
137
|
+
const result = await ops.readFileSafe({ path: "short.txt", offset: 100 });
|
|
138
138
|
expect(result.ok).toBe(true);
|
|
139
139
|
if (!result.ok) {
|
|
140
140
|
return;
|
|
@@ -142,12 +142,12 @@ describe("FileSystemOps read offset/limit edge cases", () => {
|
|
|
142
142
|
expect(result.value.content).toBe("");
|
|
143
143
|
});
|
|
144
144
|
|
|
145
|
-
test("limit of zero returns empty content", () => {
|
|
145
|
+
test("limit of zero returns empty content", async () => {
|
|
146
146
|
const dir = makeTempDir();
|
|
147
147
|
writeFileSync(join(dir, "file.txt"), "a\nb\nc");
|
|
148
148
|
const ops = new FileSystemOps(sandboxPolicyFor(dir));
|
|
149
149
|
|
|
150
|
-
const result = ops.readFileSafe({ path: "file.txt", limit: 0 });
|
|
150
|
+
const result = await ops.readFileSafe({ path: "file.txt", limit: 0 });
|
|
151
151
|
expect(result.ok).toBe(true);
|
|
152
152
|
if (!result.ok) {
|
|
153
153
|
return;
|
|
@@ -155,12 +155,16 @@ describe("FileSystemOps read offset/limit edge cases", () => {
|
|
|
155
155
|
expect(result.value.content).toBe("");
|
|
156
156
|
});
|
|
157
157
|
|
|
158
|
-
test("offset=1 reads from first line (1-indexed)", () => {
|
|
158
|
+
test("offset=1 reads from first line (1-indexed)", async () => {
|
|
159
159
|
const dir = makeTempDir();
|
|
160
160
|
writeFileSync(join(dir, "file.txt"), "first\nsecond\nthird");
|
|
161
161
|
const ops = new FileSystemOps(sandboxPolicyFor(dir));
|
|
162
162
|
|
|
163
|
-
const result = ops.readFileSafe({
|
|
163
|
+
const result = await ops.readFileSafe({
|
|
164
|
+
path: "file.txt",
|
|
165
|
+
offset: 1,
|
|
166
|
+
limit: 1,
|
|
167
|
+
});
|
|
164
168
|
expect(result.ok).toBe(true);
|
|
165
169
|
if (!result.ok) {
|
|
166
170
|
return;
|
|
@@ -169,12 +173,12 @@ describe("FileSystemOps read offset/limit edge cases", () => {
|
|
|
169
173
|
expect(result.value.content).not.toContain("second");
|
|
170
174
|
});
|
|
171
175
|
|
|
172
|
-
test("limit exceeding file length returns all remaining lines", () => {
|
|
176
|
+
test("limit exceeding file length returns all remaining lines", async () => {
|
|
173
177
|
const dir = makeTempDir();
|
|
174
178
|
writeFileSync(join(dir, "file.txt"), "a\nb");
|
|
175
179
|
const ops = new FileSystemOps(sandboxPolicyFor(dir));
|
|
176
180
|
|
|
177
|
-
const result = ops.readFileSafe({
|
|
181
|
+
const result = await ops.readFileSafe({
|
|
178
182
|
path: "file.txt",
|
|
179
183
|
offset: 1,
|
|
180
184
|
limit: 1000,
|
|
@@ -187,12 +191,16 @@ describe("FileSystemOps read offset/limit edge cases", () => {
|
|
|
187
191
|
expect(result.value.content).toContain("b");
|
|
188
192
|
});
|
|
189
193
|
|
|
190
|
-
test("read adds line numbers starting from offset", () => {
|
|
194
|
+
test("read adds line numbers starting from offset", async () => {
|
|
191
195
|
const dir = makeTempDir();
|
|
192
196
|
writeFileSync(join(dir, "file.txt"), "a\nb\nc\nd\ne");
|
|
193
197
|
const ops = new FileSystemOps(sandboxPolicyFor(dir));
|
|
194
198
|
|
|
195
|
-
const result = ops.readFileSafe({
|
|
199
|
+
const result = await ops.readFileSafe({
|
|
200
|
+
path: "file.txt",
|
|
201
|
+
offset: 3,
|
|
202
|
+
limit: 2,
|
|
203
|
+
});
|
|
196
204
|
expect(result.ok).toBe(true);
|
|
197
205
|
if (!result.ok) {
|
|
198
206
|
return;
|
|
@@ -210,7 +218,7 @@ describe("FileSystemOps read offset/limit edge cases", () => {
|
|
|
210
218
|
// ===========================================================================
|
|
211
219
|
|
|
212
220
|
describe("FileSystemOps edit match methods", () => {
|
|
213
|
-
test("whitespace-normalized match succeeds", () => {
|
|
221
|
+
test("whitespace-normalized match succeeds", async () => {
|
|
214
222
|
const dir = makeTempDir();
|
|
215
223
|
writeFileSync(
|
|
216
224
|
join(dir, "file.txt"),
|
|
@@ -218,7 +226,7 @@ describe("FileSystemOps edit match methods", () => {
|
|
|
218
226
|
);
|
|
219
227
|
const ops = new FileSystemOps(sandboxPolicyFor(dir));
|
|
220
228
|
|
|
221
|
-
const result = ops.editFileSafe({
|
|
229
|
+
const result = await ops.editFileSafe({
|
|
222
230
|
path: "file.txt",
|
|
223
231
|
oldString: "function foo() {\n return 1;\n}",
|
|
224
232
|
newString: "function bar() {\n return 2;\n}",
|
|
@@ -233,12 +241,12 @@ describe("FileSystemOps edit match methods", () => {
|
|
|
233
241
|
expect(result.value.newContent).toContain("bar");
|
|
234
242
|
});
|
|
235
243
|
|
|
236
|
-
test("fuzzy match succeeds with near-match", () => {
|
|
244
|
+
test("fuzzy match succeeds with near-match", async () => {
|
|
237
245
|
const dir = makeTempDir();
|
|
238
246
|
writeFileSync(join(dir, "file.txt"), "function fooo() {\n return 1;\n}\n");
|
|
239
247
|
const ops = new FileSystemOps(sandboxPolicyFor(dir));
|
|
240
248
|
|
|
241
|
-
const result = ops.editFileSafe({
|
|
249
|
+
const result = await ops.editFileSafe({
|
|
242
250
|
path: "file.txt",
|
|
243
251
|
oldString: "function foo() {\n return 1;\n}",
|
|
244
252
|
newString: "function bar() {\n return 2;\n}",
|
|
@@ -253,12 +261,12 @@ describe("FileSystemOps edit match methods", () => {
|
|
|
253
261
|
expect(result.value.similarity).toBeLessThan(1);
|
|
254
262
|
});
|
|
255
263
|
|
|
256
|
-
test("edit returns actualOld and actualNew for fuzzy match", () => {
|
|
264
|
+
test("edit returns actualOld and actualNew for fuzzy match", async () => {
|
|
257
265
|
const dir = makeTempDir();
|
|
258
266
|
writeFileSync(join(dir, "file.txt"), "function fooo() {\n return 1;\n}\n");
|
|
259
267
|
const ops = new FileSystemOps(sandboxPolicyFor(dir));
|
|
260
268
|
|
|
261
|
-
const result = ops.editFileSafe({
|
|
269
|
+
const result = await ops.editFileSafe({
|
|
262
270
|
path: "file.txt",
|
|
263
271
|
oldString: "function foo() {\n return 1;\n}",
|
|
264
272
|
newString: "function bar() {\n return 2;\n}",
|
|
@@ -278,11 +286,14 @@ describe("FileSystemOps edit match methods", () => {
|
|
|
278
286
|
// ===========================================================================
|
|
279
287
|
|
|
280
288
|
describe("FileSystemOps write content tracking", () => {
|
|
281
|
-
test("new file has empty oldContent", () => {
|
|
289
|
+
test("new file has empty oldContent", async () => {
|
|
282
290
|
const dir = makeTempDir();
|
|
283
291
|
const ops = new FileSystemOps(sandboxPolicyFor(dir));
|
|
284
292
|
|
|
285
|
-
const result = ops.writeFileSafe({
|
|
293
|
+
const result = await ops.writeFileSafe({
|
|
294
|
+
path: "brand-new.txt",
|
|
295
|
+
content: "new",
|
|
296
|
+
});
|
|
286
297
|
expect(result.ok).toBe(true);
|
|
287
298
|
if (!result.ok) {
|
|
288
299
|
return;
|
|
@@ -291,12 +302,12 @@ describe("FileSystemOps write content tracking", () => {
|
|
|
291
302
|
expect(result.value.isNewFile).toBe(true);
|
|
292
303
|
});
|
|
293
304
|
|
|
294
|
-
test("overwrite tracks oldContent and newContent", () => {
|
|
305
|
+
test("overwrite tracks oldContent and newContent", async () => {
|
|
295
306
|
const dir = makeTempDir();
|
|
296
307
|
writeFileSync(join(dir, "existing.txt"), "version 1");
|
|
297
308
|
const ops = new FileSystemOps(sandboxPolicyFor(dir));
|
|
298
309
|
|
|
299
|
-
const result = ops.writeFileSafe({
|
|
310
|
+
const result = await ops.writeFileSafe({
|
|
300
311
|
path: "existing.txt",
|
|
301
312
|
content: "version 2",
|
|
302
313
|
});
|
|
@@ -309,11 +320,14 @@ describe("FileSystemOps write content tracking", () => {
|
|
|
309
320
|
expect(result.value.isNewFile).toBe(false);
|
|
310
321
|
});
|
|
311
322
|
|
|
312
|
-
test("write returns resolved absolute path", () => {
|
|
323
|
+
test("write returns resolved absolute path", async () => {
|
|
313
324
|
const dir = makeTempDir();
|
|
314
325
|
const ops = new FileSystemOps(sandboxPolicyFor(dir));
|
|
315
326
|
|
|
316
|
-
const result = ops.writeFileSafe({
|
|
327
|
+
const result = await ops.writeFileSafe({
|
|
328
|
+
path: "output.txt",
|
|
329
|
+
content: "data",
|
|
330
|
+
});
|
|
317
331
|
expect(result.ok).toBe(true);
|
|
318
332
|
if (!result.ok) {
|
|
319
333
|
return;
|
|
@@ -395,11 +409,11 @@ describe("formatWriteSummary", () => {
|
|
|
395
409
|
// ===========================================================================
|
|
396
410
|
|
|
397
411
|
describe("FileSystemOps path traversal prevention", () => {
|
|
398
|
-
test("rejects absolute path outside boundary on read", () => {
|
|
412
|
+
test("rejects absolute path outside boundary on read", async () => {
|
|
399
413
|
const dir = makeTempDir();
|
|
400
414
|
const ops = new FileSystemOps(sandboxPolicyFor(dir));
|
|
401
415
|
|
|
402
|
-
const result = ops.readFileSafe({ path: "/etc/passwd" });
|
|
416
|
+
const result = await ops.readFileSafe({ path: "/etc/passwd" });
|
|
403
417
|
expect(result.ok).toBe(false);
|
|
404
418
|
if (result.ok) {
|
|
405
419
|
return;
|
|
@@ -407,11 +421,11 @@ describe("FileSystemOps path traversal prevention", () => {
|
|
|
407
421
|
expect(result.error.code).toBe("PATH_OUT_OF_BOUNDS");
|
|
408
422
|
});
|
|
409
423
|
|
|
410
|
-
test("rejects absolute path outside boundary on write", () => {
|
|
424
|
+
test("rejects absolute path outside boundary on write", async () => {
|
|
411
425
|
const dir = makeTempDir();
|
|
412
426
|
const ops = new FileSystemOps(sandboxPolicyFor(dir));
|
|
413
427
|
|
|
414
|
-
const result = ops.writeFileSafe({
|
|
428
|
+
const result = await ops.writeFileSafe({
|
|
415
429
|
path: "/tmp/evil-write.txt",
|
|
416
430
|
content: "bad",
|
|
417
431
|
});
|
|
@@ -422,11 +436,11 @@ describe("FileSystemOps path traversal prevention", () => {
|
|
|
422
436
|
expect(result.error.code).toBe("PATH_OUT_OF_BOUNDS");
|
|
423
437
|
});
|
|
424
438
|
|
|
425
|
-
test("rejects absolute path outside boundary on edit", () => {
|
|
439
|
+
test("rejects absolute path outside boundary on edit", async () => {
|
|
426
440
|
const dir = makeTempDir();
|
|
427
441
|
const ops = new FileSystemOps(sandboxPolicyFor(dir));
|
|
428
442
|
|
|
429
|
-
const result = ops.editFileSafe({
|
|
443
|
+
const result = await ops.editFileSafe({
|
|
430
444
|
path: "/etc/hosts",
|
|
431
445
|
oldString: "a",
|
|
432
446
|
newString: "b",
|
|
@@ -439,12 +453,12 @@ describe("FileSystemOps path traversal prevention", () => {
|
|
|
439
453
|
expect(result.error.code).toBe("PATH_OUT_OF_BOUNDS");
|
|
440
454
|
});
|
|
441
455
|
|
|
442
|
-
test("rejects dot-dot traversal embedded in path on read", () => {
|
|
456
|
+
test("rejects dot-dot traversal embedded in path on read", async () => {
|
|
443
457
|
const dir = makeTempDir();
|
|
444
458
|
mkdirSync(join(dir, "sub"));
|
|
445
459
|
const ops = new FileSystemOps(sandboxPolicyFor(dir));
|
|
446
460
|
|
|
447
|
-
const result = ops.readFileSafe({ path: "sub/../../etc/passwd" });
|
|
461
|
+
const result = await ops.readFileSafe({ path: "sub/../../etc/passwd" });
|
|
448
462
|
expect(result.ok).toBe(false);
|
|
449
463
|
if (result.ok) {
|
|
450
464
|
return;
|
|
@@ -452,12 +466,12 @@ describe("FileSystemOps path traversal prevention", () => {
|
|
|
452
466
|
expect(result.error.code).toBe("PATH_OUT_OF_BOUNDS");
|
|
453
467
|
});
|
|
454
468
|
|
|
455
|
-
test("accepts absolute path inside boundary", () => {
|
|
469
|
+
test("accepts absolute path inside boundary", async () => {
|
|
456
470
|
const dir = makeTempDir();
|
|
457
471
|
writeFileSync(join(dir, "inside.txt"), "safe content");
|
|
458
472
|
const ops = new FileSystemOps(sandboxPolicyFor(dir));
|
|
459
473
|
|
|
460
|
-
const result = ops.readFileSafe({ path: join(dir, "inside.txt") });
|
|
474
|
+
const result = await ops.readFileSafe({ path: join(dir, "inside.txt") });
|
|
461
475
|
expect(result.ok).toBe(true);
|
|
462
476
|
if (!result.ok) {
|
|
463
477
|
return;
|
|
@@ -471,13 +485,13 @@ describe("FileSystemOps path traversal prevention", () => {
|
|
|
471
485
|
// ===========================================================================
|
|
472
486
|
|
|
473
487
|
describe("FileSystemOps binary file read", () => {
|
|
474
|
-
test("reads binary content as utf-8 without crashing", () => {
|
|
488
|
+
test("reads binary content as utf-8 without crashing", async () => {
|
|
475
489
|
const dir = makeTempDir();
|
|
476
490
|
const binaryContent = Buffer.from([0x00, 0xff, 0x89, 0x50, 0x4e, 0x47]);
|
|
477
491
|
writeFileSync(join(dir, "binary.bin"), binaryContent);
|
|
478
492
|
const ops = new FileSystemOps(sandboxPolicyFor(dir));
|
|
479
493
|
|
|
480
|
-
const result = ops.readFileSafe({ path: "binary.bin" });
|
|
494
|
+
const result = await ops.readFileSafe({ path: "binary.bin" });
|
|
481
495
|
// Should succeed — the file is readable, even if content has replacement chars
|
|
482
496
|
expect(result.ok).toBe(true);
|
|
483
497
|
});
|
|
@@ -488,12 +502,12 @@ describe("FileSystemOps binary file read", () => {
|
|
|
488
502
|
// ===========================================================================
|
|
489
503
|
|
|
490
504
|
describe("FileSystemOps empty file operations", () => {
|
|
491
|
-
test("reads empty file successfully", () => {
|
|
505
|
+
test("reads empty file successfully", async () => {
|
|
492
506
|
const dir = makeTempDir();
|
|
493
507
|
writeFileSync(join(dir, "empty.txt"), "");
|
|
494
508
|
const ops = new FileSystemOps(sandboxPolicyFor(dir));
|
|
495
509
|
|
|
496
|
-
const result = ops.readFileSafe({ path: "empty.txt" });
|
|
510
|
+
const result = await ops.readFileSafe({ path: "empty.txt" });
|
|
497
511
|
expect(result.ok).toBe(true);
|
|
498
512
|
if (!result.ok) {
|
|
499
513
|
return;
|
|
@@ -502,11 +516,11 @@ describe("FileSystemOps empty file operations", () => {
|
|
|
502
516
|
expect(result.value.content).toBeDefined();
|
|
503
517
|
});
|
|
504
518
|
|
|
505
|
-
test("write empty content creates empty file", () => {
|
|
519
|
+
test("write empty content creates empty file", async () => {
|
|
506
520
|
const dir = makeTempDir();
|
|
507
521
|
const ops = new FileSystemOps(sandboxPolicyFor(dir));
|
|
508
522
|
|
|
509
|
-
const result = ops.writeFileSafe({ path: "empty.txt", content: "" });
|
|
523
|
+
const result = await ops.writeFileSafe({ path: "empty.txt", content: "" });
|
|
510
524
|
expect(result.ok).toBe(true);
|
|
511
525
|
if (!result.ok) {
|
|
512
526
|
return;
|
|
@@ -515,12 +529,12 @@ describe("FileSystemOps empty file operations", () => {
|
|
|
515
529
|
expect(readFileSync(join(dir, "empty.txt"), "utf-8")).toBe("");
|
|
516
530
|
});
|
|
517
531
|
|
|
518
|
-
test("edit on empty file returns MATCH_NOT_FOUND", () => {
|
|
532
|
+
test("edit on empty file returns MATCH_NOT_FOUND", async () => {
|
|
519
533
|
const dir = makeTempDir();
|
|
520
534
|
writeFileSync(join(dir, "empty.txt"), "");
|
|
521
535
|
const ops = new FileSystemOps(sandboxPolicyFor(dir));
|
|
522
536
|
|
|
523
|
-
const result = ops.editFileSafe({
|
|
537
|
+
const result = await ops.editFileSafe({
|
|
524
538
|
path: "empty.txt",
|
|
525
539
|
oldString: "something",
|
|
526
540
|
newString: "else",
|
|
@@ -539,12 +553,12 @@ describe("FileSystemOps empty file operations", () => {
|
|
|
539
553
|
// ===========================================================================
|
|
540
554
|
|
|
541
555
|
describe("FileSystemOps /workspace path remapping", () => {
|
|
542
|
-
test("read remaps /workspace/ path to boundary", () => {
|
|
556
|
+
test("read remaps /workspace/ path to boundary", async () => {
|
|
543
557
|
const dir = makeTempDir();
|
|
544
558
|
writeFileSync(join(dir, "file.txt"), "workspace content");
|
|
545
559
|
const ops = new FileSystemOps(sandboxPolicyFor(dir));
|
|
546
560
|
|
|
547
|
-
const result = ops.readFileSafe({ path: "/workspace/file.txt" });
|
|
561
|
+
const result = await ops.readFileSafe({ path: "/workspace/file.txt" });
|
|
548
562
|
expect(result.ok).toBe(true);
|
|
549
563
|
if (!result.ok) {
|
|
550
564
|
return;
|
|
@@ -552,11 +566,11 @@ describe("FileSystemOps /workspace path remapping", () => {
|
|
|
552
566
|
expect(result.value.content).toContain("workspace content");
|
|
553
567
|
});
|
|
554
568
|
|
|
555
|
-
test("write remaps /workspace/ path to boundary", () => {
|
|
569
|
+
test("write remaps /workspace/ path to boundary", async () => {
|
|
556
570
|
const dir = makeTempDir();
|
|
557
571
|
const ops = new FileSystemOps(sandboxPolicyFor(dir));
|
|
558
572
|
|
|
559
|
-
const result = ops.writeFileSafe({
|
|
573
|
+
const result = await ops.writeFileSafe({
|
|
560
574
|
path: "/workspace/new.txt",
|
|
561
575
|
content: "remapped",
|
|
562
576
|
});
|
|
@@ -568,12 +582,12 @@ describe("FileSystemOps /workspace path remapping", () => {
|
|
|
568
582
|
expect(readFileSync(join(dir, "new.txt"), "utf-8")).toBe("remapped");
|
|
569
583
|
});
|
|
570
584
|
|
|
571
|
-
test("edit remaps /workspace/ path to boundary", () => {
|
|
585
|
+
test("edit remaps /workspace/ path to boundary", async () => {
|
|
572
586
|
const dir = makeTempDir();
|
|
573
587
|
writeFileSync(join(dir, "file.txt"), "old content");
|
|
574
588
|
const ops = new FileSystemOps(sandboxPolicyFor(dir));
|
|
575
589
|
|
|
576
|
-
const result = ops.editFileSafe({
|
|
590
|
+
const result = await ops.editFileSafe({
|
|
577
591
|
path: "/workspace/file.txt",
|
|
578
592
|
oldString: "old content",
|
|
579
593
|
newString: "new content",
|
|
@@ -587,11 +601,13 @@ describe("FileSystemOps /workspace path remapping", () => {
|
|
|
587
601
|
expect(readFileSync(join(dir, "file.txt"), "utf-8")).toBe("new content");
|
|
588
602
|
});
|
|
589
603
|
|
|
590
|
-
test("/workspace traversal escape is blocked", () => {
|
|
604
|
+
test("/workspace traversal escape is blocked", async () => {
|
|
591
605
|
const dir = makeTempDir();
|
|
592
606
|
const ops = new FileSystemOps(sandboxPolicyFor(dir));
|
|
593
607
|
|
|
594
|
-
const result = ops.readFileSafe({
|
|
608
|
+
const result = await ops.readFileSafe({
|
|
609
|
+
path: "/workspace/../../../etc/passwd",
|
|
610
|
+
});
|
|
595
611
|
expect(result.ok).toBe(false);
|
|
596
612
|
if (result.ok) {
|
|
597
613
|
return;
|
|
@@ -605,12 +621,12 @@ describe("FileSystemOps /workspace path remapping", () => {
|
|
|
605
621
|
// ===========================================================================
|
|
606
622
|
|
|
607
623
|
describe("FileSystemOps custom size limit", () => {
|
|
608
|
-
test("read rejects file exceeding custom limit", () => {
|
|
624
|
+
test("read rejects file exceeding custom limit", async () => {
|
|
609
625
|
const dir = makeTempDir();
|
|
610
626
|
writeFileSync(join(dir, "big.txt"), "x".repeat(500));
|
|
611
627
|
const ops = new FileSystemOps(sandboxPolicyFor(dir), { sizeLimit: 100 });
|
|
612
628
|
|
|
613
|
-
const result = ops.readFileSafe({ path: "big.txt" });
|
|
629
|
+
const result = await ops.readFileSafe({ path: "big.txt" });
|
|
614
630
|
expect(result.ok).toBe(false);
|
|
615
631
|
if (result.ok) {
|
|
616
632
|
return;
|
|
@@ -618,20 +634,20 @@ describe("FileSystemOps custom size limit", () => {
|
|
|
618
634
|
expect(result.error.code).toBe("SIZE_LIMIT_EXCEEDED");
|
|
619
635
|
});
|
|
620
636
|
|
|
621
|
-
test("read accepts file within custom limit", () => {
|
|
637
|
+
test("read accepts file within custom limit", async () => {
|
|
622
638
|
const dir = makeTempDir();
|
|
623
639
|
writeFileSync(join(dir, "small.txt"), "x".repeat(50));
|
|
624
640
|
const ops = new FileSystemOps(sandboxPolicyFor(dir), { sizeLimit: 100 });
|
|
625
641
|
|
|
626
|
-
const result = ops.readFileSafe({ path: "small.txt" });
|
|
642
|
+
const result = await ops.readFileSafe({ path: "small.txt" });
|
|
627
643
|
expect(result.ok).toBe(true);
|
|
628
644
|
});
|
|
629
645
|
|
|
630
|
-
test("write rejects content exceeding custom limit", () => {
|
|
646
|
+
test("write rejects content exceeding custom limit", async () => {
|
|
631
647
|
const dir = makeTempDir();
|
|
632
648
|
const ops = new FileSystemOps(sandboxPolicyFor(dir), { sizeLimit: 100 });
|
|
633
649
|
|
|
634
|
-
const result = ops.writeFileSafe({
|
|
650
|
+
const result = await ops.writeFileSafe({
|
|
635
651
|
path: "big.txt",
|
|
636
652
|
content: "x".repeat(500),
|
|
637
653
|
});
|
|
@@ -642,12 +658,12 @@ describe("FileSystemOps custom size limit", () => {
|
|
|
642
658
|
expect(result.error.code).toBe("SIZE_LIMIT_EXCEEDED");
|
|
643
659
|
});
|
|
644
660
|
|
|
645
|
-
test("edit rejects file exceeding custom limit", () => {
|
|
661
|
+
test("edit rejects file exceeding custom limit", async () => {
|
|
646
662
|
const dir = makeTempDir();
|
|
647
663
|
writeFileSync(join(dir, "big.txt"), "x".repeat(500));
|
|
648
664
|
const ops = new FileSystemOps(sandboxPolicyFor(dir), { sizeLimit: 100 });
|
|
649
665
|
|
|
650
|
-
const result = ops.editFileSafe({
|
|
666
|
+
const result = await ops.editFileSafe({
|
|
651
667
|
path: "big.txt",
|
|
652
668
|
oldString: "x",
|
|
653
669
|
newString: "y",
|
|
@@ -660,12 +676,12 @@ describe("FileSystemOps custom size limit", () => {
|
|
|
660
676
|
expect(result.error.code).toBe("SIZE_LIMIT_EXCEEDED");
|
|
661
677
|
});
|
|
662
678
|
|
|
663
|
-
test("no size limit when not specified (defaults to 100MB)", () => {
|
|
679
|
+
test("no size limit when not specified (defaults to 100MB)", async () => {
|
|
664
680
|
const dir = makeTempDir();
|
|
665
681
|
writeFileSync(join(dir, "file.txt"), "x".repeat(1000));
|
|
666
682
|
const ops = new FileSystemOps(sandboxPolicyFor(dir));
|
|
667
683
|
|
|
668
|
-
const result = ops.readFileSafe({ path: "file.txt" });
|
|
684
|
+
const result = await ops.readFileSafe({ path: "file.txt" });
|
|
669
685
|
expect(result.ok).toBe(true);
|
|
670
686
|
});
|
|
671
687
|
});
|
|
@@ -440,7 +440,7 @@ describe("HTTP route: handleCreateVerificationSession (guardian path)", () => {
|
|
|
440
440
|
test("creates inbound challenge when destination is absent", async () => {
|
|
441
441
|
const result = (await handleCreateVerificationSession({
|
|
442
442
|
body: { channel: "phone" },
|
|
443
|
-
})) as Record<string, unknown>;
|
|
443
|
+
})) as unknown as Record<string, unknown>;
|
|
444
444
|
expect(result.success).toBe(true);
|
|
445
445
|
expect(result.channel).toBe("phone");
|
|
446
446
|
});
|
|
@@ -448,7 +448,7 @@ describe("HTTP route: handleCreateVerificationSession (guardian path)", () => {
|
|
|
448
448
|
test("returns success for valid voice start", async () => {
|
|
449
449
|
const result = (await handleCreateVerificationSession({
|
|
450
450
|
body: { channel: "phone", destination: "+15559999999" },
|
|
451
|
-
})) as Record<string, unknown>;
|
|
451
|
+
})) as unknown as Record<string, unknown>;
|
|
452
452
|
expect(result.success).toBe(true);
|
|
453
453
|
expect(result.verificationSessionId).toBeDefined();
|
|
454
454
|
});
|
|
@@ -471,7 +471,7 @@ describe("HTTP route: handleResendVerificationSession (guardian path)", () => {
|
|
|
471
471
|
// Start a session first
|
|
472
472
|
const startResult = (await handleCreateVerificationSession({
|
|
473
473
|
body: { channel: "phone", destination: "+15556667777" },
|
|
474
|
-
})) as Record<string, unknown>;
|
|
474
|
+
})) as unknown as Record<string, unknown>;
|
|
475
475
|
expect(startResult.success).toBe(true);
|
|
476
476
|
|
|
477
477
|
// Expire the cooldown so resend is allowed
|
|
@@ -489,7 +489,7 @@ describe("HTTP route: handleResendVerificationSession (guardian path)", () => {
|
|
|
489
489
|
channel: "phone",
|
|
490
490
|
originConversationId: "conv-resend-http-origin",
|
|
491
491
|
},
|
|
492
|
-
})) as Record<string, unknown>;
|
|
492
|
+
})) as unknown as Record<string, unknown>;
|
|
493
493
|
expect(resendResult.success).toBe(true);
|
|
494
494
|
expect(resendResult.originConversationId).toBe("conv-resend-http-origin");
|
|
495
495
|
});
|
|
@@ -505,7 +505,7 @@ describe("HTTP route: handleCancelVerificationSession (guardian path)", () => {
|
|
|
505
505
|
test("returns success even when no active session exists", async () => {
|
|
506
506
|
const result = (await handleCancelVerificationSession({
|
|
507
507
|
body: { channel: "phone" },
|
|
508
|
-
})) as Record<string, unknown>;
|
|
508
|
+
})) as unknown as Record<string, unknown>;
|
|
509
509
|
expect(result.success).toBe(true);
|
|
510
510
|
});
|
|
511
511
|
|
|
@@ -513,13 +513,13 @@ describe("HTTP route: handleCancelVerificationSession (guardian path)", () => {
|
|
|
513
513
|
// Start a session
|
|
514
514
|
const startResult = (await handleCreateVerificationSession({
|
|
515
515
|
body: { channel: "phone", destination: "+15558887777" },
|
|
516
|
-
})) as Record<string, unknown>;
|
|
516
|
+
})) as unknown as Record<string, unknown>;
|
|
517
517
|
expect(startResult.success).toBe(true);
|
|
518
518
|
|
|
519
519
|
// Cancel it
|
|
520
520
|
const cancelResult = (await handleCancelVerificationSession({
|
|
521
521
|
body: { channel: "phone" },
|
|
522
|
-
})) as Record<string, unknown>;
|
|
522
|
+
})) as unknown as Record<string, unknown>;
|
|
523
523
|
expect(cancelResult.success).toBe(true);
|
|
524
524
|
});
|
|
525
525
|
});
|
|
@@ -575,7 +575,7 @@ describe("origin conversation linkage", () => {
|
|
|
575
575
|
destination: "+15557776666",
|
|
576
576
|
originConversationId: "conv-origin-http-test",
|
|
577
577
|
},
|
|
578
|
-
})) as Record<string, unknown>;
|
|
578
|
+
})) as unknown as Record<string, unknown>;
|
|
579
579
|
expect(result.success).toBe(true);
|
|
580
580
|
expect(result.originConversationId).toBe("conv-origin-http-test");
|
|
581
581
|
});
|
|
@@ -139,9 +139,22 @@ export function createGatewayVerificationSessionsStub(options: {
|
|
|
139
139
|
throwIfUnreachable("updateSessionDelivery");
|
|
140
140
|
calls.updateSessionDelivery.push(args);
|
|
141
141
|
},
|
|
142
|
-
|
|
142
|
+
// Honours the caller's `expectedExternalUserId` filter the way the
|
|
143
|
+
// gateway does. Returning the session for every filter would make a
|
|
144
|
+
// "someone else is verifying" test pass against a lookup that, against
|
|
145
|
+
// the real gateway, would have found nothing.
|
|
146
|
+
findActiveSession: async (...args: unknown[]) => {
|
|
143
147
|
calls.sessionReads.push("findActiveSession");
|
|
144
148
|
throwIfUnreachable("findActiveSession");
|
|
149
|
+
const wantedActor = (
|
|
150
|
+
args[1] as { expectedExternalUserId?: string } | undefined
|
|
151
|
+
)?.expectedExternalUserId;
|
|
152
|
+
if (
|
|
153
|
+
wantedActor !== undefined &&
|
|
154
|
+
state.activeSession?.expectedExternalUserId !== wantedActor
|
|
155
|
+
) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
145
158
|
return state.activeSession;
|
|
146
159
|
},
|
|
147
160
|
getPendingSession: async () => {
|