@oh-my-pi/pi-coding-agent 15.10.0 → 15.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +142 -1
- package/dist/types/cli/dry-balance-cli.d.ts +15 -1
- package/dist/types/cli/startup-cwd.d.ts +2 -0
- package/dist/types/commands/launch.d.ts +3 -0
- package/dist/types/commit/analysis/conventional.d.ts +2 -2
- package/dist/types/commit/analysis/summary.d.ts +2 -2
- package/dist/types/commit/changelog/generate.d.ts +2 -2
- package/dist/types/commit/changelog/index.d.ts +2 -2
- package/dist/types/commit/map-reduce/index.d.ts +3 -3
- package/dist/types/commit/map-reduce/map-phase.d.ts +2 -2
- package/dist/types/commit/map-reduce/reduce-phase.d.ts +2 -2
- package/dist/types/commit/model-selection.d.ts +10 -4
- package/dist/types/config/api-key-resolver.d.ts +34 -0
- package/dist/types/config/keybindings.d.ts +2 -2
- package/dist/types/config/model-provider-priority.d.ts +1 -0
- package/dist/types/config/model-registry.d.ts +17 -1
- package/dist/types/config/model-resolver.d.ts +4 -1
- package/dist/types/config/settings-schema.d.ts +9 -0
- package/dist/types/config/settings.d.ts +7 -2
- package/dist/types/dap/config.d.ts +14 -1
- package/dist/types/dap/types.d.ts +10 -0
- package/dist/types/debug/report-bundle.d.ts +3 -0
- package/dist/types/edit/file-snapshot-store.d.ts +18 -10
- package/dist/types/eval/py/__tests__/prelude.test.d.ts +1 -0
- package/dist/types/extensibility/extensions/types.d.ts +4 -1
- package/dist/types/lsp/client.d.ts +10 -0
- package/dist/types/lsp/utils.d.ts +3 -2
- package/dist/types/main.d.ts +3 -9
- package/dist/types/mcp/tool-bridge.d.ts +2 -0
- package/dist/types/modes/components/chat-block.d.ts +64 -0
- package/dist/types/modes/components/custom-editor.d.ts +4 -1
- package/dist/types/modes/components/overlay-box.d.ts +17 -0
- package/dist/types/modes/components/plan-review-overlay.d.ts +59 -0
- package/dist/types/modes/components/plan-toc.d.ts +41 -0
- package/dist/types/modes/components/read-tool-group.d.ts +2 -0
- package/dist/types/modes/components/status-line.d.ts +2 -0
- package/dist/types/modes/components/transcript-container.d.ts +11 -0
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/controllers/event-controller.d.ts +17 -1
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +0 -1
- package/dist/types/modes/controllers/input-controller.d.ts +1 -1
- package/dist/types/modes/controllers/streaming-reveal.d.ts +22 -0
- package/dist/types/modes/controllers/tan-command-controller.d.ts +6 -0
- package/dist/types/modes/interactive-mode.d.ts +16 -5
- package/dist/types/modes/magic-keywords.d.ts +1 -1
- package/dist/types/modes/markdown-prose.d.ts +1 -1
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +21 -5
- package/dist/types/modes/utils/copy-targets.d.ts +21 -1
- package/dist/types/modes/workflow.d.ts +3 -3
- package/dist/types/plan-mode/approved-plan.d.ts +27 -8
- package/dist/types/plan-mode/plan-protection.d.ts +4 -4
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +21 -0
- package/dist/types/session/auth-storage.d.ts +1 -1
- package/dist/types/session/messages.d.ts +12 -0
- package/dist/types/session/session-manager.d.ts +8 -3
- package/dist/types/slash-commands/types.d.ts +4 -6
- package/dist/types/task/executor.d.ts +17 -0
- package/dist/types/task/index.d.ts +1 -0
- package/dist/types/task/render.d.ts +3 -2
- package/dist/types/tools/archive-reader.d.ts +5 -0
- package/dist/types/tools/ast-edit.d.ts +3 -0
- package/dist/types/tools/ast-grep.d.ts +3 -0
- package/dist/types/tools/bash.d.ts +1 -0
- package/dist/types/tools/eval.d.ts +8 -0
- package/dist/types/tools/find.d.ts +8 -4
- package/dist/types/tools/gh-cache-invalidation.d.ts +6 -0
- package/dist/types/tools/github-cache.d.ts +12 -0
- package/dist/types/tools/grouped-file-output.d.ts +95 -12
- package/dist/types/tools/memory-render.d.ts +4 -1
- package/dist/types/tools/path-utils.d.ts +8 -0
- package/dist/types/tools/plan-mode-guard.d.ts +8 -9
- package/dist/types/tools/render-utils.d.ts +5 -9
- package/dist/types/tools/search.d.ts +6 -2
- package/dist/types/tools/sqlite-reader.d.ts +1 -0
- package/dist/types/tools/todo.d.ts +3 -2
- package/dist/types/tools/write.d.ts +3 -0
- package/dist/types/tools/yield.d.ts +8 -0
- package/dist/types/tui/output-block.d.ts +16 -4
- package/dist/types/tui/status-line.d.ts +3 -0
- package/dist/types/utils/enhanced-paste.d.ts +20 -0
- package/dist/types/web/search/providers/kimi.d.ts +1 -1
- package/package.json +9 -9
- package/src/auto-thinking/classifier.ts +5 -1
- package/src/cli/args.ts +3 -1
- package/src/cli/dry-balance-cli.ts +54 -21
- package/src/cli/gallery-cli.ts +4 -1
- package/src/cli/gallery-fixtures/misc.ts +29 -0
- package/src/cli/startup-cwd.ts +68 -0
- package/src/commands/launch.ts +3 -0
- package/src/commit/analysis/conventional.ts +2 -2
- package/src/commit/analysis/summary.ts +2 -2
- package/src/commit/changelog/generate.ts +2 -2
- package/src/commit/changelog/index.ts +2 -2
- package/src/commit/map-reduce/index.ts +3 -3
- package/src/commit/map-reduce/map-phase.ts +2 -2
- package/src/commit/map-reduce/reduce-phase.ts +2 -2
- package/src/commit/model-selection.ts +36 -11
- package/src/commit/pipeline.ts +4 -4
- package/src/config/api-key-resolver.ts +58 -0
- package/src/config/model-provider-priority.ts +55 -0
- package/src/config/model-registry.ts +29 -24
- package/src/config/model-resolver.ts +39 -7
- package/src/config/settings-schema.ts +10 -0
- package/src/config/settings.ts +106 -43
- package/src/dap/config.ts +41 -2
- package/src/dap/defaults.json +1 -0
- package/src/dap/session.ts +1 -0
- package/src/dap/types.ts +10 -0
- package/src/debug/index.ts +47 -53
- package/src/debug/raw-sse-buffer.ts +7 -4
- package/src/debug/report-bundle.ts +9 -0
- package/src/edit/file-snapshot-store.ts +33 -1
- package/src/edit/hashline/filesystem.ts +2 -1
- package/src/edit/renderer.ts +82 -78
- package/src/eval/__tests__/llm-bridge.test.ts +110 -31
- package/src/eval/js/context-manager.ts +32 -15
- package/src/eval/llm-bridge.ts +22 -6
- package/src/eval/py/__tests__/prelude.test.ts +19 -0
- package/src/eval/py/executor.ts +23 -11
- package/src/eval/py/prelude.py +1 -1
- package/src/extensibility/extensions/types.ts +10 -1
- package/src/goals/tools/goal-tool.ts +36 -26
- package/src/internal-urls/docs-index.generated.ts +8 -8
- package/src/lsp/client.ts +23 -11
- package/src/lsp/config.ts +11 -1
- package/src/lsp/index.ts +61 -9
- package/src/lsp/utils.ts +3 -2
- package/src/main.ts +100 -72
- package/src/mcp/tool-bridge.ts +2 -0
- package/src/memories/index.ts +14 -7
- package/src/mnemopi/backend.ts +5 -1
- package/src/modes/acp/acp-agent.ts +33 -26
- package/src/modes/components/assistant-message.ts +2 -9
- package/src/modes/components/chat-block.ts +111 -0
- package/src/modes/components/copy-selector.ts +1 -44
- package/src/modes/components/custom-editor.ts +164 -109
- package/src/modes/components/custom-message.ts +1 -3
- package/src/modes/components/execution-shared.ts +1 -2
- package/src/modes/components/hook-message.ts +1 -3
- package/src/modes/components/model-selector.ts +59 -13
- package/src/modes/components/oauth-selector.ts +33 -7
- package/src/modes/components/overlay-box.ts +108 -0
- package/src/modes/components/plan-review-overlay.ts +799 -0
- package/src/modes/components/plan-toc.ts +138 -0
- package/src/modes/components/read-tool-group.ts +20 -4
- package/src/modes/components/skill-message.ts +0 -1
- package/src/modes/components/status-line.ts +19 -4
- package/src/modes/components/tips.txt +2 -1
- package/src/modes/components/todo-reminder.ts +0 -2
- package/src/modes/components/tool-execution.ts +68 -88
- package/src/modes/components/transcript-container.ts +84 -24
- package/src/modes/components/user-message.ts +2 -3
- package/src/modes/controllers/command-controller-shared.ts +7 -6
- package/src/modes/controllers/command-controller.ts +57 -55
- package/src/modes/controllers/event-controller.ts +67 -40
- package/src/modes/controllers/extension-ui-controller.ts +10 -73
- package/src/modes/controllers/input-controller.ts +170 -126
- package/src/modes/controllers/mcp-command-controller.ts +69 -60
- package/src/modes/controllers/selector-controller.ts +23 -25
- package/src/modes/controllers/streaming-reveal.ts +212 -0
- package/src/modes/controllers/tan-command-controller.ts +173 -0
- package/src/modes/interactive-mode.ts +274 -112
- package/src/modes/magic-keywords.ts +1 -1
- package/src/modes/markdown-prose.ts +1 -1
- package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
- package/src/modes/theme/shimmer.ts +20 -9
- package/src/modes/theme/theme-schema.json +1 -1
- package/src/modes/theme/theme.ts +8 -4
- package/src/modes/types.ts +21 -7
- package/src/modes/utils/copy-targets.ts +133 -27
- package/src/modes/utils/ui-helpers.ts +44 -46
- package/src/modes/workflow.ts +10 -10
- package/src/plan-mode/approved-plan.ts +66 -43
- package/src/plan-mode/plan-protection.ts +4 -4
- package/src/prompts/system/background-tan-dispatch.md +8 -0
- package/src/prompts/system/plan-mode-active.md +67 -58
- package/src/prompts/system/plan-mode-approved.md +1 -1
- package/src/prompts/system/workflow-notice.md +1 -1
- package/src/prompts/tools/bash.md +9 -0
- package/src/prompts/tools/browser.md +1 -1
- package/src/prompts/tools/eval.md +2 -1
- package/src/prompts/tools/read.md +2 -2
- package/src/sdk.ts +37 -46
- package/src/session/agent-session.ts +119 -18
- package/src/session/auth-storage.ts +2 -0
- package/src/session/messages.ts +26 -0
- package/src/session/session-manager.ts +109 -28
- package/src/slash-commands/builtin-registry.ts +36 -9
- package/src/slash-commands/types.ts +4 -6
- package/src/task/executor.ts +76 -38
- package/src/task/index.ts +4 -0
- package/src/task/render.ts +211 -147
- package/src/tools/archive-reader.ts +64 -0
- package/src/tools/ask.ts +119 -164
- package/src/tools/ast-edit.ts +98 -71
- package/src/tools/ast-grep.ts +37 -43
- package/src/tools/bash.ts +57 -6
- package/src/tools/browser/tab-supervisor.ts +13 -1
- package/src/tools/browser/tab-worker.ts +33 -4
- package/src/tools/debug.ts +20 -8
- package/src/tools/eval.ts +13 -2
- package/src/tools/fetch.ts +297 -7
- package/src/tools/find.ts +51 -30
- package/src/tools/gh-cache-invalidation.ts +200 -0
- package/src/tools/gh-renderer.ts +81 -42
- package/src/tools/github-cache.ts +25 -0
- package/src/tools/grouped-file-output.ts +272 -48
- package/src/tools/image-gen.ts +150 -103
- package/src/tools/inspect-image-renderer.ts +63 -41
- package/src/tools/inspect-image.ts +10 -3
- package/src/tools/job.ts +3 -4
- package/src/tools/memory-render.ts +4 -1
- package/src/tools/path-utils.ts +28 -2
- package/src/tools/plan-mode-guard.ts +66 -39
- package/src/tools/read.ts +48 -28
- package/src/tools/render-utils.ts +21 -37
- package/src/tools/resolve.ts +14 -0
- package/src/tools/search-tool-bm25.ts +36 -23
- package/src/tools/search.ts +118 -81
- package/src/tools/sqlite-reader.ts +9 -12
- package/src/tools/todo.ts +118 -52
- package/src/tools/write.ts +83 -64
- package/src/tools/yield.ts +10 -1
- package/src/tui/output-block.ts +60 -13
- package/src/tui/status-line.ts +5 -1
- package/src/utils/commit-message-generator.ts +11 -3
- package/src/utils/enhanced-paste.ts +230 -0
- package/src/utils/title-generator.ts +2 -1
- package/src/web/search/providers/anthropic.ts +25 -19
- package/src/web/search/providers/codex.ts +37 -8
- package/src/web/search/providers/exa.ts +11 -3
- package/src/web/search/providers/kimi.ts +28 -17
- package/src/web/search/providers/parallel.ts +35 -24
- package/src/web/search/providers/synthetic.ts +8 -6
- package/src/web/search/providers/tavily.ts +9 -8
- package/src/web/search/providers/zai.ts +8 -6
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
modelsAreEqual,
|
|
21
21
|
type UsageReport,
|
|
22
22
|
} from "@oh-my-pi/pi-ai";
|
|
23
|
-
import type { Component, EditorTheme, SlashCommand } from "@oh-my-pi/pi-tui";
|
|
23
|
+
import type { Component, EditorTheme, LoaderMessageColorFn, OverlayHandle, SlashCommand } from "@oh-my-pi/pi-tui";
|
|
24
24
|
import {
|
|
25
25
|
Container,
|
|
26
26
|
clearRenderCache,
|
|
@@ -50,7 +50,7 @@ import chalk from "chalk";
|
|
|
50
50
|
import { reset as resetCapabilities } from "../capability";
|
|
51
51
|
import { KeybindingsManager } from "../config/keybindings";
|
|
52
52
|
import { MODEL_ROLES, type ModelRole } from "../config/model-registry";
|
|
53
|
-
import { isSettingsInitialized, Settings, settings } from "../config/settings";
|
|
53
|
+
import { isSettingsInitialized, onStatusLineSessionAccentChanged, Settings, settings } from "../config/settings";
|
|
54
54
|
import { clearClaudePluginRootsCache } from "../discovery/helpers";
|
|
55
55
|
import type {
|
|
56
56
|
ContextUsage,
|
|
@@ -65,12 +65,7 @@ import { BUILTIN_SLASH_COMMANDS, loadSlashCommands } from "../extensibility/slas
|
|
|
65
65
|
import type { Goal, GoalModeState } from "../goals/state";
|
|
66
66
|
import { resolveLocalUrlToPath } from "../internal-urls";
|
|
67
67
|
import { LSP_STARTUP_EVENT_CHANNEL, type LspStartupEvent } from "../lsp/startup-events";
|
|
68
|
-
import {
|
|
69
|
-
humanizePlanTitle,
|
|
70
|
-
type PlanApprovalDetails,
|
|
71
|
-
renameApprovedPlanFile,
|
|
72
|
-
resolvePlanTitle,
|
|
73
|
-
} from "../plan-mode/approved-plan";
|
|
68
|
+
import { humanizePlanTitle, type PlanApprovalDetails, resolveApprovedPlan } from "../plan-mode/approved-plan";
|
|
74
69
|
import planModeApprovedPrompt from "../prompts/system/plan-mode-approved.md" with { type: "text" };
|
|
75
70
|
import planModeCompactInstructionsPrompt from "../prompts/system/plan-mode-compact-instructions.md" with {
|
|
76
71
|
type: "text",
|
|
@@ -94,6 +89,7 @@ import { getSessionAccentAnsi, getSessionAccentHex } from "../utils/session-colo
|
|
|
94
89
|
import { popTerminalTitle, pushTerminalTitle, setSessionTerminalTitle } from "../utils/title-generator";
|
|
95
90
|
import type { AssistantMessageComponent } from "./components/assistant-message";
|
|
96
91
|
import type { BashExecutionComponent } from "./components/bash-execution";
|
|
92
|
+
import { ChatBlock, type ChatBlockHost } from "./components/chat-block";
|
|
97
93
|
import { CustomEditor } from "./components/custom-editor";
|
|
98
94
|
import { DynamicBorder } from "./components/dynamic-border";
|
|
99
95
|
import { ErrorBannerComponent } from "./components/error-banner";
|
|
@@ -101,6 +97,7 @@ import type { EvalExecutionComponent } from "./components/eval-execution";
|
|
|
101
97
|
import type { HookEditorComponent } from "./components/hook-editor";
|
|
102
98
|
import type { HookInputComponent } from "./components/hook-input";
|
|
103
99
|
import type { HookSelectorComponent, HookSelectorSlider } from "./components/hook-selector";
|
|
100
|
+
import { PlanReviewOverlay } from "./components/plan-review-overlay";
|
|
104
101
|
import { StatusLineComponent } from "./components/status-line";
|
|
105
102
|
import type { ToolExecutionHandle } from "./components/tool-execution";
|
|
106
103
|
import { TranscriptContainer } from "./components/transcript-container";
|
|
@@ -114,6 +111,7 @@ import { MCPCommandController } from "./controllers/mcp-command-controller";
|
|
|
114
111
|
import { OmfgController } from "./controllers/omfg-controller";
|
|
115
112
|
import { SelectorController } from "./controllers/selector-controller";
|
|
116
113
|
import { SSHCommandController } from "./controllers/ssh-command-controller";
|
|
114
|
+
import { TanCommandController } from "./controllers/tan-command-controller";
|
|
117
115
|
import { TodoCommandController } from "./controllers/todo-command-controller";
|
|
118
116
|
import {
|
|
119
117
|
consumeLoopLimitIteration,
|
|
@@ -127,7 +125,7 @@ import {
|
|
|
127
125
|
import { OAuthManualInputManager } from "./oauth-manual-input";
|
|
128
126
|
import { SessionObserverRegistry } from "./session-observer-registry";
|
|
129
127
|
import { interruptHint } from "./shared";
|
|
130
|
-
import { type ShimmerPalette, shimmerSegments, shimmerText } from "./theme/shimmer";
|
|
128
|
+
import { type ShimmerPalette, shimmerEnabled, shimmerSegments, shimmerText } from "./theme/shimmer";
|
|
131
129
|
import type { Theme } from "./theme/theme";
|
|
132
130
|
import {
|
|
133
131
|
getEditorTheme,
|
|
@@ -159,6 +157,12 @@ interface WorkingMessageAccent {
|
|
|
159
157
|
dim: string;
|
|
160
158
|
}
|
|
161
159
|
|
|
160
|
+
interface WorkingMessageAccentCacheKey {
|
|
161
|
+
sessionName: string | undefined;
|
|
162
|
+
accentSurfaceLuminance: number | undefined;
|
|
163
|
+
sessionAccentEnabled: boolean;
|
|
164
|
+
}
|
|
165
|
+
|
|
162
166
|
function renderWorkingMessage(message: string, accent?: WorkingMessageAccent): string {
|
|
163
167
|
const palette = accent
|
|
164
168
|
? ({
|
|
@@ -250,20 +254,6 @@ export interface InteractiveModeOptions {
|
|
|
250
254
|
initialMessages?: string[];
|
|
251
255
|
}
|
|
252
256
|
|
|
253
|
-
/**
|
|
254
|
-
* Plan-review preview block. Once rendered it is static (a one-shot Markdown of
|
|
255
|
-
* the plan file), so even while it sits as the live bottom block beneath the
|
|
256
|
-
* approval selector its scrolled-off head is safe to commit to native
|
|
257
|
-
* scrollback. Reporting append-only lets an over-tall plan + selector commit the
|
|
258
|
-
* plan's head instead of clipping it — without this a plain {@link Container} is
|
|
259
|
-
* deferred and a long plan is cut off the top on ED3-risk terminals.
|
|
260
|
-
*/
|
|
261
|
-
class PlanReviewBlock extends Container {
|
|
262
|
-
isTranscriptBlockAppendOnly(): boolean {
|
|
263
|
-
return true;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
257
|
export class InteractiveMode implements InteractiveModeContext {
|
|
268
258
|
session: AgentSession;
|
|
269
259
|
sessionManager: SessionManager;
|
|
@@ -287,7 +277,6 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
287
277
|
statusLine: StatusLineComponent;
|
|
288
278
|
|
|
289
279
|
isInitialized = false;
|
|
290
|
-
isBackgrounded = false;
|
|
291
280
|
isBashMode = false;
|
|
292
281
|
toolOutputExpanded = false;
|
|
293
282
|
todoExpanded = false;
|
|
@@ -318,6 +307,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
318
307
|
autoCompactionLoader: Loader | undefined = undefined;
|
|
319
308
|
retryLoader: Loader | undefined = undefined;
|
|
320
309
|
#pendingWorkingMessage: string | undefined;
|
|
310
|
+
#workingMessageAccentCacheKey?: WorkingMessageAccentCacheKey;
|
|
311
|
+
#workingMessageAccentCacheValue?: WorkingMessageAccent;
|
|
312
|
+
#workingMessageAccentCacheHasValue = false;
|
|
321
313
|
get #defaultWorkingMessage(): string {
|
|
322
314
|
return `Working…${interruptHint()}`;
|
|
323
315
|
}
|
|
@@ -355,12 +347,14 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
355
347
|
#planModePreviousModelState: { model: Model; thinkingLevel?: ThinkingLevel } | undefined;
|
|
356
348
|
#pendingModelSwitch: { model: Model; thinkingLevel?: ThinkingLevel } | undefined;
|
|
357
349
|
#planModeHasEntered = false;
|
|
358
|
-
#
|
|
350
|
+
#planReviewOverlay: PlanReviewOverlay | undefined;
|
|
351
|
+
#planReviewOverlayHandle: OverlayHandle | undefined;
|
|
359
352
|
readonly lspServers: LspStartupServerInfo[] | undefined = undefined;
|
|
360
353
|
mcpManager?: import("../mcp").MCPManager;
|
|
361
354
|
readonly #toolUiContextSetter: (uiContext: ExtensionUIContext, hasUI: boolean) => void;
|
|
362
355
|
|
|
363
356
|
readonly #btwController: BtwController;
|
|
357
|
+
readonly #tanCommandController: TanCommandController;
|
|
364
358
|
readonly #omfgController: OmfgController;
|
|
365
359
|
readonly #commandController: CommandController;
|
|
366
360
|
readonly #todoCommandController: TodoCommandController;
|
|
@@ -379,6 +373,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
379
373
|
#eventBus?: EventBus;
|
|
380
374
|
#eventBusUnsubscribers: Array<() => void> = [];
|
|
381
375
|
#welcomeComponent?: WelcomeComponent;
|
|
376
|
+
readonly #chatHost: ChatBlockHost = { requestRender: () => this.ui.requestRender() };
|
|
382
377
|
|
|
383
378
|
constructor(
|
|
384
379
|
session: AgentSession,
|
|
@@ -484,6 +479,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
484
479
|
|
|
485
480
|
this.#uiHelpers = new UiHelpers(this);
|
|
486
481
|
this.#btwController = new BtwController(this);
|
|
482
|
+
this.#tanCommandController = new TanCommandController(this);
|
|
487
483
|
this.#omfgController = new OmfgController(this);
|
|
488
484
|
this.#extensionUiController = new ExtensionUiController(this);
|
|
489
485
|
this.#eventController = new EventController(this);
|
|
@@ -613,8 +609,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
613
609
|
// Load initial todos
|
|
614
610
|
await this.#loadTodoList();
|
|
615
611
|
|
|
616
|
-
// Start the UI
|
|
617
|
-
|
|
612
|
+
// Start the UI. Cold `omp` launch opts into clearing on the first paint so
|
|
613
|
+
// the initial welcome frame does not append over the previous run's scrollback.
|
|
614
|
+
this.ui.start({ clearScrollback: options.clearInitialTerminalHistory === true });
|
|
618
615
|
pushTerminalTitle();
|
|
619
616
|
setSessionTerminalTitle(this.sessionManager.getSessionName(), this.sessionManager.getCwd());
|
|
620
617
|
this.updateEditorBorderColor();
|
|
@@ -650,9 +647,17 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
650
647
|
this.session.subscribe(event => {
|
|
651
648
|
void this.#handleGoalSessionEvent(event);
|
|
652
649
|
}),
|
|
650
|
+
this.sessionManager.onSessionNameChanged(() => {
|
|
651
|
+
this.#handleSessionAccentInputsChanged();
|
|
652
|
+
}),
|
|
653
|
+
onStatusLineSessionAccentChanged(() => {
|
|
654
|
+
this.#syncStatusLineSettings();
|
|
655
|
+
this.#handleSessionAccentInputsChanged();
|
|
656
|
+
}),
|
|
653
657
|
);
|
|
654
658
|
// Set up theme file watcher
|
|
655
659
|
onThemeChange(() => {
|
|
660
|
+
this.#clearWorkingMessageAccentCache();
|
|
656
661
|
clearRenderCache();
|
|
657
662
|
this.ui.invalidate();
|
|
658
663
|
this.updateEditorBorderColor();
|
|
@@ -977,9 +982,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
977
982
|
this.#goalContinuationTurnInFlight = false;
|
|
978
983
|
}
|
|
979
984
|
if (this.loadingAnimation) {
|
|
980
|
-
this
|
|
981
|
-
this.loadingAnimation = undefined;
|
|
982
|
-
this.statusContainer.clear();
|
|
985
|
+
this.#stopLoadingAnimation(true);
|
|
983
986
|
}
|
|
984
987
|
if (!submission.customType) {
|
|
985
988
|
this.pendingImages = submission.images ? [...submission.images] : [];
|
|
@@ -1017,9 +1020,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1017
1020
|
pendingSubmissionDispose?.();
|
|
1018
1021
|
this.#pendingWorkingMessage = undefined;
|
|
1019
1022
|
if (this.loadingAnimation) {
|
|
1020
|
-
this
|
|
1021
|
-
this.loadingAnimation = undefined;
|
|
1022
|
-
this.statusContainer.clear();
|
|
1023
|
+
this.#stopLoadingAnimation(true);
|
|
1023
1024
|
}
|
|
1024
1025
|
}
|
|
1025
1026
|
}
|
|
@@ -1035,6 +1036,24 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1035
1036
|
this.editor.setMaxHeight(this.#computeEditorMaxHeight());
|
|
1036
1037
|
}
|
|
1037
1038
|
|
|
1039
|
+
#syncStatusLineSettings(): void {
|
|
1040
|
+
this.statusLine.updateSettings({
|
|
1041
|
+
preset: settings.get("statusLine.preset"),
|
|
1042
|
+
leftSegments: settings.get("statusLine.leftSegments"),
|
|
1043
|
+
rightSegments: settings.get("statusLine.rightSegments"),
|
|
1044
|
+
separator: settings.get("statusLine.separator"),
|
|
1045
|
+
showHookStatus: settings.get("statusLine.showHookStatus"),
|
|
1046
|
+
sessionAccent: settings.get("statusLine.sessionAccent"),
|
|
1047
|
+
segmentOptions: settings.get("statusLine.segmentOptions"),
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
#handleSessionAccentInputsChanged(): void {
|
|
1052
|
+
this.#clearWorkingMessageAccentCache();
|
|
1053
|
+
this.statusLine.invalidate();
|
|
1054
|
+
this.updateEditorBorderColor();
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1038
1057
|
updateEditorBorderColor(): void {
|
|
1039
1058
|
if (this.isBashMode) {
|
|
1040
1059
|
this.editor.borderColor = theme.getBashModeBorderColor();
|
|
@@ -1536,22 +1555,15 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1536
1555
|
if (!state?.enabled) {
|
|
1537
1556
|
throw new ToolError("Plan mode is not active.");
|
|
1538
1557
|
}
|
|
1539
|
-
const planFilePath =
|
|
1540
|
-
const planContent = await this.#readPlanFile(planFilePath);
|
|
1541
|
-
if (planContent === null) {
|
|
1542
|
-
throw new ToolError(
|
|
1543
|
-
`Plan file not found at ${planFilePath}. Write the finalized plan to ${planFilePath} before requesting approval.`,
|
|
1544
|
-
);
|
|
1545
|
-
}
|
|
1546
|
-
const normalized = resolvePlanTitle({
|
|
1558
|
+
const { planFilePath, title } = await resolveApprovedPlan({
|
|
1547
1559
|
suppliedTitle: extra?.title,
|
|
1548
|
-
|
|
1549
|
-
|
|
1560
|
+
statePlanFilePath: state.planFilePath,
|
|
1561
|
+
readPlan: url => this.#readPlanFile(url),
|
|
1562
|
+
listPlanFiles: () => this.#listLocalPlanFiles(),
|
|
1550
1563
|
});
|
|
1551
1564
|
const details: PlanApprovalDetails = {
|
|
1552
1565
|
planFilePath,
|
|
1553
|
-
|
|
1554
|
-
title: normalized.title,
|
|
1566
|
+
title,
|
|
1555
1567
|
planExists: true,
|
|
1556
1568
|
};
|
|
1557
1569
|
return {
|
|
@@ -1691,22 +1703,87 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1691
1703
|
}
|
|
1692
1704
|
}
|
|
1693
1705
|
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1706
|
+
/** `local://` URLs of plan files in the session-local root, newest first.
|
|
1707
|
+
* A fallback for `resolveApprovedPlan` when the agent dropped `extra.title`,
|
|
1708
|
+
* so the plan it wrote is still found by scanning recent `*-plan.md` files. */
|
|
1709
|
+
async #listLocalPlanFiles(): Promise<string[]> {
|
|
1710
|
+
const localRoot = this.#resolvePlanFilePath("local://");
|
|
1711
|
+
try {
|
|
1712
|
+
const entries = await fs.readdir(localRoot, { withFileTypes: true });
|
|
1713
|
+
const plans = await Promise.all(
|
|
1714
|
+
entries
|
|
1715
|
+
.filter(entry => entry.isFile() && /plan\.md$/i.test(entry.name))
|
|
1716
|
+
.map(async name => {
|
|
1717
|
+
const stat = await fs.stat(path.join(localRoot, name.name)).catch(() => null);
|
|
1718
|
+
return { url: `local://${name.name}`, mtime: stat?.mtimeMs ?? 0 };
|
|
1719
|
+
}),
|
|
1720
|
+
);
|
|
1721
|
+
return plans.sort((a, b) => b.mtime - a.mtime).map(plan => plan.url);
|
|
1722
|
+
} catch {
|
|
1723
|
+
return [];
|
|
1707
1724
|
}
|
|
1708
|
-
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
showPlanReview(
|
|
1728
|
+
planContent: string,
|
|
1729
|
+
title: string,
|
|
1730
|
+
options: string[],
|
|
1731
|
+
dialogOptions?: {
|
|
1732
|
+
helpText?: string;
|
|
1733
|
+
disabledIndices?: number[];
|
|
1734
|
+
onExternalEditor?: () => void;
|
|
1735
|
+
onPlanEdited?: (content: string) => void;
|
|
1736
|
+
onFeedbackChange?: (feedback: string) => void;
|
|
1737
|
+
initialIndex?: number;
|
|
1738
|
+
},
|
|
1739
|
+
extra?: { slider?: HookSelectorSlider },
|
|
1740
|
+
): Promise<string | undefined> {
|
|
1741
|
+
this.#hidePlanReview();
|
|
1742
|
+
const { promise, resolve } = Promise.withResolvers<string | undefined>();
|
|
1743
|
+
let settled = false;
|
|
1744
|
+
const finish = (choice: string | undefined): void => {
|
|
1745
|
+
if (settled) return;
|
|
1746
|
+
settled = true;
|
|
1747
|
+
this.#hidePlanReview();
|
|
1748
|
+
this.ui.requestRender();
|
|
1749
|
+
resolve(choice);
|
|
1750
|
+
};
|
|
1751
|
+
const overlay = new PlanReviewOverlay(
|
|
1752
|
+
planContent,
|
|
1753
|
+
{
|
|
1754
|
+
promptTitle: title,
|
|
1755
|
+
options,
|
|
1756
|
+
disabledIndices: dialogOptions?.disabledIndices,
|
|
1757
|
+
helpText: dialogOptions?.helpText,
|
|
1758
|
+
initialIndex: dialogOptions?.initialIndex,
|
|
1759
|
+
slider: extra?.slider,
|
|
1760
|
+
externalEditorLabel: this.keybindings.getDisplayString("app.editor.external") || undefined,
|
|
1761
|
+
},
|
|
1762
|
+
{
|
|
1763
|
+
onPick: choice => finish(choice),
|
|
1764
|
+
onCancel: () => finish(undefined),
|
|
1765
|
+
onExternalEditor: dialogOptions?.onExternalEditor,
|
|
1766
|
+
onPlanEdited: dialogOptions?.onPlanEdited,
|
|
1767
|
+
onFeedbackChange: dialogOptions?.onFeedbackChange,
|
|
1768
|
+
},
|
|
1769
|
+
);
|
|
1770
|
+
this.#planReviewOverlay = overlay;
|
|
1771
|
+
this.#planReviewOverlayHandle = this.ui.showOverlay(overlay, {
|
|
1772
|
+
anchor: "bottom-center",
|
|
1773
|
+
width: "100%",
|
|
1774
|
+
maxHeight: "100%",
|
|
1775
|
+
margin: 0,
|
|
1776
|
+
fullscreen: true,
|
|
1777
|
+
});
|
|
1778
|
+
this.ui.setFocus(overlay);
|
|
1709
1779
|
this.ui.requestRender();
|
|
1780
|
+
return promise;
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
#hidePlanReview(): void {
|
|
1784
|
+
this.#planReviewOverlayHandle?.hide();
|
|
1785
|
+
this.#planReviewOverlayHandle = undefined;
|
|
1786
|
+
this.#planReviewOverlay = undefined;
|
|
1710
1787
|
}
|
|
1711
1788
|
|
|
1712
1789
|
#getEditorTerminalPath(): string | null {
|
|
@@ -1728,14 +1805,6 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1728
1805
|
}
|
|
1729
1806
|
}
|
|
1730
1807
|
|
|
1731
|
-
#getPlanReviewHelpText(): string {
|
|
1732
|
-
const externalEditorKey = this.keybindings.getDisplayString("app.editor.external");
|
|
1733
|
-
if (!externalEditorKey) {
|
|
1734
|
-
return "up/down navigate enter select esc cancel";
|
|
1735
|
-
}
|
|
1736
|
-
return `up/down navigate enter select ${externalEditorKey.toLowerCase()} open in editor esc cancel`;
|
|
1737
|
-
}
|
|
1738
|
-
|
|
1739
1808
|
#getPlanApprovalContextUsage(): ContextUsage | undefined {
|
|
1740
1809
|
const executionModel = this.#planModePreviousModelState?.model ?? this.session.model;
|
|
1741
1810
|
const contextWindow = executionModel?.contextWindow;
|
|
@@ -1794,7 +1863,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1794
1863
|
});
|
|
1795
1864
|
if (result !== null) {
|
|
1796
1865
|
await Bun.write(resolvedPath, result);
|
|
1797
|
-
this.#
|
|
1866
|
+
this.#planReviewOverlay?.setPlanContent(result);
|
|
1798
1867
|
this.showStatus("Plan updated in external editor.");
|
|
1799
1868
|
}
|
|
1800
1869
|
} catch (error) {
|
|
@@ -1826,19 +1895,12 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1826
1895
|
planContent: string,
|
|
1827
1896
|
options: {
|
|
1828
1897
|
planFilePath: string;
|
|
1829
|
-
finalPlanFilePath: string;
|
|
1830
1898
|
title: string;
|
|
1831
1899
|
preserveContext?: boolean;
|
|
1832
1900
|
compactBeforeExecute?: boolean;
|
|
1833
1901
|
executionModel?: ResolvedRoleModel;
|
|
1834
1902
|
},
|
|
1835
1903
|
): Promise<void> {
|
|
1836
|
-
await renameApprovedPlanFile({
|
|
1837
|
-
planFilePath: options.planFilePath,
|
|
1838
|
-
finalPlanFilePath: options.finalPlanFilePath,
|
|
1839
|
-
getArtifactsDir: () => this.sessionManager.getArtifactsDir(),
|
|
1840
|
-
getSessionId: () => this.sessionManager.getSessionId(),
|
|
1841
|
-
});
|
|
1842
1904
|
const previousTools = this.#planModePreviousTools ?? this.session.getActiveToolNames();
|
|
1843
1905
|
|
|
1844
1906
|
// Mark the pending abort caused by the plan-mode → compaction transition as
|
|
@@ -1857,8 +1919,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1857
1919
|
if (!options.preserveContext) {
|
|
1858
1920
|
await this.handleClearCommand();
|
|
1859
1921
|
// The new session has a fresh local:// root — persist the approved plan there
|
|
1860
|
-
// so `local://<
|
|
1861
|
-
const newLocalPath = resolveLocalUrlToPath(options.
|
|
1922
|
+
// so `local://<slug>-plan.md` resolves correctly in the execution session.
|
|
1923
|
+
const newLocalPath = resolveLocalUrlToPath(options.planFilePath, {
|
|
1862
1924
|
getArtifactsDir: () => this.sessionManager.getArtifactsDir(),
|
|
1863
1925
|
getSessionId: () => this.sessionManager.getSessionId(),
|
|
1864
1926
|
});
|
|
@@ -1872,7 +1934,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1872
1934
|
// Cancellation skips the synthetic-prompt dispatch (operator's explicit
|
|
1873
1935
|
// abort is honored); failure proceeds best-effort — approval intent stands.
|
|
1874
1936
|
const compactionPrompt = prompt.render(planModeCompactInstructionsPrompt, {
|
|
1875
|
-
planFilePath: options.
|
|
1937
|
+
planFilePath: options.planFilePath,
|
|
1876
1938
|
});
|
|
1877
1939
|
// Pin the plan reference path BEFORE compaction so any user messages
|
|
1878
1940
|
// queued during the compaction await (which `handleCompactCommand`
|
|
@@ -1880,7 +1942,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1880
1942
|
// approved plan in `#buildPlanReferenceMessage`. Reassignment after
|
|
1881
1943
|
// the try/finally is idempotent and kept for the !compactBeforeExecute
|
|
1882
1944
|
// branch.
|
|
1883
|
-
this.session.setPlanReferencePath(options.
|
|
1945
|
+
this.session.setPlanReferencePath(options.planFilePath);
|
|
1884
1946
|
compactOutcome = await this.handleCompactCommand(compactionPrompt);
|
|
1885
1947
|
}
|
|
1886
1948
|
} finally {
|
|
@@ -1896,7 +1958,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1896
1958
|
if (previousTools.length > 0) {
|
|
1897
1959
|
await this.session.setActiveToolsByName(previousTools);
|
|
1898
1960
|
}
|
|
1899
|
-
this.session.setPlanReferencePath(options.
|
|
1961
|
+
this.session.setPlanReferencePath(options.planFilePath);
|
|
1900
1962
|
|
|
1901
1963
|
if (compactOutcome === "cancelled") {
|
|
1902
1964
|
// Explicit abort: honor it. `executeCompaction` already surfaced
|
|
@@ -1933,7 +1995,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1933
1995
|
this.session.markPlanReferenceSent();
|
|
1934
1996
|
const planModePrompt = prompt.render(planModeApprovedPrompt, {
|
|
1935
1997
|
planContent,
|
|
1936
|
-
|
|
1998
|
+
planFilePath: options.planFilePath,
|
|
1937
1999
|
contextPreserved: options.preserveContext === true,
|
|
1938
2000
|
});
|
|
1939
2001
|
await this.session.prompt(planModePrompt, { synthetic: true });
|
|
@@ -2223,7 +2285,6 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2223
2285
|
return;
|
|
2224
2286
|
}
|
|
2225
2287
|
|
|
2226
|
-
this.#renderPlanPreview(planContent, { append: true });
|
|
2227
2288
|
const contextUsage = this.#getPlanApprovalContextUsage();
|
|
2228
2289
|
const keepContextLabel = this.#formatKeepContextLabel(contextUsage);
|
|
2229
2290
|
const keepContextDisabled = this.#isKeepContextDisabled(contextUsage);
|
|
@@ -2253,23 +2314,40 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2253
2314
|
},
|
|
2254
2315
|
}
|
|
2255
2316
|
: undefined;
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
const
|
|
2317
|
+
// The overlay now owns the dynamic, focus-aware help line; the caller only
|
|
2318
|
+
// supplies the trailing cancel hint.
|
|
2319
|
+
const helpText = "esc cancel";
|
|
2320
|
+
// In-overlay edits (section deletes/undo) and section annotations. Deletes
|
|
2321
|
+
// update `editedContent` (and mirror to disk); annotations build `feedback`
|
|
2322
|
+
// that the Refine branch re-prompts the model with.
|
|
2323
|
+
let editedContent: string | undefined;
|
|
2324
|
+
let feedback = "";
|
|
2325
|
+
|
|
2326
|
+
const choice = await this.showPlanReview(
|
|
2327
|
+
planContent,
|
|
2259
2328
|
"Plan mode - next step",
|
|
2260
2329
|
["Approve and execute", "Approve and compact context", keepContextLabel, "Refine plan"],
|
|
2261
2330
|
{
|
|
2262
2331
|
helpText,
|
|
2263
2332
|
onExternalEditor: () => void this.#openPlanInExternalEditor(planFilePath),
|
|
2333
|
+
onPlanEdited: content => {
|
|
2334
|
+
editedContent = content;
|
|
2335
|
+
void Bun.write(this.#resolvePlanFilePath(planFilePath), content);
|
|
2336
|
+
},
|
|
2337
|
+
onFeedbackChange: value => {
|
|
2338
|
+
feedback = value;
|
|
2339
|
+
},
|
|
2264
2340
|
disabledIndices: keepContextDisabled ? [PLAN_KEEP_CONTEXT_OPTION_INDEX] : undefined,
|
|
2265
2341
|
},
|
|
2266
2342
|
{ slider },
|
|
2267
2343
|
);
|
|
2268
2344
|
|
|
2269
2345
|
if (choice === "Approve and execute" || choice === "Approve and compact context" || choice === keepContextLabel) {
|
|
2270
|
-
const finalPlanFilePath = details.finalPlanFilePath || planFilePath;
|
|
2271
2346
|
try {
|
|
2272
|
-
|
|
2347
|
+
// Prefer in-overlay edits (already in memory) over a disk re-read; the
|
|
2348
|
+
// `onPlanEdited` write is fire-and-forget, so reading the file here could
|
|
2349
|
+
// race ahead of it.
|
|
2350
|
+
const latestPlanContent = editedContent ?? (await this.#readPlanFile(planFilePath));
|
|
2273
2351
|
if (!latestPlanContent) {
|
|
2274
2352
|
this.showError(`Plan file not found at ${planFilePath}`);
|
|
2275
2353
|
return;
|
|
@@ -2287,7 +2365,6 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2287
2365
|
cycle && selectedTierIndex !== cycle.currentIndex ? cycle.models[selectedTierIndex] : undefined;
|
|
2288
2366
|
await this.#approvePlan(latestPlanContent, {
|
|
2289
2367
|
planFilePath,
|
|
2290
|
-
finalPlanFilePath,
|
|
2291
2368
|
title: details.title,
|
|
2292
2369
|
preserveContext: choice !== "Approve and execute",
|
|
2293
2370
|
compactBeforeExecute: choice === "Approve and compact context",
|
|
@@ -2300,6 +2377,16 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2300
2377
|
}
|
|
2301
2378
|
return;
|
|
2302
2379
|
}
|
|
2380
|
+
|
|
2381
|
+
if (choice === "Refine plan") {
|
|
2382
|
+
// Section annotations entered in the overlay become a refinement prompt
|
|
2383
|
+
// re-submitted to the model. With no annotations, fall back to today's
|
|
2384
|
+
// behavior: close the overlay and let the operator type their own.
|
|
2385
|
+
if (feedback.trim() && this.onInputCallback) {
|
|
2386
|
+
this.onInputCallback(this.startPendingSubmission({ text: feedback }));
|
|
2387
|
+
}
|
|
2388
|
+
return;
|
|
2389
|
+
}
|
|
2303
2390
|
}
|
|
2304
2391
|
|
|
2305
2392
|
/**
|
|
@@ -2360,8 +2447,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2360
2447
|
|
|
2361
2448
|
stop(): void {
|
|
2362
2449
|
if (this.loadingAnimation) {
|
|
2363
|
-
this
|
|
2364
|
-
this.loadingAnimation = undefined;
|
|
2450
|
+
this.#stopLoadingAnimation(false);
|
|
2365
2451
|
}
|
|
2366
2452
|
this.#cleanupMicAnimation();
|
|
2367
2453
|
this.#cancelTodoAutoClearTimer();
|
|
@@ -2453,9 +2539,6 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2453
2539
|
initializeHookRunner(uiContext: ExtensionUIContext, hasUI: boolean): void {
|
|
2454
2540
|
this.#extensionUiController.initializeHookRunner(uiContext, hasUI);
|
|
2455
2541
|
}
|
|
2456
|
-
createBackgroundUiContext(): ExtensionUIContext {
|
|
2457
|
-
return this.#extensionUiController.createBackgroundUiContext();
|
|
2458
|
-
}
|
|
2459
2542
|
|
|
2460
2543
|
setEditorComponent(
|
|
2461
2544
|
factory: ((tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager) => CustomEditor) | undefined,
|
|
@@ -2497,12 +2580,26 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2497
2580
|
this.ui.requestRender();
|
|
2498
2581
|
}
|
|
2499
2582
|
|
|
2500
|
-
//
|
|
2501
|
-
|
|
2502
|
-
|
|
2583
|
+
// UI helpers
|
|
2584
|
+
present(content: Component | readonly Component[]): void {
|
|
2585
|
+
if (Array.isArray(content)) {
|
|
2586
|
+
for (const item of content) this.#mountChatChild(item);
|
|
2587
|
+
} else {
|
|
2588
|
+
this.#mountChatChild(content as Component);
|
|
2589
|
+
}
|
|
2590
|
+
this.ui.requestRender();
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2593
|
+
#mountChatChild(item: Component): void {
|
|
2594
|
+
this.chatContainer.addChild(item);
|
|
2595
|
+
if (item instanceof ChatBlock) item.mount(this.#chatHost);
|
|
2596
|
+
}
|
|
2597
|
+
|
|
2598
|
+
resetTranscript(): void {
|
|
2599
|
+
this.chatContainer.dispose();
|
|
2600
|
+
this.chatContainer.clear();
|
|
2503
2601
|
}
|
|
2504
2602
|
|
|
2505
|
-
// UI helpers
|
|
2506
2603
|
showStatus(message: string, options?: { dim?: boolean }): void {
|
|
2507
2604
|
this.#uiHelpers.showStatus(message, options);
|
|
2508
2605
|
}
|
|
@@ -2514,15 +2611,12 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2514
2611
|
this.#pendingSubmissionDispose = undefined;
|
|
2515
2612
|
this.#pendingWorkingMessage = undefined;
|
|
2516
2613
|
if (this.loadingAnimation) {
|
|
2517
|
-
this
|
|
2518
|
-
this.loadingAnimation = undefined;
|
|
2519
|
-
this.statusContainer.clear();
|
|
2614
|
+
this.#stopLoadingAnimation(true);
|
|
2520
2615
|
}
|
|
2521
2616
|
this.#uiHelpers.showError(message);
|
|
2522
2617
|
}
|
|
2523
2618
|
|
|
2524
2619
|
showPinnedError(message: string): void {
|
|
2525
|
-
if (this.isBackgrounded) return;
|
|
2526
2620
|
this.errorBannerContainer.clear();
|
|
2527
2621
|
this.errorBannerContainer.addChild(new ErrorBannerComponent(message));
|
|
2528
2622
|
this.ui.requestRender();
|
|
@@ -2580,26 +2674,76 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2580
2674
|
this.ui.requestRender();
|
|
2581
2675
|
}
|
|
2582
2676
|
|
|
2677
|
+
#clearWorkingMessageAccentCache(): void {
|
|
2678
|
+
this.#workingMessageAccentCacheKey = undefined;
|
|
2679
|
+
this.#workingMessageAccentCacheValue = undefined;
|
|
2680
|
+
this.#workingMessageAccentCacheHasValue = false;
|
|
2681
|
+
}
|
|
2682
|
+
|
|
2683
|
+
#buildWorkingMessageAccentCacheKey(): WorkingMessageAccentCacheKey {
|
|
2684
|
+
const sessionAccentEnabled = !isSettingsInitialized() || settings.get("statusLine.sessionAccent") !== false;
|
|
2685
|
+
return {
|
|
2686
|
+
sessionAccentEnabled,
|
|
2687
|
+
sessionName: sessionAccentEnabled ? this.sessionManager.getSessionName() : undefined,
|
|
2688
|
+
accentSurfaceLuminance: theme.accentSurfaceLuminance,
|
|
2689
|
+
};
|
|
2690
|
+
}
|
|
2691
|
+
|
|
2692
|
+
#workingMessageAccentCacheKeyEquals(a: WorkingMessageAccentCacheKey, b: WorkingMessageAccentCacheKey): boolean {
|
|
2693
|
+
return (
|
|
2694
|
+
a.sessionName === b.sessionName &&
|
|
2695
|
+
a.accentSurfaceLuminance === b.accentSurfaceLuminance &&
|
|
2696
|
+
a.sessionAccentEnabled === b.sessionAccentEnabled
|
|
2697
|
+
);
|
|
2698
|
+
}
|
|
2699
|
+
|
|
2700
|
+
#cacheWorkingMessageAccent(
|
|
2701
|
+
key: WorkingMessageAccentCacheKey,
|
|
2702
|
+
value: WorkingMessageAccent | undefined,
|
|
2703
|
+
): WorkingMessageAccent | undefined {
|
|
2704
|
+
this.#workingMessageAccentCacheKey = key;
|
|
2705
|
+
this.#workingMessageAccentCacheValue = value;
|
|
2706
|
+
this.#workingMessageAccentCacheHasValue = true;
|
|
2707
|
+
return value;
|
|
2708
|
+
}
|
|
2709
|
+
|
|
2583
2710
|
#getWorkingMessageAccent(): WorkingMessageAccent | undefined {
|
|
2584
|
-
const
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2711
|
+
const key = this.#buildWorkingMessageAccentCacheKey();
|
|
2712
|
+
if (
|
|
2713
|
+
this.#workingMessageAccentCacheHasValue &&
|
|
2714
|
+
this.#workingMessageAccentCacheKey &&
|
|
2715
|
+
this.#workingMessageAccentCacheKeyEquals(key, this.#workingMessageAccentCacheKey)
|
|
2716
|
+
) {
|
|
2717
|
+
return this.#workingMessageAccentCacheValue;
|
|
2718
|
+
}
|
|
2719
|
+
if (!key.sessionAccentEnabled || !key.sessionName) {
|
|
2720
|
+
return this.#cacheWorkingMessageAccent(key, undefined);
|
|
2721
|
+
}
|
|
2722
|
+
const hex = getSessionAccentHex(key.sessionName, key.accentSurfaceLuminance);
|
|
2588
2723
|
const main = getSessionAccentAnsi(hex);
|
|
2589
2724
|
const dim = getSessionAccentAnsi(adjustHsv(hex, { s: 0.55, v: 0.65 }));
|
|
2590
|
-
return main && dim ? { main, dim } : undefined;
|
|
2725
|
+
return this.#cacheWorkingMessageAccent(key, main && dim ? { main, dim } : undefined);
|
|
2591
2726
|
}
|
|
2592
2727
|
|
|
2593
2728
|
ensureLoadingAnimation(): void {
|
|
2594
2729
|
if (!this.loadingAnimation) {
|
|
2730
|
+
this.#clearWorkingMessageAccentCache();
|
|
2595
2731
|
this.statusContainer.clear();
|
|
2732
|
+
const messageColorFn = ((message: string) =>
|
|
2733
|
+
renderWorkingMessage(message, this.#getWorkingMessageAccent())) as LoaderMessageColorFn & {
|
|
2734
|
+
animated?: true;
|
|
2735
|
+
};
|
|
2736
|
+
// Shimmer drives the 30fps redraw; when it is disabled the working
|
|
2737
|
+
// message is static, so leave `animated` unset and let the loader use
|
|
2738
|
+
// the spinner-only ~12.5fps cadence instead of repainting a frozen line.
|
|
2739
|
+
if (shimmerEnabled()) messageColorFn.animated = true;
|
|
2596
2740
|
this.loadingAnimation = new Loader(
|
|
2597
2741
|
this.ui,
|
|
2598
2742
|
spinner => {
|
|
2599
2743
|
const accent = this.#getWorkingMessageAccent();
|
|
2600
2744
|
return accent ? `${accent.main}${spinner}\x1b[39m` : theme.fg("accent", spinner);
|
|
2601
2745
|
},
|
|
2602
|
-
|
|
2746
|
+
messageColorFn,
|
|
2603
2747
|
this.#defaultWorkingMessage,
|
|
2604
2748
|
getSymbolTheme().spinnerFrames,
|
|
2605
2749
|
);
|
|
@@ -2609,6 +2753,16 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2609
2753
|
this.applyPendingWorkingMessage();
|
|
2610
2754
|
}
|
|
2611
2755
|
|
|
2756
|
+
#stopLoadingAnimation(clearStatusContainer: boolean): void {
|
|
2757
|
+
if (!this.loadingAnimation) return;
|
|
2758
|
+
this.loadingAnimation.stop();
|
|
2759
|
+
this.loadingAnimation = undefined;
|
|
2760
|
+
this.#clearWorkingMessageAccentCache();
|
|
2761
|
+
if (clearStatusContainer) {
|
|
2762
|
+
this.statusContainer.clear();
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
|
|
2612
2766
|
setWorkingMessage(message?: string): void {
|
|
2613
2767
|
if (message === undefined) {
|
|
2614
2768
|
this.#pendingWorkingMessage = undefined;
|
|
@@ -2636,6 +2790,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2636
2790
|
this.setWorkingMessage(message);
|
|
2637
2791
|
}
|
|
2638
2792
|
|
|
2793
|
+
notifyInterrupting(): void {
|
|
2794
|
+
this.#eventController.notifyInterrupting();
|
|
2795
|
+
}
|
|
2796
|
+
|
|
2639
2797
|
showNewVersionNotification(newVersion: string): void {
|
|
2640
2798
|
this.#uiHelpers.showNewVersionNotification(newVersion);
|
|
2641
2799
|
}
|
|
@@ -2751,7 +2909,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2751
2909
|
this.#omfgController.dispose();
|
|
2752
2910
|
this.#extensionUiController.clearExtensionTerminalInputListeners();
|
|
2753
2911
|
this.clearPinnedError();
|
|
2754
|
-
this.#
|
|
2912
|
+
this.#hidePlanReview();
|
|
2755
2913
|
}
|
|
2756
2914
|
|
|
2757
2915
|
handleClearCommand(): Promise<void> {
|
|
@@ -2759,6 +2917,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2759
2917
|
return this.#commandController.handleClearCommand();
|
|
2760
2918
|
}
|
|
2761
2919
|
|
|
2920
|
+
handleFreshCommand(): Promise<void> {
|
|
2921
|
+
return this.#commandController.handleFreshCommand();
|
|
2922
|
+
}
|
|
2923
|
+
|
|
2762
2924
|
handleDropCommand(): Promise<void> {
|
|
2763
2925
|
this.#prepareSessionSwitch();
|
|
2764
2926
|
return this.#commandController.handleDropCommand();
|
|
@@ -2994,10 +3156,6 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2994
3156
|
this.#inputController.handleDequeue();
|
|
2995
3157
|
}
|
|
2996
3158
|
|
|
2997
|
-
handleBackgroundCommand(): void {
|
|
2998
|
-
this.#inputController.handleBackgroundCommand();
|
|
2999
|
-
}
|
|
3000
|
-
|
|
3001
3159
|
handleImagePaste(): Promise<boolean> {
|
|
3002
3160
|
return this.#inputController.handleImagePaste();
|
|
3003
3161
|
}
|
|
@@ -3006,6 +3164,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3006
3164
|
return this.#btwController.start(question);
|
|
3007
3165
|
}
|
|
3008
3166
|
|
|
3167
|
+
handleTanCommand(work: string): Promise<void> {
|
|
3168
|
+
return this.#tanCommandController.start(work);
|
|
3169
|
+
}
|
|
3170
|
+
|
|
3009
3171
|
hasActiveBtw(): boolean {
|
|
3010
3172
|
return this.#btwController.hasActiveRequest();
|
|
3011
3173
|
}
|