@oh-my-pi/pi-coding-agent 17.0.8 → 17.1.0
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/CHANGELOG.md +87 -1
- package/README.md +1 -1
- package/dist/cli.js +5495 -5021
- package/dist/types/async/job-manager.d.ts +37 -1
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/bench-cli.d.ts +41 -0
- package/dist/types/commands/bench.d.ts +15 -3
- package/dist/types/commands/launch.d.ts +4 -0
- package/dist/types/config/__tests__/model-registry.test.d.ts +1 -0
- package/dist/types/config/model-registry.d.ts +30 -1
- package/dist/types/config/models-config-schema.d.ts +18 -6
- package/dist/types/config/models-config.d.ts +14 -5
- package/dist/types/config/provider-globals.d.ts +2 -2
- package/dist/types/config/service-tier.d.ts +5 -1
- package/dist/types/config/settings-schema.d.ts +350 -50
- package/dist/types/config/settings.d.ts +7 -0
- package/dist/types/dap/client.d.ts +20 -0
- package/dist/types/exec/bash-executor.d.ts +38 -0
- package/dist/types/exec/direnv.d.ts +33 -0
- package/dist/types/export/html/args.d.ts +15 -0
- package/dist/types/export/html/index.d.ts +11 -26
- package/dist/types/export/html/web-palette.d.ts +55 -116
- package/dist/types/extensibility/extensions/loader.d.ts +3 -0
- package/dist/types/extensibility/extensions/types.d.ts +17 -2
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +1 -0
- package/dist/types/extensibility/legacy-pi-tui-shim.d.ts +10 -0
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +14 -1
- package/dist/types/internal-urls/registry-helpers.d.ts +11 -0
- package/dist/types/live/controller.d.ts +40 -0
- package/dist/types/live/protocol.d.ts +85 -0
- package/dist/types/live/protocol.test.d.ts +1 -0
- package/dist/types/live/transport.d.ts +48 -0
- package/dist/types/live/visualizer.d.ts +33 -0
- package/dist/types/lsp/client.d.ts +18 -1
- package/dist/types/mcp/config-writer.test.d.ts +1 -0
- package/dist/types/mcp/manager.d.ts +6 -2
- package/dist/types/mcp/oauth-discovery.d.ts +2 -0
- package/dist/types/mcp/render.d.ts +2 -2
- package/dist/types/mcp/smithery-auth.d.ts +1 -1
- package/dist/types/mcp/smithery-auth.test.d.ts +1 -0
- package/dist/types/mcp/tool-bridge.d.ts +7 -3
- package/dist/types/mcp/types.d.ts +6 -0
- package/dist/types/memory-backend/tool-names.d.ts +2 -0
- package/dist/types/modes/components/login-dialog.d.ts +2 -2
- package/dist/types/modes/components/oauth-selector.d.ts +8 -0
- package/dist/types/modes/components/plan-review-overlay.d.ts +26 -0
- package/dist/types/modes/components/settings-defs.d.ts +8 -1
- package/dist/types/modes/components/status-line/component.jj-cache.test.d.ts +1 -0
- package/dist/types/modes/components/tool-execution.d.ts +9 -1
- package/dist/types/modes/controllers/command-controller.d.ts +1 -1
- package/dist/types/modes/controllers/event-controller.d.ts +6 -1
- package/dist/types/modes/controllers/live-command-controller.d.ts +14 -0
- package/dist/types/modes/controllers/mcp-command-controller.d.ts +3 -0
- package/dist/types/modes/interactive-mode.d.ts +5 -0
- package/dist/types/modes/rpc/rpc-client.d.ts +10 -1
- package/dist/types/modes/rpc/rpc-frame.d.ts +16 -0
- package/dist/types/modes/rpc/rpc-messages.d.ts +26 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +40 -0
- package/dist/types/modes/setup-wizard/scenes/sign-in.d.ts +1 -1
- package/dist/types/modes/setup-wizard/scenes/types.d.ts +9 -1
- package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +1 -1
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +3 -1
- package/dist/types/modes/utils/context-usage.d.ts +12 -2
- package/dist/types/plan-mode/plan-files.d.ts +10 -0
- package/dist/types/registry/agent-lifecycle.d.ts +21 -9
- package/dist/types/registry/agent-registry.d.ts +11 -4
- package/dist/types/sdk.d.ts +15 -1
- package/dist/types/secrets/index.d.ts +15 -2
- package/dist/types/secrets/obfuscator.d.ts +113 -15
- package/dist/types/session/acp-permission-gate.d.ts +19 -0
- package/dist/types/session/agent-session-events.d.ts +84 -0
- package/dist/types/session/agent-session-types.d.ts +314 -0
- package/dist/types/session/agent-session.d.ts +120 -750
- package/dist/types/session/async-job-delivery.d.ts +28 -0
- package/dist/types/session/bash-runner.d.ts +75 -0
- package/dist/types/session/blob-store.d.ts +11 -1
- package/dist/types/session/blob-store.test.d.ts +1 -0
- package/dist/types/session/checkpoint-entries.d.ts +28 -0
- package/dist/types/session/eval-runner.d.ts +49 -0
- package/dist/types/session/irc-bridge.d.ts +46 -0
- package/dist/types/session/messages.d.ts +22 -0
- package/dist/types/session/model-controls.d.ts +180 -0
- package/dist/types/session/prewalk.d.ts +46 -0
- package/dist/types/session/queued-messages.d.ts +22 -0
- package/dist/types/session/redis-session-storage.d.ts +1 -0
- package/dist/types/session/retry-fallback-chains.d.ts +74 -0
- package/dist/types/session/role-models.d.ts +15 -0
- package/dist/types/session/session-advisors.d.ts +228 -0
- package/dist/types/session/session-entries.d.ts +8 -0
- package/dist/types/session/session-handoff.d.ts +70 -0
- package/dist/types/session/session-history-format.d.ts +6 -1
- package/dist/types/session/session-loader.d.ts +6 -4
- package/dist/types/session/session-maintenance.d.ts +246 -0
- package/dist/types/session/session-manager.d.ts +41 -0
- package/dist/types/session/session-memory.d.ts +56 -0
- package/dist/types/session/session-provider-boundary.d.ts +62 -0
- package/dist/types/session/session-stats.d.ts +48 -0
- package/dist/types/session/session-tools.d.ts +143 -0
- package/dist/types/session/session-workspace.d.ts +32 -0
- package/dist/types/session/stream-guards.d.ts +44 -0
- package/dist/types/session/todo-tracker.d.ts +56 -0
- package/dist/types/session/ttsr-coordinator.d.ts +51 -0
- package/dist/types/session/turn-recovery.d.ts +225 -0
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/task/executor.d.ts +4 -1
- package/dist/types/task/output-manager.d.ts +3 -1
- package/dist/types/task/types.d.ts +14 -0
- package/dist/types/tools/approval.d.ts +1 -0
- package/dist/types/tools/browser/launch.d.ts +5 -0
- package/dist/types/tools/hub/jobs.d.ts +1 -1
- package/dist/types/tools/image-gen.d.ts +4 -4
- package/dist/types/tools/image-providers.d.ts +38 -0
- package/dist/types/tools/index.d.ts +8 -0
- package/dist/types/tools/path-utils.d.ts +8 -0
- package/dist/types/tools/report-tool-issue.d.ts +24 -9
- package/dist/types/tools/todo.d.ts +10 -4
- package/dist/types/tools/xdev.d.ts +5 -1
- package/dist/types/tts/streaming-player.d.ts +14 -0
- package/dist/types/tts/vocalizer.d.ts +5 -0
- package/dist/types/utils/jj.d.ts +29 -0
- package/dist/types/utils/lang-from-path.d.ts +1 -0
- package/dist/types/utils/title-generator.d.ts +30 -0
- package/dist/types/vibe/runtime.d.ts +36 -8
- package/dist/types/web/search/index.d.ts +1 -1
- package/dist/types/web/search/provider.d.ts +15 -5
- package/dist/types/web/search/providers/firecrawl.d.ts +9 -0
- package/dist/types/web/search/types.d.ts +95 -1
- package/package.json +13 -12
- package/scripts/generate-share-viewer.ts +4 -6
- package/scripts/legacy-pi-virtual-module.ts +1 -1
- package/src/advisor/__tests__/advisor.test.ts +643 -0
- package/src/advisor/runtime.ts +144 -43
- package/src/async/job-manager.ts +90 -2
- package/src/cli/args.ts +2 -0
- package/src/cli/auth-gateway-cli.ts +18 -3
- package/src/cli/bench-cli.ts +395 -29
- package/src/cli/flag-tables.ts +3 -0
- package/src/cli/gc-cli.ts +2 -2
- package/src/cli/grievances-cli.ts +2 -2
- package/src/cli/usage-cli.ts +11 -2
- package/src/collab/host.ts +3 -2
- package/src/commands/bench.ts +18 -4
- package/src/commands/launch.ts +4 -0
- package/src/config/__tests__/model-registry.test.ts +147 -0
- package/src/config/model-registry.ts +50 -2
- package/src/config/model-resolver.ts +0 -1
- package/src/config/models-config-schema.ts +15 -4
- package/src/config/provider-globals.ts +9 -9
- package/src/config/service-tier.ts +26 -1
- package/src/config/settings-schema.ts +250 -54
- package/src/config/settings.ts +81 -2
- package/src/dap/client.ts +37 -4
- package/src/edit/index.ts +49 -12
- package/src/eval/py/executor.ts +103 -33
- package/src/exec/bash-executor.ts +89 -6
- package/src/exec/direnv.ts +145 -0
- package/src/export/html/args.ts +20 -0
- package/src/export/html/index.ts +45 -47
- package/src/export/html/template.css +19 -1
- package/src/export/html/template.html +6 -0
- package/src/export/html/template.js +21 -0
- package/src/export/html/tool-views.generated.js +31 -31
- package/src/export/html/web-palette.ts +116 -132
- package/src/export/share.ts +249 -49
- package/src/extensibility/extensions/loader.ts +29 -1
- package/src/extensibility/extensions/runner.ts +6 -0
- package/src/extensibility/extensions/types.ts +33 -2
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +1 -0
- package/src/extensibility/legacy-pi-tui-shim.ts +10 -0
- package/src/extensibility/plugins/legacy-pi-compat.ts +870 -311
- package/src/hindsight/state.ts +64 -6
- package/src/internal-urls/local-protocol.ts +2 -1
- package/src/internal-urls/memory-protocol.ts +2 -2
- package/src/internal-urls/registry-helpers.ts +40 -0
- package/src/internal-urls/skill-protocol.ts +2 -2
- package/src/internal-urls/ssh-protocol.ts +2 -1
- package/src/internal-urls/vault-protocol.ts +2 -1
- package/src/live/audio-worklet.txt +59 -0
- package/src/live/browser-runtime.txt +221 -0
- package/src/live/controller.ts +464 -0
- package/src/live/prompts/agent-final-message.md +3 -0
- package/src/live/prompts/live-instructions.md +23 -0
- package/src/live/protocol.test.ts +140 -0
- package/src/live/protocol.ts +233 -0
- package/src/live/transport.ts +490 -0
- package/src/live/visualizer.ts +234 -0
- package/src/lsp/client.ts +52 -9
- package/src/lsp/clients/biome-client.ts +3 -4
- package/src/lsp/index.ts +44 -11
- package/src/main.ts +9 -1
- package/src/mcp/config-writer.test.ts +43 -0
- package/src/mcp/config-writer.ts +109 -82
- package/src/mcp/manager.ts +39 -8
- package/src/mcp/oauth-discovery.ts +10 -2
- package/src/mcp/render.ts +94 -35
- package/src/mcp/smithery-auth.test.ts +29 -0
- package/src/mcp/smithery-auth.ts +3 -2
- package/src/mcp/tool-bridge.ts +107 -11
- package/src/mcp/types.ts +7 -0
- package/src/memories/index.ts +40 -20
- package/src/memory-backend/tool-names.ts +2 -0
- package/src/mnemopi/backend.ts +24 -7
- package/src/modes/acp/acp-agent.ts +56 -10
- package/src/modes/acp/acp-event-mapper.ts +8 -1
- package/src/modes/components/agent-hub.ts +27 -10
- package/src/modes/components/login-dialog.ts +14 -4
- package/src/modes/components/oauth-selector.ts +24 -7
- package/src/modes/components/plan-review-overlay.ts +350 -35
- package/src/modes/components/settings-defs.ts +28 -3
- package/src/modes/components/settings-selector.ts +178 -6
- package/src/modes/components/status-line/component.jj-cache.test.ts +229 -0
- package/src/modes/components/status-line/component.ts +123 -2
- package/src/modes/components/tool-execution.test.ts +63 -2
- package/src/modes/components/tool-execution.ts +11 -2
- package/src/modes/controllers/command-controller.ts +70 -22
- package/src/modes/controllers/event-controller.ts +111 -16
- package/src/modes/controllers/extension-ui-controller.test.ts +1 -0
- package/src/modes/controllers/extension-ui-controller.ts +20 -4
- package/src/modes/controllers/live-command-controller.ts +178 -0
- package/src/modes/controllers/mcp-command-controller.ts +80 -41
- package/src/modes/controllers/omfg-controller.ts +44 -40
- package/src/modes/controllers/selector-controller.ts +18 -10
- package/src/modes/controllers/session-focus-controller.ts +6 -1
- package/src/modes/interactive-mode.ts +160 -38
- package/src/modes/rpc/rpc-client.ts +94 -3
- package/src/modes/rpc/rpc-frame.ts +164 -4
- package/src/modes/rpc/rpc-messages.ts +127 -0
- package/src/modes/rpc/rpc-mode.ts +79 -7
- package/src/modes/rpc/rpc-types.ts +34 -2
- package/src/modes/runtime-init.ts +2 -0
- package/src/modes/setup-wizard/scenes/model.ts +5 -7
- package/src/modes/setup-wizard/scenes/providers.ts +3 -2
- package/src/modes/setup-wizard/scenes/sign-in.ts +8 -2
- package/src/modes/setup-wizard/scenes/theme.ts +13 -3
- package/src/modes/setup-wizard/scenes/types.ts +9 -1
- package/src/modes/setup-wizard/scenes/web-search.ts +23 -10
- package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
- package/src/modes/theme/theme.ts +1 -1
- package/src/modes/types.ts +3 -1
- package/src/modes/utils/context-usage.ts +23 -7
- package/src/modes/utils/ui-helpers.ts +10 -5
- package/src/plan-mode/plan-files.ts +40 -0
- package/src/prompts/bench/cache-prefix-chunk.md +1 -0
- package/src/prompts/bench/cache-prefix.md +3 -0
- package/src/prompts/bench/cache-suffix.md +1 -0
- package/src/prompts/goals/guided-goal-system.md +24 -3
- package/src/prompts/system/custom-system-prompt.md +1 -1
- package/src/prompts/system/project-prompt.md +8 -1
- package/src/prompts/system/subagent-async-pending.md +6 -0
- package/src/prompts/system/system-prompt.md +1 -1
- package/src/prompts/system/workflow-notice.md +48 -40
- package/src/prompts/system/xdev-mount-notice.md +4 -0
- package/src/prompts/tools/task.md +12 -2
- package/src/prompts/tools/todo.md +3 -1
- package/src/registry/agent-lifecycle.ts +116 -46
- package/src/registry/agent-registry.ts +35 -8
- package/src/registry/persisted-agents.ts +26 -2
- package/src/sdk.ts +255 -139
- package/src/secrets/index.ts +127 -4
- package/src/secrets/obfuscator.ts +2328 -105
- package/src/session/acp-permission-gate.ts +165 -0
- package/src/session/agent-session-error-log.test.ts +1 -1
- package/src/session/agent-session-events.ts +66 -0
- package/src/session/agent-session-types.ts +334 -0
- package/src/session/agent-session.ts +1910 -12213
- package/src/session/artifacts.ts +6 -5
- package/src/session/async-job-delivery.ts +74 -0
- package/src/session/bash-runner.ts +326 -0
- package/src/session/blob-store.test.ts +56 -0
- package/src/session/blob-store.ts +18 -2
- package/src/session/checkpoint-entries.ts +81 -0
- package/src/session/eval-runner.ts +212 -0
- package/src/session/indexed-session-storage.ts +9 -2
- package/src/session/irc-bridge.ts +203 -0
- package/src/session/messages.ts +263 -1
- package/src/session/model-controls.ts +714 -0
- package/src/session/prewalk.ts +252 -0
- package/src/session/queued-messages.ts +93 -0
- package/src/session/redis-session-storage.ts +51 -11
- package/src/session/retry-fallback-chains.ts +455 -0
- package/src/session/role-models.ts +85 -0
- package/src/session/session-advisors.ts +1679 -0
- package/src/session/session-context.test.ts +224 -1
- package/src/session/session-context.ts +47 -5
- package/src/session/session-entries.ts +8 -0
- package/src/session/session-handoff.ts +308 -0
- package/src/session/session-history-format.ts +20 -9
- package/src/session/session-loader.ts +10 -51
- package/src/session/session-maintenance.ts +2983 -0
- package/src/session/session-manager.ts +390 -30
- package/src/session/session-memory.ts +222 -0
- package/src/session/session-provider-boundary.ts +307 -0
- package/src/session/session-stats.ts +293 -0
- package/src/session/session-tools.ts +943 -0
- package/src/session/session-workspace.ts +53 -0
- package/src/session/stream-guards.ts +417 -0
- package/src/session/todo-tracker.ts +380 -0
- package/src/session/ttsr-coordinator.ts +496 -0
- package/src/session/turn-recovery.ts +1629 -0
- package/src/slash-commands/builtin-registry.ts +92 -11
- package/src/slash-commands/helpers/usage-report.ts +25 -4
- package/src/system-prompt.ts +41 -17
- package/src/task/executor.ts +288 -70
- package/src/task/index.ts +148 -111
- package/src/task/output-manager.ts +25 -3
- package/src/task/persisted-revive.ts +4 -3
- package/src/task/structured-subagent.ts +4 -1
- package/src/task/types.ts +16 -0
- package/src/tools/approval.ts +55 -10
- package/src/tools/bash-interactive.ts +26 -0
- package/src/tools/bash-skill-urls.ts +28 -2
- package/src/tools/bash.ts +339 -123
- package/src/tools/browser/launch.ts +9 -1
- package/src/tools/browser/tab-supervisor.ts +8 -5
- package/src/tools/browser.ts +94 -47
- package/src/tools/hub/index.ts +1 -1
- package/src/tools/hub/jobs.ts +67 -8
- package/src/tools/image-gen.ts +20 -28
- package/src/tools/image-providers.ts +50 -0
- package/src/tools/index.ts +12 -0
- package/src/tools/path-utils.ts +49 -2
- package/src/tools/read.ts +156 -88
- package/src/tools/renderers.ts +7 -1
- package/src/tools/report-tool-issue.ts +79 -28
- package/src/tools/todo.ts +101 -11
- package/src/tools/vibe.ts +1 -2
- package/src/tools/write.ts +51 -1
- package/src/tools/xdev.ts +67 -3
- package/src/tts/streaming-player.ts +62 -4
- package/src/tts/vocalizer.ts +18 -1
- package/src/utils/jj.ts +125 -4
- package/src/utils/lang-from-path.ts +7 -0
- package/src/utils/title-generator.ts +137 -1
- package/src/vibe/runtime.ts +852 -72
- package/src/web/search/index.ts +3 -6
- package/src/web/search/provider.ts +31 -16
- package/src/web/search/providers/firecrawl.ts +46 -13
- package/src/web/search/providers/xai.ts +7 -1
- package/src/web/search/types.ts +8 -1
|
@@ -55,7 +55,7 @@ import { reset as resetCapabilities } from "../capability";
|
|
|
55
55
|
import type { CollabGuestLink } from "../collab/guest";
|
|
56
56
|
import type { CollabHost } from "../collab/host";
|
|
57
57
|
import { KeybindingsManager } from "../config/keybindings";
|
|
58
|
-
import type
|
|
58
|
+
import { formatModelString, type ResolvedModelRoleValue } from "../config/model-resolver";
|
|
59
59
|
import { applyProviderGlobalsFromSettings } from "../config/provider-globals";
|
|
60
60
|
import {
|
|
61
61
|
isSettingsInitialized,
|
|
@@ -130,8 +130,19 @@ import type { EventBus } from "../utils/event-bus";
|
|
|
130
130
|
import { getEditorCommand, openInEditor } from "../utils/external-editor";
|
|
131
131
|
import { getSessionAccentAnsi, getSessionAccentHex } from "../utils/session-color";
|
|
132
132
|
import { messageHasDisplayableThinking } from "../utils/thinking-display";
|
|
133
|
-
import {
|
|
134
|
-
|
|
133
|
+
import {
|
|
134
|
+
disposeTerminalTitleState,
|
|
135
|
+
popTerminalTitle,
|
|
136
|
+
pushTerminalTitle,
|
|
137
|
+
setSessionTerminalTitle,
|
|
138
|
+
setTerminalTitleStateEnabled,
|
|
139
|
+
} from "../utils/title-generator";
|
|
140
|
+
import {
|
|
141
|
+
aggregateVibeWorkerTokensPerSecond,
|
|
142
|
+
type VibeOwnerScope,
|
|
143
|
+
type VibeParentSession,
|
|
144
|
+
VibeSessionRegistry,
|
|
145
|
+
} from "../vibe/runtime";
|
|
135
146
|
import type { AssistantMessageComponent } from "./components/assistant-message";
|
|
136
147
|
import type { BashExecutionComponent } from "./components/bash-execution";
|
|
137
148
|
import { ChatBlock, type ChatBlockHost } from "./components/chat-block";
|
|
@@ -142,7 +153,7 @@ import type { EvalExecutionComponent } from "./components/eval-execution";
|
|
|
142
153
|
import type { HookEditorComponent } from "./components/hook-editor";
|
|
143
154
|
import type { HookInputComponent } from "./components/hook-input";
|
|
144
155
|
import type { HookSelectorComponent, HookSelectorSlider } from "./components/hook-selector";
|
|
145
|
-
import { PlanReviewOverlay } from "./components/plan-review-overlay";
|
|
156
|
+
import { type PlanReviewAnnotationState, PlanReviewOverlay } from "./components/plan-review-overlay";
|
|
146
157
|
import { StatusLineComponent } from "./components/status-line";
|
|
147
158
|
import type { ToolExecutionHandle } from "./components/tool-execution";
|
|
148
159
|
import { TranscriptContainer } from "./components/transcript-container";
|
|
@@ -152,6 +163,7 @@ import { CommandController } from "./controllers/command-controller";
|
|
|
152
163
|
import { EventController } from "./controllers/event-controller";
|
|
153
164
|
import { ExtensionUiController } from "./controllers/extension-ui-controller";
|
|
154
165
|
import { InputController } from "./controllers/input-controller";
|
|
166
|
+
import { LiveCommandController } from "./controllers/live-command-controller";
|
|
155
167
|
import { MCPCommandController } from "./controllers/mcp-command-controller";
|
|
156
168
|
import { OmfgController } from "./controllers/omfg-controller";
|
|
157
169
|
import { SelectorController } from "./controllers/selector-controller";
|
|
@@ -543,6 +555,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
543
555
|
#planModePreviousTools: string[] | undefined;
|
|
544
556
|
#goalModePreviousTools: string[] | undefined;
|
|
545
557
|
#vibeModePreviousTools: string[] | undefined;
|
|
558
|
+
#vibeModeOwnerScope: VibeOwnerScope | undefined;
|
|
559
|
+
#vibeScopeSuspendedForSwitch = false;
|
|
546
560
|
#goalContinuationTimer: NodeJS.Timeout | undefined;
|
|
547
561
|
#goalTurnHadToolCalls = false;
|
|
548
562
|
#goalContinuationTurnInFlight = false;
|
|
@@ -555,6 +569,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
555
569
|
#planReviewOverlay: PlanReviewOverlay | undefined;
|
|
556
570
|
#planReviewOverlayHandle: OverlayHandle | undefined;
|
|
557
571
|
#planReviewCancel: (() => void) | undefined;
|
|
572
|
+
/** Serializable review annotations keyed by the resolved plan file path. */
|
|
573
|
+
#planReviewAnnotationState = new Map<string, PlanReviewAnnotationState>();
|
|
574
|
+
/** Annotation state held until the associated queued refinement actually starts. */
|
|
575
|
+
#planReviewAnnotationStateBySubmission = new WeakMap<SubmittedUserInput, string>();
|
|
558
576
|
readonly lspServers: LspStartupServerInfo[] | undefined = undefined;
|
|
559
577
|
mcpManager?: MCPManager;
|
|
560
578
|
readonly #toolUiContextSetter: (uiContext: ExtensionUIContext, hasUI: boolean) => void;
|
|
@@ -564,6 +582,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
564
582
|
readonly #omfgController: OmfgController;
|
|
565
583
|
readonly #commandController: CommandController;
|
|
566
584
|
readonly #todoCommandController: TodoCommandController;
|
|
585
|
+
readonly #liveCommandController: LiveCommandController;
|
|
567
586
|
readonly #eventController: EventController;
|
|
568
587
|
get eventController(): EventController {
|
|
569
588
|
return this.#eventController;
|
|
@@ -656,6 +675,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
656
675
|
this.#toolUiContextSetter = setToolUIContext;
|
|
657
676
|
this.lspServers = lspServers;
|
|
658
677
|
this.mcpManager = mcpManager;
|
|
678
|
+
this.mcpManager?.setAuthHandler((serverName, challenge) =>
|
|
679
|
+
new MCPCommandController(this).handleMCPAuthChallenge(serverName, challenge),
|
|
680
|
+
);
|
|
659
681
|
this.#eventBus = eventBus;
|
|
660
682
|
if (eventBus) {
|
|
661
683
|
this.#eventBusUnsubscribers.push(
|
|
@@ -770,6 +792,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
770
792
|
this.#eventController = new EventController(this);
|
|
771
793
|
this.#commandController = new CommandController(this);
|
|
772
794
|
this.#todoCommandController = new TodoCommandController(this);
|
|
795
|
+
this.#liveCommandController = new LiveCommandController(this);
|
|
773
796
|
this.#selectorController = new SelectorController(this);
|
|
774
797
|
this.#focusController = new SessionFocusController(this);
|
|
775
798
|
this.#inputController = new InputController(this);
|
|
@@ -973,6 +996,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
973
996
|
// the initial welcome frame does not append over the previous run's scrollback.
|
|
974
997
|
this.ui.start({ clearScrollback: options.clearInitialTerminalHistory === true });
|
|
975
998
|
pushTerminalTitle();
|
|
999
|
+
setTerminalTitleStateEnabled(this.settings.get("tui.titleState"));
|
|
976
1000
|
setSessionTerminalTitle(this.sessionManager.getSessionName(), this.sessionManager.getCwd());
|
|
977
1001
|
this.updateEditorBorderColor();
|
|
978
1002
|
// Single side-effect point for title changes: every setSessionName caller
|
|
@@ -994,6 +1018,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
994
1018
|
await this.initHooksAndCustomTools();
|
|
995
1019
|
|
|
996
1020
|
// Restore mode from session (e.g. plan mode on resume)
|
|
1021
|
+
this.session.setSessionBeforeSwitchReconciler?.(async () => {
|
|
1022
|
+
await this.#liveCommandController.stop();
|
|
1023
|
+
await this.#quiesceVibeForSessionSwitch();
|
|
1024
|
+
});
|
|
997
1025
|
this.session.setSessionSwitchReconciler?.(() => this.#reconcileModeFromSession({ preserveActiveGoal: true }));
|
|
998
1026
|
await this.#reconcileModeFromSession();
|
|
999
1027
|
|
|
@@ -1563,6 +1591,11 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1563
1591
|
return false;
|
|
1564
1592
|
}
|
|
1565
1593
|
input.started = true;
|
|
1594
|
+
const annotationStateKey = this.#planReviewAnnotationStateBySubmission.get(input);
|
|
1595
|
+
if (annotationStateKey) {
|
|
1596
|
+
this.#planReviewAnnotationStateBySubmission.delete(input);
|
|
1597
|
+
this.#planReviewAnnotationState.delete(annotationStateKey);
|
|
1598
|
+
}
|
|
1566
1599
|
return true;
|
|
1567
1600
|
}
|
|
1568
1601
|
|
|
@@ -1753,6 +1786,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1753
1786
|
return theme.fg("accent", `${prefix}${checkbox.unchecked} ${todo.content}`) + marker;
|
|
1754
1787
|
case "abandoned":
|
|
1755
1788
|
return theme.fg("error", `${prefix}${checkbox.unchecked} ${chalk.strikethrough(todo.content)}`) + marker;
|
|
1789
|
+
case "blocked":
|
|
1790
|
+
return theme.fg("warning", `${prefix}${checkbox.unchecked} ${todo.content} (blocked)`) + marker;
|
|
1756
1791
|
default:
|
|
1757
1792
|
if (matched) return theme.fg("accent", `${prefix}${checkbox.unchecked} ${todo.content}`) + marker;
|
|
1758
1793
|
return theme.fg("dim", `${prefix}${checkbox.unchecked} ${todo.content}`) + marker;
|
|
@@ -1772,12 +1807,15 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1772
1807
|
}
|
|
1773
1808
|
|
|
1774
1809
|
/**
|
|
1775
|
-
* Auto-complete any pending/in_progress
|
|
1776
|
-
* subagent that has finished successfully. Fires on every observer
|
|
1810
|
+
* Auto-complete any open todo (pending/in_progress/blocked) whose content
|
|
1811
|
+
* matches a subagent that has finished successfully. Fires on every observer
|
|
1777
1812
|
* `onChange` so the visual state stays in sync with subagent lifecycle
|
|
1778
|
-
* without requiring the agent to issue a follow-up `todo`.
|
|
1779
|
-
*
|
|
1780
|
-
*
|
|
1813
|
+
* without requiring the agent to issue a follow-up `todo`. A todo `block`ed
|
|
1814
|
+
* while waiting on a detached subagent is included: that subagent completing
|
|
1815
|
+
* is exactly the unblock signal, and blocked todos are excluded from the stop
|
|
1816
|
+
* reminder, so leaving it blocked would strand it silently. Failed and aborted
|
|
1817
|
+
* subagents are intentionally NOT auto-completed — those stay open so the user
|
|
1818
|
+
* (or the next agent turn) can decide what to do.
|
|
1781
1819
|
*
|
|
1782
1820
|
* Idempotent: only flips open tasks, never re-touches completed ones.
|
|
1783
1821
|
*/
|
|
@@ -1796,10 +1834,14 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1796
1834
|
const next: TodoPhase[] = this.todoPhases.map(phase => ({
|
|
1797
1835
|
name: phase.name,
|
|
1798
1836
|
tasks: phase.tasks.map(task => {
|
|
1799
|
-
if (task.status !== "pending" && task.status !== "in_progress"
|
|
1837
|
+
if (task.status !== "pending" && task.status !== "in_progress" && task.status !== "blocked") {
|
|
1838
|
+
return task;
|
|
1839
|
+
}
|
|
1800
1840
|
if (!todoMatchesAnyDescription(task.content, completedDescs)) return task;
|
|
1801
1841
|
mutated = true;
|
|
1802
|
-
|
|
1842
|
+
// Drop any blocker note along with the blocked status — the wait the
|
|
1843
|
+
// note described is over.
|
|
1844
|
+
return { content: task.content, status: "completed" as const };
|
|
1803
1845
|
}),
|
|
1804
1846
|
}));
|
|
1805
1847
|
if (!mutated) return;
|
|
@@ -2055,6 +2097,29 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2055
2097
|
this.ui.requestRender();
|
|
2056
2098
|
}
|
|
2057
2099
|
|
|
2100
|
+
#vibeParentSession(): VibeParentSession {
|
|
2101
|
+
return {
|
|
2102
|
+
getAgentId: () => this.session.getAgentId() ?? null,
|
|
2103
|
+
getSessionId: () => this.sessionManager.getSessionId(),
|
|
2104
|
+
getSessionFile: () => this.sessionManager.getSessionFile() ?? null,
|
|
2105
|
+
sessionManager: this.sessionManager,
|
|
2106
|
+
asyncJobManager: this.session.asyncJobManager,
|
|
2107
|
+
settings: this.session.settings,
|
|
2108
|
+
// Resolve restored/switched-to workers against this session's active model
|
|
2109
|
+
// (same as the spawn-path ToolSession), not the settings default. This is
|
|
2110
|
+
// the primary fallback in resolveAgentModelPatterns, so the `good` worker's
|
|
2111
|
+
// pi/task inheritance tracks the reopened session's model.
|
|
2112
|
+
getActiveModelString: () => (this.session.model ? formatModelString(this.session.model) : undefined),
|
|
2113
|
+
};
|
|
2114
|
+
}
|
|
2115
|
+
|
|
2116
|
+
async #quiesceVibeForSessionSwitch(): Promise<void> {
|
|
2117
|
+
const ownerScope = this.#vibeModeOwnerScope;
|
|
2118
|
+
if (!this.vibeModeEnabled || !ownerScope) return;
|
|
2119
|
+
await VibeSessionRegistry.global().suspendScope(ownerScope, this.session.asyncJobManager);
|
|
2120
|
+
this.#vibeScopeSuspendedForSwitch = true;
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2058
2123
|
#updateGoalModeStatus(): void {
|
|
2059
2124
|
const status =
|
|
2060
2125
|
this.goalModeEnabled || this.goalModePaused
|
|
@@ -2238,7 +2303,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2238
2303
|
}
|
|
2239
2304
|
}
|
|
2240
2305
|
|
|
2241
|
-
async #clearTransientModeState(
|
|
2306
|
+
async #clearTransientModeState(options?: {
|
|
2307
|
+
preserveVibe?: boolean;
|
|
2308
|
+
vibeScopeAlreadySuspended?: boolean;
|
|
2309
|
+
}): Promise<void> {
|
|
2242
2310
|
if (this.planModeEnabled || this.planModePaused) {
|
|
2243
2311
|
this.session.setPlanModeState(undefined);
|
|
2244
2312
|
try {
|
|
@@ -2274,23 +2342,40 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2274
2342
|
this.#updateGoalModeStatus();
|
|
2275
2343
|
}
|
|
2276
2344
|
|
|
2277
|
-
if (this.vibeModeEnabled) {
|
|
2278
|
-
|
|
2345
|
+
if (this.vibeModeEnabled && !options?.preserveVibe) {
|
|
2346
|
+
const ownerScope = this.#vibeModeOwnerScope;
|
|
2347
|
+
// This runs only from #reconcileModeFromSession, i.e. after switchSession
|
|
2348
|
+
// already loaded and restored the target session's active tools. The
|
|
2349
|
+
// #vibeModePreviousTools snapshot belongs to the SOURCE session, so
|
|
2350
|
+
// applying it here would clobber the target's tools — strip only the
|
|
2351
|
+
// transient vibe tools and keep the target's active set intact.
|
|
2352
|
+
await this.session.removeVibeToolsPreservingActive();
|
|
2279
2353
|
this.session.setVibeModeState(undefined);
|
|
2280
2354
|
this.vibeModeEnabled = false;
|
|
2281
2355
|
this.#vibeModePreviousTools = undefined;
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
this.session.asyncJobManager
|
|
2285
|
-
|
|
2356
|
+
this.#vibeModeOwnerScope = undefined;
|
|
2357
|
+
if (ownerScope && !options?.vibeScopeAlreadySuspended) {
|
|
2358
|
+
await VibeSessionRegistry.global().suspendScope(ownerScope, this.session.asyncJobManager);
|
|
2359
|
+
}
|
|
2286
2360
|
this.#updateVibeModeStatus();
|
|
2287
2361
|
}
|
|
2288
2362
|
}
|
|
2289
2363
|
|
|
2290
2364
|
/** Reconcile mode state from session entries on resume/switch. */
|
|
2291
2365
|
async #reconcileModeFromSession(options?: { preserveActiveGoal?: boolean }): Promise<void> {
|
|
2292
|
-
|
|
2366
|
+
const vibeScopeAlreadySuspended = this.#vibeScopeSuspendedForSwitch;
|
|
2367
|
+
this.#vibeScopeSuspendedForSwitch = false;
|
|
2293
2368
|
const sessionContext = this.sessionManager.buildSessionContext();
|
|
2369
|
+
const vibeSession = this.#vibeParentSession();
|
|
2370
|
+
const targetVibeScope = VibeSessionRegistry.global().ownerScope(vibeSession);
|
|
2371
|
+
const preserveVibe =
|
|
2372
|
+
this.vibeModeEnabled &&
|
|
2373
|
+
sessionContext.mode === "vibe" &&
|
|
2374
|
+
this.#vibeModeOwnerScope?.ownerId === targetVibeScope.ownerId &&
|
|
2375
|
+
this.#vibeModeOwnerScope.parentSessionId === targetVibeScope.parentSessionId &&
|
|
2376
|
+
this.#vibeModeOwnerScope.parentSessionFile === targetVibeScope.parentSessionFile;
|
|
2377
|
+
await this.#clearTransientModeState({ preserveVibe, vibeScopeAlreadySuspended });
|
|
2378
|
+
await VibeSessionRegistry.global().rehydrate(vibeSession);
|
|
2294
2379
|
const goalEnabled = this.session.settings.get("goal.enabled");
|
|
2295
2380
|
if (!goalEnabled && (sessionContext.mode === "goal" || sessionContext.mode === "goal_paused")) {
|
|
2296
2381
|
this.session.goalRuntime.clearAccounting();
|
|
@@ -2325,7 +2410,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2325
2410
|
}
|
|
2326
2411
|
this.session.goalRuntime.clearAccounting();
|
|
2327
2412
|
if (sessionContext.mode === "vibe") {
|
|
2328
|
-
await this.#enterVibeMode();
|
|
2413
|
+
if (!preserveVibe) await this.#enterVibeMode({ persistModeChange: false });
|
|
2329
2414
|
return;
|
|
2330
2415
|
}
|
|
2331
2416
|
if (!this.session.settings.get("plan.enabled")) {
|
|
@@ -2643,6 +2728,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2643
2728
|
onExternalEditor?: () => void;
|
|
2644
2729
|
onPlanEdited?: (content: string) => void;
|
|
2645
2730
|
onFeedbackChange?: (feedback: string) => void;
|
|
2731
|
+
annotationState?: PlanReviewAnnotationState;
|
|
2732
|
+
onAnnotationStateChange?: (state: PlanReviewAnnotationState) => void;
|
|
2646
2733
|
initialIndex?: number;
|
|
2647
2734
|
},
|
|
2648
2735
|
extra?: { slider?: HookSelectorSlider },
|
|
@@ -2666,6 +2753,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2666
2753
|
initialIndex: dialogOptions?.initialIndex,
|
|
2667
2754
|
slider: extra?.slider,
|
|
2668
2755
|
externalEditorLabel: this.keybindings.getDisplayString("app.editor.external") || undefined,
|
|
2756
|
+
annotationState: dialogOptions?.annotationState,
|
|
2669
2757
|
},
|
|
2670
2758
|
{
|
|
2671
2759
|
onPick: choice => finish(choice),
|
|
@@ -2675,6 +2763,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2675
2763
|
onAnnotationExternalEditor: (draft, commit) => void this.#openPlanAnnotationInExternalEditor(draft, commit),
|
|
2676
2764
|
onPlanEdited: dialogOptions?.onPlanEdited,
|
|
2677
2765
|
onFeedbackChange: dialogOptions?.onFeedbackChange,
|
|
2766
|
+
onAnnotationStateChange: dialogOptions?.onAnnotationStateChange,
|
|
2678
2767
|
},
|
|
2679
2768
|
);
|
|
2680
2769
|
this.#planReviewOverlay = overlay;
|
|
@@ -2904,7 +2993,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2904
2993
|
compactBeforeExecute?: boolean;
|
|
2905
2994
|
executionModel?: ResolvedRoleModel;
|
|
2906
2995
|
},
|
|
2907
|
-
): Promise<
|
|
2996
|
+
): Promise<boolean> {
|
|
2908
2997
|
const previousTools = this.#planModePreviousTools ?? this.session.getEnabledToolNames();
|
|
2909
2998
|
|
|
2910
2999
|
// Mark the pending abort caused by the plan-mode → compaction transition as
|
|
@@ -3003,7 +3092,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3003
3092
|
this.showWarning(
|
|
3004
3093
|
"Plan approved, but compaction was cancelled — execution not dispatched. Submit a turn to continue.",
|
|
3005
3094
|
);
|
|
3006
|
-
return;
|
|
3095
|
+
return false;
|
|
3007
3096
|
}
|
|
3008
3097
|
|
|
3009
3098
|
// Approved plans land in a fresh (or compacted) session whose first user-visible
|
|
@@ -3045,14 +3134,15 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3045
3134
|
// noted below), catch `AgentBusyError` and fall back to the same queue.
|
|
3046
3135
|
if (this.session.isStreaming) {
|
|
3047
3136
|
await this.session.followUp(planModePrompt, undefined, { synthetic: true });
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3137
|
+
} else {
|
|
3138
|
+
try {
|
|
3139
|
+
await this.session.prompt(planModePrompt, { synthetic: true });
|
|
3140
|
+
} catch (error) {
|
|
3141
|
+
if (!(error instanceof AgentBusyError)) throw error;
|
|
3142
|
+
await this.session.followUp(planModePrompt, undefined, { synthetic: true });
|
|
3143
|
+
}
|
|
3055
3144
|
}
|
|
3145
|
+
return true;
|
|
3056
3146
|
}
|
|
3057
3147
|
async #abortPlanApprovalTurnSilently(): Promise<void> {
|
|
3058
3148
|
this.session.markPlanInternalAbortPending();
|
|
@@ -3132,7 +3222,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3132
3222
|
}
|
|
3133
3223
|
}
|
|
3134
3224
|
|
|
3135
|
-
async #enterVibeMode(): Promise<void> {
|
|
3225
|
+
async #enterVibeMode(options?: { persistModeChange?: boolean }): Promise<void> {
|
|
3136
3226
|
if (this.vibeModeEnabled) {
|
|
3137
3227
|
return;
|
|
3138
3228
|
}
|
|
@@ -3145,9 +3235,13 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3145
3235
|
return;
|
|
3146
3236
|
}
|
|
3147
3237
|
|
|
3238
|
+
const vibeRegistry = VibeSessionRegistry.global();
|
|
3239
|
+
const ownerScope = vibeRegistry.ownerScope(this.#vibeParentSession());
|
|
3240
|
+
vibeRegistry.activateScope(ownerScope);
|
|
3148
3241
|
const previousTools = this.session.getEnabledToolNames();
|
|
3149
3242
|
await this.session.activateVibeTools(["read"]);
|
|
3150
3243
|
this.#vibeModePreviousTools = previousTools;
|
|
3244
|
+
this.#vibeModeOwnerScope = ownerScope;
|
|
3151
3245
|
this.vibeModeEnabled = true;
|
|
3152
3246
|
// Suppress cache-miss marker on the next turn: vibe mode changes the
|
|
3153
3247
|
// injected context, which predictably invalidates the cache.
|
|
@@ -3157,7 +3251,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3157
3251
|
await this.session.sendVibeModeContext({ deliverAs: "steer" });
|
|
3158
3252
|
}
|
|
3159
3253
|
this.#updateVibeModeStatus();
|
|
3160
|
-
this.sessionManager.appendModeChange("vibe");
|
|
3254
|
+
if (options?.persistModeChange !== false) this.sessionManager.appendModeChange("vibe");
|
|
3161
3255
|
this.showStatus("Vibe mode enabled. You direct fast/good worker sessions; toolset is read + vibe tools.");
|
|
3162
3256
|
}
|
|
3163
3257
|
|
|
@@ -3165,17 +3259,15 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3165
3259
|
if (!this.vibeModeEnabled) {
|
|
3166
3260
|
return;
|
|
3167
3261
|
}
|
|
3262
|
+
const ownerScope = this.#vibeModeOwnerScope;
|
|
3263
|
+
const killed = await VibeSessionRegistry.global().killAll(this.#vibeParentSession(), ownerScope);
|
|
3168
3264
|
await this.session.deactivateVibeTools(this.#vibeModePreviousTools ?? []);
|
|
3169
3265
|
this.session.setVibeModeState(undefined);
|
|
3170
3266
|
this.vibeModeEnabled = false;
|
|
3171
3267
|
this.#vibeModePreviousTools = undefined;
|
|
3268
|
+
this.#vibeModeOwnerScope = undefined;
|
|
3172
3269
|
this.lastAssistantUsage = undefined;
|
|
3173
|
-
const killed = await VibeSessionRegistry.global().killAll(
|
|
3174
|
-
this.session.getAgentId() ?? MAIN_AGENT_ID,
|
|
3175
|
-
this.session.asyncJobManager,
|
|
3176
|
-
);
|
|
3177
3270
|
this.#updateVibeModeStatus();
|
|
3178
|
-
this.sessionManager.appendModeChange("none");
|
|
3179
3271
|
this.showStatus(
|
|
3180
3272
|
killed > 0
|
|
3181
3273
|
? `Vibe mode disabled. Killed ${killed} worker session${killed === 1 ? "" : "s"}.`
|
|
@@ -3579,6 +3671,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3579
3671
|
// that the Refine branch re-prompts the model with.
|
|
3580
3672
|
let editedContent: string | undefined;
|
|
3581
3673
|
let feedback = "";
|
|
3674
|
+
const annotationStateKey = this.#resolvePlanFilePath(planFilePath);
|
|
3582
3675
|
|
|
3583
3676
|
const choice = await this.showPlanReview(
|
|
3584
3677
|
planContent,
|
|
@@ -3594,6 +3687,11 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3594
3687
|
onFeedbackChange: value => {
|
|
3595
3688
|
feedback = value;
|
|
3596
3689
|
},
|
|
3690
|
+
annotationState: this.#planReviewAnnotationState.get(annotationStateKey),
|
|
3691
|
+
onAnnotationStateChange: state => {
|
|
3692
|
+
if (state.annotations.length > 0) this.#planReviewAnnotationState.set(annotationStateKey, state);
|
|
3693
|
+
else this.#planReviewAnnotationState.delete(annotationStateKey);
|
|
3694
|
+
},
|
|
3597
3695
|
disabledIndices: keepContextDisabled ? [PLAN_KEEP_CONTEXT_OPTION_INDEX] : undefined,
|
|
3598
3696
|
},
|
|
3599
3697
|
{ slider },
|
|
@@ -3645,13 +3743,14 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3645
3743
|
: -1;
|
|
3646
3744
|
const executionModel =
|
|
3647
3745
|
slider && cycle && selectedTierIndex !== restoredIndex ? cycle.models[selectedTierIndex] : undefined;
|
|
3648
|
-
await this.#approvePlan(latestPlanContent, {
|
|
3746
|
+
const executionDispatched = await this.#approvePlan(latestPlanContent, {
|
|
3649
3747
|
planFilePath,
|
|
3650
3748
|
title: details.title,
|
|
3651
3749
|
preserveContext: choice !== "Approve and execute",
|
|
3652
3750
|
compactBeforeExecute: choice === "Approve and compact context",
|
|
3653
3751
|
executionModel,
|
|
3654
3752
|
});
|
|
3753
|
+
if (executionDispatched) this.#planReviewAnnotationState.delete(annotationStateKey);
|
|
3655
3754
|
} catch (error) {
|
|
3656
3755
|
this.showError(
|
|
3657
3756
|
`Failed to finalize approved plan: ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -3666,9 +3765,12 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3666
3765
|
try {
|
|
3667
3766
|
if (refinement) {
|
|
3668
3767
|
if (this.onInputCallback) {
|
|
3669
|
-
this.
|
|
3768
|
+
const input = this.startPendingSubmission({ text: feedback });
|
|
3769
|
+
this.#planReviewAnnotationStateBySubmission.set(input, annotationStateKey);
|
|
3770
|
+
this.onInputCallback(input);
|
|
3670
3771
|
} else {
|
|
3671
3772
|
await this.session.prompt(feedback);
|
|
3773
|
+
this.#planReviewAnnotationState.delete(annotationStateKey);
|
|
3672
3774
|
}
|
|
3673
3775
|
} else {
|
|
3674
3776
|
this.showStatus("Refine plan: enter a follow-up prompt.");
|
|
@@ -3743,6 +3845,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3743
3845
|
this.#stopLoadingAnimation(false);
|
|
3744
3846
|
}
|
|
3745
3847
|
this.#cleanupMicAnimation();
|
|
3848
|
+
this.#liveCommandController.dispose();
|
|
3746
3849
|
this.#cancelTodoAutoClearTimer();
|
|
3747
3850
|
this.#cancelObserverUiSyncTimer();
|
|
3748
3851
|
this.#cancelGoalContinuation();
|
|
@@ -3785,6 +3888,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3785
3888
|
if (this.#isShuttingDown) return;
|
|
3786
3889
|
this.#isShuttingDown = true;
|
|
3787
3890
|
|
|
3891
|
+
await this.#liveCommandController.stop();
|
|
3892
|
+
|
|
3788
3893
|
this.#btwController.dispose();
|
|
3789
3894
|
this.#omfgController.dispose();
|
|
3790
3895
|
this.#focusController.dispose();
|
|
@@ -3822,6 +3927,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3822
3927
|
// Drain any in-flight Kitty key release events before stopping.
|
|
3823
3928
|
// This prevents escape sequences from leaking to the parent shell over slow SSH.
|
|
3824
3929
|
await this.ui.terminal.drainInput(1000);
|
|
3930
|
+
// Stop the run-state spinner interval BEFORE restoring the shell title, so a
|
|
3931
|
+
// pending tick cannot re-emit an OSC title after `popTerminalTitle` hands the
|
|
3932
|
+
// terminal back (which would leave the parent shell with a `π ⠋ …` tab).
|
|
3933
|
+
disposeTerminalTitleState();
|
|
3825
3934
|
popTerminalTitle();
|
|
3826
3935
|
this.stop();
|
|
3827
3936
|
|
|
@@ -4294,6 +4403,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
4294
4403
|
}
|
|
4295
4404
|
|
|
4296
4405
|
async handleSTTToggle(): Promise<void> {
|
|
4406
|
+
if (this.#liveCommandController.active) {
|
|
4407
|
+
this.showWarning("End live mode before using push-to-talk speech input.");
|
|
4408
|
+
return;
|
|
4409
|
+
}
|
|
4297
4410
|
if (!settings.get("stt.enabled")) {
|
|
4298
4411
|
this.showWarning("Speech-to-text is disabled. Enable it in settings: stt.enabled");
|
|
4299
4412
|
return;
|
|
@@ -4326,6 +4439,15 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
4326
4439
|
});
|
|
4327
4440
|
}
|
|
4328
4441
|
|
|
4442
|
+
/** Start or stop the Codex-backed realtime voice surface. */
|
|
4443
|
+
async handleLiveCommand(): Promise<void> {
|
|
4444
|
+
if (this.#sttController && this.#sttController.state !== "idle") {
|
|
4445
|
+
this.showWarning("Finish the current speech-to-text capture before starting live mode.");
|
|
4446
|
+
return;
|
|
4447
|
+
}
|
|
4448
|
+
await this.#liveCommandController.handleCommand();
|
|
4449
|
+
}
|
|
4450
|
+
|
|
4329
4451
|
#setMicCursor(color: { r: number; g: number; b: number }): void {
|
|
4330
4452
|
this.editor.cursorOverride = `\x1b[38;2;${color.r};${color.g};${color.b}m${theme.icon.mic}\x1b[0m`;
|
|
4331
4453
|
// Theme symbols can be wide (for example, 🎤), so measure the rendered override.
|
|
@@ -12,6 +12,13 @@ import { isRecord, ptree, readJsonl } from "@oh-my-pi/pi-utils";
|
|
|
12
12
|
import type { FileSink } from "bun";
|
|
13
13
|
import type { BashResult } from "../../exec/bash-executor";
|
|
14
14
|
import type { AgentSessionEvent, SessionStats } from "../../session/agent-session";
|
|
15
|
+
import { MAX_RPC_FRAME_BYTES, MAX_RPC_REASSEMBLED_BYTES, RpcFrameDecoder, type RpcProtocolVersion } from "./rpc-frame";
|
|
16
|
+
import {
|
|
17
|
+
RPC_MESSAGES_PAGE_BUSY_ERROR,
|
|
18
|
+
RPC_MESSAGES_PAGE_STALE_ERROR,
|
|
19
|
+
type RpcMessagesPage,
|
|
20
|
+
type RpcMessagesPageOptions,
|
|
21
|
+
} from "./rpc-messages";
|
|
15
22
|
import type {
|
|
16
23
|
RpcAvailableCommandsUpdateFrame,
|
|
17
24
|
RpcAvailableSlashCommand,
|
|
@@ -136,6 +143,16 @@ function isRpcResponse(value: unknown): value is RpcResponse {
|
|
|
136
143
|
return true;
|
|
137
144
|
}
|
|
138
145
|
|
|
146
|
+
function supportsRpcProtocolV2(value: Record<string, unknown>): boolean {
|
|
147
|
+
return (
|
|
148
|
+
value.type === "ready" &&
|
|
149
|
+
Array.isArray(value.supportedProtocolVersions) &&
|
|
150
|
+
value.supportedProtocolVersions.includes(2) &&
|
|
151
|
+
value.maxFrameBytes === MAX_RPC_FRAME_BYTES &&
|
|
152
|
+
value.maxReassembledFrameBytes === MAX_RPC_REASSEMBLED_BYTES
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
139
156
|
function isAgentEvent(value: unknown): value is AgentEvent {
|
|
140
157
|
if (!isRecord(value)) return false;
|
|
141
158
|
const type = value.type;
|
|
@@ -200,6 +217,26 @@ function normalizeToolResult<TDetails>(result: RpcClientToolResult<TDetails>): A
|
|
|
200
217
|
return result;
|
|
201
218
|
}
|
|
202
219
|
|
|
220
|
+
/** Failed RPC command; `code` mirrors the server's machine-readable error code when present. */
|
|
221
|
+
export class RpcCommandError extends Error {
|
|
222
|
+
constructor(
|
|
223
|
+
message: string,
|
|
224
|
+
readonly command: string,
|
|
225
|
+
readonly code?: string,
|
|
226
|
+
) {
|
|
227
|
+
super(message);
|
|
228
|
+
this.name = "RpcCommandError";
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** True when a high-level `getMessages()` drain should discard partial pages and fall back to `get_messages`. */
|
|
233
|
+
function isPageFallbackError(error: unknown): boolean {
|
|
234
|
+
if (!(error instanceof Error)) return false;
|
|
235
|
+
if (error instanceof RpcCommandError && (error.code === "session_busy" || error.code === "stale_cursor"))
|
|
236
|
+
return true;
|
|
237
|
+
return error.message === RPC_MESSAGES_PAGE_BUSY_ERROR || error.message === RPC_MESSAGES_PAGE_STALE_ERROR;
|
|
238
|
+
}
|
|
239
|
+
|
|
203
240
|
// ============================================================================
|
|
204
241
|
// RPC Client
|
|
205
242
|
// ============================================================================
|
|
@@ -218,6 +255,7 @@ export class RpcClient {
|
|
|
218
255
|
#customTools: RpcClientCustomTool[] = [];
|
|
219
256
|
#pendingHostToolCalls = new Map<string, { controller: AbortController }>();
|
|
220
257
|
#requestId = 0;
|
|
258
|
+
#protocolVersion: RpcProtocolVersion = 1;
|
|
221
259
|
#extensionUiListeners: Set<(req: RpcExtensionUIRequest) => void> = new Set();
|
|
222
260
|
#abortController = new AbortController();
|
|
223
261
|
|
|
@@ -242,6 +280,7 @@ export class RpcClient {
|
|
|
242
280
|
// Mint a fresh controller so a previous stop()'s abort does not
|
|
243
281
|
// short-circuit the new stdout reader (issue #4079).
|
|
244
282
|
this.#abortController = new AbortController();
|
|
283
|
+
this.#protocolVersion = 1;
|
|
245
284
|
|
|
246
285
|
const cliPath = this.options.cliPath ?? "dist/cli.js";
|
|
247
286
|
const args = ["--mode", "rpc"];
|
|
@@ -269,6 +308,9 @@ export class RpcClient {
|
|
|
269
308
|
// Wait for the "ready" signal or process exit
|
|
270
309
|
const { promise: readyPromise, resolve: readyResolve, reject: readyReject } = Promise.withResolvers<void>();
|
|
271
310
|
let readySettled = false;
|
|
311
|
+
let protocolV2Supported = false;
|
|
312
|
+
let protocolV2Enabled = false;
|
|
313
|
+
const frameDecoder = new RpcFrameDecoder();
|
|
272
314
|
|
|
273
315
|
const reapAfterOutputFailure = async (error: Error) => {
|
|
274
316
|
if (this.#process !== child) return;
|
|
@@ -294,11 +336,15 @@ export class RpcClient {
|
|
|
294
336
|
void (async () => {
|
|
295
337
|
for await (const line of lines) {
|
|
296
338
|
if (!readySettled && isRecord(line) && line.type === "ready") {
|
|
339
|
+
protocolV2Supported = supportsRpcProtocolV2(line);
|
|
297
340
|
readySettled = true;
|
|
298
341
|
readyResolve();
|
|
299
342
|
continue;
|
|
300
343
|
}
|
|
301
|
-
|
|
344
|
+
if (isRecord(line) && line.type === "rpc_chunk" && !protocolV2Enabled)
|
|
345
|
+
throw new Error("RPC chunk received before protocol negotiation");
|
|
346
|
+
const decoded = frameDecoder.push(line);
|
|
347
|
+
if (decoded) this.#handleLine(decoded);
|
|
302
348
|
}
|
|
303
349
|
// A closed stdout is terminal even if the child remains alive. Startup
|
|
304
350
|
// failures are reaped by the readyPromise catch below; established
|
|
@@ -359,6 +405,18 @@ export class RpcClient {
|
|
|
359
405
|
|
|
360
406
|
try {
|
|
361
407
|
await readyPromise;
|
|
408
|
+
if (protocolV2Supported) {
|
|
409
|
+
protocolV2Enabled = true;
|
|
410
|
+
const response = await this.#send({ type: "negotiate_protocol", protocolVersion: 2 });
|
|
411
|
+
if (
|
|
412
|
+
!response.success ||
|
|
413
|
+
response.command !== "negotiate_protocol" ||
|
|
414
|
+
!isRecord(response.data) ||
|
|
415
|
+
response.data.protocolVersion !== 2
|
|
416
|
+
)
|
|
417
|
+
throw new Error("RPC protocol v2 negotiation failed");
|
|
418
|
+
this.#protocolVersion = 2;
|
|
419
|
+
}
|
|
362
420
|
if (this.#customTools.length > 0) {
|
|
363
421
|
await this.setCustomTools(this.#customTools);
|
|
364
422
|
}
|
|
@@ -744,9 +802,42 @@ export class RpcClient {
|
|
|
744
802
|
}
|
|
745
803
|
|
|
746
804
|
/**
|
|
747
|
-
* Get
|
|
805
|
+
* Get one stable, byte-bounded message page.
|
|
748
806
|
*/
|
|
807
|
+
async getMessagesPage(options: RpcMessagesPageOptions = {}): Promise<RpcMessagesPage> {
|
|
808
|
+
const response = await this.#send({ type: "get_messages_page", ...options });
|
|
809
|
+
return this.#getData<RpcMessagesPage>(response);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
/** Get all messages, draining stable pages when protocol v2 is available. */
|
|
749
813
|
async getMessages(): Promise<AgentMessage[]> {
|
|
814
|
+
if (this.#protocolVersion === 2) {
|
|
815
|
+
try {
|
|
816
|
+
const messages: AgentMessage[] = [];
|
|
817
|
+
const seenCursors = new Set<string>();
|
|
818
|
+
let totalMessages: number | undefined;
|
|
819
|
+
let cursor: string | undefined;
|
|
820
|
+
do {
|
|
821
|
+
const page = await this.getMessagesPage({ cursor, limit: 256 });
|
|
822
|
+
if (
|
|
823
|
+
!Number.isSafeInteger(page.totalMessages) ||
|
|
824
|
+
page.totalMessages < 0 ||
|
|
825
|
+
(totalMessages !== undefined && page.totalMessages !== totalMessages)
|
|
826
|
+
)
|
|
827
|
+
throw new Error("RPC message pagination returned an inconsistent total");
|
|
828
|
+
totalMessages = page.totalMessages;
|
|
829
|
+
messages.push(...page.messages);
|
|
830
|
+
cursor = page.nextCursor;
|
|
831
|
+
if (cursor && seenCursors.has(cursor)) throw new Error("RPC message pagination repeated a cursor");
|
|
832
|
+
if (cursor) seenCursors.add(cursor);
|
|
833
|
+
} while (cursor);
|
|
834
|
+
if (messages.length !== totalMessages)
|
|
835
|
+
throw new Error("RPC message pagination ended before the advertised total");
|
|
836
|
+
return messages;
|
|
837
|
+
} catch (error) {
|
|
838
|
+
if (!isPageFallbackError(error)) throw error;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
750
841
|
const response = await this.#send({ type: "get_messages" });
|
|
751
842
|
return this.#getData<{ messages: AgentMessage[] }>(response).messages;
|
|
752
843
|
}
|
|
@@ -1094,7 +1185,7 @@ export class RpcClient {
|
|
|
1094
1185
|
#getData<T>(response: RpcResponse): T {
|
|
1095
1186
|
if (!response.success) {
|
|
1096
1187
|
const errorResponse = response as Extract<RpcResponse, { success: false }>;
|
|
1097
|
-
throw new
|
|
1188
|
+
throw new RpcCommandError(errorResponse.error, errorResponse.command, errorResponse.code);
|
|
1098
1189
|
}
|
|
1099
1190
|
// Type assertion: we trust response.data matches T based on the command sent.
|
|
1100
1191
|
// This is safe because each public method specifies the correct T for its command.
|