@vellumai/assistant 0.12.1 → 0.12.2-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/AGENTS.md +4 -0
- package/ARCHITECTURE.md +20 -21
- package/docs/architecture/turn-actor.md +15 -8
- package/docs/guardian-request-flow.md +16 -0
- package/openapi.yaml +327 -52
- package/package.json +3 -1
- package/scripts/smoke-desktop-window-theme.ts +186 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +18 -10
- package/src/__tests__/anthropic-provider.test.ts +0 -63
- package/src/__tests__/app-compiler.test.ts +6 -0
- package/src/__tests__/app-control-tool-schemas.test.ts +5 -2
- package/src/__tests__/app-dir-path-guard.test.ts +1 -3
- package/src/__tests__/assistant-event-hub-machine-name.test.ts +6 -7
- package/src/__tests__/assistant-event-hub.test.ts +44 -0
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
- package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +5 -2
- package/src/__tests__/computer-use-tools.test.ts +19 -7
- package/src/__tests__/config-schema-cmd.test.ts +2 -2
- package/src/__tests__/config-schema.test.ts +28 -29
- package/src/__tests__/config-watcher.test.ts +23 -8
- package/src/__tests__/connection-model-compat.test.ts +2 -2
- package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
- package/src/__tests__/conversation-pairing.test.ts +26 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -10
- package/src/__tests__/conversation-queue.test.ts +48 -207
- package/src/__tests__/conversation-retry-discard.test.ts +54 -1
- package/src/__tests__/conversation-skill-tools.test.ts +27 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +8 -3
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
- package/src/__tests__/conversation-title-service.test.ts +14 -10
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +3 -5
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +51 -0
- package/src/__tests__/credential-record-retire.test.ts +171 -1
- package/src/__tests__/credential-routes.test.ts +212 -3
- package/src/__tests__/credential-security-invariants.test.ts +4 -2
- package/src/__tests__/cu-unified-flow.test.ts +8 -2
- package/src/__tests__/document-sync-tags.test.ts +25 -0
- package/src/__tests__/drain-kick-guard.test.ts +0 -2
- package/src/__tests__/drain-requeue-on-contention.test.ts +0 -6
- package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
- package/src/__tests__/git-porcelain.test.ts +47 -0
- package/src/__tests__/heartbeat-service.test.ts +56 -40
- package/src/__tests__/helpers/set-workspace-mcp.ts +11 -0
- package/src/__tests__/host-bash-proxy.test.ts +26 -45
- package/src/__tests__/host-browser-proxy.test.ts +6 -17
- package/src/__tests__/host-cu-proxy.test.ts +539 -0
- package/src/__tests__/host-file-proxy-targeted.test.ts +23 -0
- package/src/__tests__/host-file-proxy.test.ts +11 -23
- package/src/__tests__/host-transfer-proxy-targeted.test.ts +30 -9
- package/src/__tests__/list-messages-camera-frame.test.ts +121 -0
- package/src/__tests__/llm-catalog-parity.test.ts +25 -0
- package/src/__tests__/mcp-auth-routes.test.ts +6 -9
- package/src/__tests__/mcp-config-secret-boundary.test.ts +78 -136
- package/src/__tests__/mcp-health-check.test.ts +8 -11
- package/src/__tests__/mcp-list-plugin-servers.test.ts +8 -10
- package/src/__tests__/message-deleted-hook-dispatch.test.ts +119 -0
- package/src/__tests__/messages-after-tiebreaker.test.ts +91 -0
- package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
- package/src/__tests__/messaging-send-tool.test.ts +355 -548
- package/src/__tests__/model-intents.test.ts +3 -0
- package/src/__tests__/notification-vellum-adapter.test.ts +85 -2
- package/src/__tests__/oauth-commands-routes.test.ts +113 -0
- package/src/__tests__/oauth-provider-profiles.test.ts +18 -0
- package/src/__tests__/openai-provider.test.ts +1 -0
- package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
- package/src/__tests__/platform-callback-registration.test.ts +8 -16
- package/src/__tests__/platform-client-verify-credential.test.ts +2 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +14 -0
- package/src/__tests__/qdrant-manager.test.ts +132 -0
- package/src/__tests__/resolve-app.test.ts +135 -0
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +229 -6
- package/src/__tests__/script-proxy-certs.test.ts +35 -0
- package/src/__tests__/service-account-token.test.ts +247 -0
- package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
- package/src/__tests__/skill-feature-flags.test.ts +2 -2
- package/src/__tests__/skills.test.ts +9 -4
- package/src/__tests__/source-fingerprint.test.ts +184 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +19 -3
- package/src/__tests__/subagent-tools.test.ts +3 -2
- package/src/__tests__/telephony-tts-guard.test.ts +38 -3
- package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
- package/src/__tests__/unicode.test.ts +13 -0
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +4 -0
- package/src/__tests__/watch-retro-tool-availability.test.ts +8 -17
- package/src/__tests__/workspace-git-service.test.ts +26 -0
- package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +336 -0
- package/src/acp/__tests__/agent-process.test.ts +258 -4
- package/src/acp/__tests__/auth-required.test.ts +96 -0
- package/src/acp/__tests__/client-handler.test.ts +90 -2
- package/src/acp/__tests__/helpers/acp-model-option.ts +51 -0
- package/src/acp/__tests__/session-manager-persistence.test.ts +5 -1
- package/src/acp/__tests__/session-manager-resume.test.ts +270 -6
- package/src/acp/__tests__/session-manager-usage.test.ts +4 -2
- package/src/acp/__tests__/session-manager.test.ts +895 -4
- package/src/acp/agent-process.test.ts +4 -1
- package/src/acp/agent-process.ts +96 -10
- package/src/acp/auth-required.ts +50 -1
- package/src/acp/auto-install.test.ts +9 -7
- package/src/acp/client-handler.ts +16 -3
- package/src/acp/model-config.test.ts +166 -0
- package/src/acp/model-config.ts +138 -0
- package/src/acp/resolve-agent.test.ts +220 -6
- package/src/acp/resolve-agent.ts +88 -23
- package/src/acp/session-manager.test.ts +196 -3
- package/src/acp/session-manager.ts +441 -18
- package/src/acp/types.ts +50 -5
- package/src/agent/loop.ts +1 -0
- package/src/api/events/acp-session-model-update.test.ts +88 -0
- package/src/api/events/acp-session-model-update.ts +46 -0
- package/src/api/events/app-files-changed.ts +4 -3
- package/src/api/events/notification-intent.ts +2 -0
- package/src/api/events/user-message-echo.ts +7 -1
- package/src/api/index.ts +6 -0
- package/src/api/responses/conversation-message.ts +5 -0
- package/src/apps/app-store.ts +1 -1
- package/src/apps/resolve-app.ts +81 -0
- package/src/apps/source-fingerprint.ts +266 -0
- package/src/avatar/identity-avatar.ts +7 -2
- package/src/bundler/app-compiler.ts +15 -0
- package/src/calls/__tests__/barge-in-guard.test.ts +81 -0
- package/src/{__tests__ → calls/__tests__}/call-controller.test.ts +503 -82
- package/src/{__tests__ → calls/__tests__}/media-stream-audio-transcode.test.ts +1 -1
- package/src/{__tests__ → calls/__tests__}/media-stream-output.test.ts +37 -8
- package/src/{__tests__ → calls/__tests__}/media-stream-parser.test.ts +1 -1
- package/src/{__tests__ → calls/__tests__}/media-stream-server-integration.test.ts +150 -57
- package/src/{__tests__ → calls/__tests__}/media-stream-stt-session.test.ts +5 -5
- package/src/{__tests__ → calls/__tests__}/media-turn-detector.test.ts +1 -1
- package/src/{live-voice → calls}/__tests__/progress-narration.test.ts +4 -4
- package/src/{live-voice → calls}/__tests__/progress-phrases.test.ts +1 -1
- package/src/calls/__tests__/voice-control-protocol.test.ts +58 -25
- package/src/calls/__tests__/voice-leg-coordinator.test.ts +229 -0
- package/src/calls/__tests__/voice-progress-cadence.test.ts +274 -0
- package/src/{__tests__/voice-session-bridge.test.ts → calls/__tests__/voice-session-bridge-turn-flow.test.ts} +20 -12
- package/src/calls/__tests__/voice-session-bridge.test.ts +37 -1
- package/src/calls/__tests__/voice-triage-escalate.test.ts +105 -0
- package/src/calls/barge-in-guard.ts +118 -0
- package/src/calls/call-constants.ts +24 -4
- package/src/calls/call-controller.ts +499 -199
- package/src/calls/call-transport.ts +16 -0
- package/src/calls/media-stream-output.ts +32 -0
- package/src/calls/media-stream-server.ts +82 -30
- package/src/calls/media-stream-stt-session.ts +15 -0
- package/src/{live-voice → calls}/progress-narration.ts +2 -2
- package/src/calls/resolve-call-tts-provider.ts +33 -2
- package/src/calls/tts-call-strategy.ts +9 -5
- package/src/calls/types.ts +2 -0
- package/src/calls/voice-control-protocol.ts +53 -21
- package/src/calls/voice-leg-coordinator.ts +189 -0
- package/src/calls/voice-progress-cadence.ts +423 -0
- package/src/calls/voice-session-bridge.ts +59 -46
- package/src/calls/voice-triage-escalate.ts +175 -56
- package/src/cli/commands/__tests__/apps.test.ts +245 -0
- package/src/cli/commands/__tests__/bash.test.ts +53 -0
- package/src/cli/commands/__tests__/roadmap.help.test.ts +19 -0
- package/src/cli/commands/apps.help.ts +67 -13
- package/src/cli/commands/apps.ts +183 -0
- package/src/cli/commands/bash.help.ts +8 -13
- package/src/cli/commands/bash.ts +7 -25
- package/src/cli/commands/channels/__tests__/send.test.ts +218 -0
- package/src/cli/commands/channels/index.help.ts +70 -2
- package/src/cli/commands/channels/index.ts +16 -3
- package/src/cli/commands/channels/send.ts +128 -0
- package/src/cli/commands/credentials.help.ts +6 -2
- package/src/cli/commands/credentials.ts +2 -2
- package/src/cli/commands/mcp.help.ts +8 -8
- package/src/cli/commands/mcp.ts +1 -1
- package/src/cli/commands/roadmap.help.ts +5 -1
- package/src/cli/commands/roadmap.ts +1 -1
- package/src/cli/lib/bundled-marketplace.json +103 -90
- package/src/config/__tests__/default-profile-catalog.test.ts +22 -0
- package/src/config/__tests__/memory-retrospective-schema.test.ts +15 -1
- package/src/config/__tests__/platform-identity.test.ts +255 -0
- package/src/config/__tests__/platform-rehydration.test.ts +99 -9
- package/src/config/acp-agent-write.ts +43 -0
- package/src/config/acp-defaults.test.ts +13 -3
- package/src/config/acp-defaults.ts +23 -6
- package/src/config/acp-schema.test.ts +81 -0
- package/src/config/acp-schema.ts +30 -2
- package/src/config/bundled-skills/acp/SKILL.md +22 -14
- package/src/config/bundled-skills/acp/TOOLS.json +5 -1
- package/src/config/bundled-skills/app-builder/SKILL.md +12 -9
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
- package/src/config/bundled-skills/computer-use/SKILL.md +20 -2
- package/src/config/bundled-skills/computer-use/TOOLS.json +99 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-sequence.ts +12 -0
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +2 -1
- package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +2 -1
- package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -1
- package/src/config/bundled-skills/messaging/SKILL.md +3 -3
- package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +67 -139
- package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -1
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -1
- package/src/config/bundled-skills/schedule/SKILL.md +3 -4
- package/src/config/bundled-skills/screen-annotation/SKILL.md +8 -1
- package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +4 -1
- package/src/config/bundled-skills/skill-management/TOOLS.json +6 -2
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +5 -2
- package/src/config/default-profile-catalog.ts +1 -1
- package/src/config/env.ts +2 -2
- package/src/config/feature-flag-registry.json +24 -12
- package/src/config/loader.ts +1 -0
- package/src/config/memory-v3-gate.test.ts +31 -0
- package/src/config/memory-v3-gate.ts +20 -16
- package/src/config/platform-identity.ts +201 -0
- package/src/config/platform-rehydration.ts +11 -27
- package/src/config/schema.ts +2 -2
- package/src/config/schemas/__tests__/live-voice.test.ts +8 -158
- package/src/config/schemas/__tests__/voice.test.ts +148 -0
- package/src/config/schemas/calls.ts +1 -28
- package/src/config/schemas/live-voice.ts +1 -173
- package/src/config/schemas/mcp.ts +1 -1
- package/src/config/schemas/memory-retrospective.ts +10 -0
- package/src/config/schemas/services.ts +8 -0
- package/src/config/schemas/skills.ts +0 -14
- package/src/config/schemas/voice.ts +150 -0
- package/src/credential-execution/process-manager.test.ts +18 -19
- package/src/credential-health/credential-health-service.ts +11 -17
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -121
- package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +4 -1
- package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +133 -0
- package/src/daemon/app-change-notify.ts +45 -0
- package/src/daemon/config-watcher.ts +13 -194
- package/src/daemon/conversation-error.ts +3 -2
- package/src/daemon/conversation-media-retry.ts +2 -1
- package/src/daemon/conversation-process.ts +35 -64
- package/src/daemon/conversation-runtime-assembly.ts +2 -1
- package/src/daemon/conversation-skill-tools.ts +13 -31
- package/src/daemon/conversation-store.ts +1 -1
- package/src/daemon/conversation-surfaces.ts +15 -9
- package/src/daemon/conversation-tool-setup.ts +37 -43
- package/src/daemon/conversation.ts +0 -3
- package/src/daemon/dictation-profile-store.ts +2 -1
- package/src/daemon/handlers/shared.ts +2 -1
- package/src/daemon/handlers/skills.ts +6 -3
- package/src/daemon/host-bash-proxy.ts +8 -12
- package/src/daemon/host-browser-proxy.ts +15 -1
- package/src/daemon/host-cu-proxy.ts +186 -19
- package/src/daemon/host-file-proxy.ts +8 -10
- package/src/daemon/host-proxy-preactivation.ts +2 -1
- package/src/daemon/host-transfer-proxy.ts +14 -18
- package/src/daemon/lifecycle.ts +0 -5
- package/src/daemon/mcp-reload-service.ts +5 -5
- package/src/daemon/message-types/sync.ts +1 -0
- package/src/daemon/orphan-reaper.ts +1 -1
- package/src/daemon/providers-setup.ts +1 -1
- package/src/daemon/reaction-history-render.ts +2 -1
- package/src/daemon/shutdown-handlers.ts +1 -9
- package/src/daemon/skill-memory-refresh.ts +64 -0
- package/src/daemon/tool-side-effects.ts +7 -18
- package/src/desktop/__tests__/fake-desktop.ts +122 -0
- package/src/desktop/desktop-chrome-policy.test.ts +79 -0
- package/src/desktop/desktop-chrome-policy.ts +47 -0
- package/src/desktop/desktop-chrome-session.ts +37 -0
- package/src/desktop/desktop-dependencies.test.ts +102 -0
- package/src/desktop/desktop-dependencies.ts +308 -0
- package/src/desktop/desktop-feature.test.ts +18 -0
- package/src/desktop/desktop-feature.ts +19 -0
- package/src/desktop/desktop-panel-config.test.ts +135 -0
- package/src/desktop/desktop-panel-config.ts +149 -0
- package/src/desktop/desktop-session-manager.test.ts +730 -0
- package/src/desktop/desktop-session-manager.ts +850 -0
- package/src/desktop/desktop-stream-bridge.test.ts +395 -0
- package/src/desktop/desktop-stream-bridge.ts +214 -0
- package/src/desktop/desktop-wallpaper.test.ts +191 -0
- package/src/desktop/desktop-wallpaper.ts +113 -0
- package/src/desktop/desktop-window-drag.ts +177 -0
- package/src/desktop/desktop-window-manager-config.test.ts +230 -0
- package/src/desktop/desktop-window-manager-config.ts +238 -0
- package/src/desktop/desktop-window-theme.ts +170 -0
- package/src/heartbeat/heartbeat-service.ts +21 -3
- package/src/home/__tests__/feed-writer.test.ts +22 -0
- package/src/home/feed-writer.ts +11 -2
- package/src/hooks/types.ts +33 -0
- package/src/i18n/AGENTS.md +22 -0
- package/src/i18n/__tests__/i18n.test.ts +108 -0
- package/src/i18n/index.ts +16 -0
- package/src/i18n/locales.ts +93 -0
- package/src/i18n/messages.ts +61 -0
- package/src/i18n/title.ts +25 -0
- package/src/inbound/platform-callback-registration.ts +4 -5
- package/src/live-voice/__tests__/live-voice-metrics.test.ts +0 -3
- package/src/live-voice/__tests__/live-voice-progress.test.ts +15 -15
- package/src/live-voice/__tests__/live-voice-sight-frame-echo.test.ts +238 -0
- package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
- package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +13 -69
- package/src/live-voice/live-voice-photo.ts +25 -6
- package/src/live-voice/live-voice-session.ts +213 -693
- package/src/live-voice/protocol.ts +0 -2
- package/src/mcp/__tests__/effective-config.test.ts +2 -2
- package/src/mcp/__tests__/workspace-mcp-config.test.ts +135 -0
- package/src/mcp/effective-config.ts +3 -3
- package/src/mcp/mcp-header-store.ts +29 -45
- package/src/mcp/spec-schema.ts +111 -0
- package/src/mcp/startup.ts +8 -4
- package/src/mcp/workspace-mcp-config.ts +188 -0
- package/src/messaging/provider.ts +10 -3
- package/src/messaging/providers/__tests__/proactive-address.test.ts +95 -0
- package/src/messaging/providers/callback-routing.ts +18 -0
- package/src/messaging/providers/channel-transport.ts +56 -0
- package/src/messaging/providers/discord/transport.ts +17 -0
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +6 -17
- package/src/messaging/providers/slack/adapter.ts +7 -44
- package/src/messaging/providers/slack/transport.ts +16 -0
- package/src/messaging/providers/telegram-bot/adapter.ts +1 -48
- package/src/messaging/providers/telegram-bot/client.ts +0 -26
- package/src/messaging/providers/telegram-bot/transport.ts +20 -0
- package/src/messaging/providers/whatsapp/adapter.ts +1 -48
- package/src/messaging/providers/whatsapp/transport.ts +18 -0
- package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +8 -0
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +55 -0
- package/src/notifications/__tests__/guardian-feed-projection.test.ts +158 -2
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +153 -3
- package/src/notifications/__tests__/schedule-result-producer.test.ts +99 -3
- package/src/notifications/adapters/macos.ts +3 -0
- package/src/notifications/assistant-reply-producer.ts +8 -3
- package/src/notifications/conversation-pairing.ts +36 -16
- package/src/notifications/delivered-post-record.ts +11 -1
- package/src/notifications/guardian-feed-projection.ts +125 -5
- package/src/notifications/home-feed-side-effect.ts +55 -7
- package/src/notifications/schedule-result-producer.ts +66 -22
- package/src/oauth/AGENTS.md +2 -0
- package/src/oauth/__tests__/seed-providers-managed.test.ts +54 -0
- package/src/oauth/byo-connection.test.ts +105 -0
- package/src/oauth/byo-connection.ts +36 -6
- package/src/oauth/connection-resolver.test.ts +73 -0
- package/src/oauth/connection-resolver.ts +95 -2
- package/src/oauth/scope-utils.ts +24 -0
- package/src/oauth/seed-providers.ts +204 -25
- package/src/outbound-proxy/certs.ts +3 -5
- package/src/permissions/checker.test.ts +11 -6
- package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
- package/src/persistence/conversation-crud.ts +71 -63
- package/src/persistence/conversation-lineage.ts +12 -4
- package/src/persistence/conversation-queries.ts +2 -2
- package/src/persistence/conversation-title-placeholders.ts +23 -0
- package/src/persistence/conversation-title-service.ts +9 -26
- package/src/persistence/embeddings/qdrant-manager.ts +163 -18
- package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
- package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
- package/src/persistence/message-cursor.ts +78 -0
- package/src/persistence/message-reads.ts +30 -0
- package/src/persistence/migrations/157-invite-contact-id.ts +18 -6
- package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +149 -0
- package/src/persistence/schema/memory-core.ts +5 -0
- package/src/platform/client.test.ts +4 -2
- package/src/platform/client.ts +2 -10
- package/src/plugin-api/constants.ts +2 -0
- package/src/plugin-api/index.ts +1 -0
- package/src/plugin-api/types.ts +2 -0
- package/src/plugins/defaults/index.ts +2 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +46 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +118 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +83 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +9 -5
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +20 -10
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +5 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +219 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +31 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +63 -0
- package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +4 -0
- package/src/plugins/defaults/memory/context-search/agent-runner.ts +3 -2
- package/src/plugins/defaults/memory/context-search/format.ts +3 -2
- package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +6 -3
- package/src/plugins/defaults/memory/context-search/sources/workspace.ts +3 -2
- package/src/plugins/defaults/memory/graph/capability-seed.ts +6 -7
- package/src/plugins/defaults/memory/hooks/init.ts +10 -0
- package/src/plugins/defaults/memory/hooks/message-deleted.ts +21 -0
- package/src/plugins/defaults/memory/hooks/shutdown.ts +2 -0
- package/src/plugins/defaults/memory/host-utils.ts +10 -0
- package/src/plugins/defaults/memory/jobs-worker.ts +13 -0
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +34 -60
- package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +126 -0
- package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +79 -0
- package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +28 -0
- package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +3 -5
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +12 -3
- package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
- package/src/plugins/defaults/memory/memory-retrospective-state.ts +312 -71
- package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +2 -1
- package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +5 -3
- package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +9 -0
- package/src/plugins/defaults/memory/startup.ts +28 -2
- package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +7 -9
- package/src/plugins/defaults/memory/substrate/page-index.ts +6 -1
- package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
- package/src/plugins/defaults/memory/v1/graph/consolidation.ts +4 -2
- package/src/plugins/defaults/memory/v1/graph/extraction.ts +4 -2
- package/src/plugins/defaults/memory/v1/graph/retriever.ts +4 -1
- package/src/plugins/defaults/memory/v2/reranker.ts +6 -1
- package/src/plugins/defaults/memory/v3/__tests__/card.test.ts +24 -0
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +125 -0
- package/src/plugins/defaults/memory/v3/__tests__/sections.test.ts +26 -0
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +30 -0
- package/src/plugins/defaults/memory/v3/card.ts +3 -2
- package/src/plugins/defaults/memory/v3/pool-select.ts +5 -3
- package/src/plugins/defaults/memory/v3/sections.ts +9 -3
- package/src/plugins/defaults/memory/v3/shadow-plugin.ts +5 -2
- package/src/plugins/defaults/memory/worker.ts +6 -0
- package/src/plugins/mcp-servers.ts +15 -75
- package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
- package/src/providers/__tests__/retry-callsite.test.ts +41 -0
- package/src/providers/__tests__/retry-outbound-sanitize.test.ts +176 -0
- package/src/providers/anthropic/client.ts +0 -47
- package/src/providers/gemini/client.ts +9 -23
- package/src/providers/gemini-thought-signature.test.ts +63 -0
- package/src/providers/gemini-thought-signature.ts +44 -0
- package/src/providers/inference/__tests__/endpoint-probe.test.ts +42 -3
- package/src/providers/inference/auth.ts +2 -3
- package/src/providers/inference/connection-availability.ts +27 -8
- package/src/providers/inference/endpoint-probe.ts +10 -1
- package/src/providers/inference/resolve-auth.ts +22 -4
- package/src/providers/inference/service-account-token.ts +248 -0
- package/src/providers/model-catalog.ts +34 -0
- package/src/providers/model-intents.ts +10 -5
- package/src/providers/openai/__tests__/chat-completions-output-token-limit.test.ts +141 -0
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +146 -18
- package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +613 -0
- package/src/providers/openai/__tests__/google-thought-signature.test.ts +235 -0
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +83 -1
- package/src/providers/openai/chat-completions-provider.ts +212 -79
- package/src/providers/openai/codex-models.ts +0 -4
- package/src/providers/openai/google-thought-signature.ts +320 -0
- package/src/providers/opencode/client.test.ts +73 -4
- package/src/providers/opencode/client.ts +60 -3
- package/src/providers/openrouter/client.ts +20 -0
- package/src/providers/outbound-request-sanitize.ts +96 -0
- package/src/providers/retry.ts +30 -19
- package/src/providers/unparseable-tool-args.ts +5 -1
- package/src/runtime/AGENTS.md +3 -1
- package/src/runtime/__tests__/channel-send.test.ts +443 -0
- package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +89 -0
- package/src/runtime/__tests__/host-observe.test.ts +40 -1
- package/src/runtime/__tests__/live-voice-upgrade.test.ts +58 -0
- package/src/runtime/__tests__/runtime-ws-test-utils.ts +72 -0
- package/src/runtime/__tests__/watch-stream-upgrade.test.ts +59 -0
- package/src/runtime/agent-wake.ts +2 -1
- package/src/runtime/assistant-event-hub.ts +21 -3
- package/src/runtime/auth/same-actor.ts +38 -3
- package/src/runtime/channel-send.ts +316 -0
- package/src/runtime/host-observe.ts +11 -1
- package/src/runtime/http-server.ts +160 -151
- package/src/runtime/routes/__tests__/acp-routes.test.ts +159 -4
- package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +205 -0
- package/src/runtime/routes/__tests__/channel-send-routes.test.ts +127 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +108 -1
- package/src/runtime/routes/__tests__/default-provider-routes.test.ts +2 -2
- package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +107 -0
- package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +16 -12
- package/src/runtime/routes/acp-routes.test.ts +15 -0
- package/src/runtime/routes/acp-routes.ts +72 -9
- package/src/runtime/routes/app-management-routes.ts +73 -0
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/channel-send-routes.ts +85 -0
- package/src/runtime/routes/conversation-list-routes.ts +14 -3
- package/src/runtime/routes/conversation-query-routes.ts +40 -0
- package/src/runtime/routes/conversation-routes.ts +16 -3
- package/src/runtime/routes/credential-routes.ts +19 -10
- package/src/runtime/routes/desktop-setup-routes.test.ts +74 -0
- package/src/runtime/routes/desktop-setup-routes.ts +35 -0
- package/src/runtime/routes/diagnostics-routes.ts +61 -4
- package/src/runtime/routes/events-routes.ts +4 -0
- package/src/runtime/routes/host-cu-routes.ts +6 -0
- package/src/runtime/routes/index.ts +4 -2
- package/src/runtime/routes/log-export-routes.ts +2 -0
- package/src/runtime/routes/mcp-auth-routes.ts +39 -47
- package/src/runtime/routes/migration-routes.ts +6 -20
- package/src/runtime/routes/oauth-commands-routes.ts +36 -0
- package/src/runtime/routes/platform-routes.ts +18 -15
- package/src/runtime/routes/workspace-routes.test.ts +55 -26
- package/src/runtime/routes/workspace-routes.ts +46 -63
- package/src/runtime/routes/workspace-tree-walk.test.ts +244 -0
- package/src/runtime/routes/workspace-tree-walk.ts +240 -0
- package/src/runtime/routes/workspace-utils.ts +17 -0
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +28 -0
- package/src/runtime/services/conversation-serializer.ts +16 -3
- package/src/runtime/sync/resource-sync-events.ts +2 -1
- package/src/schedule/run-script.ts +2 -1
- package/src/schedule/schedule-store.ts +9 -2
- package/src/schedule/worker.ts +1 -1
- package/src/security/__tests__/ces-http-record-backend.test.ts +137 -0
- package/src/security/ces-http-record-backend.ts +120 -0
- package/src/security/ces-rpc-record-backend.ts +7 -0
- package/src/security/untrusted-content.ts +3 -1
- package/src/skills/__tests__/workspace-skill-md-signature.test.ts +60 -0
- package/src/skills/workspace-skill-md-signature.ts +50 -0
- package/src/subagent/consult-context.ts +12 -25
- package/src/tools/__tests__/side-effect-abort-guard.test.ts +1 -0
- package/src/tools/acp/list-agents.test.ts +3 -1
- package/src/tools/acp/spawn.test.ts +146 -11
- package/src/tools/acp/spawn.ts +21 -3
- package/src/tools/acp/status.test.ts +94 -0
- package/src/tools/acp/status.ts +40 -3
- package/src/tools/app-control/skill-proxy-bridge.ts +2 -1
- package/src/tools/browser/browser-execution.ts +1 -1
- package/src/tools/browser/cdp-client/accessibility-snapshot.ts +9 -3
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +2 -1
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +3 -2
- package/src/tools/browser/network-recorder.ts +2 -1
- package/src/tools/capability-offer.test.ts +22 -2
- package/src/tools/capability-offer.ts +16 -1
- package/src/tools/computer-use/definitions.ts +121 -2
- package/src/tools/computer-use/skill-proxy-bridge.ts +2 -1
- package/src/tools/credentials/metadata-store.ts +100 -0
- package/src/tools/filesystem/search.ts +2 -1
- package/src/tools/host-filesystem/edit.test.ts +2 -1
- package/src/tools/host-filesystem/edit.ts +2 -2
- package/src/tools/host-filesystem/read.test.ts +2 -1
- package/src/tools/host-filesystem/read.ts +2 -2
- package/src/tools/host-filesystem/transfer.test.ts +2 -1
- package/src/tools/host-filesystem/transfer.ts +2 -2
- package/src/tools/host-filesystem/write.test.ts +2 -1
- package/src/tools/host-filesystem/write.ts +2 -2
- package/src/tools/host-terminal/host-shell.test.ts +16 -0
- package/src/tools/host-terminal/host-shell.ts +4 -20
- package/src/tools/network/web-search.ts +6 -1
- package/src/tools/policy-context.ts +6 -7
- package/src/tools/skills/scaffold-managed.ts +61 -3
- package/src/tools/subagent/spawn.ts +5 -8
- package/src/tools/terminal/__tests__/sanitized-bash.test.ts +57 -0
- package/src/tools/terminal/sanitized-bash.ts +105 -0
- package/src/tools/watch/watch-retro-report.ts +0 -8
- package/src/types/host-capabilities.ts +5 -3
- package/src/util/__tests__/language-subtag.test.ts +23 -1
- package/src/util/ca-bundle.ts +13 -0
- package/src/util/language-subtag.ts +16 -0
- package/src/util/platform.ts +8 -0
- package/src/util/unicode.ts +15 -1
- package/src/watch/watch-timeline.ts +2 -2
- package/src/workflows/run-manager.ts +2 -1
- package/src/workspace/git-porcelain.ts +66 -0
- package/src/workspace/git-service.ts +6 -55
- package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +258 -0
- package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +78 -0
- package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +214 -0
- package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +102 -0
- package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +199 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/__tests__/app-source-watcher.test.ts +0 -218
- package/src/__tests__/config-watcher-skill-reseed.test.ts +0 -359
- package/src/__tests__/speaker-identification.test.ts +0 -56
- package/src/calls/speaker-identification.ts +0 -233
- package/src/daemon/app-source-watcher.ts +0 -218
- package/src/messaging/providers/whatsapp/client.ts +0 -29
- package/src/runtime/routes/debug-bash-routes.ts +0 -173
- package/src/util/fs-watcher-error.ts +0 -36
- /package/src/{live-voice → calls}/progress-phrases.ts +0 -0
|
@@ -211,8 +211,8 @@ describe("isTextMimeType", () => {
|
|
|
211
211
|
describe("GET /v1/workspace/tree", () => {
|
|
212
212
|
const { handler } = getRoute("workspace_tree");
|
|
213
213
|
|
|
214
|
-
test("root listing returns entries", () => {
|
|
215
|
-
const result = handler({ queryParams: {} }) as {
|
|
214
|
+
test("root listing returns entries", async () => {
|
|
215
|
+
const result = (await handler({ queryParams: {} })) as {
|
|
216
216
|
path: string;
|
|
217
217
|
entries: Array<{ name: string; type: string }>;
|
|
218
218
|
};
|
|
@@ -222,8 +222,8 @@ describe("GET /v1/workspace/tree", () => {
|
|
|
222
222
|
expect(names).toContain("subdir");
|
|
223
223
|
});
|
|
224
224
|
|
|
225
|
-
test("subdirectory listing returns child entries", () => {
|
|
226
|
-
const result = handler({ queryParams: { path: "subdir" } }) as {
|
|
225
|
+
test("subdirectory listing returns child entries", async () => {
|
|
226
|
+
const result = (await handler({ queryParams: { path: "subdir" } })) as {
|
|
227
227
|
path: string;
|
|
228
228
|
entries: Array<{ name: string; type: string }>;
|
|
229
229
|
};
|
|
@@ -232,20 +232,20 @@ describe("GET /v1/workspace/tree", () => {
|
|
|
232
232
|
expect(result.entries[0].type).toBe("file");
|
|
233
233
|
});
|
|
234
234
|
|
|
235
|
-
test("non-existent directory throws NotFoundError", () => {
|
|
236
|
-
expect(
|
|
235
|
+
test("non-existent directory throws NotFoundError", async () => {
|
|
236
|
+
await expect(handler({ queryParams: { path: "nope" } })).rejects.toThrow(
|
|
237
237
|
NotFoundError,
|
|
238
238
|
);
|
|
239
239
|
});
|
|
240
240
|
|
|
241
|
-
test("path traversal attempt throws BadRequestError", () => {
|
|
242
|
-
expect(
|
|
243
|
-
|
|
244
|
-
);
|
|
241
|
+
test("path traversal attempt throws BadRequestError", async () => {
|
|
242
|
+
await expect(
|
|
243
|
+
handler({ queryParams: { path: "../../etc" } }),
|
|
244
|
+
).rejects.toThrow(BadRequestError);
|
|
245
245
|
});
|
|
246
246
|
|
|
247
|
-
test("entries have correct type field", () => {
|
|
248
|
-
const result = handler({ queryParams: {} }) as {
|
|
247
|
+
test("entries have correct type field", async () => {
|
|
248
|
+
const result = (await handler({ queryParams: {} })) as {
|
|
249
249
|
entries: Array<{ name: string; type: "file" | "directory" }>;
|
|
250
250
|
};
|
|
251
251
|
const subdirEntry = result.entries.find((e) => e.name === "subdir");
|
|
@@ -254,8 +254,8 @@ describe("GET /v1/workspace/tree", () => {
|
|
|
254
254
|
expect(fileEntry?.type).toBe("file");
|
|
255
255
|
});
|
|
256
256
|
|
|
257
|
-
test("dotfiles and dot-directories are excluded", () => {
|
|
258
|
-
const result = handler({ queryParams: {} }) as {
|
|
257
|
+
test("dotfiles and dot-directories are excluded", async () => {
|
|
258
|
+
const result = (await handler({ queryParams: {} })) as {
|
|
259
259
|
entries: Array<{ name: string }>;
|
|
260
260
|
};
|
|
261
261
|
const names = result.entries.map((e) => e.name);
|
|
@@ -263,8 +263,8 @@ describe("GET /v1/workspace/tree", () => {
|
|
|
263
263
|
expect(names).not.toContain(".hidden");
|
|
264
264
|
});
|
|
265
265
|
|
|
266
|
-
test("directory entries have null size and mimeType", () => {
|
|
267
|
-
const result = handler({ queryParams: {} }) as {
|
|
266
|
+
test("directory entries have null size and mimeType", async () => {
|
|
267
|
+
const result = (await handler({ queryParams: {} })) as {
|
|
268
268
|
entries: Array<{
|
|
269
269
|
name: string;
|
|
270
270
|
type: string;
|
|
@@ -278,8 +278,8 @@ describe("GET /v1/workspace/tree", () => {
|
|
|
278
278
|
expect(dirEntry!.mimeType).toBeNull();
|
|
279
279
|
});
|
|
280
280
|
|
|
281
|
-
test("directories sorted before files", () => {
|
|
282
|
-
const result = handler({ queryParams: {} }) as {
|
|
281
|
+
test("directories sorted before files", async () => {
|
|
282
|
+
const result = (await handler({ queryParams: {} })) as {
|
|
283
283
|
entries: Array<{ type: string }>;
|
|
284
284
|
};
|
|
285
285
|
const firstFileIdx = result.entries.findIndex((e) => e.type === "file");
|
|
@@ -295,8 +295,10 @@ describe("GET /v1/workspace/tree", () => {
|
|
|
295
295
|
}
|
|
296
296
|
});
|
|
297
297
|
|
|
298
|
-
test("includeDirSizes=true returns recursive byte size for directories", () => {
|
|
299
|
-
const result = handler({
|
|
298
|
+
test("includeDirSizes=true returns recursive byte size for directories", async () => {
|
|
299
|
+
const result = (await handler({
|
|
300
|
+
queryParams: { includeDirSizes: "true" },
|
|
301
|
+
})) as {
|
|
300
302
|
entries: Array<{ name: string; type: string; size: number | null }>;
|
|
301
303
|
};
|
|
302
304
|
const subdirEntry = result.entries.find((e) => e.name === "subdir");
|
|
@@ -307,8 +309,10 @@ describe("GET /v1/workspace/tree", () => {
|
|
|
307
309
|
expect(subdirEntry!.size).toBe("nested content".length);
|
|
308
310
|
});
|
|
309
311
|
|
|
310
|
-
test("includeDirSizes=true preserves accurate file sizes", () => {
|
|
311
|
-
const result = handler({
|
|
312
|
+
test("includeDirSizes=true preserves accurate file sizes", async () => {
|
|
313
|
+
const result = (await handler({
|
|
314
|
+
queryParams: { includeDirSizes: "true" },
|
|
315
|
+
})) as {
|
|
312
316
|
entries: Array<{ name: string; type: string; size: number | null }>;
|
|
313
317
|
};
|
|
314
318
|
const fileEntry = result.entries.find((e) => e.name === "hello.txt");
|
|
@@ -316,8 +320,8 @@ describe("GET /v1/workspace/tree", () => {
|
|
|
316
320
|
expect(fileEntry!.size).toBe("Hello, world!".length);
|
|
317
321
|
});
|
|
318
322
|
|
|
319
|
-
test("includeDirSizes omitted preserves null size for directories", () => {
|
|
320
|
-
const result = handler({ queryParams: {} }) as {
|
|
323
|
+
test("includeDirSizes omitted preserves null size for directories", async () => {
|
|
324
|
+
const result = (await handler({ queryParams: {} })) as {
|
|
321
325
|
entries: Array<{ type: string; size: number | null }>;
|
|
322
326
|
};
|
|
323
327
|
const dirEntry = result.entries.find((e) => e.type === "directory");
|
|
@@ -325,11 +329,36 @@ describe("GET /v1/workspace/tree", () => {
|
|
|
325
329
|
expect(dirEntry!.size).toBeNull();
|
|
326
330
|
});
|
|
327
331
|
|
|
328
|
-
test("
|
|
332
|
+
test("recursive=true lists nested entries and the bound fields", async () => {
|
|
333
|
+
const result = (await handler({ queryParams: { recursive: "true" } })) as {
|
|
334
|
+
entries: Array<{ path: string }>;
|
|
335
|
+
truncated: boolean;
|
|
336
|
+
skipped: string[];
|
|
337
|
+
};
|
|
338
|
+
expect(result.entries.map((e) => e.path)).toContain("subdir/nested.txt");
|
|
339
|
+
expect(result.truncated).toBe(false);
|
|
340
|
+
expect(Array.isArray(result.skipped)).toBe(true);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
test("a non-recursive listing carries the same fields, unset", async () => {
|
|
344
|
+
const result = (await handler({ queryParams: {} })) as {
|
|
345
|
+
entries: Array<{ path: string }>;
|
|
346
|
+
truncated: boolean;
|
|
347
|
+
skipped: string[];
|
|
348
|
+
};
|
|
349
|
+
expect(result.entries.map((e) => e.path)).not.toContain(
|
|
350
|
+
"subdir/nested.txt",
|
|
351
|
+
);
|
|
352
|
+
expect(result).toMatchObject({ truncated: false, skipped: [] });
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
test("includeDirSizes=true returns 0 for empty directory", async () => {
|
|
329
356
|
const emptyDir = join(testWorkspaceDir, "empty-for-size");
|
|
330
357
|
mkdirSync(emptyDir, { recursive: true });
|
|
331
358
|
|
|
332
|
-
const result = handler({
|
|
359
|
+
const result = (await handler({
|
|
360
|
+
queryParams: { includeDirSizes: "true" },
|
|
361
|
+
})) as {
|
|
333
362
|
entries: Array<{ name: string; size: number | null }>;
|
|
334
363
|
};
|
|
335
364
|
const emptyEntry = result.entries.find((e) => e.name === "empty-for-size");
|
|
@@ -30,24 +30,15 @@ import {
|
|
|
30
30
|
} from "./errors.js";
|
|
31
31
|
import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
|
|
32
32
|
import { RouteResponse } from "./types.js";
|
|
33
|
+
import { walkWorkspaceTree } from "./workspace-tree-walk.js";
|
|
33
34
|
import {
|
|
34
35
|
isTextMimeType,
|
|
35
36
|
MAX_INLINE_TEXT_SIZE,
|
|
36
37
|
resolveWorkspacePath,
|
|
38
|
+
workspaceEntrySchema,
|
|
37
39
|
writeWorkspaceFile,
|
|
38
40
|
} from "./workspace-utils.js";
|
|
39
41
|
|
|
40
|
-
const workspaceTreeEntrySchema = z.object({
|
|
41
|
-
name: z.string(),
|
|
42
|
-
path: z.string(),
|
|
43
|
-
type: z.enum(["file", "directory"]),
|
|
44
|
-
size: z.number().nullable(),
|
|
45
|
-
mimeType: z.string().nullable(),
|
|
46
|
-
modifiedAt: z.string(),
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
type TreeEntry = z.infer<typeof workspaceTreeEntrySchema>;
|
|
50
|
-
|
|
51
42
|
// Recursive directory sizing walks the filesystem synchronously, so it is
|
|
52
43
|
// bounded by two caps that work together:
|
|
53
44
|
//
|
|
@@ -175,10 +166,11 @@ function publishSoundsConfigUpdatedForPaths(
|
|
|
175
166
|
// GET /v1/workspace/tree — list directory contents
|
|
176
167
|
// ---------------------------------------------------------------------------
|
|
177
168
|
|
|
178
|
-
function handleWorkspaceTree({ queryParams }: RouteHandlerArgs) {
|
|
169
|
+
async function handleWorkspaceTree({ queryParams }: RouteHandlerArgs) {
|
|
179
170
|
const requestedPath = queryParams?.path ?? "";
|
|
180
171
|
const showHidden = queryParams?.showHidden === "true";
|
|
181
172
|
const includeDirSizes = queryParams?.includeDirSizes === "true";
|
|
173
|
+
const recursive = queryParams?.recursive === "true";
|
|
182
174
|
const resolved = resolveWorkspacePath(requestedPath, {
|
|
183
175
|
allowHidden: showHidden,
|
|
184
176
|
});
|
|
@@ -186,62 +178,36 @@ function handleWorkspaceTree({ queryParams }: RouteHandlerArgs) {
|
|
|
186
178
|
throw new BadRequestError("Invalid path");
|
|
187
179
|
}
|
|
188
180
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
// subtree can't starve its siblings, while the shared remainder still caps
|
|
196
|
-
// total synchronous traversal for the whole listing.
|
|
197
|
-
const listingBudget: DirSizeBudget | undefined = includeDirSizes
|
|
181
|
+
// Listing-wide budget, only constructed when sizes are requested. Each
|
|
182
|
+
// directory draws a per-directory allotment from it so one oversized
|
|
183
|
+
// subtree can't starve its siblings, while the shared remainder still caps
|
|
184
|
+
// total synchronous traversal for the whole listing.
|
|
185
|
+
const listingBudget: DirSizeBudget | undefined =
|
|
186
|
+
includeDirSizes && !recursive
|
|
198
187
|
? { remaining: DIR_SIZE_TOTAL_ENTRY_BUDGET }
|
|
199
188
|
: undefined;
|
|
200
189
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
stats = statSync(fullPath);
|
|
212
|
-
} catch {
|
|
213
|
-
continue;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const isDir = stats.isDirectory();
|
|
217
|
-
const relativePath = fullPath.slice(workspaceDir.length + 1);
|
|
218
|
-
|
|
219
|
-
const dirSize =
|
|
220
|
-
isDir && listingBudget
|
|
221
|
-
? computeDirSizeWithinListing(fullPath, listingBudget)
|
|
222
|
-
: null;
|
|
223
|
-
|
|
224
|
-
entries.push({
|
|
225
|
-
name: entry.name,
|
|
226
|
-
path: relativePath,
|
|
227
|
-
type: isDir ? "directory" : "file",
|
|
228
|
-
size: isDir ? dirSize : stats.size,
|
|
229
|
-
mimeType: isDir ? null : Bun.file(fullPath).type,
|
|
230
|
-
modifiedAt: stats.mtime.toISOString(),
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
entries.sort((a, b) => {
|
|
235
|
-
if (a.type !== b.type) {
|
|
236
|
-
return a.type === "directory" ? -1 : 1;
|
|
237
|
-
}
|
|
238
|
-
return a.name.localeCompare(b.name);
|
|
190
|
+
let walk: Awaited<ReturnType<typeof walkWorkspaceTree>>;
|
|
191
|
+
try {
|
|
192
|
+
walk = await walkWorkspaceTree({
|
|
193
|
+
rootPath: resolved,
|
|
194
|
+
workspaceDir: getWorkspaceDir(),
|
|
195
|
+
showHidden,
|
|
196
|
+
recursive,
|
|
197
|
+
directorySize: listingBudget
|
|
198
|
+
? (absPath) => computeDirSizeWithinListing(absPath, listingBudget)
|
|
199
|
+
: undefined,
|
|
239
200
|
});
|
|
240
|
-
|
|
241
|
-
return { path: requestedPath, entries };
|
|
242
201
|
} catch {
|
|
243
202
|
throw new NotFoundError("Directory not found");
|
|
244
203
|
}
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
path: requestedPath,
|
|
207
|
+
entries: walk.entries,
|
|
208
|
+
truncated: walk.truncated,
|
|
209
|
+
skipped: walk.skipped,
|
|
210
|
+
};
|
|
245
211
|
}
|
|
246
212
|
|
|
247
213
|
// ---------------------------------------------------------------------------
|
|
@@ -569,14 +535,31 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
569
535
|
{
|
|
570
536
|
name: "includeDirSizes",
|
|
571
537
|
description:
|
|
572
|
-
"Compute recursive byte size for each directory entry (true/false). Budget-bounded
|
|
538
|
+
"Compute recursive byte size for each directory entry (true/false). Budget-bounded: large subtrees may return size: null. Not applied to a recursive listing, which carries every file for the client to sum.",
|
|
539
|
+
},
|
|
540
|
+
{
|
|
541
|
+
name: "recursive",
|
|
542
|
+
description:
|
|
543
|
+
"List the whole subtree in one response (true/false). Depth-first, each directory's entries in listing order, so grouping by parent path recovers per-directory listings. Bounded: sets truncated when the entry cap or deadline stops the walk, and does not enter directories the workspace gitignore rules exclude, which are named in skipped.",
|
|
573
544
|
},
|
|
574
545
|
],
|
|
575
546
|
responseBody: z.object({
|
|
576
547
|
path: z.string(),
|
|
577
548
|
entries: z
|
|
578
|
-
.array(
|
|
549
|
+
.array(workspaceEntrySchema)
|
|
579
550
|
.describe("Directory entry objects"),
|
|
551
|
+
truncated: z
|
|
552
|
+
.boolean()
|
|
553
|
+
.optional()
|
|
554
|
+
.describe(
|
|
555
|
+
"A recursive walk stopped at its entry cap or deadline, so deeper entries are missing. Absent from assistants that predate recursive listings.",
|
|
556
|
+
),
|
|
557
|
+
skipped: z
|
|
558
|
+
.array(z.string())
|
|
559
|
+
.optional()
|
|
560
|
+
.describe(
|
|
561
|
+
"Directories a recursive walk listed but did not enter, whether because a workspace gitignore rule excludes them, they are symlinks, or they could not be read. Each can still be listed on its own. Absent from assistants that predate recursive listings.",
|
|
562
|
+
),
|
|
580
563
|
}),
|
|
581
564
|
handler: handleWorkspaceTree,
|
|
582
565
|
},
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The recursive workspace walk: what it enters, what it lists without
|
|
3
|
+
* entering, the order it reports, and the bounds that stop it.
|
|
4
|
+
*/
|
|
5
|
+
import {
|
|
6
|
+
chmodSync,
|
|
7
|
+
mkdirSync,
|
|
8
|
+
rmSync,
|
|
9
|
+
symlinkSync,
|
|
10
|
+
writeFileSync,
|
|
11
|
+
} from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { beforeAll, describe, expect, test } from "bun:test";
|
|
14
|
+
|
|
15
|
+
import { walkWorkspaceTree } from "./workspace-tree-walk.js";
|
|
16
|
+
|
|
17
|
+
const workspaceDir = process.env.VELLUM_WORKSPACE_DIR!;
|
|
18
|
+
const root = join(workspaceDir, "walk-fixture");
|
|
19
|
+
|
|
20
|
+
function write(relative: string, content = "x") {
|
|
21
|
+
const abs = join(root, relative);
|
|
22
|
+
mkdirSync(join(abs, ".."), { recursive: true });
|
|
23
|
+
writeFileSync(abs, content);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
beforeAll(() => {
|
|
27
|
+
mkdirSync(root, { recursive: true });
|
|
28
|
+
write("b-dir/deep/leaf.md");
|
|
29
|
+
write("b-dir/file-in-b.md");
|
|
30
|
+
write("a-dir/file-in-a.md");
|
|
31
|
+
write("top.md");
|
|
32
|
+
write(".secret/inside-hidden.md");
|
|
33
|
+
write("node_modules/dep/index.js");
|
|
34
|
+
// Rules with an inner slash (data/db/) anchor to the workspace root, so
|
|
35
|
+
// these live there rather than under the fixture directory.
|
|
36
|
+
mkdirSync(join(workspaceDir, "data", "db"), { recursive: true });
|
|
37
|
+
writeFileSync(join(workspaceDir, "data", "db", "state.sqlite"), "x");
|
|
38
|
+
mkdirSync(join(workspaceDir, "data", "avatar"), { recursive: true });
|
|
39
|
+
writeFileSync(join(workspaceDir, "data", "avatar", "face.png"), "x");
|
|
40
|
+
mkdirSync(join(workspaceDir, "walk-link-target"), { recursive: true });
|
|
41
|
+
writeFileSync(join(workspaceDir, "walk-link-target", "behind.md"), "x");
|
|
42
|
+
try {
|
|
43
|
+
symlinkSync(
|
|
44
|
+
join(workspaceDir, "walk-link-target"),
|
|
45
|
+
join(root, "linked"),
|
|
46
|
+
"dir",
|
|
47
|
+
);
|
|
48
|
+
} catch (err) {
|
|
49
|
+
if ((err as NodeJS.ErrnoException).code !== "EEXIST") {
|
|
50
|
+
throw err;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
function walk(
|
|
56
|
+
overrides: Partial<Parameters<typeof walkWorkspaceTree>[0]> = {},
|
|
57
|
+
) {
|
|
58
|
+
return walkWorkspaceTree({
|
|
59
|
+
rootPath: root,
|
|
60
|
+
workspaceDir,
|
|
61
|
+
showHidden: false,
|
|
62
|
+
recursive: true,
|
|
63
|
+
...overrides,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
describe("walkWorkspaceTree recursive", () => {
|
|
68
|
+
test("a non-recursive walk lists one level and reports nothing skipped", async () => {
|
|
69
|
+
const result = await walk({ recursive: false });
|
|
70
|
+
expect(result.entries.map((e) => e.path)).not.toContain(
|
|
71
|
+
"walk-fixture/a-dir/file-in-a.md",
|
|
72
|
+
);
|
|
73
|
+
expect(result.entries.map((e) => e.name)).toContain("a-dir");
|
|
74
|
+
expect(result).toMatchObject({ truncated: false, skipped: [] });
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("lists every level, depth-first, each directory in listing order", async () => {
|
|
78
|
+
const { entries } = await walk();
|
|
79
|
+
const paths = entries.map((e) => e.path.replace(/^walk-fixture\//, ""));
|
|
80
|
+
const indexOf = (p: string) => paths.indexOf(p);
|
|
81
|
+
expect(indexOf("a-dir/file-in-a.md")).toBeGreaterThan(-1);
|
|
82
|
+
expect(indexOf("b-dir/deep/leaf.md")).toBeGreaterThan(-1);
|
|
83
|
+
// Root listing first (directories before files), then a-dir's contents
|
|
84
|
+
// before b-dir's, and b-dir's subdirectory before its file.
|
|
85
|
+
expect(indexOf("a-dir")).toBeLessThan(indexOf("top.md"));
|
|
86
|
+
expect(indexOf("top.md")).toBeLessThan(indexOf("a-dir/file-in-a.md"));
|
|
87
|
+
expect(indexOf("a-dir/file-in-a.md")).toBeLessThan(indexOf("b-dir/deep"));
|
|
88
|
+
expect(indexOf("b-dir/deep")).toBeLessThan(indexOf("b-dir/file-in-b.md"));
|
|
89
|
+
expect(indexOf("b-dir/file-in-b.md")).toBeLessThan(
|
|
90
|
+
indexOf("b-dir/deep/leaf.md"),
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("grouping by parent recovers each directory's own listing", async () => {
|
|
95
|
+
const { entries } = await walk();
|
|
96
|
+
const shallow = await walkWorkspaceTree({
|
|
97
|
+
rootPath: join(root, "b-dir"),
|
|
98
|
+
workspaceDir,
|
|
99
|
+
showHidden: false,
|
|
100
|
+
recursive: false,
|
|
101
|
+
});
|
|
102
|
+
const underB = entries.filter(
|
|
103
|
+
(e) =>
|
|
104
|
+
e.path.startsWith("walk-fixture/b-dir/") &&
|
|
105
|
+
!e.path.slice("walk-fixture/b-dir/".length).includes("/"),
|
|
106
|
+
);
|
|
107
|
+
expect(underB.map((e) => e.path)).toEqual(
|
|
108
|
+
shallow.entries.map((e) => e.path),
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("paths use forward slashes", async () => {
|
|
113
|
+
const { entries } = await walk();
|
|
114
|
+
expect(entries.every((e) => !e.path.includes("\\"))).toBe(true);
|
|
115
|
+
expect(entries.map((e) => e.path)).toContain(
|
|
116
|
+
"walk-fixture/b-dir/deep/leaf.md",
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("hidden directories are neither listed nor entered unless asked", async () => {
|
|
121
|
+
const hidden = (await walk()).entries.map((e) => e.path);
|
|
122
|
+
expect(hidden).not.toContain("walk-fixture/.secret");
|
|
123
|
+
expect(hidden).not.toContain("walk-fixture/.secret/inside-hidden.md");
|
|
124
|
+
const shown = (await walk({ showHidden: true })).entries.map((e) => e.path);
|
|
125
|
+
expect(shown).toContain("walk-fixture/.secret/inside-hidden.md");
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("a dependency directory is listed at any depth but not entered", async () => {
|
|
129
|
+
const { entries, skipped } = await walk();
|
|
130
|
+
const paths = entries.map((e) => e.path);
|
|
131
|
+
expect(paths).toContain("walk-fixture/node_modules");
|
|
132
|
+
expect(paths).not.toContain("walk-fixture/node_modules/dep");
|
|
133
|
+
expect(skipped).toContain("walk-fixture/node_modules");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("root-anchored runtime state is listed, not entered, and named", async () => {
|
|
137
|
+
const { entries, skipped } = await walk({ rootPath: workspaceDir });
|
|
138
|
+
const paths = entries.map((e) => e.path);
|
|
139
|
+
expect(paths).toContain("data/db");
|
|
140
|
+
expect(paths).not.toContain("data/db/state.sqlite");
|
|
141
|
+
// A directory the rules do not exclude is entered, and a file the rules
|
|
142
|
+
// match (*.png) is still listed: only descent is decided by them.
|
|
143
|
+
expect(paths).toContain("data/avatar/face.png");
|
|
144
|
+
expect(skipped).toContain("data/db");
|
|
145
|
+
expect(skipped).not.toContain("data/avatar");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("a symlinked directory is listed, not entered, and reported skipped", async () => {
|
|
149
|
+
const { entries, skipped } = await walk();
|
|
150
|
+
const paths = entries.map((e) => e.path);
|
|
151
|
+
expect(paths).toContain("walk-fixture/linked");
|
|
152
|
+
expect(paths).not.toContain("walk-fixture/linked/behind.md");
|
|
153
|
+
expect(skipped).toContain("walk-fixture/linked");
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("an unreadable directory is listed, not entered, and reported skipped", async () => {
|
|
157
|
+
// Root bypasses permission bits, so the check proves nothing there.
|
|
158
|
+
if (process.getuid?.() === 0) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const locked = join(root, "locked");
|
|
162
|
+
mkdirSync(locked, { recursive: true });
|
|
163
|
+
writeFileSync(join(locked, "inside.md"), "x");
|
|
164
|
+
chmodSync(locked, 0o000);
|
|
165
|
+
try {
|
|
166
|
+
const { entries, skipped, truncated } = await walk();
|
|
167
|
+
const paths = entries.map((e) => e.path);
|
|
168
|
+
expect(paths).toContain("walk-fixture/locked");
|
|
169
|
+
expect(paths).not.toContain("walk-fixture/locked/inside.md");
|
|
170
|
+
expect(skipped).toContain("walk-fixture/locked");
|
|
171
|
+
expect(truncated).toBe(false);
|
|
172
|
+
} finally {
|
|
173
|
+
chmodSync(locked, 0o755);
|
|
174
|
+
rmSync(locked, { recursive: true, force: true });
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("a directory wider than the remaining room is not scanned", async () => {
|
|
179
|
+
// Root (5) leaves one slot at a cap of 6; a-dir (1) fits and b-dir (2)
|
|
180
|
+
// does not. Nothing under b-dir is stat'd: its walk-time stat would
|
|
181
|
+
// otherwise show up as a listed entry.
|
|
182
|
+
const wide = join(root, "wide");
|
|
183
|
+
mkdirSync(wide, { recursive: true });
|
|
184
|
+
for (let i = 0; i < 50; i++) {
|
|
185
|
+
writeFileSync(join(wide, `w${i}.md`), "x");
|
|
186
|
+
}
|
|
187
|
+
try {
|
|
188
|
+
const result = await walk({ maxEntries: 7 });
|
|
189
|
+
const paths = result.entries.map((e) => e.path);
|
|
190
|
+
expect(paths).toContain("walk-fixture/wide");
|
|
191
|
+
expect(paths.some((p) => p.startsWith("walk-fixture/wide/"))).toBe(false);
|
|
192
|
+
expect(result.truncated).toBe(true);
|
|
193
|
+
} finally {
|
|
194
|
+
rmSync(wide, { recursive: true, force: true });
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("the deadline is honored inside a directory, not only between them", async () => {
|
|
199
|
+
// A clock that advances one second per read: the deadline is set at 0,
|
|
200
|
+
// the check before the first nested directory reads 1000 and passes,
|
|
201
|
+
// and the first entry inside it reads 2000 and stops the listing. Were
|
|
202
|
+
// the deadline checked only between directories, that one-entry
|
|
203
|
+
// directory would be carried and the walk would stop one step later.
|
|
204
|
+
let reads = 0;
|
|
205
|
+
const result = await walk({ maxWalkMs: 1500, now: () => reads++ * 1000 });
|
|
206
|
+
expect(result.truncated).toBe(true);
|
|
207
|
+
expect(result.entries.length).toBe(5);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("the entry cap stops descent and reports truncated", async () => {
|
|
211
|
+
// The fixture root lists five entries and holds nine in all.
|
|
212
|
+
const result = await walk({ maxEntries: 6 });
|
|
213
|
+
expect(result.entries.length).toBeLessThanOrEqual(6);
|
|
214
|
+
expect(result.entries.length).toBeGreaterThanOrEqual(5);
|
|
215
|
+
expect(result.truncated).toBe(true);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test("a directory is carried whole or not at all", async () => {
|
|
219
|
+
// Root (5) plus a-dir (1) fit in 6; b-dir's two entries do not, so
|
|
220
|
+
// neither of them appears rather than one of them.
|
|
221
|
+
const { entries } = await walk({ maxEntries: 6 });
|
|
222
|
+
const paths = entries.map((e) => e.path);
|
|
223
|
+
expect(paths).toContain("walk-fixture/a-dir/file-in-a.md");
|
|
224
|
+
expect(paths).not.toContain("walk-fixture/b-dir/deep");
|
|
225
|
+
expect(paths).not.toContain("walk-fixture/b-dir/file-in-b.md");
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test("the root listing is whole even under a cap smaller than it", async () => {
|
|
229
|
+
const result = await walk({ maxEntries: 1 });
|
|
230
|
+
expect(result.entries.map((e) => e.name)).toContain("top.md");
|
|
231
|
+
expect(result.truncated).toBe(true);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test("the deadline stops the walk and reports truncated", async () => {
|
|
235
|
+
const result = await walk({ maxWalkMs: -1 });
|
|
236
|
+
expect(result.truncated).toBe(true);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test("directory sizes are null in a recursive walk even when a sizer is given", async () => {
|
|
240
|
+
const { entries } = await walk({ directorySize: () => 42 });
|
|
241
|
+
const dir = entries.find((e) => e.type === "directory");
|
|
242
|
+
expect(dir?.size).toBeNull();
|
|
243
|
+
});
|
|
244
|
+
});
|