@vellumai/assistant 0.12.1-staging.1 → 0.12.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 +0 -4
- package/ARCHITECTURE.md +12 -11
- package/docs/architecture/turn-actor.md +8 -15
- package/openapi.yaml +1 -291
- package/package.json +1 -1
- package/src/__tests__/always-loaded-tools-guard.test.ts +10 -18
- package/src/__tests__/app-compiler.test.ts +0 -6
- package/src/__tests__/app-control-tool-schemas.test.ts +2 -5
- package/src/__tests__/app-dir-path-guard.test.ts +3 -1
- package/src/__tests__/app-source-watcher.test.ts +218 -0
- package/src/{calls/__tests__ → __tests__}/call-controller.test.ts +82 -503
- package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +2 -5
- package/src/__tests__/computer-use-tools.test.ts +5 -17
- package/src/__tests__/config-schema-cmd.test.ts +2 -2
- package/src/__tests__/config-schema.test.ts +29 -28
- package/src/__tests__/config-watcher-skill-reseed.test.ts +359 -0
- package/src/__tests__/config-watcher.test.ts +8 -23
- 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 +0 -26
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +10 -4
- package/src/__tests__/conversation-queue.test.ts +207 -48
- package/src/__tests__/conversation-retry-discard.test.ts +1 -54
- package/src/__tests__/conversation-skill-tools.test.ts +0 -27
- package/src/__tests__/conversation-surfaces-app-control.test.ts +3 -8
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +0 -67
- package/src/__tests__/conversation-title-service.test.ts +10 -14
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +5 -3
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +0 -51
- package/src/__tests__/credential-record-retire.test.ts +1 -171
- package/src/__tests__/credential-routes.test.ts +3 -212
- package/src/__tests__/credential-security-invariants.test.ts +1 -2
- package/src/__tests__/cu-unified-flow.test.ts +2 -8
- package/src/__tests__/drain-kick-guard.test.ts +2 -0
- package/src/__tests__/drain-requeue-on-contention.test.ts +6 -0
- package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
- package/src/__tests__/host-bash-proxy.test.ts +45 -26
- package/src/__tests__/host-browser-proxy.test.ts +17 -6
- package/src/__tests__/host-file-proxy-targeted.test.ts +0 -23
- package/src/__tests__/host-file-proxy.test.ts +23 -11
- package/src/__tests__/host-transfer-proxy-targeted.test.ts +9 -30
- package/src/__tests__/llm-catalog-parity.test.ts +0 -25
- package/src/__tests__/mcp-auth-routes.test.ts +9 -6
- package/src/__tests__/mcp-config-secret-boundary.test.ts +136 -78
- package/src/__tests__/mcp-health-check.test.ts +11 -8
- package/src/__tests__/mcp-list-plugin-servers.test.ts +10 -8
- package/src/{calls/__tests__ → __tests__}/media-stream-audio-transcode.test.ts +1 -1
- package/src/{calls/__tests__ → __tests__}/media-stream-output.test.ts +8 -37
- package/src/{calls/__tests__ → __tests__}/media-stream-parser.test.ts +1 -1
- package/src/{calls/__tests__ → __tests__}/media-stream-server-integration.test.ts +57 -150
- package/src/{calls/__tests__ → __tests__}/media-stream-stt-session.test.ts +5 -5
- package/src/{calls/__tests__ → __tests__}/media-turn-detector.test.ts +1 -1
- package/src/__tests__/messages-after-tiebreaker.test.ts +0 -91
- package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
- package/src/__tests__/messaging-send-tool.test.ts +548 -355
- package/src/__tests__/model-intents.test.ts +0 -3
- package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
- package/src/__tests__/platform-client-verify-credential.test.ts +0 -2
- package/src/__tests__/plugin-import-boundary-guard.test.ts +0 -13
- package/src/__tests__/script-proxy-certs.test.ts +0 -35
- package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
- package/src/__tests__/skill-feature-flags.test.ts +2 -2
- package/src/__tests__/speaker-identification.test.ts +56 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +3 -19
- package/src/__tests__/subagent-tools.test.ts +2 -3
- package/src/__tests__/telephony-tts-guard.test.ts +3 -38
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +0 -4
- package/src/{calls/__tests__/voice-session-bridge-turn-flow.test.ts → __tests__/voice-session-bridge.test.ts} +12 -20
- package/src/__tests__/watch-retro-tool-availability.test.ts +17 -8
- package/src/__tests__/workspace-git-service.test.ts +0 -26
- package/src/acp/__tests__/agent-process.test.ts +4 -258
- package/src/acp/__tests__/auth-required.test.ts +0 -96
- package/src/acp/__tests__/client-handler.test.ts +2 -90
- package/src/acp/__tests__/session-manager-persistence.test.ts +1 -5
- package/src/acp/__tests__/session-manager-resume.test.ts +6 -270
- package/src/acp/__tests__/session-manager-usage.test.ts +2 -4
- package/src/acp/__tests__/session-manager.test.ts +4 -895
- package/src/acp/agent-process.test.ts +1 -4
- package/src/acp/agent-process.ts +9 -94
- package/src/acp/auth-required.ts +1 -50
- package/src/acp/auto-install.test.ts +7 -9
- package/src/acp/client-handler.ts +3 -16
- package/src/acp/resolve-agent.test.ts +6 -220
- package/src/acp/resolve-agent.ts +23 -88
- package/src/acp/session-manager.test.ts +3 -196
- package/src/acp/session-manager.ts +18 -441
- package/src/acp/types.ts +5 -50
- package/src/api/events/app-files-changed.ts +3 -4
- package/src/api/index.ts +0 -6
- package/src/apps/app-store.ts +1 -1
- package/src/bundler/app-compiler.ts +0 -15
- package/src/calls/__tests__/voice-control-protocol.test.ts +25 -58
- package/src/calls/__tests__/voice-session-bridge.test.ts +1 -37
- package/src/calls/__tests__/voice-triage-escalate.test.ts +0 -105
- package/src/calls/call-constants.ts +4 -24
- package/src/calls/call-controller.ts +199 -499
- package/src/calls/call-transport.ts +0 -16
- package/src/calls/media-stream-output.ts +0 -32
- package/src/calls/media-stream-server.ts +30 -82
- package/src/calls/media-stream-stt-session.ts +0 -15
- package/src/calls/resolve-call-tts-provider.ts +2 -33
- package/src/calls/speaker-identification.ts +233 -0
- package/src/calls/tts-call-strategy.ts +5 -9
- package/src/calls/types.ts +0 -2
- package/src/calls/voice-control-protocol.ts +21 -53
- package/src/calls/voice-session-bridge.ts +46 -59
- package/src/calls/voice-triage-escalate.ts +56 -175
- package/src/cli/commands/apps.help.ts +13 -67
- package/src/cli/commands/apps.ts +0 -183
- package/src/cli/commands/channels/index.help.ts +2 -70
- package/src/cli/commands/channels/index.ts +3 -16
- package/src/cli/commands/credentials.help.ts +2 -6
- 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 +1 -5
- package/src/cli/commands/roadmap.ts +1 -1
- package/src/cli/lib/bundled-marketplace.json +90 -103
- package/src/config/__tests__/default-profile-catalog.test.ts +0 -22
- package/src/config/__tests__/memory-retrospective-schema.test.ts +1 -15
- package/src/config/__tests__/platform-rehydration.test.ts +0 -93
- package/src/config/acp-defaults.test.ts +3 -13
- package/src/config/acp-defaults.ts +6 -23
- package/src/config/acp-schema.ts +2 -30
- package/src/config/bundled-skills/acp/SKILL.md +14 -22
- package/src/config/bundled-skills/acp/TOOLS.json +1 -5
- package/src/config/bundled-skills/app-builder/SKILL.md +9 -12
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
- package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +139 -67
- package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
- package/src/config/bundled-skills/screen-annotation/SKILL.md +1 -8
- package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +1 -4
- package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
- package/src/config/default-profile-catalog.ts +1 -1
- package/src/config/env.ts +2 -2
- package/src/config/feature-flag-registry.json +12 -8
- package/src/config/memory-v3-gate.test.ts +0 -31
- package/src/config/memory-v3-gate.ts +16 -20
- package/src/config/platform-rehydration.ts +21 -75
- package/src/config/schema.ts +2 -2
- package/src/config/schemas/__tests__/live-voice.test.ts +158 -8
- package/src/config/schemas/calls.ts +28 -1
- package/src/config/schemas/live-voice.ts +173 -1
- package/src/config/schemas/mcp.ts +1 -1
- package/src/config/schemas/memory-retrospective.ts +0 -10
- package/src/config/schemas/skills.ts +14 -0
- package/src/credential-execution/process-manager.test.ts +19 -18
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +121 -66
- package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +1 -4
- package/src/daemon/app-source-watcher.ts +218 -0
- package/src/daemon/config-watcher.ts +194 -13
- package/src/daemon/conversation-process.ts +64 -35
- package/src/daemon/conversation-skill-tools.ts +31 -13
- package/src/daemon/conversation-store.ts +1 -1
- package/src/daemon/conversation-surfaces.ts +8 -10
- package/src/daemon/conversation-tool-setup.ts +43 -37
- package/src/daemon/conversation.ts +3 -0
- package/src/daemon/host-bash-proxy.ts +12 -8
- package/src/daemon/host-browser-proxy.ts +1 -15
- package/src/daemon/host-file-proxy.ts +10 -8
- package/src/daemon/host-transfer-proxy.ts +18 -14
- package/src/daemon/lifecycle.ts +5 -0
- package/src/daemon/mcp-reload-service.ts +5 -5
- package/src/daemon/message-types/sync.ts +0 -1
- package/src/daemon/providers-setup.ts +1 -1
- package/src/daemon/shutdown-handlers.ts +9 -1
- package/src/daemon/skill-memory-refresh.ts +0 -64
- package/src/daemon/tool-side-effects.ts +18 -7
- package/src/hooks/types.ts +0 -33
- package/src/inbound/platform-callback-registration.ts +5 -4
- package/src/live-voice/__tests__/live-voice-metrics.test.ts +3 -0
- package/src/live-voice/__tests__/live-voice-progress.test.ts +15 -15
- package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
- package/src/{calls → live-voice}/__tests__/progress-narration.test.ts +4 -4
- package/src/{calls → live-voice}/__tests__/progress-phrases.test.ts +1 -1
- package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +69 -13
- package/src/live-voice/live-voice-session.ts +693 -213
- package/src/{calls → live-voice}/progress-narration.ts +2 -2
- package/src/live-voice/protocol.ts +2 -0
- package/src/mcp/__tests__/effective-config.test.ts +2 -2
- package/src/mcp/effective-config.ts +3 -3
- package/src/mcp/mcp-header-store.ts +45 -29
- package/src/mcp/startup.ts +4 -8
- package/src/messaging/provider.ts +3 -10
- package/src/messaging/providers/callback-routing.ts +0 -18
- package/src/messaging/providers/channel-transport.ts +0 -56
- package/src/messaging/providers/discord/transport.ts +0 -17
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +17 -6
- package/src/messaging/providers/slack/adapter.ts +44 -7
- package/src/messaging/providers/slack/transport.ts +0 -16
- package/src/messaging/providers/telegram-bot/adapter.ts +48 -1
- package/src/messaging/providers/telegram-bot/client.ts +26 -0
- package/src/messaging/providers/telegram-bot/transport.ts +0 -20
- package/src/messaging/providers/whatsapp/adapter.ts +48 -1
- package/src/messaging/providers/whatsapp/client.ts +29 -0
- package/src/messaging/providers/whatsapp/transport.ts +0 -18
- package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +0 -8
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +0 -55
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +3 -153
- package/src/notifications/assistant-reply-producer.ts +3 -8
- package/src/notifications/conversation-pairing.ts +16 -36
- package/src/notifications/delivered-post-record.ts +1 -11
- package/src/notifications/home-feed-side-effect.ts +7 -55
- package/src/outbound-proxy/certs.ts +5 -3
- package/src/permissions/checker.test.ts +6 -11
- package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
- package/src/persistence/conversation-crud.ts +63 -71
- package/src/persistence/conversation-lineage.ts +4 -12
- package/src/persistence/conversation-title-service.ts +26 -9
- 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-reads.ts +0 -30
- package/src/persistence/migrations/157-invite-contact-id.ts +6 -18
- package/src/persistence/schema/memory-core.ts +0 -5
- package/src/platform/client.test.ts +0 -2
- package/src/platform/client.ts +10 -2
- package/src/plugin-api/constants.ts +0 -2
- package/src/plugin-api/index.ts +0 -1
- package/src/plugin-api/types.ts +0 -2
- package/src/plugins/defaults/index.ts +0 -2
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +0 -46
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +5 -9
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +10 -20
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +0 -219
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +0 -31
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +0 -63
- package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +0 -4
- package/src/plugins/defaults/memory/graph/capability-seed.ts +6 -4
- package/src/plugins/defaults/memory/hooks/init.ts +0 -10
- package/src/plugins/defaults/memory/hooks/shutdown.ts +0 -2
- package/src/plugins/defaults/memory/jobs-worker.ts +0 -13
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +60 -34
- package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +5 -3
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +3 -12
- package/src/plugins/defaults/memory/memory-retrospective-state.ts +71 -312
- package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +1 -2
- package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +3 -5
- package/src/plugins/defaults/memory/startup.ts +1 -6
- package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +9 -7
- package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +0 -89
- package/src/plugins/defaults/memory/worker.ts +0 -6
- package/src/plugins/mcp-servers.ts +75 -15
- package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
- package/src/providers/gemini/client.ts +23 -9
- package/src/providers/model-catalog.ts +0 -34
- package/src/providers/model-intents.ts +5 -10
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +18 -146
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +1 -83
- package/src/providers/openai/chat-completions-provider.ts +78 -192
- package/src/providers/openai/codex-models.ts +4 -0
- package/src/runtime/AGENTS.md +0 -2
- package/src/runtime/auth/same-actor.ts +3 -38
- package/src/runtime/http-server.ts +151 -160
- package/src/runtime/routes/__tests__/acp-routes.test.ts +4 -159
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +1 -108
- package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +12 -16
- package/src/runtime/routes/acp-routes.test.ts +0 -15
- package/src/runtime/routes/acp-routes.ts +9 -72
- package/src/runtime/routes/app-management-routes.ts +0 -73
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/conversation-list-routes.ts +3 -14
- package/src/runtime/routes/conversation-query-routes.ts +0 -40
- package/src/runtime/routes/credential-routes.ts +10 -19
- package/src/runtime/routes/diagnostics-routes.ts +3 -55
- package/src/runtime/routes/index.ts +0 -4
- package/src/runtime/routes/log-export-routes.ts +0 -2
- package/src/runtime/routes/mcp-auth-routes.ts +47 -39
- package/src/runtime/routes/migration-routes.ts +21 -8
- package/src/runtime/routes/platform-routes.ts +15 -18
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +0 -28
- package/src/runtime/services/conversation-serializer.ts +3 -16
- package/src/runtime/sync/resource-sync-events.ts +1 -2
- package/src/schedule/worker.ts +1 -1
- package/src/security/ces-rpc-record-backend.ts +0 -7
- package/src/subagent/consult-context.ts +25 -12
- package/src/tools/acp/list-agents.test.ts +1 -3
- package/src/tools/acp/spawn.test.ts +11 -146
- package/src/tools/acp/spawn.ts +3 -21
- package/src/tools/acp/status.ts +3 -40
- package/src/tools/app-control/skill-proxy-bridge.ts +1 -2
- package/src/tools/capability-offer.test.ts +2 -22
- package/src/tools/capability-offer.ts +1 -16
- package/src/tools/computer-use/definitions.ts +2 -4
- package/src/tools/computer-use/skill-proxy-bridge.ts +1 -2
- package/src/tools/credentials/metadata-store.ts +0 -100
- package/src/tools/host-filesystem/edit.test.ts +1 -2
- package/src/tools/host-filesystem/edit.ts +2 -2
- package/src/tools/host-filesystem/read.test.ts +1 -2
- package/src/tools/host-filesystem/read.ts +2 -2
- package/src/tools/host-filesystem/transfer.test.ts +1 -2
- package/src/tools/host-filesystem/transfer.ts +2 -2
- package/src/tools/host-filesystem/write.test.ts +1 -2
- package/src/tools/host-filesystem/write.ts +2 -2
- package/src/tools/host-terminal/host-shell.test.ts +0 -16
- package/src/tools/host-terminal/host-shell.ts +20 -4
- package/src/tools/policy-context.ts +7 -6
- package/src/tools/subagent/spawn.ts +8 -5
- package/src/tools/watch/watch-retro-report.ts +8 -0
- package/src/util/__tests__/language-subtag.test.ts +1 -23
- package/src/util/fs-watcher-error.ts +36 -0
- package/src/util/language-subtag.ts +0 -16
- package/src/util/platform.ts +0 -8
- package/src/workspace/git-service.ts +54 -5
- package/src/workspace/migrations/registry.ts +0 -6
- package/scripts/smoke-desktop-window-theme.ts +0 -186
- package/src/__tests__/git-porcelain.test.ts +0 -47
- package/src/__tests__/helpers/set-workspace-mcp.ts +0 -11
- package/src/__tests__/message-deleted-hook-dispatch.test.ts +0 -119
- package/src/__tests__/resolve-app.test.ts +0 -135
- package/src/__tests__/source-fingerprint.test.ts +0 -184
- package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +0 -336
- package/src/acp/__tests__/helpers/acp-model-option.ts +0 -51
- package/src/acp/model-config.test.ts +0 -166
- package/src/acp/model-config.ts +0 -138
- package/src/api/events/acp-session-model-update.test.ts +0 -88
- package/src/api/events/acp-session-model-update.ts +0 -46
- package/src/apps/resolve-app.ts +0 -81
- package/src/apps/source-fingerprint.ts +0 -266
- package/src/calls/__tests__/barge-in-guard.test.ts +0 -81
- package/src/calls/__tests__/voice-leg-coordinator.test.ts +0 -229
- package/src/calls/__tests__/voice-progress-cadence.test.ts +0 -274
- package/src/calls/barge-in-guard.ts +0 -118
- package/src/calls/voice-leg-coordinator.ts +0 -189
- package/src/calls/voice-progress-cadence.ts +0 -423
- package/src/cli/commands/__tests__/apps.test.ts +0 -245
- package/src/cli/commands/__tests__/roadmap.help.test.ts +0 -19
- package/src/cli/commands/channels/__tests__/send.test.ts +0 -218
- package/src/cli/commands/channels/send.ts +0 -128
- package/src/config/__tests__/platform-identity.test.ts +0 -117
- package/src/config/acp-agent-write.ts +0 -43
- package/src/config/acp-schema.test.ts +0 -81
- package/src/config/platform-identity.ts +0 -144
- package/src/config/schemas/__tests__/voice.test.ts +0 -148
- package/src/config/schemas/voice.ts +0 -150
- package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +0 -133
- package/src/daemon/app-change-notify.ts +0 -45
- package/src/desktop/__tests__/fake-desktop.ts +0 -119
- package/src/desktop/desktop-chrome-policy.test.ts +0 -71
- package/src/desktop/desktop-chrome-policy.ts +0 -42
- package/src/desktop/desktop-dependencies.test.ts +0 -102
- package/src/desktop/desktop-dependencies.ts +0 -306
- package/src/desktop/desktop-feature.test.ts +0 -18
- package/src/desktop/desktop-feature.ts +0 -19
- package/src/desktop/desktop-panel-config.test.ts +0 -135
- package/src/desktop/desktop-panel-config.ts +0 -149
- package/src/desktop/desktop-session-manager.test.ts +0 -627
- package/src/desktop/desktop-session-manager.ts +0 -814
- package/src/desktop/desktop-stream-bridge.test.ts +0 -395
- package/src/desktop/desktop-stream-bridge.ts +0 -214
- package/src/desktop/desktop-wallpaper.test.ts +0 -191
- package/src/desktop/desktop-wallpaper.ts +0 -113
- package/src/desktop/desktop-window-theme.ts +0 -170
- package/src/i18n/AGENTS.md +0 -22
- package/src/i18n/__tests__/i18n.test.ts +0 -108
- package/src/i18n/index.ts +0 -16
- package/src/i18n/locales.ts +0 -93
- package/src/i18n/messages.ts +0 -61
- package/src/i18n/title.ts +0 -25
- package/src/mcp/__tests__/workspace-mcp-config.test.ts +0 -135
- package/src/mcp/spec-schema.ts +0 -111
- package/src/mcp/workspace-mcp-config.ts +0 -188
- package/src/messaging/providers/__tests__/proactive-address.test.ts +0 -95
- package/src/persistence/conversation-title-placeholders.ts +0 -23
- package/src/persistence/message-cursor.ts +0 -78
- package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +0 -149
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +0 -118
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +0 -83
- package/src/plugins/defaults/memory/hooks/message-deleted.ts +0 -21
- package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +0 -126
- package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +0 -79
- package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +0 -28
- package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +0 -9
- package/src/providers/gemini-thought-signature.test.ts +0 -63
- package/src/providers/gemini-thought-signature.ts +0 -44
- package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +0 -613
- package/src/providers/openai/__tests__/google-thought-signature.test.ts +0 -235
- package/src/providers/openai/google-thought-signature.ts +0 -320
- package/src/runtime/__tests__/channel-send.test.ts +0 -443
- package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +0 -89
- package/src/runtime/__tests__/live-voice-upgrade.test.ts +0 -58
- package/src/runtime/__tests__/runtime-ws-test-utils.ts +0 -72
- package/src/runtime/__tests__/watch-stream-upgrade.test.ts +0 -59
- package/src/runtime/channel-send.ts +0 -316
- package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +0 -205
- package/src/runtime/routes/__tests__/channel-send-routes.test.ts +0 -127
- package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +0 -107
- package/src/runtime/routes/channel-send-routes.ts +0 -85
- package/src/runtime/routes/desktop-setup-routes.test.ts +0 -74
- package/src/runtime/routes/desktop-setup-routes.ts +0 -35
- package/src/security/__tests__/ces-http-record-backend.test.ts +0 -137
- package/src/security/ces-http-record-backend.ts +0 -120
- package/src/skills/__tests__/workspace-skill-md-signature.test.ts +0 -60
- package/src/skills/workspace-skill-md-signature.ts +0 -50
- package/src/tools/acp/status.test.ts +0 -94
- package/src/util/ca-bundle.ts +0 -13
- package/src/workspace/git-porcelain.ts +0 -66
- package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +0 -258
- package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +0 -78
- package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +0 -214
- package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +0 -102
- package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +0 -199
- /package/src/{calls → live-voice}/progress-phrases.ts +0 -0
|
@@ -335,9 +335,8 @@ describe("host_file_transfer cross-client guards", () => {
|
|
|
335
335
|
|
|
336
336
|
expect(result.isError).toBe(true);
|
|
337
337
|
expect(result.content).toContain(
|
|
338
|
-
"
|
|
338
|
+
"no client with host_file capability is connected",
|
|
339
339
|
);
|
|
340
|
-
expect(result.content).toContain("https://www.vellum.ai/downloads");
|
|
341
340
|
expect(toSandboxCalls.length).toBe(0);
|
|
342
341
|
});
|
|
343
342
|
|
|
@@ -6,7 +6,7 @@ import { supportsHostProxy } from "../../channels/types.js";
|
|
|
6
6
|
import { HostTransferProxy } from "../../daemon/host-transfer-proxy.js";
|
|
7
7
|
import { RiskLevel } from "../../permissions/types.js";
|
|
8
8
|
import { assistantEventHub } from "../../runtime/assistant-event-hub.js";
|
|
9
|
-
import {
|
|
9
|
+
import { desktopClientName } from "../client-os.js";
|
|
10
10
|
import { declareDaemonActivityField } from "../schema-transforms.js";
|
|
11
11
|
import { sandboxPolicy } from "../shared/filesystem/path-policy.js";
|
|
12
12
|
import type {
|
|
@@ -126,7 +126,7 @@ export const hostFileTransferTool = {
|
|
|
126
126
|
!HostTransferProxy.instance.isAvailable()
|
|
127
127
|
) {
|
|
128
128
|
return {
|
|
129
|
-
content:
|
|
129
|
+
content: `Error: no client with host_file capability is connected. Connect a ${desktopClientName(context)} client to use host_file from a non-desktop interface.`,
|
|
130
130
|
isError: true,
|
|
131
131
|
};
|
|
132
132
|
}
|
|
@@ -67,9 +67,8 @@ describe("host_file_write cross-client guards", () => {
|
|
|
67
67
|
);
|
|
68
68
|
expect(result.isError).toBe(true);
|
|
69
69
|
expect(result.content).toContain(
|
|
70
|
-
"
|
|
70
|
+
"no client with host_file capability is connected",
|
|
71
71
|
);
|
|
72
|
-
expect(result.content).toContain("https://www.vellum.ai/downloads");
|
|
73
72
|
});
|
|
74
73
|
|
|
75
74
|
test("returns 'specified client disconnected' error when targetClientId set but proxy unavailable on web", async () => {
|
|
@@ -4,7 +4,7 @@ import { supportsHostProxy } from "../../channels/types.js";
|
|
|
4
4
|
import { HostFileProxy } from "../../daemon/host-file-proxy.js";
|
|
5
5
|
import { RiskLevel } from "../../permissions/types.js";
|
|
6
6
|
import { assistantEventHub } from "../../runtime/assistant-event-hub.js";
|
|
7
|
-
import {
|
|
7
|
+
import { desktopClientName } from "../client-os.js";
|
|
8
8
|
import { FileSystemOps } from "../shared/filesystem/file-ops-service.js";
|
|
9
9
|
import { formatWriteSummary } from "../shared/filesystem/format-diff.js";
|
|
10
10
|
import { hostPolicy } from "../shared/filesystem/path-policy.js";
|
|
@@ -90,7 +90,7 @@ export const hostFileWriteTool = {
|
|
|
90
90
|
!HostFileProxy.instance.isAvailable()
|
|
91
91
|
) {
|
|
92
92
|
return {
|
|
93
|
-
content:
|
|
93
|
+
content: `Error: no client with host_file capability is connected. Connect a ${desktopClientName(context)} client to use host_file from a non-desktop interface.`,
|
|
94
94
|
isError: true,
|
|
95
95
|
};
|
|
96
96
|
}
|
|
@@ -170,22 +170,6 @@ afterEach(() => {
|
|
|
170
170
|
mockProxyAvailable = false;
|
|
171
171
|
});
|
|
172
172
|
|
|
173
|
-
describe("host_bash unavailable desktop-app recovery", () => {
|
|
174
|
-
test("offers the desktop app download when a web turn has no host connection", async () => {
|
|
175
|
-
const result = await hostShellTool.execute(
|
|
176
|
-
{ command: "pwd" },
|
|
177
|
-
makeContext({ transportInterface: "web" }),
|
|
178
|
-
);
|
|
179
|
-
|
|
180
|
-
expect(result.isError).toBe(true);
|
|
181
|
-
expect(result.content).toContain(
|
|
182
|
-
"The Vellum desktop app is needed to run commands on your computer",
|
|
183
|
-
);
|
|
184
|
-
expect(result.content).toContain("https://www.vellum.ai/downloads");
|
|
185
|
-
expect(result.content).toContain("Open it, then retry.");
|
|
186
|
-
});
|
|
187
|
-
});
|
|
188
|
-
|
|
189
173
|
// ---------------------------------------------------------------------------
|
|
190
174
|
// Proxy path — background: true
|
|
191
175
|
// ---------------------------------------------------------------------------
|
|
@@ -18,7 +18,10 @@ import { getConfig } from "../../config/loader.js";
|
|
|
18
18
|
import { HostBashProxy } from "../../daemon/host-bash-proxy.js";
|
|
19
19
|
import { RiskLevel } from "../../permissions/types.js";
|
|
20
20
|
import { wakeAgentForOpportunity } from "../../runtime/agent-wake.js";
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
assistantEventHub,
|
|
23
|
+
broadcastMessage,
|
|
24
|
+
} from "../../runtime/assistant-event-hub.js";
|
|
22
25
|
import { conversationRevealNonce } from "../../runtime/reveal-nonce.js";
|
|
23
26
|
import { redactSecrets } from "../../security/secret-scanner.js";
|
|
24
27
|
import {
|
|
@@ -36,7 +39,7 @@ import {
|
|
|
36
39
|
registerBackgroundTool,
|
|
37
40
|
removeBackgroundTool,
|
|
38
41
|
} from "../background-tool-registry.js";
|
|
39
|
-
import {
|
|
42
|
+
import { desktopClientName } from "../client-os.js";
|
|
40
43
|
import {
|
|
41
44
|
attachBoundedStdio,
|
|
42
45
|
MAX_OUTPUT_LENGTH,
|
|
@@ -139,7 +142,7 @@ export const hostShellInputSchema = z.looseObject({
|
|
|
139
142
|
target_client_id: z
|
|
140
143
|
.string()
|
|
141
144
|
.describe(
|
|
142
|
-
"
|
|
145
|
+
"ID of the specific client to execute this command on. Required when multiple clients support host_bash; omit when only one client is connected. Obtain IDs from `assistant clients list --capability host_bash`.",
|
|
143
146
|
)
|
|
144
147
|
.optional()
|
|
145
148
|
.catch(undefined),
|
|
@@ -200,7 +203,20 @@ export const hostShellTool = {
|
|
|
200
203
|
const config = getConfig();
|
|
201
204
|
const { shellDefaultTimeoutSec, shellMaxTimeoutSec } = config.timeouts;
|
|
202
205
|
|
|
206
|
+
// Guard: non-host-proxy interfaces need an explicit target when multiple
|
|
207
|
+
// capable clients are connected to avoid ambiguous untargeted broadcasts.
|
|
203
208
|
const transportInterface = context.transportInterface;
|
|
209
|
+
if (
|
|
210
|
+
targetClientId == null &&
|
|
211
|
+
transportInterface != null &&
|
|
212
|
+
!supportsHostProxy(transportInterface) &&
|
|
213
|
+
assistantEventHub.listClientsByCapability("host_bash").length > 1
|
|
214
|
+
) {
|
|
215
|
+
return {
|
|
216
|
+
content: `Error: multiple clients support host_bash. Specify which client to use with \`target_client_id\`. Run \`assistant clients list --capability host_bash\` to see client IDs and labels.`,
|
|
217
|
+
isError: true,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
204
220
|
|
|
205
221
|
// Guard: non-host-proxy interfaces with no capable clients connected.
|
|
206
222
|
if (
|
|
@@ -210,7 +226,7 @@ export const hostShellTool = {
|
|
|
210
226
|
!HostBashProxy.instance.isAvailable()
|
|
211
227
|
) {
|
|
212
228
|
return {
|
|
213
|
-
content:
|
|
229
|
+
content: `Error: no client with host_bash capability is connected. Connect a ${desktopClientName(context)} client to use host_bash from a non-desktop interface.`,
|
|
214
230
|
isError: true,
|
|
215
231
|
};
|
|
216
232
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getConfig } from "../config/loader.js";
|
|
2
|
-
import {
|
|
2
|
+
import { isV3TierActive } from "../config/memory-v3-gate.js";
|
|
3
3
|
import type { ExecutionContext } from "../permissions/approval-policy.js";
|
|
4
4
|
import type { ChannelPermissionCoordinates } from "../permissions/channel-permission-query.js";
|
|
5
5
|
import type { PolicyContext } from "../permissions/types.js";
|
|
@@ -63,11 +63,12 @@ export function buildPolicyContext(
|
|
|
63
63
|
requestOrigin: context?.requestOrigin,
|
|
64
64
|
...channelCoordinatesFromToolContext(context),
|
|
65
65
|
requesterContactId: context?.requesterContactId,
|
|
66
|
-
// Precompute the
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
// inactive just by reading
|
|
70
|
-
|
|
66
|
+
// Precompute the proc-to-skills gate — the v3 tier being active, i.e.
|
|
67
|
+
// memory on AND v3 live — here so the permission checker, a leaf module
|
|
68
|
+
// that must not read config, can deny the memory-retrospective
|
|
69
|
+
// skill-authoring grant whenever the feature is inactive just by reading
|
|
70
|
+
// this boolean.
|
|
71
|
+
procToSkillsActive: isV3TierActive(getConfig()),
|
|
71
72
|
};
|
|
72
73
|
|
|
73
74
|
const ownerKind = getToolOwner(tool.name)?.kind;
|
|
@@ -484,9 +484,9 @@ function spawnCancelledResult(label: string): ToolExecutionResult {
|
|
|
484
484
|
* repeat-spawn guard folds, so a consult has to record it the same way every
|
|
485
485
|
* other spawn does or the guard could never match one consult against another.
|
|
486
486
|
*
|
|
487
|
-
* The context pack is best effort:
|
|
488
|
-
*
|
|
489
|
-
* section
|
|
487
|
+
* The context pack is best effort: the parent conversation is looked up only
|
|
488
|
+
* for its warm skill catalog, so an unresolvable one (e.g. evicted) costs the
|
|
489
|
+
* skills section of the pack and nothing else.
|
|
490
490
|
*
|
|
491
491
|
* The budget is the advisor's alone. It is the only spawn the agent may issue
|
|
492
492
|
* unprompted and the only one that runs on the premium profile, so it is the
|
|
@@ -502,18 +502,21 @@ async function buildAdvisorFields(
|
|
|
502
502
|
maxRuntimeMs: number;
|
|
503
503
|
maxToolCalls: number;
|
|
504
504
|
}> {
|
|
505
|
+
const parentConversation = findConversation(context.conversationId);
|
|
505
506
|
// Situational awareness for the advisor: the parent's live tool set, the
|
|
506
507
|
// full skill catalog, and its workspace. Assembled off the per-turn
|
|
507
508
|
// ToolContext snapshot (trust, channel) so the personal-memory sections are
|
|
508
509
|
// gated exactly like the runtime injectors. A null pack just means the
|
|
509
|
-
// consult runs on the brief alone.
|
|
510
|
-
// `loadSkillCatalog()` inside the context pack, not a conversation snapshot.
|
|
510
|
+
// consult runs on the brief alone.
|
|
511
511
|
const situationalContext = await buildAdvisorContext({
|
|
512
512
|
conversationId: context.conversationId,
|
|
513
513
|
workingDir: context.workingDir,
|
|
514
514
|
allowedToolNames: context.allowedToolNames,
|
|
515
515
|
trustClass: context.trustClass,
|
|
516
516
|
enabledPluginSet: context.enabledPluginSet,
|
|
517
|
+
// The parent's warm per-turn catalog keeps the synchronous on-disk catalog
|
|
518
|
+
// scan out of the spawn path.
|
|
519
|
+
skillCatalog: parentConversation?.skillProjectionCache?.catalog,
|
|
517
520
|
});
|
|
518
521
|
return {
|
|
519
522
|
requestText: advisorRequestText(objective, situationalContext),
|
|
@@ -14,6 +14,14 @@ import type {
|
|
|
14
14
|
/**
|
|
15
15
|
* How a watch retrospective hands its report to the daemon.
|
|
16
16
|
*
|
|
17
|
+
* **The card cannot come from `ui_show`.** A retrospective runs as a
|
|
18
|
+
* `clientless` wake, which pins the turn non-interactive, and
|
|
19
|
+
* `conversation-tool-setup` gates the whole `ui_surface` tool family on a
|
|
20
|
+
* client being present (`return channelCapabilities?.supportsDynamicUi ??
|
|
21
|
+
* !hasNoClient`). So `ui_show` is not merely denied in this turn, it is absent
|
|
22
|
+
* from the tool set, and a retrospective told to call it can only report that
|
|
23
|
+
* it cannot. This tool is an ordinary one and passes that gate untouched.
|
|
24
|
+
*
|
|
17
25
|
* **It records; it does not render.** The executor validates the payload and
|
|
18
26
|
* returns, and nothing here writes to the conversation. What makes the card is
|
|
19
27
|
* `watch-retro.ts` reading this call back out of the turn's own history once
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
baseLanguageSubtag,
|
|
5
|
-
fixedPhraseLanguage,
|
|
6
|
-
localizedOrDefault,
|
|
7
|
-
} from "../language-subtag.js";
|
|
3
|
+
import { baseLanguageSubtag, localizedOrDefault } from "../language-subtag.js";
|
|
8
4
|
|
|
9
5
|
describe("baseLanguageSubtag", () => {
|
|
10
6
|
test("lowercases a bare tag", () => {
|
|
@@ -56,21 +52,3 @@ describe("localizedOrDefault", () => {
|
|
|
56
52
|
expect(localizedOrDefault(table, "__proto__", "fallback")).toBe("fallback");
|
|
57
53
|
});
|
|
58
54
|
});
|
|
59
|
-
|
|
60
|
-
describe("fixedPhraseLanguage", () => {
|
|
61
|
-
const table = { en: "one", es: "uno" };
|
|
62
|
-
|
|
63
|
-
test("pins English when the speaker's language has no table entry", () => {
|
|
64
|
-
expect(fixedPhraseLanguage(table, "ko")).toBe("en");
|
|
65
|
-
expect(fixedPhraseLanguage(table, "ar-EG")).toBe("en");
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test("rides the speaker's language when the table covers it", () => {
|
|
69
|
-
expect(fixedPhraseLanguage(table, "es")).toBeUndefined();
|
|
70
|
-
expect(fixedPhraseLanguage(table, "es-419")).toBeUndefined();
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
test("carries no hint when the language is unknown", () => {
|
|
74
|
-
expect(fixedPhraseLanguage(table, undefined)).toBeUndefined();
|
|
75
|
-
});
|
|
76
|
-
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared `'error'`-event handler for `fs.watch()` FSWatchers.
|
|
3
|
+
*
|
|
4
|
+
* An `FSWatcher` is an EventEmitter. When the underlying inotify/FSEvents
|
|
5
|
+
* backend fails *after* the watch was established — e.g. ENOSPC when the
|
|
6
|
+
* kernel's `fs.inotify.max_user_watches` limit is exhausted while walking a
|
|
7
|
+
* recursive watch into a large subtree (a plugin's `node_modules`), or ENXIO
|
|
8
|
+
* when a Unix socket file appears in a watched directory — the failure is
|
|
9
|
+
* delivered asynchronously as an `'error'` event rather than a synchronous
|
|
10
|
+
* throw from `watch()`. An emitter with no `'error'` listener rethrows, which
|
|
11
|
+
* surfaces as an `uncaughtException` and takes the whole daemon down (→
|
|
12
|
+
* CrashLoopBackOff).
|
|
13
|
+
*
|
|
14
|
+
* Attaching this handler degrades the failure to "this watcher stops
|
|
15
|
+
* delivering events", in line with the daemon startup philosophy: a subsystem
|
|
16
|
+
* failure must never crash the process.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type { FSWatcher } from "node:fs";
|
|
20
|
+
|
|
21
|
+
import type { Logger } from "pino";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Attach a resilient `'error'` listener so async FSWatcher failures are logged
|
|
25
|
+
* instead of crashing the process. Pass the owning module's logger and the
|
|
26
|
+
* watched directory for diagnostic context.
|
|
27
|
+
*/
|
|
28
|
+
export function attachFsWatcherErrorHandler(
|
|
29
|
+
watcher: FSWatcher,
|
|
30
|
+
log: Logger,
|
|
31
|
+
dir: string,
|
|
32
|
+
): void {
|
|
33
|
+
watcher.on("error", (err) => {
|
|
34
|
+
log.warn({ err, dir }, "FSWatcher error (non-fatal, continuing)");
|
|
35
|
+
});
|
|
36
|
+
}
|
|
@@ -41,19 +41,3 @@ export function localizedOrDefault<T>(
|
|
|
41
41
|
? (table[baseLanguageSubtag(language)!] as T)
|
|
42
42
|
: fallback;
|
|
43
43
|
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* The TTS language hint for a fixed phrase picked from a localized table:
|
|
47
|
-
* "en" when the speaker has a language the table does not cover (the
|
|
48
|
-
* picker fell back to English text, which must not be synthesized under
|
|
49
|
-
* an ar/ko/ta hint), undefined otherwise (the phrase rides the speaker's
|
|
50
|
-
* language, or carries no hint at all when the language is unknown).
|
|
51
|
-
*/
|
|
52
|
-
export function fixedPhraseLanguage(
|
|
53
|
-
table: Readonly<Record<string, unknown>>,
|
|
54
|
-
language: string | undefined,
|
|
55
|
-
): string | undefined {
|
|
56
|
-
return language !== undefined && !hasLocalizedEntry(table, language)
|
|
57
|
-
? "en"
|
|
58
|
-
: undefined;
|
|
59
|
-
}
|
package/src/util/platform.ts
CHANGED
|
@@ -470,14 +470,6 @@ export function getWorkspaceConfigPath(): string {
|
|
|
470
470
|
return join(getWorkspaceDir(), "config.json");
|
|
471
471
|
}
|
|
472
472
|
|
|
473
|
-
/** Filename for the user-owned Agent Plugins MCP document. */
|
|
474
|
-
export const WORKSPACE_MCP_FILENAME = "mcp.json";
|
|
475
|
-
|
|
476
|
-
/** Returns $VELLUM_WORKSPACE_DIR/mcp.json */
|
|
477
|
-
export function getWorkspaceMcpConfigPath(): string {
|
|
478
|
-
return join(getWorkspaceDir(), WORKSPACE_MCP_FILENAME);
|
|
479
|
-
}
|
|
480
|
-
|
|
481
473
|
/** Returns $VELLUM_WORKSPACE_DIR/skills */
|
|
482
474
|
export function getWorkspaceSkillsDir(): string {
|
|
483
475
|
return join(getWorkspaceDir(), "skills");
|
|
@@ -17,10 +17,6 @@ import { getLogger } from "../util/logger.js";
|
|
|
17
17
|
import { Mutex } from "../util/mutex.js";
|
|
18
18
|
import { addToPathEnv, getExtraToolPathDirs } from "../util/platform.js";
|
|
19
19
|
import { PromiseGuard } from "../util/promise-guard.js";
|
|
20
|
-
import {
|
|
21
|
-
collectDirtyPathsFromPorcelain,
|
|
22
|
-
parsePorcelainZ,
|
|
23
|
-
} from "./git-porcelain.js";
|
|
24
20
|
|
|
25
21
|
const execFileAsync = promisify(execFile);
|
|
26
22
|
const log = getLogger("workspace-git");
|
|
@@ -230,6 +226,59 @@ done
|
|
|
230
226
|
exit 0
|
|
231
227
|
`;
|
|
232
228
|
|
|
229
|
+
/**
|
|
230
|
+
* Parse NUL-terminated `git status --porcelain -z` output into status/path
|
|
231
|
+
* pairs. NUL termination is required so paths with special characters
|
|
232
|
+
* (non-ASCII, quotes, newlines) arrive verbatim instead of C-style quoted.
|
|
233
|
+
* A rename/copy record is followed by a bare origin-path entry, which is
|
|
234
|
+
* skipped.
|
|
235
|
+
*/
|
|
236
|
+
function parsePorcelainZ(
|
|
237
|
+
stdout: string,
|
|
238
|
+
): Array<{ status: string; path: string }> {
|
|
239
|
+
const entries = stdout.split("\0");
|
|
240
|
+
const parsed: Array<{ status: string; path: string }> = [];
|
|
241
|
+
for (let i = 0; i < entries.length; i++) {
|
|
242
|
+
const entry = entries[i] ?? "";
|
|
243
|
+
if (entry.length < 4) {
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
const status = entry.substring(0, 2);
|
|
247
|
+
parsed.push({ status, path: entry.substring(3) });
|
|
248
|
+
if (status[0] === "R" || status[0] === "C") {
|
|
249
|
+
i++;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return parsed;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Every path git status knows about, including rename/copy origins.
|
|
257
|
+
* Origins are skipped by {@link parsePorcelainZ} (they have no status
|
|
258
|
+
* prefix) but still need to be staged so a deletion is not left behind
|
|
259
|
+
* when a rename is split across add batches.
|
|
260
|
+
*/
|
|
261
|
+
function collectDirtyPathsFromPorcelain(stdout: string): string[] {
|
|
262
|
+
const entries = stdout.split("\0");
|
|
263
|
+
const paths: string[] = [];
|
|
264
|
+
for (let i = 0; i < entries.length; i++) {
|
|
265
|
+
const entry = entries[i] ?? "";
|
|
266
|
+
if (entry.length < 4) {
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
const status = entry.substring(0, 2);
|
|
270
|
+
paths.push(entry.substring(3));
|
|
271
|
+
if (status[0] === "R" || status[0] === "C") {
|
|
272
|
+
i++;
|
|
273
|
+
const origin = entries[i];
|
|
274
|
+
if (origin && origin.length > 0) {
|
|
275
|
+
paths.push(origin);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return paths;
|
|
280
|
+
}
|
|
281
|
+
|
|
233
282
|
/** Properties added by Node's child_process errors. */
|
|
234
283
|
interface ExecError extends Error {
|
|
235
284
|
killed?: boolean;
|
|
@@ -384,7 +433,7 @@ export class WorkspaceGitService {
|
|
|
384
433
|
*
|
|
385
434
|
* This method is always called inside the mutex, so no git operation from
|
|
386
435
|
* our code can be concurrently holding the lock. However, an external git
|
|
387
|
-
*
|
|
436
|
+
* process (user running `git add`, IDE tooling, etc.) could legitimately
|
|
388
437
|
* hold the lock. We use `lsof` to check — if any process has the file
|
|
389
438
|
* open, we leave it alone. If no process holds it, it's stale (crashed
|
|
390
439
|
* process) and safe to remove.
|
|
@@ -151,9 +151,6 @@ import { sttFluxProviderToModelFamilyMigration } from "./150-stt-flux-provider-t
|
|
|
151
151
|
import { repairRenamedFireworksDeepseekProModelIdMigration } from "./151-repair-renamed-fireworks-deepseek-pro-model-id.js";
|
|
152
152
|
import { repairRetiredFireworksMinimaxM2p7ModelIdMigration } from "./152-repair-retired-fireworks-minimax-m2p7-model-id.js";
|
|
153
153
|
import { stripMcpPolicyFieldsMigration } from "./153-strip-mcp-policy-fields.js";
|
|
154
|
-
import { repairRetiredCodexGpt54ModelIdsMigration } from "./154-repair-retired-codex-gpt-5-4-model-ids.js";
|
|
155
|
-
import { moveFrontModelConfigToVoiceMigration } from "./155-move-front-model-config-to-voice.js";
|
|
156
|
-
import { extractWorkspaceMcpJsonMigration } from "./156-extract-workspace-mcp-json.js";
|
|
157
154
|
import { migrateToWorkspaceVolumeMigration } from "./migrate-to-workspace-volume.js";
|
|
158
155
|
import type { WorkspaceMigration } from "./types.js";
|
|
159
156
|
|
|
@@ -320,7 +317,4 @@ export const WORKSPACE_MIGRATIONS: WorkspaceMigration[] = [
|
|
|
320
317
|
repairRenamedFireworksDeepseekProModelIdMigration,
|
|
321
318
|
repairRetiredFireworksMinimaxM2p7ModelIdMigration,
|
|
322
319
|
stripMcpPolicyFieldsMigration,
|
|
323
|
-
repairRetiredCodexGpt54ModelIdsMigration,
|
|
324
|
-
moveFrontModelConfigToVoiceMigration,
|
|
325
|
-
extractWorkspaceMcpJsonMigration,
|
|
326
320
|
];
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
|
|
6
|
-
import { writeDesktopWindowTheme } from "../src/desktop/desktop-window-theme.js";
|
|
7
|
-
|
|
8
|
-
if (process.platform !== "linux") {
|
|
9
|
-
throw new Error(
|
|
10
|
-
"Run in a disposable Linux environment with Xvfb, Openbox, xterm, xdotool, xwininfo and xmllint installed",
|
|
11
|
-
);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const directory = await mkdtemp(join(tmpdir(), "window-theme-smoke-"));
|
|
15
|
-
const env = { ...process.env, DISPLAY: ":97" };
|
|
16
|
-
const children: ReturnType<typeof Bun.spawn>[] = [];
|
|
17
|
-
const start = (cmd: string[]) => {
|
|
18
|
-
const child = Bun.spawn(cmd, {
|
|
19
|
-
env,
|
|
20
|
-
stdout: "ignore",
|
|
21
|
-
stderr: "inherit",
|
|
22
|
-
windowsHide: true,
|
|
23
|
-
});
|
|
24
|
-
children.push(child);
|
|
25
|
-
return child;
|
|
26
|
-
};
|
|
27
|
-
const run = (...cmd: string[]) => {
|
|
28
|
-
const result = Bun.spawnSync(cmd, {
|
|
29
|
-
env,
|
|
30
|
-
|
|
31
|
-
windowsHide: true,
|
|
32
|
-
});
|
|
33
|
-
assert.equal(result.exitCode, 0, result.stderr.toString());
|
|
34
|
-
return result.stdout.toString();
|
|
35
|
-
};
|
|
36
|
-
const waitFor = async (predicate: () => boolean) => {
|
|
37
|
-
for (let attempt = 0; attempt < 100; attempt++) {
|
|
38
|
-
try {
|
|
39
|
-
if (predicate()) {
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
} catch {}
|
|
43
|
-
await Bun.sleep(50);
|
|
44
|
-
}
|
|
45
|
-
throw new Error("Window theme smoke condition timed out");
|
|
46
|
-
};
|
|
47
|
-
const pointer = async (...args: string[]) => {
|
|
48
|
-
run("xdotool", ...args);
|
|
49
|
-
await Bun.sleep(200);
|
|
50
|
-
};
|
|
51
|
-
const geometry = () => {
|
|
52
|
-
const info = run("xwininfo", "-name", "Theme smoke");
|
|
53
|
-
return [
|
|
54
|
-
"Absolute upper-left X",
|
|
55
|
-
"Absolute upper-left Y",
|
|
56
|
-
"Width",
|
|
57
|
-
"Height",
|
|
58
|
-
].map((label) =>
|
|
59
|
-
Number(new RegExp(`${label}:\\s+(-?\\d+)`).exec(info)?.[1]),
|
|
60
|
-
) as [number, number, number, number];
|
|
61
|
-
};
|
|
62
|
-
const state = () => run("xprop", "-name", "Theme smoke", "_NET_WM_STATE");
|
|
63
|
-
const button = async (offset: number) => {
|
|
64
|
-
const [x, y, width] = geometry();
|
|
65
|
-
await pointer(
|
|
66
|
-
"mousemove",
|
|
67
|
-
String(x + width - offset),
|
|
68
|
-
String(y - 16),
|
|
69
|
-
"click",
|
|
70
|
-
"1",
|
|
71
|
-
);
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
try {
|
|
75
|
-
const home = join(directory, "home");
|
|
76
|
-
const sourceDir = join(home, ".config", "openbox");
|
|
77
|
-
await mkdir(sourceDir, { recursive: true });
|
|
78
|
-
const original = await readFile("/etc/xdg/openbox/rc.xml", "utf8");
|
|
79
|
-
const mouse = original.match(/<mouse>[^]*?<\/mouse>/)![0];
|
|
80
|
-
const keyboard = original.match(/<keyboard>[^]*?<\/keyboard>/)![0];
|
|
81
|
-
const source = original
|
|
82
|
-
.replace(
|
|
83
|
-
mouse,
|
|
84
|
-
`<xi:include href="bindings.xml" xpointer="xpointer(/*/*)"/>`,
|
|
85
|
-
)
|
|
86
|
-
.replace(keyboard, "");
|
|
87
|
-
const sourcePath = join(sourceDir, "rc.xml");
|
|
88
|
-
await writeFile(sourcePath, source);
|
|
89
|
-
await writeFile(
|
|
90
|
-
join(sourceDir, "bindings.xml"),
|
|
91
|
-
`<bindings xmlns="http://openbox.org/3.4/rc">${mouse}${keyboard}</bindings>`,
|
|
92
|
-
);
|
|
93
|
-
const configDir = join(directory, "managed & theme");
|
|
94
|
-
const config = writeDesktopWindowTheme(configDir, home, "#53b7aa");
|
|
95
|
-
const expanded = run("xmllint", "--xinclude", config);
|
|
96
|
-
assert(
|
|
97
|
-
expanded.includes("NextWindow") && expanded.includes("Move"),
|
|
98
|
-
"Relative includes must retain the actual keyboard and mouse bindings",
|
|
99
|
-
);
|
|
100
|
-
assert.equal(await readFile(sourcePath, "utf8"), source);
|
|
101
|
-
writeDesktopWindowTheme(configDir, home, "#bc7799");
|
|
102
|
-
writeDesktopWindowTheme(configDir, home, "#53b7aa");
|
|
103
|
-
assert.equal(await readFile(sourcePath, "utf8"), source);
|
|
104
|
-
const blocked = join(directory, "blocked");
|
|
105
|
-
await writeFile(blocked, "not a directory");
|
|
106
|
-
assert.throws(() => writeDesktopWindowTheme(blocked, home));
|
|
107
|
-
assert.equal(await readFile(sourcePath, "utf8"), source);
|
|
108
|
-
const customBase = source.replace(
|
|
109
|
-
"<openbox_config",
|
|
110
|
-
'<openbox_config xml:base="./custom/"',
|
|
111
|
-
);
|
|
112
|
-
await writeFile(sourcePath, customBase);
|
|
113
|
-
assert.throws(() => writeDesktopWindowTheme(configDir, home));
|
|
114
|
-
assert.equal(await readFile(sourcePath, "utf8"), customBase);
|
|
115
|
-
await writeFile(sourcePath, source);
|
|
116
|
-
|
|
117
|
-
start(["Xvfb", ":97", "-screen", "0", "1100x700x24"]);
|
|
118
|
-
await waitFor(
|
|
119
|
-
() => run("xdotool", "getdisplaygeometry").trim() === "1100 700",
|
|
120
|
-
);
|
|
121
|
-
start(["openbox", "--config-file", config]);
|
|
122
|
-
await waitFor(() =>
|
|
123
|
-
run("xprop", "-root", "_OB_THEME").includes("window-theme"),
|
|
124
|
-
);
|
|
125
|
-
start([
|
|
126
|
-
"xterm",
|
|
127
|
-
"-title",
|
|
128
|
-
"Theme smoke",
|
|
129
|
-
"-geometry",
|
|
130
|
-
"48x12+450+250",
|
|
131
|
-
"-e",
|
|
132
|
-
"sleep",
|
|
133
|
-
"60",
|
|
134
|
-
]);
|
|
135
|
-
await waitFor(
|
|
136
|
-
() => run("xdotool", "search", "--name", "^Theme smoke$").trim().length > 0,
|
|
137
|
-
);
|
|
138
|
-
await Bun.sleep(300);
|
|
139
|
-
const id = run("xdotool", "search", "--name", "^Theme smoke$").trim();
|
|
140
|
-
await button(39);
|
|
141
|
-
assert(
|
|
142
|
-
state().includes("MAXIMIZED_HORZ") && state().includes("MAXIMIZED_VERT"),
|
|
143
|
-
);
|
|
144
|
-
await button(39);
|
|
145
|
-
assert(!state().includes("MAXIMIZED"));
|
|
146
|
-
await button(62);
|
|
147
|
-
assert(state().includes("HIDDEN"));
|
|
148
|
-
await pointer("windowactivate", id);
|
|
149
|
-
assert(!state().includes("HIDDEN"));
|
|
150
|
-
let [x, y, width, height] = geometry();
|
|
151
|
-
await pointer("mousemove", String(x + 100), String(y - 16), "mousedown", "1");
|
|
152
|
-
await pointer("mousemove", String(x + 90), String(y - 26));
|
|
153
|
-
await pointer("mousemove", String(x + 45), String(y - 65));
|
|
154
|
-
await pointer("mouseup", "1");
|
|
155
|
-
const [movedX, movedY] = geometry();
|
|
156
|
-
assert(Math.abs(movedX - x) >= 30 && Math.abs(movedY - y) >= 30);
|
|
157
|
-
[x, y, width, height] = geometry();
|
|
158
|
-
await pointer(
|
|
159
|
-
"mousemove",
|
|
160
|
-
String(x + width - 2),
|
|
161
|
-
String(y + height + 2),
|
|
162
|
-
"mousedown",
|
|
163
|
-
"1",
|
|
164
|
-
);
|
|
165
|
-
await pointer("mousemove", String(x + width + 10), String(y + height + 10));
|
|
166
|
-
await pointer("mousemove", String(x + width + 60), String(y + height + 40));
|
|
167
|
-
await pointer("mouseup", "1");
|
|
168
|
-
const [, , resizedWidth, resizedHeight] = geometry();
|
|
169
|
-
assert(resizedWidth > width && resizedHeight > height);
|
|
170
|
-
await pointer("key", "alt+F4");
|
|
171
|
-
await waitFor(
|
|
172
|
-
() =>
|
|
173
|
-
!run("xprop", "-root", "_NET_CLIENT_LIST").includes(
|
|
174
|
-
`0x${Number(id).toString(16)}`,
|
|
175
|
-
),
|
|
176
|
-
);
|
|
177
|
-
console.log(
|
|
178
|
-
"PASS: relative includes, preserved source, repeated theme updates, maximize/restore, minimize/activation, dragging, resizing, and keyboard close",
|
|
179
|
-
);
|
|
180
|
-
} finally {
|
|
181
|
-
for (const child of children.reverse()) {
|
|
182
|
-
child.kill();
|
|
183
|
-
}
|
|
184
|
-
await Promise.all(children.map((child) => child.exited));
|
|
185
|
-
await rm(directory, { recursive: true, force: true });
|
|
186
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
collectDirtyPathsFromPorcelain,
|
|
5
|
-
parsePorcelainZ,
|
|
6
|
-
} from "../workspace/git-porcelain.js";
|
|
7
|
-
|
|
8
|
-
describe("parsePorcelainZ", () => {
|
|
9
|
-
test("parses ordinary modified and untracked records", () => {
|
|
10
|
-
expect(parsePorcelainZ("M tracked.txt\0?? new.txt\0")).toEqual([
|
|
11
|
-
{ status: "M ", path: "tracked.txt" },
|
|
12
|
-
{ status: "??", path: "new.txt" },
|
|
13
|
-
]);
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
test("captures rename origin on the same record", () => {
|
|
17
|
-
expect(parsePorcelainZ("R dest.txt\0src.txt\0")).toEqual([
|
|
18
|
-
{ status: "R ", path: "dest.txt", origin: "src.txt" },
|
|
19
|
-
]);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
test("captures copy origin on the same record", () => {
|
|
23
|
-
expect(parsePorcelainZ("C dest.txt\0src.txt\0")).toEqual([
|
|
24
|
-
{ status: "C ", path: "dest.txt", origin: "src.txt" },
|
|
25
|
-
]);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test("skips short leftover NUL entries", () => {
|
|
29
|
-
expect(parsePorcelainZ("M a.txt\0\0")).toEqual([
|
|
30
|
-
{ status: "M ", path: "a.txt" },
|
|
31
|
-
]);
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
describe("collectDirtyPathsFromPorcelain", () => {
|
|
36
|
-
test("flattens rename origin so both paths are staged", () => {
|
|
37
|
-
expect(
|
|
38
|
-
collectDirtyPathsFromPorcelain("R dest.txt\0src.txt\0?? extra.txt\0"),
|
|
39
|
-
).toEqual(["dest.txt", "src.txt", "extra.txt"]);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test("dedupes a path that appears as both origin and destination", () => {
|
|
43
|
-
expect(collectDirtyPathsFromPorcelain("R same.txt\0same.txt\0")).toEqual([
|
|
44
|
-
"same.txt",
|
|
45
|
-
]);
|
|
46
|
-
});
|
|
47
|
-
});
|