@oh-my-pi/pi-coding-agent 15.12.4 → 15.13.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 +304 -6
- package/dist/cli.js +1015 -881
- package/dist/types/async/job-manager.d.ts +15 -0
- package/dist/types/autolearn/controller.d.ts +25 -0
- package/dist/types/autolearn/managed-skills.d.ts +45 -0
- package/dist/types/autoresearch/state.d.ts +1 -1
- package/dist/types/autoresearch/types.d.ts +1 -1
- package/dist/types/cli/args.d.ts +19 -1
- package/dist/types/cli/session-picker.d.ts +1 -1
- package/dist/types/cli/setup-cli.d.ts +1 -1
- package/dist/types/cli/setup-model-picker.d.ts +14 -0
- package/dist/types/collab/protocol.d.ts +1 -1
- package/dist/types/commands/say.d.ts +24 -0
- package/dist/types/config/keybindings.d.ts +3 -3
- package/dist/types/config/model-registry.d.ts +10 -0
- package/dist/types/config/models-config-schema.d.ts +12 -0
- package/dist/types/config/models-config.d.ts +8 -2
- package/dist/types/config/settings-schema.d.ts +261 -58
- package/dist/types/export/html/index.d.ts +2 -1
- package/dist/types/extensibility/extensions/model-api.d.ts +17 -0
- package/dist/types/extensibility/extensions/runner.d.ts +3 -1
- package/dist/types/extensibility/extensions/types.d.ts +47 -1
- package/dist/types/extensibility/hooks/index.d.ts +2 -1
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +9 -0
- package/dist/types/extensibility/plugins/loader.d.ts +11 -0
- package/dist/types/extensibility/shared-events.d.ts +1 -1
- package/dist/types/extensibility/skills.d.ts +10 -0
- package/dist/types/goals/guided-setup.d.ts +18 -0
- package/dist/types/goals/state.d.ts +1 -1
- package/dist/types/hindsight/transcript.d.ts +1 -1
- package/dist/types/index.d.ts +5 -0
- package/dist/types/internal-urls/local-protocol.d.ts +4 -2
- package/dist/types/main.d.ts +4 -3
- package/dist/types/mcp/startup-events.d.ts +11 -0
- package/dist/types/memories/index.d.ts +7 -0
- package/dist/types/memory-backend/local-backend.d.ts +4 -3
- package/dist/types/mnemopi/config.d.ts +4 -4
- package/dist/types/modes/components/agent-hub.d.ts +6 -0
- package/dist/types/modes/components/assistant-message.d.ts +1 -2
- package/dist/types/modes/components/compaction-summary-message.d.ts +15 -1
- package/dist/types/modes/components/custom-editor.d.ts +39 -1
- package/dist/types/modes/components/custom-editor.test.d.ts +1 -0
- package/dist/types/modes/components/session-selector.d.ts +1 -1
- package/dist/types/modes/components/tool-execution.d.ts +26 -16
- package/dist/types/modes/components/transcript-container.d.ts +23 -2
- package/dist/types/modes/components/tree-selector.d.ts +1 -1
- package/dist/types/modes/components/usage-row.d.ts +3 -0
- package/dist/types/modes/controllers/command-controller.d.ts +2 -2
- package/dist/types/modes/controllers/input-controller.d.ts +14 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +3 -1
- package/dist/types/modes/gradient-highlight.d.ts +9 -4
- package/dist/types/modes/image-references.d.ts +6 -0
- package/dist/types/modes/interactive-mode.d.ts +27 -3
- package/dist/types/modes/magic-keywords.d.ts +13 -1
- package/dist/types/modes/rpc/rpc-mode.d.ts +35 -1
- package/dist/types/modes/rpc/rpc-types.d.ts +9 -1
- package/dist/types/modes/runtime-init.d.ts +4 -0
- package/dist/types/modes/theme/theme.d.ts +13 -2
- package/dist/types/modes/types.d.ts +8 -2
- package/dist/types/modes/utils/ui-helpers.d.ts +1 -1
- package/dist/types/registry/agent-registry.d.ts +17 -0
- package/dist/types/secrets/obfuscator.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +14 -2
- package/dist/types/session/indexed-session-storage.d.ts +3 -4
- package/dist/types/session/session-context.d.ts +39 -0
- package/dist/types/session/session-entries.d.ts +159 -0
- package/dist/types/session/session-listing.d.ts +69 -0
- package/dist/types/session/session-loader.d.ts +16 -0
- package/dist/types/session/session-manager.d.ts +82 -474
- package/dist/types/session/session-migrations.d.ts +12 -0
- package/dist/types/session/session-paths.d.ts +25 -0
- package/dist/types/session/session-persistence.d.ts +8 -0
- package/dist/types/session/session-storage.d.ts +11 -12
- package/dist/types/session/snapcompact-inline.d.ts +12 -1
- package/dist/types/session/snapcompact-savings-journal.d.ts +46 -0
- package/dist/types/session/tool-choice-queue.d.ts +6 -6
- package/dist/types/stt/asr-client.d.ts +90 -0
- package/dist/types/stt/asr-protocol.d.ts +97 -0
- package/dist/types/stt/asr-worker.d.ts +2 -0
- package/dist/types/stt/downloader.d.ts +38 -0
- package/dist/types/stt/endpointer.d.ts +59 -0
- package/dist/types/stt/index.d.ts +5 -1
- package/dist/types/stt/models.d.ts +120 -0
- package/dist/types/stt/recorder.d.ts +17 -0
- package/dist/types/stt/stt-controller.d.ts +6 -0
- package/dist/types/stt/transcriber.d.ts +5 -7
- package/dist/types/stt/wav.d.ts +29 -0
- package/dist/types/system-prompt.d.ts +4 -0
- package/dist/types/task/executor.d.ts +2 -0
- package/dist/types/task/index.d.ts +9 -1
- package/dist/types/task/types.d.ts +36 -0
- package/dist/types/tools/bash.d.ts +2 -2
- package/dist/types/tools/eval-render.d.ts +1 -1
- package/dist/types/tools/index.d.ts +11 -1
- package/dist/types/tools/irc.d.ts +1 -0
- package/dist/types/tools/learn.d.ts +51 -0
- package/dist/types/tools/manage-skill.d.ts +40 -0
- package/dist/types/tools/plan-mode-guard.d.ts +10 -0
- package/dist/types/tools/renderers.d.ts +7 -11
- package/dist/types/tools/ssh.d.ts +1 -1
- package/dist/types/tools/todo.d.ts +1 -1
- package/dist/types/tools/tts.d.ts +25 -0
- package/dist/types/tools/write.d.ts +1 -1
- package/dist/types/tts/downloader.d.ts +20 -0
- package/dist/types/tts/index.d.ts +8 -0
- package/dist/types/tts/models.d.ts +82 -0
- package/dist/types/tts/player.d.ts +32 -0
- package/dist/types/tts/runtime.d.ts +6 -0
- package/dist/types/tts/streaming-player.d.ts +41 -0
- package/dist/types/tts/tts-client.d.ts +93 -0
- package/dist/types/tts/tts-protocol.d.ts +95 -0
- package/dist/types/tts/tts-worker.d.ts +2 -0
- package/dist/types/tts/vocalizer.d.ts +41 -0
- package/dist/types/tts/wav.d.ts +8 -0
- package/dist/types/utils/tool-choice.d.ts +8 -0
- package/dist/types/utils/tools-manager.d.ts +2 -1
- package/dist/types/utils/tools-manager.test.d.ts +1 -0
- package/dist/types/web/scrapers/github.d.ts +1 -1
- package/package.json +15 -14
- package/src/async/job-manager.ts +49 -0
- package/src/autolearn/controller.ts +139 -0
- package/src/autolearn/managed-skills.ts +257 -0
- package/src/autoresearch/state.ts +1 -1
- package/src/autoresearch/types.ts +1 -1
- package/src/cli/args.ts +56 -2
- package/src/cli/session-picker.ts +2 -1
- package/src/cli/setup-cli.ts +148 -47
- package/src/cli/setup-model-picker.ts +43 -0
- package/src/cli-commands.ts +1 -0
- package/src/cli.ts +45 -13
- package/src/collab/host.ts +1 -1
- package/src/collab/protocol.ts +1 -1
- package/src/commands/say.ts +102 -0
- package/src/commands/setup.ts +1 -1
- package/src/commit/agentic/tools/analyze-file.ts +3 -0
- package/src/config/keybindings.ts +2 -2
- package/src/config/model-discovery.ts +11 -5
- package/src/config/model-registry.ts +64 -9
- package/src/config/models-config-schema.ts +4 -1
- package/src/config/models-config.ts +2 -1
- package/src/config/settings-schema.ts +248 -32
- package/src/config/settings.ts +10 -0
- package/src/discovery/builtin.ts +23 -1
- package/src/discovery/claude-plugins.ts +44 -5
- package/src/discovery/helpers.ts +41 -1
- package/src/eval/__tests__/budget-bridge.test.ts +1 -1
- package/src/eval/js/shared/prelude.txt +69 -17
- package/src/export/html/index.ts +3 -6
- package/src/extensibility/extensions/model-api.ts +41 -0
- package/src/extensibility/extensions/runner.ts +4 -0
- package/src/extensibility/extensions/types.ts +52 -1
- package/src/extensibility/extensions/wrapper.ts +41 -5
- package/src/extensibility/hooks/index.ts +2 -1
- package/src/extensibility/plugins/legacy-pi-compat.ts +43 -13
- package/src/extensibility/plugins/loader.ts +30 -19
- package/src/extensibility/plugins/manager.ts +221 -90
- package/src/extensibility/shared-events.ts +1 -1
- package/src/extensibility/skills.ts +96 -15
- package/src/goals/guided-setup.ts +133 -0
- package/src/goals/state.ts +1 -1
- package/src/hindsight/transcript.ts +1 -1
- package/src/index.ts +5 -0
- package/src/internal-urls/docs-index.generated.ts +10 -10
- package/src/internal-urls/history-protocol.ts +1 -1
- package/src/internal-urls/local-protocol.ts +29 -7
- package/src/main.ts +27 -7
- package/src/mcp/startup-events.ts +21 -0
- package/src/mcp/transports/stdio.ts +2 -1
- package/src/memories/index.ts +146 -11
- package/src/memory-backend/local-backend.ts +11 -5
- package/src/mnemopi/backend.ts +1 -0
- package/src/mnemopi/config.ts +26 -10
- package/src/modes/acp/acp-agent.ts +3 -5
- package/src/modes/components/agent-hub.ts +49 -4
- package/src/modes/components/assistant-message.ts +4 -37
- package/src/modes/components/compaction-summary-message.ts +125 -26
- package/src/modes/components/custom-editor.test.ts +96 -0
- package/src/modes/components/custom-editor.ts +164 -8
- package/src/modes/components/session-selector.ts +1 -1
- package/src/modes/components/settings-defs.ts +7 -0
- package/src/modes/components/tool-execution.ts +82 -43
- package/src/modes/components/transcript-container.ts +70 -1
- package/src/modes/components/tree-selector.ts +1 -1
- package/src/modes/components/usage-row.ts +18 -0
- package/src/modes/components/user-message.ts +4 -2
- package/src/modes/controllers/command-controller.ts +14 -4
- package/src/modes/controllers/event-controller.ts +78 -11
- package/src/modes/controllers/extension-ui-controller.ts +6 -0
- package/src/modes/controllers/input-controller.ts +258 -27
- package/src/modes/controllers/selector-controller.ts +12 -2
- package/src/modes/gradient-highlight.ts +21 -9
- package/src/modes/image-references.ts +20 -0
- package/src/modes/interactive-mode.ts +286 -40
- package/src/modes/magic-keywords.ts +27 -5
- package/src/modes/rpc/rpc-mode.ts +146 -14
- package/src/modes/rpc/rpc-subagents.ts +2 -2
- package/src/modes/rpc/rpc-types.ts +8 -2
- package/src/modes/runtime-init.ts +28 -3
- package/src/modes/theme/theme.ts +98 -50
- package/src/modes/types.ts +6 -2
- package/src/modes/utils/hotkeys-markdown.ts +1 -1
- package/src/modes/utils/ui-helpers.ts +34 -6
- package/src/priority.json +5 -1
- package/src/prompts/agents/task.md +1 -0
- package/src/prompts/goals/guided-goal-interview.md +8 -0
- package/src/prompts/goals/guided-goal-system.md +12 -0
- package/src/prompts/memories/read-path.md +6 -0
- package/src/prompts/system/autolearn-guidance-learn.md +1 -0
- package/src/prompts/system/autolearn-guidance.md +7 -0
- package/src/prompts/system/autolearn-nudge.md +3 -0
- package/src/prompts/system/eager-task.md +7 -0
- package/src/prompts/system/eager-todo.md +11 -6
- package/src/prompts/system/subagent-system-prompt.md +4 -0
- package/src/prompts/system/system-prompt.md +10 -5
- package/src/prompts/system/title-marker-instruction.md +1 -0
- package/src/prompts/system/title-system-marker.md +16 -0
- package/src/prompts/tools/job.md +1 -0
- package/src/prompts/tools/learn.md +7 -0
- package/src/prompts/tools/manage-skill.md +9 -0
- package/src/prompts/tools/task.md +3 -0
- package/src/registry/agent-registry.ts +30 -0
- package/src/sdk.ts +88 -24
- package/src/secrets/obfuscator.ts +1 -1
- package/src/session/agent-session.ts +209 -87
- package/src/session/history-storage.ts +2 -2
- package/src/session/indexed-session-storage.ts +7 -17
- package/src/session/session-context.ts +352 -0
- package/src/session/session-entries.ts +194 -0
- package/src/session/session-listing.ts +588 -0
- package/src/session/session-loader.ts +106 -0
- package/src/session/session-manager.ts +933 -3145
- package/src/session/session-migrations.ts +78 -0
- package/src/session/session-paths.ts +193 -0
- package/src/session/session-persistence.ts +131 -0
- package/src/session/session-storage.ts +91 -50
- package/src/session/snapcompact-inline.ts +21 -1
- package/src/session/snapcompact-savings-journal.ts +113 -0
- package/src/session/tool-choice-queue.ts +23 -11
- package/src/slash-commands/builtin-registry.ts +25 -3
- package/src/stt/asr-client.ts +520 -0
- package/src/stt/asr-protocol.ts +65 -0
- package/src/stt/asr-worker.ts +790 -0
- package/src/stt/downloader.ts +107 -47
- package/src/stt/endpointer.ts +259 -0
- package/src/stt/index.ts +5 -1
- package/src/stt/models.ts +150 -0
- package/src/stt/recorder.ts +247 -60
- package/src/stt/stt-controller.ts +201 -22
- package/src/stt/transcriber.ts +37 -68
- package/src/stt/wav.ts +173 -0
- package/src/system-prompt.ts +8 -0
- package/src/task/agents.ts +1 -2
- package/src/task/executor.ts +49 -15
- package/src/task/index.ts +60 -6
- package/src/task/render.ts +83 -8
- package/src/task/types.ts +53 -0
- package/src/tools/ask.ts +8 -0
- package/src/tools/bash.ts +4 -3
- package/src/tools/eval-render.ts +4 -3
- package/src/tools/index.ts +40 -4
- package/src/tools/irc.ts +10 -2
- package/src/tools/job.ts +14 -2
- package/src/tools/learn.ts +144 -0
- package/src/tools/manage-skill.ts +104 -0
- package/src/tools/plan-mode-guard.ts +53 -19
- package/src/tools/renderers.ts +7 -11
- package/src/tools/ssh.ts +4 -3
- package/src/tools/todo.ts +1 -1
- package/src/tools/tts.ts +203 -92
- package/src/tools/write.ts +18 -2
- package/src/tts/downloader.ts +64 -0
- package/src/tts/index.ts +8 -0
- package/src/tts/models.ts +137 -0
- package/src/tts/player.ts +137 -0
- package/src/tts/runtime.ts +21 -0
- package/src/tts/streaming-player.ts +266 -0
- package/src/tts/tts-client.ts +647 -0
- package/src/tts/tts-protocol.ts +60 -0
- package/src/tts/tts-worker.ts +497 -0
- package/src/tts/vocalizer.ts +162 -0
- package/src/tts/wav.ts +58 -0
- package/src/utils/title-generator.ts +48 -5
- package/src/utils/tool-choice.ts +16 -0
- package/src/utils/tools-manager.test.ts +25 -0
- package/src/utils/tools-manager.ts +19 -1
- package/src/web/scrapers/github.ts +96 -0
- package/src/web/search/index.ts +13 -0
- package/src/web/search/providers/searxng.ts +13 -1
- package/dist/types/stt/setup.d.ts +0 -18
- package/src/stt/setup.ts +0 -52
- package/src/stt/transcribe.py +0 -70
|
@@ -64,10 +64,12 @@ import type {
|
|
|
64
64
|
} from "../extensibility/extensions";
|
|
65
65
|
import type { CompactOptions } from "../extensibility/extensions/types";
|
|
66
66
|
import { loadSlashCommands } from "../extensibility/slash-commands";
|
|
67
|
+
import { type GuidedGoalMessage, runGuidedGoalTurn } from "../goals/guided-setup";
|
|
67
68
|
import type { Goal, GoalModeState } from "../goals/state";
|
|
68
69
|
import { resolveLocalUrlToPath } from "../internal-urls";
|
|
69
70
|
import { LSP_STARTUP_EVENT_CHANNEL, type LspStartupEvent } from "../lsp/startup-events";
|
|
70
71
|
import type { MCPManager } from "../mcp";
|
|
72
|
+
import { formatMCPConnectingMessage, isMcpConnectingEvent, MCP_CONNECTING_EVENT_CHANNEL } from "../mcp/startup-events";
|
|
71
73
|
import {
|
|
72
74
|
humanizePlanTitle,
|
|
73
75
|
type PlanApprovalDetails,
|
|
@@ -80,8 +82,9 @@ import planModeCompactInstructionsPrompt from "../prompts/system/plan-mode-compa
|
|
|
80
82
|
};
|
|
81
83
|
import type { AgentSession, AgentSessionEvent, ResolvedRoleModel } from "../session/agent-session";
|
|
82
84
|
import { HistoryStorage } from "../session/history-storage";
|
|
83
|
-
import type { SessionContext
|
|
84
|
-
import { getRecentSessions } from "../session/session-
|
|
85
|
+
import type { SessionContext } from "../session/session-context";
|
|
86
|
+
import { getRecentSessions } from "../session/session-listing";
|
|
87
|
+
import type { SessionManager } from "../session/session-manager";
|
|
85
88
|
import type { ShakeMode } from "../session/shake-types";
|
|
86
89
|
import { BUILTIN_SLASH_COMMAND_RESERVED_NAMES, BUILTIN_SLASH_COMMANDS } from "../slash-commands/builtin-registry";
|
|
87
90
|
import { formatDuration } from "../slash-commands/helpers/format";
|
|
@@ -95,6 +98,7 @@ import { setAutoQaConsentHandler } from "../tools/report-tool-issue";
|
|
|
95
98
|
import { type ResolveToolDetails, runResolveInvocation } from "../tools/resolve";
|
|
96
99
|
import { formatPhaseDisplayName, selectStickyTodoWindow, todoMatchesAnyDescription } from "../tools/todo";
|
|
97
100
|
import { ToolError } from "../tools/tool-errors";
|
|
101
|
+
import { vocalizer } from "../tts/vocalizer";
|
|
98
102
|
import type { EventBus } from "../utils/event-bus";
|
|
99
103
|
import { getEditorCommand, openInEditor } from "../utils/external-editor";
|
|
100
104
|
import { getSessionAccentAnsi, getSessionAccentHex } from "../utils/session-color";
|
|
@@ -210,6 +214,25 @@ const EDITOR_MAX_HEIGHT_MIN = 6;
|
|
|
210
214
|
const EDITOR_MAX_HEIGHT_MAX = 18;
|
|
211
215
|
const EDITOR_RESERVED_ROWS = 12;
|
|
212
216
|
const EDITOR_FALLBACK_ROWS = 24;
|
|
217
|
+
const EDITOR_MIN_CHROME_ROWS = 4; // rows reserved for transcript + status on small terms
|
|
218
|
+
const EDITOR_MIN_RENDERED_ROWS = 3; // bordered editor floor: top+bottom border + 1 content row
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Editor max-height cap for a terminal of `terminalRows` rows.
|
|
222
|
+
*
|
|
223
|
+
* Roomy terminals get the comfortable [6, 18] band. Small terminals shrink the
|
|
224
|
+
* cap so the editor leaves at least EDITOR_MIN_CHROME_ROWS rows for the
|
|
225
|
+
* transcript + status line. The editor is bordered, so it never renders fewer
|
|
226
|
+
* than EDITOR_MIN_RENDERED_ROWS rows; once the terminal is too small for both
|
|
227
|
+
* (terminalRows < EDITOR_MIN_RENDERED_ROWS + EDITOR_MIN_CHROME_ROWS) the cap is
|
|
228
|
+
* pinned to that floor — returning a smaller number would not shrink the editor
|
|
229
|
+
* any further, it would only misreport the rows it actually occupies.
|
|
230
|
+
*/
|
|
231
|
+
export function computeEditorMaxHeight(terminalRows: number): number {
|
|
232
|
+
const rows = Number.isFinite(terminalRows) && terminalRows > 0 ? terminalRows : EDITOR_FALLBACK_ROWS;
|
|
233
|
+
const comfortable = Math.max(EDITOR_MAX_HEIGHT_MIN, Math.min(EDITOR_MAX_HEIGHT_MAX, rows - EDITOR_RESERVED_ROWS));
|
|
234
|
+
return Math.max(EDITOR_MIN_RENDERED_ROWS, Math.min(comfortable, rows - EDITOR_MIN_CHROME_ROWS));
|
|
235
|
+
}
|
|
213
236
|
|
|
214
237
|
const HUD_NOTE_SUP_DIGITS: Record<string, string> = {
|
|
215
238
|
"0": "\u2070",
|
|
@@ -282,6 +305,10 @@ class StatusContainer extends Container implements NativeScrollbackLiveRegion {
|
|
|
282
305
|
}
|
|
283
306
|
}
|
|
284
307
|
|
|
308
|
+
/** How long the ctrl+p model-role cycle chip track lingers above the editor
|
|
309
|
+
* before it auto-clears, mirroring the todo HUD's auto-clear timer. */
|
|
310
|
+
const MODEL_CYCLE_TRACK_CLEAR_MS = 4000;
|
|
311
|
+
|
|
285
312
|
/**
|
|
286
313
|
* Build the anchored subagent HUD block: a bold accent "Subagents" header plus
|
|
287
314
|
* one hooked row per running agent in the same `Id: description` shape the
|
|
@@ -340,6 +367,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
340
367
|
btwContainer: Container;
|
|
341
368
|
omfgContainer: Container;
|
|
342
369
|
errorBannerContainer: Container;
|
|
370
|
+
modelCycleContainer: Container;
|
|
343
371
|
editor: CustomEditor;
|
|
344
372
|
editorContainer: Container;
|
|
345
373
|
hookWidgetContainerAbove: Container;
|
|
@@ -360,6 +388,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
360
388
|
loopLimit: LoopLimitRuntime | undefined = undefined;
|
|
361
389
|
#loopAutoSubmitTimer: NodeJS.Timeout | undefined;
|
|
362
390
|
#todoAutoClearTimer: NodeJS.Timeout | undefined;
|
|
391
|
+
#modelCycleClearTimer: NodeJS.Timeout | undefined;
|
|
363
392
|
todoPhases: TodoPhase[] = [];
|
|
364
393
|
hideThinkingBlock = false;
|
|
365
394
|
pendingImages: ImageContent[] = [];
|
|
@@ -462,6 +491,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
462
491
|
}
|
|
463
492
|
this.statusContainer.clear();
|
|
464
493
|
this.pendingMessagesContainer.clear();
|
|
494
|
+
this.#cancelModelCycleClearTimer();
|
|
495
|
+
this.modelCycleContainer.clear();
|
|
465
496
|
this.compactionQueuedMessages = [];
|
|
466
497
|
this.streamingComponent = undefined;
|
|
467
498
|
this.streamingMessage = undefined;
|
|
@@ -508,6 +539,15 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
508
539
|
this.#handleLspStartupEvent(data as LspStartupEvent);
|
|
509
540
|
}),
|
|
510
541
|
);
|
|
542
|
+
this.#eventBusUnsubscribers.push(
|
|
543
|
+
eventBus.on(MCP_CONNECTING_EVENT_CHANNEL, data => {
|
|
544
|
+
if (!isMcpConnectingEvent(data)) {
|
|
545
|
+
logger.warn("Ignoring malformed mcp:connecting event", { data });
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
this.showStatus(formatMCPConnectingMessage(data.serverNames));
|
|
549
|
+
}),
|
|
550
|
+
);
|
|
511
551
|
}
|
|
512
552
|
|
|
513
553
|
this.ui = new TUI(new ProcessTerminal(), settings.get("showHardwareCursor"));
|
|
@@ -524,6 +564,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
524
564
|
this.btwContainer = new Container();
|
|
525
565
|
this.omfgContainer = new Container();
|
|
526
566
|
this.errorBannerContainer = new Container();
|
|
567
|
+
this.modelCycleContainer = new Container();
|
|
527
568
|
this.editor = new CustomEditor(getEditorTheme());
|
|
528
569
|
this.editor.setUseTerminalCursor(this.ui.getShowHardwareCursor());
|
|
529
570
|
this.editor.setAutocompleteMaxVisible(settings.get("autocompleteMaxVisible"));
|
|
@@ -533,6 +574,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
533
574
|
this.editor.onAutocompleteUpdate = () => {
|
|
534
575
|
this.ui.requestRender();
|
|
535
576
|
};
|
|
577
|
+
this.editor.setShimmerRepaintHandler(() => this.ui.requestComponentRender(this.editor));
|
|
536
578
|
this.#syncEditorMaxHeight();
|
|
537
579
|
this.#resizeHandler = () => {
|
|
538
580
|
this.#syncEditorMaxHeight();
|
|
@@ -692,6 +734,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
692
734
|
this.ui.addChild(this.btwContainer);
|
|
693
735
|
this.ui.addChild(this.omfgContainer);
|
|
694
736
|
this.ui.addChild(this.errorBannerContainer);
|
|
737
|
+
this.ui.addChild(this.modelCycleContainer);
|
|
695
738
|
this.ui.addChild(this.statusLine); // Only renders hook statuses (main status in editor border)
|
|
696
739
|
this.ui.addChild(this.hookWidgetContainerAbove);
|
|
697
740
|
this.ui.addChild(this.editorContainer);
|
|
@@ -810,8 +853,30 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
810
853
|
name: cmd.name,
|
|
811
854
|
description: cmd.description,
|
|
812
855
|
}));
|
|
856
|
+
// Surface discovered prompt templates in the picker. AgentSession.prompt() expands
|
|
857
|
+
// `expandSlashCommand` before `expandPromptTemplate`, and builtin command
|
|
858
|
+
// execution resolves aliases before template expansion. Mirror that command
|
|
859
|
+
// resolution order by skipping templates whose names already appear in any
|
|
860
|
+
// builtin/hook/custom/skill/file command token.
|
|
861
|
+
const reservedNames = new Set<string>();
|
|
862
|
+
for (const command of this.#pendingSlashCommands) {
|
|
863
|
+
reservedNames.add(command.name);
|
|
864
|
+
for (const alias of command.aliases ?? []) reservedNames.add(alias);
|
|
865
|
+
}
|
|
866
|
+
for (const command of fileSlashCommands) {
|
|
867
|
+
reservedNames.add(command.name);
|
|
868
|
+
for (const alias of command.aliases ?? []) reservedNames.add(alias);
|
|
869
|
+
}
|
|
870
|
+
const promptTemplateCommands: SlashCommand[] = this.session.promptTemplates
|
|
871
|
+
.filter(template => !reservedNames.has(template.name))
|
|
872
|
+
.map(template => ({
|
|
873
|
+
name: template.name,
|
|
874
|
+
// `PromptTemplate.description` from `loadTemplatesFromDir` already includes the
|
|
875
|
+
// source suffix (e.g. "Review code (project)"), so pass it through verbatim.
|
|
876
|
+
description: template.description,
|
|
877
|
+
}));
|
|
813
878
|
const autocompleteProvider = this.#inputController.createAutocompleteProvider(
|
|
814
|
-
[...this.#pendingSlashCommands, ...fileSlashCommands],
|
|
879
|
+
[...this.#pendingSlashCommands, ...fileSlashCommands, ...promptTemplateCommands],
|
|
815
880
|
basePath,
|
|
816
881
|
);
|
|
817
882
|
this.editor.setAutocompleteProvider(autocompleteProvider);
|
|
@@ -905,6 +970,14 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
905
970
|
this.#goalContinuationTimer = undefined;
|
|
906
971
|
if (!this.onInputCallback) return;
|
|
907
972
|
if (!this.goalModeEnabled || this.goalModePaused) return;
|
|
973
|
+
// The 800ms timer can outlive the idle window that scheduled it: a
|
|
974
|
+
// `/goal set` taken via the streaming branch (or any extension/hook
|
|
975
|
+
// path that starts a turn while we wait) leaves the agent busy. Firing
|
|
976
|
+
// the continuation now would route through `submitInteractiveInput` →
|
|
977
|
+
// `promptCustomMessage` with no `streamingBehavior` and resurface
|
|
978
|
+
// `AgentBusyError`. Drop this tick; `#handleGoalSessionEvent` reschedules
|
|
979
|
+
// on the next `agent_end`.
|
|
980
|
+
if (this.#isAutoSubmitBlocked()) return;
|
|
908
981
|
if (this.#pendingSubmittedInput) return;
|
|
909
982
|
if (this.editor.getText().trim().length > 0) return;
|
|
910
983
|
if ((this.pendingImages?.length ?? 0) > 0) return;
|
|
@@ -928,7 +1001,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
928
1001
|
}
|
|
929
1002
|
}
|
|
930
1003
|
|
|
931
|
-
#
|
|
1004
|
+
#isAutoSubmitBlocked(): boolean {
|
|
932
1005
|
return this.session.isStreaming || this.session.isCompacting || this.session.hasPostPromptWork;
|
|
933
1006
|
}
|
|
934
1007
|
|
|
@@ -938,7 +1011,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
938
1011
|
this.disableLoopMode("Loop time limit reached. Loop mode disabled.");
|
|
939
1012
|
return;
|
|
940
1013
|
}
|
|
941
|
-
if (this.#
|
|
1014
|
+
if (this.#isAutoSubmitBlocked()) {
|
|
942
1015
|
this.#deferLoopAutoSubmit(() => this.#submitLoopPromptWhenReady(prompt));
|
|
943
1016
|
return;
|
|
944
1017
|
}
|
|
@@ -947,7 +1020,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
947
1020
|
|
|
948
1021
|
async #runLoopIteration(action: "prompt" | "compact" | "reset", prompt: string): Promise<void> {
|
|
949
1022
|
if (!this.loopModeEnabled || this.loopPrompt !== prompt || !this.onInputCallback) return;
|
|
950
|
-
if (this.#
|
|
1023
|
+
if (this.#isAutoSubmitBlocked()) {
|
|
951
1024
|
this.#deferLoopAutoSubmit(() => {
|
|
952
1025
|
void this.#runLoopIteration(action, prompt);
|
|
953
1026
|
});
|
|
@@ -1140,10 +1213,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1140
1213
|
}
|
|
1141
1214
|
|
|
1142
1215
|
#computeEditorMaxHeight(): number {
|
|
1143
|
-
|
|
1144
|
-
const terminalRows = Number.isFinite(rows) && rows > 0 ? rows : EDITOR_FALLBACK_ROWS;
|
|
1145
|
-
const maxHeight = terminalRows - EDITOR_RESERVED_ROWS;
|
|
1146
|
-
return Math.max(EDITOR_MAX_HEIGHT_MIN, Math.min(EDITOR_MAX_HEIGHT_MAX, maxHeight));
|
|
1216
|
+
return computeEditorMaxHeight(this.ui.terminal.rows);
|
|
1147
1217
|
}
|
|
1148
1218
|
|
|
1149
1219
|
#syncEditorMaxHeight(): void {
|
|
@@ -1343,6 +1413,41 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1343
1413
|
this.#todoAutoClearTimer.unref?.();
|
|
1344
1414
|
}
|
|
1345
1415
|
|
|
1416
|
+
/**
|
|
1417
|
+
* Render the ctrl+p model-role cycle chip track into its own anchored
|
|
1418
|
+
* container (just above the editor), mirroring the todo HUD: the container is
|
|
1419
|
+
* cleared and rebuilt in place on every cycle, so rapid presses or concurrent
|
|
1420
|
+
* chat activity can never stack duplicate tracks into the scrollback.
|
|
1421
|
+
*/
|
|
1422
|
+
showModelCycleTrack(track: string): void {
|
|
1423
|
+
this.#renderModelCycleTrack(track);
|
|
1424
|
+
this.#syncModelCycleClearTimer();
|
|
1425
|
+
this.ui.requestRender();
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
#renderModelCycleTrack(track: string | null): void {
|
|
1429
|
+
this.modelCycleContainer.clear();
|
|
1430
|
+
if (!track) return;
|
|
1431
|
+
this.modelCycleContainer.addChild(new Spacer(1));
|
|
1432
|
+
this.modelCycleContainer.addChild(new Text(track, 1, 0));
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
#cancelModelCycleClearTimer(): void {
|
|
1436
|
+
if (!this.#modelCycleClearTimer) return;
|
|
1437
|
+
clearTimeout(this.#modelCycleClearTimer);
|
|
1438
|
+
this.#modelCycleClearTimer = undefined;
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
#syncModelCycleClearTimer(): void {
|
|
1442
|
+
this.#cancelModelCycleClearTimer();
|
|
1443
|
+
this.#modelCycleClearTimer = setTimeout(() => {
|
|
1444
|
+
this.#modelCycleClearTimer = undefined;
|
|
1445
|
+
this.#renderModelCycleTrack(null);
|
|
1446
|
+
this.ui.requestRender();
|
|
1447
|
+
}, MODEL_CYCLE_TRACK_CLEAR_MS);
|
|
1448
|
+
this.#modelCycleClearTimer.unref?.();
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1346
1451
|
#getActivePhase(phases: TodoPhase[]): TodoPhase | undefined {
|
|
1347
1452
|
const nonEmpty = phases.filter(phase => phase.tasks.length > 0);
|
|
1348
1453
|
const active = nonEmpty.find(phase =>
|
|
@@ -1736,7 +1841,40 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1736
1841
|
});
|
|
1737
1842
|
}
|
|
1738
1843
|
|
|
1739
|
-
async #
|
|
1844
|
+
async #restorePlanPreviousModel(prev: { model: Model; thinkingLevel?: ThinkingLevel }): Promise<void> {
|
|
1845
|
+
if (modelsAreEqual(this.session.model, prev.model)) {
|
|
1846
|
+
// Same model — only thinking level may differ. Avoid setModelTemporary()
|
|
1847
|
+
// which would reset provider-side sessions and break continuity.
|
|
1848
|
+
this.session.setThinkingLevel(prev.thinkingLevel);
|
|
1849
|
+
} else if (this.session.isStreaming) {
|
|
1850
|
+
this.#pendingModelSwitch = { model: prev.model, thinkingLevel: prev.thinkingLevel };
|
|
1851
|
+
} else {
|
|
1852
|
+
await this.session.setModelTemporary(prev.model, prev.thinkingLevel);
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
/**
|
|
1857
|
+
* Idempotent post-compaction model transition for the plan-approval compact
|
|
1858
|
+
* path. The deferred pre-plan state is consumed on first application, so a
|
|
1859
|
+
* second call (the before-flush hook vs. the short-circuit fallback) is a
|
|
1860
|
+
* no-op. "failed" intentionally stays on the plan model — the context is
|
|
1861
|
+
* intact and we dispatch best-effort.
|
|
1862
|
+
*/
|
|
1863
|
+
async #applyDeferredPlanModelTransition(
|
|
1864
|
+
outcome: CompactionOutcome | undefined,
|
|
1865
|
+
executionModel: ResolvedRoleModel | undefined,
|
|
1866
|
+
): Promise<void> {
|
|
1867
|
+
const deferredPrev = this.#planModePreviousModelState;
|
|
1868
|
+
if (deferredPrev === undefined || outcome === "failed") return;
|
|
1869
|
+
this.#planModePreviousModelState = undefined;
|
|
1870
|
+
if (executionModel) {
|
|
1871
|
+
await this.#applyPlanExecutionModel(executionModel);
|
|
1872
|
+
} else {
|
|
1873
|
+
await this.#restorePlanPreviousModel(deferredPrev);
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
async #exitPlanMode(options?: { silent?: boolean; paused?: boolean; deferModelRestore?: boolean }): Promise<void> {
|
|
1740
1878
|
if (!this.planModeEnabled) {
|
|
1741
1879
|
return;
|
|
1742
1880
|
}
|
|
@@ -1746,23 +1884,18 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1746
1884
|
await this.session.setActiveToolsByName(previousTools);
|
|
1747
1885
|
}
|
|
1748
1886
|
if (this.#planModePreviousModelState) {
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
// Same model — only thinking level may differ. Avoid setModelTemporary()
|
|
1752
|
-
// which would reset provider-side sessions (openai-responses/Codex) and
|
|
1753
|
-
// break conversation continuity.
|
|
1754
|
-
this.session.setThinkingLevel(prev.thinkingLevel);
|
|
1755
|
-
} else if (this.session.isStreaming) {
|
|
1756
|
-
this.#pendingModelSwitch = { model: prev.model, thinkingLevel: prev.thinkingLevel };
|
|
1757
|
-
} else {
|
|
1758
|
-
await this.session.setModelTemporary(prev.model, prev.thinkingLevel);
|
|
1887
|
+
if (!options?.deferModelRestore) {
|
|
1888
|
+
await this.#restorePlanPreviousModel(this.#planModePreviousModelState);
|
|
1759
1889
|
}
|
|
1760
1890
|
// If #applyPlanModeModel queued a deferred switch to the plan-role model
|
|
1761
1891
|
// (because the session was streaming on entry), drop it now: we are
|
|
1762
1892
|
// leaving plan mode, so flushing it on the next agent_end would land the
|
|
1763
1893
|
// session on the plan-role model after the user has exited plan mode
|
|
1764
|
-
// (issue #816).
|
|
1765
|
-
//
|
|
1894
|
+
// (issue #816). This runs even when deferModelRestore is set
|
|
1895
|
+
// (compact-approval path): otherwise the stale plan switch survives and
|
|
1896
|
+
// flushPendingModelSwitch() later clobbers the restored/execution model.
|
|
1897
|
+
// Only clear when the pending target matches the plan-role model — leave
|
|
1898
|
+
// any unrelated user-queued switch intact.
|
|
1766
1899
|
const pending = this.#pendingModelSwitch;
|
|
1767
1900
|
if (pending) {
|
|
1768
1901
|
const planResolution = this.session.resolveRoleModelWithThinking("plan");
|
|
@@ -1777,7 +1910,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1777
1910
|
this.planModePaused = options?.paused ?? false;
|
|
1778
1911
|
this.planModePlanFilePath = undefined;
|
|
1779
1912
|
this.#planModePreviousTools = undefined;
|
|
1780
|
-
this.#planModePreviousModelState = undefined;
|
|
1913
|
+
if (!options?.deferModelRestore) this.#planModePreviousModelState = undefined;
|
|
1781
1914
|
this.#updatePlanModeStatus();
|
|
1782
1915
|
const paused = options?.paused ?? false;
|
|
1783
1916
|
this.sessionManager.appendModeChange(paused ? "plan_paused" : "none");
|
|
@@ -2117,7 +2250,11 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2117
2250
|
}
|
|
2118
2251
|
let compactOutcome: CompactionOutcome | undefined;
|
|
2119
2252
|
try {
|
|
2120
|
-
await this.#exitPlanMode({
|
|
2253
|
+
await this.#exitPlanMode({
|
|
2254
|
+
silent: true,
|
|
2255
|
+
paused: false,
|
|
2256
|
+
deferModelRestore: options.compactBeforeExecute === true,
|
|
2257
|
+
});
|
|
2121
2258
|
|
|
2122
2259
|
if (!options.preserveContext) {
|
|
2123
2260
|
await this.handleClearCommand();
|
|
@@ -2146,7 +2283,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2146
2283
|
// the try/finally is idempotent and kept for the !compactBeforeExecute
|
|
2147
2284
|
// branch.
|
|
2148
2285
|
this.session.setPlanReferencePath(options.planFilePath);
|
|
2149
|
-
compactOutcome = await this.handleCompactCommand(compactionPrompt
|
|
2286
|
+
compactOutcome = await this.handleCompactCommand(compactionPrompt, outcome =>
|
|
2287
|
+
this.#applyDeferredPlanModelTransition(outcome, options.executionModel),
|
|
2288
|
+
);
|
|
2150
2289
|
}
|
|
2151
2290
|
} finally {
|
|
2152
2291
|
// Unconditional clear. Idempotent: a no-op when the flag was never set
|
|
@@ -2163,22 +2302,33 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2163
2302
|
}
|
|
2164
2303
|
this.session.setPlanReferencePath(options.planFilePath);
|
|
2165
2304
|
|
|
2305
|
+
// Resolve the deferred plan-approval model transition. On the compact path
|
|
2306
|
+
// the before-flush hook passed to handleCompactCommand already ran this (so
|
|
2307
|
+
// any input queued during compaction executed on the post-compaction
|
|
2308
|
+
// model); the re-run here is idempotent and covers the short-circuit where
|
|
2309
|
+
// compaction never executed. It runs for "cancelled" too — the operator
|
|
2310
|
+
// aborted only the compaction, not the approval — so the next turn no longer
|
|
2311
|
+
// lands on the plan model. "failed" stays on the plan model (context
|
|
2312
|
+
// intact) and dispatches best-effort.
|
|
2313
|
+
if (options.compactBeforeExecute) {
|
|
2314
|
+
await this.#applyDeferredPlanModelTransition(compactOutcome, options.executionModel);
|
|
2315
|
+
} else {
|
|
2316
|
+
await this.#applyPlanExecutionModel(options.executionModel);
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2166
2319
|
if (compactOutcome === "cancelled") {
|
|
2167
2320
|
// Explicit abort: honor it. `executeCompaction` already surfaced
|
|
2168
|
-
// `showError("Compaction cancelled")
|
|
2169
|
-
//
|
|
2170
|
-
//
|
|
2171
|
-
// `AgentSession.#buildPlanReferenceMessage`
|
|
2172
|
-
//
|
|
2173
|
-
// sent here, the executor's first turn would have no plan context.
|
|
2321
|
+
// `showError("Compaction cancelled")`; we add the deferred-dispatch
|
|
2322
|
+
// warning and exit without dispatching the synthetic plan-approved
|
|
2323
|
+
// prompt. `markPlanReferenceSent` stays unset so
|
|
2324
|
+
// `AgentSession.#buildPlanReferenceMessage` injects the plan reference
|
|
2325
|
+
// on the operator's next `prompt()` call.
|
|
2174
2326
|
this.showWarning(
|
|
2175
2327
|
"Plan approved, but compaction was cancelled — execution not dispatched. Submit a turn to continue.",
|
|
2176
2328
|
);
|
|
2177
2329
|
return;
|
|
2178
2330
|
}
|
|
2179
2331
|
|
|
2180
|
-
await this.#applyPlanExecutionModel(options.executionModel);
|
|
2181
|
-
|
|
2182
2332
|
// Approved plans land in a fresh (or compacted) session whose first user-visible
|
|
2183
2333
|
// turn is the synthetic plan-approved prompt — that path bypasses the
|
|
2184
2334
|
// input-controller's title generation. Seed an auto-name from the plan title
|
|
@@ -2201,6 +2351,15 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2201
2351
|
planFilePath: options.planFilePath,
|
|
2202
2352
|
contextPreserved: options.preserveContext === true,
|
|
2203
2353
|
});
|
|
2354
|
+
// The executor's first turn must start on an idle session. The agent may still
|
|
2355
|
+
// be streaming the post-`resolve` continuation (Agent.#emit is fire-and-forget)
|
|
2356
|
+
// or a turn kicked off by the compaction/clear above; prompt() would then throw
|
|
2357
|
+
// AgentBusyError ("Failed to finalize approved plan"). Abort the now-irrelevant
|
|
2358
|
+
// in-flight turn first — abort() bumps the prompt generation and cancels pending
|
|
2359
|
+
// continuations, so nothing re-streams in the synchronous gap before prompt().
|
|
2360
|
+
if (this.session.isStreaming) {
|
|
2361
|
+
await this.session.abort();
|
|
2362
|
+
}
|
|
2204
2363
|
await this.session.prompt(planModePrompt, { synthetic: true });
|
|
2205
2364
|
}
|
|
2206
2365
|
|
|
@@ -2221,6 +2380,19 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2221
2380
|
await this.#exitPlanMode({ paused: true });
|
|
2222
2381
|
return;
|
|
2223
2382
|
}
|
|
2383
|
+
if (this.planModePaused && !initialPrompt) {
|
|
2384
|
+
// No-arg third toggle: paused → off. Tools, model, and plan state were
|
|
2385
|
+
// already restored by the prior #exitPlanMode({ paused: true }); only the
|
|
2386
|
+
// paused flag, the reentry marker, and the session mode entry remain.
|
|
2387
|
+
// Prompted /plan invocations fall through to #enterPlanMode below so the
|
|
2388
|
+
// supplied prompt is still submitted as the first plan-mode turn.
|
|
2389
|
+
this.planModePaused = false;
|
|
2390
|
+
this.#planModeHasEntered = false;
|
|
2391
|
+
this.#updatePlanModeStatus();
|
|
2392
|
+
this.sessionManager.appendModeChange("none");
|
|
2393
|
+
this.showStatus("Plan mode disabled.");
|
|
2394
|
+
return;
|
|
2395
|
+
}
|
|
2224
2396
|
if (!this.session.settings.get("plan.enabled")) {
|
|
2225
2397
|
this.showWarning("Plan mode is disabled. Enable it in settings (plan.enabled).");
|
|
2226
2398
|
return;
|
|
@@ -2302,6 +2474,70 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2302
2474
|
this.showError(error instanceof Error ? error.message : String(error));
|
|
2303
2475
|
}
|
|
2304
2476
|
}
|
|
2477
|
+
async handleGuidedGoalCommand(rest?: string): Promise<void> {
|
|
2478
|
+
try {
|
|
2479
|
+
if (this.planModeEnabled || this.planModePaused) {
|
|
2480
|
+
this.showWarning("Exit plan mode first.");
|
|
2481
|
+
return;
|
|
2482
|
+
}
|
|
2483
|
+
if (!this.session.settings.get("goal.enabled")) {
|
|
2484
|
+
this.showWarning("Goal mode is disabled. Enable it in settings (goal.enabled).");
|
|
2485
|
+
return;
|
|
2486
|
+
}
|
|
2487
|
+
if (this.goalModeEnabled) {
|
|
2488
|
+
this.showStatus("Goal mode is already active. Use /goal to manage it, or /goal drop to start over.");
|
|
2489
|
+
return;
|
|
2490
|
+
}
|
|
2491
|
+
if (this.#getPausedGoalState()) {
|
|
2492
|
+
this.showWarning("Resume the current goal first, or drop it before setting a new objective.");
|
|
2493
|
+
return;
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2496
|
+
const initial = rest?.trim()
|
|
2497
|
+
? rest.trim()
|
|
2498
|
+
: (await this.showHookEditor("Guided goal", undefined, undefined, { promptStyle: true }))?.trim();
|
|
2499
|
+
if (!initial) return;
|
|
2500
|
+
|
|
2501
|
+
const messages: GuidedGoalMessage[] = [{ role: "user", content: initial }];
|
|
2502
|
+
let latestDraftObjective: string | undefined;
|
|
2503
|
+
for (let turn = 0; turn < 6; turn++) {
|
|
2504
|
+
const result = await runGuidedGoalTurn(this.session, { messages });
|
|
2505
|
+
if (result.objective?.trim()) latestDraftObjective = result.objective.trim();
|
|
2506
|
+
if (result.kind === "question") {
|
|
2507
|
+
messages.push({ role: "assistant", content: result.question });
|
|
2508
|
+
const answer = (
|
|
2509
|
+
await this.showHookEditor(result.question, undefined, undefined, { promptStyle: true })
|
|
2510
|
+
)?.trim();
|
|
2511
|
+
if (!answer) return;
|
|
2512
|
+
messages.push({ role: "user", content: answer });
|
|
2513
|
+
continue;
|
|
2514
|
+
}
|
|
2515
|
+
|
|
2516
|
+
const finalObjective = (
|
|
2517
|
+
await this.showHookEditor("Review guided goal", result.objective, undefined, { promptStyle: true })
|
|
2518
|
+
)?.trim();
|
|
2519
|
+
if (!finalObjective) return;
|
|
2520
|
+
await this.#startGoalFromObjective(finalObjective);
|
|
2521
|
+
return;
|
|
2522
|
+
}
|
|
2523
|
+
|
|
2524
|
+
// Hit the turn cap without an explicit `ready`. Rather than discard the whole interview,
|
|
2525
|
+
// salvage the latest non-empty model objective draft seen on any earlier turn. A final
|
|
2526
|
+
// question turn may omit `objective`; that must not erase a usable draft.
|
|
2527
|
+
if (latestDraftObjective) {
|
|
2528
|
+
const finalObjective = (
|
|
2529
|
+
await this.showHookEditor("Review guided goal", latestDraftObjective, undefined, { promptStyle: true })
|
|
2530
|
+
)?.trim();
|
|
2531
|
+
if (finalObjective) {
|
|
2532
|
+
await this.#startGoalFromObjective(finalObjective);
|
|
2533
|
+
return;
|
|
2534
|
+
}
|
|
2535
|
+
}
|
|
2536
|
+
this.showWarning("Guided goal setup needs more detail. Run /guided-goal again with a narrower objective.");
|
|
2537
|
+
} catch (error) {
|
|
2538
|
+
this.showError(error instanceof Error ? error.message : String(error));
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2305
2541
|
|
|
2306
2542
|
async #dispatchGoalSubcommand(sub: GoalSubcommand, rest: string): Promise<void> {
|
|
2307
2543
|
switch (sub) {
|
|
@@ -2585,11 +2821,13 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2585
2821
|
return;
|
|
2586
2822
|
}
|
|
2587
2823
|
// Capture the operator's tier choice and hand it to #approvePlan, which
|
|
2588
|
-
// applies it AFTER #exitPlanMode. #exitPlanMode restores
|
|
2824
|
+
// applies it AFTER #exitPlanMode. #exitPlanMode normally restores
|
|
2589
2825
|
// #planModePreviousModelState (the model from before plan mode), so
|
|
2590
2826
|
// applying the slider choice any earlier would be silently reverted —
|
|
2591
2827
|
// the bug that made "continue with slow" keep executing on the default
|
|
2592
|
-
// model.
|
|
2828
|
+
// model. For compact-context approval, the plan model is kept through
|
|
2829
|
+
// compaction, then a successful compaction transitions to the slider model
|
|
2830
|
+
// (or restores the pre-plan model when no slider choice was made).
|
|
2593
2831
|
// `cycle.currentIndex` is exactly that restored model, so any chosen tier
|
|
2594
2832
|
// differing from it needs an explicit executionModel — this also covers
|
|
2595
2833
|
// leaving the slider on its `default` anchor while planning ran elsewhere.
|
|
@@ -2790,6 +3028,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2790
3028
|
nextEditor.onAutocompleteUpdate = () => {
|
|
2791
3029
|
this.ui.requestRender();
|
|
2792
3030
|
};
|
|
3031
|
+
nextEditor.setShimmerRepaintHandler(() => this.ui.requestComponentRender(this.editor));
|
|
2793
3032
|
nextEditor.setMaxHeight(this.#computeEditorMaxHeight());
|
|
2794
3033
|
if (this.historyStorage) {
|
|
2795
3034
|
nextEditor.setHistoryStorage(this.historyStorage);
|
|
@@ -3181,7 +3420,11 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3181
3420
|
await this.#sttController.toggle(this.editor, {
|
|
3182
3421
|
showWarning: (msg: string) => this.showWarning(msg),
|
|
3183
3422
|
showStatus: (msg: string) => this.showStatus(msg),
|
|
3423
|
+
requestRender: () => this.ui.requestRender(),
|
|
3184
3424
|
onStateChange: (state: SttState) => {
|
|
3425
|
+
// Duck assistant speech while the user is talking (push-to-talk); restore after.
|
|
3426
|
+
if (state === "recording") vocalizer.duck();
|
|
3427
|
+
else vocalizer.unduck();
|
|
3185
3428
|
if (state === "recording") {
|
|
3186
3429
|
this.#voicePreviousShowHardwareCursor = this.ui.getShowHardwareCursor();
|
|
3187
3430
|
this.#voicePreviousUseTerminalCursor = this.editor.getUseTerminalCursor();
|
|
@@ -3252,8 +3495,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3252
3495
|
await this.#selectorController.showDebugSelector();
|
|
3253
3496
|
}
|
|
3254
3497
|
|
|
3255
|
-
showAgentHub(): void {
|
|
3256
|
-
this.#selectorController.showAgentHub(this.#observerRegistry);
|
|
3498
|
+
showAgentHub(options?: { requireContent?: boolean }): void {
|
|
3499
|
+
this.#selectorController.showAgentHub(this.#observerRegistry, options);
|
|
3257
3500
|
}
|
|
3258
3501
|
|
|
3259
3502
|
resetObserverRegistry(): void {
|
|
@@ -3279,8 +3522,11 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3279
3522
|
await controller.handle(text);
|
|
3280
3523
|
}
|
|
3281
3524
|
|
|
3282
|
-
handleCompactCommand(
|
|
3283
|
-
|
|
3525
|
+
handleCompactCommand(
|
|
3526
|
+
customInstructions?: string,
|
|
3527
|
+
beforeFlush?: (outcome: CompactionOutcome) => void | Promise<void>,
|
|
3528
|
+
): Promise<CompactionOutcome> {
|
|
3529
|
+
return this.#commandController.handleCompactCommand(customInstructions, beforeFlush);
|
|
3284
3530
|
}
|
|
3285
3531
|
|
|
3286
3532
|
handleHandoffCommand(customInstructions?: string): Promise<void> {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { highlightOrchestrate } from "./orchestrate";
|
|
2
|
-
import { highlightUltrathink } from "./ultrathink";
|
|
3
|
-
import { highlightWorkflow } from "./workflow";
|
|
1
|
+
import { containsOrchestrate, highlightOrchestrate } from "./orchestrate";
|
|
2
|
+
import { containsUltrathink, highlightUltrathink } from "./ultrathink";
|
|
3
|
+
import { containsWorkflow, highlightWorkflow } from "./workflow";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Gradient-highlight every magic keyword ("ultrathink", "orchestrate",
|
|
@@ -14,7 +14,29 @@ import { highlightWorkflow } from "./workflow";
|
|
|
14
14
|
* pass the surrounding text color when decorating already-colored content (e.g.
|
|
15
15
|
* a themed message bubble) so the gradient does not bleed into the rest of the
|
|
16
16
|
* line. Defaults to a plain foreground reset for default-colored editor text.
|
|
17
|
+
*
|
|
18
|
+
* `phase` ∈ [0, 1) cyclically rotates each gradient — the editor passes a
|
|
19
|
+
* `Date.now()`-derived value to animate a Claude-Code-style shimmer while a
|
|
20
|
+
* keyword is on screen and the prompt is focused; sent message bubbles omit it
|
|
21
|
+
* to keep the static gradient.
|
|
22
|
+
*/
|
|
23
|
+
export function highlightMagicKeywords(text: string, resetTo?: string, phase?: number): string {
|
|
24
|
+
return highlightWorkflow(
|
|
25
|
+
highlightOrchestrate(highlightUltrathink(text, resetTo, phase), resetTo, phase),
|
|
26
|
+
resetTo,
|
|
27
|
+
phase,
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Cheap test for "does this text contain any magic keyword as standalone prose?".
|
|
33
|
+
* Short-circuits on a substring probe before paying for the markdown-aware
|
|
34
|
+
* prose check, so the common "no keyword in buffer" path is just three
|
|
35
|
+
* `String#indexOf`s. Used by the live editor to gate the shimmer timer.
|
|
17
36
|
*/
|
|
18
|
-
export function
|
|
19
|
-
|
|
37
|
+
export function hasMagicKeyword(text: string): boolean {
|
|
38
|
+
if (!text.includes("ultrathink") && !text.includes("orchestrate") && !text.includes("workflowz")) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
return containsUltrathink(text) || containsOrchestrate(text) || containsWorkflow(text);
|
|
20
42
|
}
|