@oh-my-pi/pi-coding-agent 17.2.12 → 17.2.13
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 +57 -0
- package/dist/{CHANGELOG-k9ghy5sn.md → CHANGELOG-d8xh7keh.md} +57 -0
- package/dist/cli.js +3069 -3047
- package/dist/types/advisor/delta-split.d.ts +24 -0
- package/dist/types/advisor/runtime.d.ts +2 -2
- package/dist/types/async/job-manager.d.ts +8 -1
- package/dist/types/cli/update-cli.d.ts +53 -1
- package/dist/types/config/keybindings.d.ts +10 -0
- package/dist/types/config/model-resolver.d.ts +15 -2
- package/dist/types/config/settings-schema.d.ts +4 -0
- package/dist/types/discovery/agents-md.d.ts +10 -1
- package/dist/types/eval/runner-cache.d.ts +12 -0
- package/dist/types/extensibility/extensions/runner.d.ts +12 -3
- package/dist/types/extensibility/extensions/types.d.ts +15 -4
- package/dist/types/extensibility/plugins/marketplace/manager.d.ts +4 -1
- package/dist/types/lib/xai-http.d.ts +0 -1
- package/dist/types/mcp/tool-bridge.d.ts +8 -5
- package/dist/types/modes/components/agent-hub-renderer.d.ts +6 -1
- package/dist/types/modes/components/status-line/types.d.ts +4 -0
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +2 -4
- package/dist/types/modes/interactive-mode.d.ts +17 -8
- package/dist/types/modes/types.d.ts +7 -10
- package/dist/types/modes/utils/hotkeys-markdown.d.ts +1 -1
- package/dist/types/session/agent-session-types.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +22 -3
- package/dist/types/session/messages.d.ts +20 -0
- package/dist/types/session/retry-fallback-chains.d.ts +13 -0
- package/dist/types/session/session-advisors.d.ts +1 -1
- package/dist/types/session/session-history-format.d.ts +10 -0
- package/dist/types/session/session-maintenance.d.ts +1 -1
- package/dist/types/session/session-tools.d.ts +24 -5
- package/dist/types/session/turn-recovery.d.ts +34 -5
- package/dist/types/slash-commands/types.d.ts +5 -1
- package/dist/types/task/executor.d.ts +1 -1
- package/dist/types/tools/approval.d.ts +7 -0
- package/dist/types/tools/todo.d.ts +14 -15
- package/dist/types/tools/write.d.ts +2 -2
- package/dist/types/utils/local-date.d.ts +2 -0
- package/dist/types/vibe/runtime.d.ts +1 -1
- package/dist/types/web/parallel.d.ts +1 -0
- package/dist/types/web/search/providers/brave.d.ts +8 -3
- package/dist/types/web/search/providers/codex.d.ts +6 -0
- package/dist/types/web/search/providers/firecrawl.d.ts +3 -2
- package/dist/types/web/search/providers/jina.d.ts +3 -3
- package/dist/types/web/search/providers/parallel.d.ts +1 -0
- package/dist/types/web/search/providers/perplexity.d.ts +4 -0
- package/dist/types/web/search/providers/tinyfish.d.ts +2 -0
- package/package.json +13 -13
- package/src/advisor/delta-split.ts +98 -0
- package/src/advisor/runtime.ts +321 -69
- package/src/async/job-manager.ts +14 -3
- package/src/cli/plugin-cli.ts +30 -2
- package/src/cli/update-cli.ts +259 -24
- package/src/config/keybindings.ts +52 -9
- package/src/config/model-resolver.ts +19 -3
- package/src/config/settings-schema.ts +5 -0
- package/src/cursor.ts +10 -5
- package/src/discovery/agents-md.ts +61 -23
- package/src/eval/jl/kernel.ts +2 -20
- package/src/eval/py/kernel.ts +2 -20
- package/src/eval/rb/kernel.ts +2 -20
- package/src/eval/runner-cache.ts +41 -0
- package/src/exec/non-interactive-env.ts +14 -3
- package/src/extensibility/extensions/loader.ts +5 -2
- package/src/extensibility/extensions/runner.ts +184 -66
- package/src/extensibility/extensions/types.ts +26 -2
- package/src/extensibility/extensions/wrapper.ts +13 -7
- package/src/extensibility/plugins/marketplace/manager.ts +6 -2
- package/src/hindsight/client.ts +1 -1
- package/src/lib/xai-http.ts +0 -4
- package/src/lsp/client.ts +2 -0
- package/src/lsp/servers.ts +1 -1
- package/src/mcp/tool-bridge.ts +15 -6
- package/src/modes/components/agent-hub-renderer.ts +9 -3
- package/src/modes/components/agent-hub.ts +2 -1
- package/src/modes/components/status-line/component.ts +58 -6
- package/src/modes/components/status-line/segments.ts +12 -1
- package/src/modes/components/status-line/types.ts +1 -0
- package/src/modes/components/user-message.ts +20 -5
- package/src/modes/controllers/event-controller.ts +48 -0
- package/src/modes/controllers/extension-ui-controller.ts +14 -7
- package/src/modes/controllers/input-controller.ts +25 -6
- package/src/modes/interactive-mode.ts +315 -129
- package/src/modes/rpc/rpc-frame.ts +13 -5
- package/src/modes/theme/tui-adapters.ts +4 -5
- package/src/modes/types.ts +13 -7
- package/src/modes/utils/hotkeys-markdown.ts +10 -6
- package/src/prompts/system/system-prompt.md +1 -1
- package/src/registry/persisted-agents.ts +43 -8
- package/src/sdk.ts +139 -8
- package/src/session/agent-session-types.ts +2 -0
- package/src/session/agent-session.ts +66 -10
- package/src/session/messages.ts +98 -28
- package/src/session/retry-fallback-chains.ts +14 -0
- package/src/session/session-advisors.ts +32 -15
- package/src/session/session-history-format.ts +15 -1
- package/src/session/session-maintenance.ts +8 -8
- package/src/session/session-manager.ts +6 -2
- package/src/session/session-tools.ts +321 -184
- package/src/session/turn-recovery.ts +225 -47
- package/src/slash-commands/builtin-modes.ts +41 -12
- package/src/slash-commands/types.ts +5 -1
- package/src/task/executor.ts +92 -45
- package/src/task/structured-subagent.ts +5 -5
- package/src/tools/approval.ts +44 -10
- package/src/tools/fetch.ts +21 -2
- package/src/tools/image-gen.ts +6 -8
- package/src/tools/todo.ts +70 -26
- package/src/tools/tts.ts +3 -2
- package/src/tools/write.ts +7 -3
- package/src/utils/local-date.ts +13 -0
- package/src/utils/tools-manager.ts +2 -2
- package/src/vibe/runtime.ts +22 -14
- package/src/web/kagi.ts +91 -34
- package/src/web/parallel.ts +11 -2
- package/src/web/scrapers/crates-io.ts +2 -2
- package/src/web/scrapers/discogs.ts +2 -2
- package/src/web/scrapers/docs-rs.ts +2 -2
- package/src/web/scrapers/github.ts +2 -2
- package/src/web/scrapers/musicbrainz.ts +1 -2
- package/src/web/scrapers/pubmed.ts +2 -2
- package/src/web/scrapers/sec-edgar.ts +2 -2
- package/src/web/search/providers/brave.ts +121 -46
- package/src/web/search/providers/codex.ts +88 -12
- package/src/web/search/providers/exa.ts +45 -10
- package/src/web/search/providers/firecrawl.ts +53 -11
- package/src/web/search/providers/gemini.ts +139 -27
- package/src/web/search/providers/jina.ts +48 -25
- package/src/web/search/providers/parallel.ts +23 -9
- package/src/web/search/providers/perplexity.ts +24 -7
- package/src/web/search/providers/searxng.ts +77 -1
- package/src/web/search/providers/tavily.ts +23 -22
- package/src/web/search/providers/tinyfish.ts +44 -10
- package/src/web/search/providers/xai.ts +85 -14
|
@@ -70,6 +70,7 @@ import { clearClaudePluginRootsCache } from "../discovery/helpers";
|
|
|
70
70
|
import type {
|
|
71
71
|
AutocompleteProviderFactory,
|
|
72
72
|
ContextUsage,
|
|
73
|
+
ExtensionCustomOptions,
|
|
73
74
|
ExtensionUIContext,
|
|
74
75
|
ExtensionUIDialogOptions,
|
|
75
76
|
ExtensionUISelectItem,
|
|
@@ -123,6 +124,7 @@ import { replaceTabs, TRUNCATE_LENGTHS, truncateToWidth } from "../tools/render-
|
|
|
123
124
|
import { setAutoQaConsentHandler } from "../tools/report-tool-issue";
|
|
124
125
|
import {
|
|
125
126
|
formatPhaseDisplayName,
|
|
127
|
+
isClosedTodo,
|
|
126
128
|
selectCollapsedTodos,
|
|
127
129
|
setActiveTodoDescriptionsProvider,
|
|
128
130
|
todoMatchesAnyDescription,
|
|
@@ -344,6 +346,17 @@ function formatContextTokenCount(value: number): string {
|
|
|
344
346
|
return formatNumber(Math.max(0, Math.round(value))).toLowerCase();
|
|
345
347
|
}
|
|
346
348
|
|
|
349
|
+
/**
|
|
350
|
+
* Reads a tool-name snapshot out of a persisted `mode_change` payload. Session
|
|
351
|
+
* files are user-editable and survive across versions, so anything that is not
|
|
352
|
+
* a plain array of strings is treated as absent rather than trusted.
|
|
353
|
+
*/
|
|
354
|
+
function readPersistedToolNames(value: unknown): string[] | undefined {
|
|
355
|
+
if (!Array.isArray(value)) return undefined;
|
|
356
|
+
if (!value.every(name => typeof name === "string")) return undefined;
|
|
357
|
+
return value as string[];
|
|
358
|
+
}
|
|
359
|
+
|
|
347
360
|
/** Options for creating an InteractiveMode instance (for future API use) */
|
|
348
361
|
export interface InteractiveModeOptions {
|
|
349
362
|
/** Providers that were migrated during startup */
|
|
@@ -373,6 +386,42 @@ class AnchoredLiveContainer extends Container implements NativeScrollbackLiveReg
|
|
|
373
386
|
}
|
|
374
387
|
}
|
|
375
388
|
|
|
389
|
+
/**
|
|
390
|
+
* Preview of the command panels queued while the agent streams, rendered above
|
|
391
|
+
* the editor so `/usage` and friends answer immediately mid-turn.
|
|
392
|
+
*
|
|
393
|
+
* Capped in height: the panels are shown in full in the transcript at the next
|
|
394
|
+
* settle, so the preview only has to answer the question, not reproduce the
|
|
395
|
+
* whole report. Rendering is delegated to the real panels at the real width, so
|
|
396
|
+
* the preview cannot drift from what eventually lands in the transcript.
|
|
397
|
+
*/
|
|
398
|
+
class DeferredCommandPreview implements Component {
|
|
399
|
+
constructor(
|
|
400
|
+
private readonly items: readonly Component[],
|
|
401
|
+
private readonly maxRows: number,
|
|
402
|
+
private readonly commandCount: number,
|
|
403
|
+
) {}
|
|
404
|
+
|
|
405
|
+
render(width: number): readonly string[] {
|
|
406
|
+
const rows: string[] = [];
|
|
407
|
+
for (const item of this.items) rows.push(...item.render(width));
|
|
408
|
+
const queued = this.commandCount === 1 ? "1 command output" : `${this.commandCount} command outputs`;
|
|
409
|
+
if (rows.length <= this.maxRows) {
|
|
410
|
+
rows.push(theme.fg("dim", `${queued} — repeated in the transcript when the agent pauses`));
|
|
411
|
+
return rows;
|
|
412
|
+
}
|
|
413
|
+
const shown = rows.slice(0, Math.max(1, this.maxRows - 1));
|
|
414
|
+
const hidden = rows.length - shown.length;
|
|
415
|
+
shown.push(theme.fg("dim", `… ${hidden} more rows — ${queued} shown in full when the agent pauses`));
|
|
416
|
+
return shown;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/** Never shrink the queued-output preview below this, even on a short terminal. */
|
|
421
|
+
const DEFERRED_PREVIEW_MIN_ROWS = 6;
|
|
422
|
+
/** Ceiling for the preview as a share of the viewport, so the prompt stays visible. */
|
|
423
|
+
const DEFERRED_PREVIEW_VIEWPORT_FRACTION = 0.4;
|
|
424
|
+
|
|
376
425
|
/** How long the ctrl+p model-role cycle chip track lingers above the editor
|
|
377
426
|
* before it auto-clears, mirroring the todo HUD's auto-clear timer. */
|
|
378
427
|
const MODEL_CYCLE_TRACK_CLEAR_MS = 4000;
|
|
@@ -450,6 +499,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
450
499
|
omfgContainer: Container;
|
|
451
500
|
errorBannerContainer: Container;
|
|
452
501
|
modelCycleContainer: Container;
|
|
502
|
+
deferredCommandContainer: Container;
|
|
453
503
|
editor: CustomEditor;
|
|
454
504
|
editorContainer: Container;
|
|
455
505
|
hookWidgetContainerAbove: Container;
|
|
@@ -531,6 +581,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
531
581
|
locallySubmittedUserSignatures: Set<string> = new Set();
|
|
532
582
|
#pendingSubmittedInput: SubmittedUserInput | undefined;
|
|
533
583
|
#pendingSubmissionDispose: (() => void) | undefined;
|
|
584
|
+
#pendingSubmissionPreservesDraft = false;
|
|
534
585
|
#optimisticUserMessageComponents: Component[] = [];
|
|
535
586
|
lastSigintTime = 0;
|
|
536
587
|
lastEscapeTime = 0;
|
|
@@ -556,6 +607,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
556
607
|
|
|
557
608
|
#pendingCommandOutput: Component[] = [];
|
|
558
609
|
#pendingCommandOutputSessionId: string | undefined;
|
|
610
|
+
/** Commands (not components) queued while streaming, for the deferral hint. */
|
|
611
|
+
#pendingCommandOutputCommands = 0;
|
|
559
612
|
#pendingSlashCommands: SlashCommand[] = [];
|
|
560
613
|
/** Built-in editor autocomplete provider, before extension wrapping. */
|
|
561
614
|
#baseAutocompleteProvider: AutocompleteProvider | undefined;
|
|
@@ -643,6 +696,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
643
696
|
this.pendingMessagesContainer.disposeChildren();
|
|
644
697
|
this.#cancelModelCycleClearTimer();
|
|
645
698
|
this.modelCycleContainer.disposeChildren();
|
|
699
|
+
this.deferredCommandContainer.disposeChildren();
|
|
700
|
+
this.#pendingCommandOutput = [];
|
|
701
|
+
this.#pendingCommandOutputSessionId = undefined;
|
|
702
|
+
this.#pendingCommandOutputCommands = 0;
|
|
646
703
|
this.compactionQueuedMessages = [];
|
|
647
704
|
this.streamingComponent = undefined;
|
|
648
705
|
this.streamingMessage = undefined;
|
|
@@ -729,6 +786,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
729
786
|
this.omfgContainer = new AnchoredLiveContainer();
|
|
730
787
|
this.errorBannerContainer = new AnchoredLiveContainer();
|
|
731
788
|
this.modelCycleContainer = new AnchoredLiveContainer();
|
|
789
|
+
this.deferredCommandContainer = new AnchoredLiveContainer();
|
|
732
790
|
this.editor = new CustomEditor(getEditorTheme());
|
|
733
791
|
this.ui.enableScopedInputRender(this.editor);
|
|
734
792
|
this.editor.setUseTerminalCursor(this.ui.getShowHardwareCursor());
|
|
@@ -983,6 +1041,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
983
1041
|
this.ui.addChild(this.omfgContainer);
|
|
984
1042
|
this.ui.addChild(this.errorBannerContainer);
|
|
985
1043
|
this.ui.addChild(this.modelCycleContainer);
|
|
1044
|
+
this.ui.addChild(this.deferredCommandContainer);
|
|
986
1045
|
// Working loader / transient status sits below the sticky todo + subagent
|
|
987
1046
|
// HUDs, just above the editor's hook-widget top margin — so it reads next to
|
|
988
1047
|
// the prompt while keeping the one-line gap above the editor.
|
|
@@ -1584,14 +1643,17 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1584
1643
|
this.addMessageToChat(message, options);
|
|
1585
1644
|
}
|
|
1586
1645
|
|
|
1587
|
-
startPendingSubmission(
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1646
|
+
startPendingSubmission(
|
|
1647
|
+
input: {
|
|
1648
|
+
text: string;
|
|
1649
|
+
images?: ImageContent[];
|
|
1650
|
+
imageLinks?: (string | undefined)[];
|
|
1651
|
+
customType?: string;
|
|
1652
|
+
display?: boolean;
|
|
1653
|
+
streamingBehavior?: "steer" | "followUp";
|
|
1654
|
+
},
|
|
1655
|
+
options?: { preserveDraft?: boolean },
|
|
1656
|
+
): SubmittedUserInput {
|
|
1595
1657
|
const submission: SubmittedUserInput = {
|
|
1596
1658
|
text: input.text,
|
|
1597
1659
|
images: input.images,
|
|
@@ -1603,6 +1665,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1603
1665
|
started: false,
|
|
1604
1666
|
};
|
|
1605
1667
|
this.#pendingSubmittedInput = submission;
|
|
1668
|
+
this.#pendingSubmissionPreservesDraft = options?.preserveDraft === true;
|
|
1606
1669
|
if (!submission.customType) {
|
|
1607
1670
|
this.#resetGoalContinuationSuppression();
|
|
1608
1671
|
const imageCount = submission.images?.length ?? 0;
|
|
@@ -1622,8 +1685,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1622
1685
|
} else {
|
|
1623
1686
|
this.clearOptimisticUserMessage();
|
|
1624
1687
|
}
|
|
1625
|
-
|
|
1626
|
-
|
|
1688
|
+
if (!options?.preserveDraft) {
|
|
1689
|
+
this.editor.setText("");
|
|
1690
|
+
this.editor.imageLinks = undefined;
|
|
1691
|
+
}
|
|
1627
1692
|
this.ensureLoadingAnimation();
|
|
1628
1693
|
this.ui.requestRender();
|
|
1629
1694
|
return submission;
|
|
@@ -1634,9 +1699,11 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1634
1699
|
if (!submission || submission.started) {
|
|
1635
1700
|
return false;
|
|
1636
1701
|
}
|
|
1702
|
+
const preserveDraft = this.#pendingSubmissionPreservesDraft;
|
|
1637
1703
|
|
|
1638
1704
|
submission.cancelled = true;
|
|
1639
1705
|
this.#pendingSubmittedInput = undefined;
|
|
1706
|
+
this.#pendingSubmissionPreservesDraft = false;
|
|
1640
1707
|
this.clearOptimisticUserMessage();
|
|
1641
1708
|
this.#pendingWorkingMessage = undefined;
|
|
1642
1709
|
if (submission.customType === "goal-continuation") {
|
|
@@ -1645,7 +1712,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1645
1712
|
if (this.loadingAnimation) {
|
|
1646
1713
|
this.#stopLoadingAnimation(true);
|
|
1647
1714
|
}
|
|
1648
|
-
if (!submission.customType) {
|
|
1715
|
+
if (!submission.customType && !preserveDraft) {
|
|
1649
1716
|
this.editor.pendingImages = submission.images ? [...submission.images] : [];
|
|
1650
1717
|
this.editor.pendingImageLinks = submission.imageLinks ? [...submission.imageLinks] : [];
|
|
1651
1718
|
this.editor.imageLinks = this.editor.pendingImageLinks;
|
|
@@ -1662,6 +1729,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1662
1729
|
return false;
|
|
1663
1730
|
}
|
|
1664
1731
|
input.started = true;
|
|
1732
|
+
this.#pendingSubmissionPreservesDraft = false;
|
|
1665
1733
|
const annotationStateKey = this.#planReviewAnnotationStateBySubmission.get(input);
|
|
1666
1734
|
if (annotationStateKey) {
|
|
1667
1735
|
this.#planReviewAnnotationStateBySubmission.delete(input);
|
|
@@ -1676,6 +1744,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1676
1744
|
if (wasPendingSubmission) {
|
|
1677
1745
|
this.#pendingSubmittedInput = undefined;
|
|
1678
1746
|
this.#pendingSubmissionDispose = undefined;
|
|
1747
|
+
this.#pendingSubmissionPreservesDraft = false;
|
|
1679
1748
|
}
|
|
1680
1749
|
if (input.customType === "goal-continuation") {
|
|
1681
1750
|
this.#goalContinuationTurnInFlight = false;
|
|
@@ -1983,35 +2052,40 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1983
2052
|
this.#todoAutoClearTimer = undefined;
|
|
1984
2053
|
}
|
|
1985
2054
|
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
2055
|
+
/**
|
|
2056
|
+
* Whether every todo is closed, so the HUD has nothing left to track.
|
|
2057
|
+
*
|
|
2058
|
+
* The auto-clear only fires on a settled list. Scrubbing closed tasks while
|
|
2059
|
+
* open work remains is destructive: the walking viewport already hides all but
|
|
2060
|
+
* the newest closed row, and those tasks are what the phase progress counters
|
|
2061
|
+
* and the stage roman numerals are computed from — dropping them mid-run reset
|
|
2062
|
+
* an in-flight phase to `0/n` and renumbered the stages, so a plan the agent
|
|
2063
|
+
* was four tasks into rendered as untouched until the next `todo` call
|
|
2064
|
+
* restored the real snapshot.
|
|
2065
|
+
*/
|
|
2066
|
+
#isTodoListSettled(phases: TodoPhase[]): boolean {
|
|
2067
|
+
let seenTask = false;
|
|
1996
2068
|
for (const phase of phases) {
|
|
1997
|
-
const
|
|
1998
|
-
|
|
2069
|
+
for (const task of phase.tasks) {
|
|
2070
|
+
if (!isClosedTodo(task)) return false;
|
|
2071
|
+
seenTask = true;
|
|
2072
|
+
}
|
|
1999
2073
|
}
|
|
2000
|
-
return
|
|
2074
|
+
return seenTask;
|
|
2001
2075
|
}
|
|
2002
2076
|
|
|
2003
2077
|
#syncTodoAutoClearTimer(): void {
|
|
2004
2078
|
this.#cancelTodoAutoClearTimer();
|
|
2005
2079
|
const delaySeconds = this.settings.get("tasks.todoClearDelay");
|
|
2006
|
-
if (!Number.isFinite(delaySeconds) || delaySeconds < 0 || !this.#
|
|
2080
|
+
if (!Number.isFinite(delaySeconds) || delaySeconds < 0 || !this.#isTodoListSettled(this.todoPhases)) return;
|
|
2007
2081
|
if (delaySeconds === 0) {
|
|
2008
|
-
this.todoPhases =
|
|
2082
|
+
this.todoPhases = [];
|
|
2009
2083
|
return;
|
|
2010
2084
|
}
|
|
2011
2085
|
|
|
2012
2086
|
this.#todoAutoClearTimer = setTimeout(() => {
|
|
2013
2087
|
this.#todoAutoClearTimer = undefined;
|
|
2014
|
-
this.todoPhases =
|
|
2088
|
+
this.todoPhases = [];
|
|
2015
2089
|
this.#renderTodoList();
|
|
2016
2090
|
this.ui.requestRender();
|
|
2017
2091
|
}, delaySeconds * 1000);
|
|
@@ -2142,7 +2216,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2142
2216
|
// brighter muted gray. The root header carries overall stage progression.
|
|
2143
2217
|
const renderPhase = (phase: TodoPhase, oneBased: number, isActive: boolean): string | string[] => {
|
|
2144
2218
|
const label = multiPhase ? formatPhaseDisplayName(phase.name, oneBased) : phase.name;
|
|
2145
|
-
|
|
2219
|
+
// Closed, not just completed: the collapsed task window hides abandoned
|
|
2220
|
+
// tasks too, so counting only completions leaves the phase reading stuck.
|
|
2221
|
+
const done = phase.tasks.filter(isClosedTodo).length;
|
|
2146
2222
|
const progress = ` · ${done}/${phase.tasks.length}`;
|
|
2147
2223
|
if (!isActive) {
|
|
2148
2224
|
const header = theme.fg("muted", label) + theme.fg("dim", progress);
|
|
@@ -2502,6 +2578,14 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2502
2578
|
this.#vibeModeOwnerScope?.ownerId === targetVibeScope.ownerId &&
|
|
2503
2579
|
this.#vibeModeOwnerScope.parentSessionId === targetVibeScope.parentSessionId &&
|
|
2504
2580
|
this.#vibeModeOwnerScope.parentSessionFile === targetVibeScope.parentSessionFile;
|
|
2581
|
+
// #clearTransientModeState below keeps the live active set instead of
|
|
2582
|
+
// applying a snapshot, so for a vibe -> vibe switch the live toolset is
|
|
2583
|
+
// already the reduced vibe set and cannot serve as the pre-vibe snapshot.
|
|
2584
|
+
// That is the only case the persisted snapshot is for: a cold resume or a
|
|
2585
|
+
// switch in from a non-vibe session built its toolset from the current CLI
|
|
2586
|
+
// flags and settings, and that set — not a historical one — is what exiting
|
|
2587
|
+
// vibe must restore.
|
|
2588
|
+
const vibeToolsetLostToTeardown = this.vibeModeEnabled && !preserveVibe;
|
|
2505
2589
|
await this.#clearTransientModeState({ preserveVibe, vibeScopeAlreadySuspended });
|
|
2506
2590
|
await VibeSessionRegistry.global().rehydrate(vibeSession);
|
|
2507
2591
|
const goalEnabled = this.session.settings.get("goal.enabled");
|
|
@@ -2538,7 +2622,14 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2538
2622
|
}
|
|
2539
2623
|
this.session.goalRuntime.clearAccounting();
|
|
2540
2624
|
if (sessionContext.mode === "vibe") {
|
|
2541
|
-
if (!preserveVibe)
|
|
2625
|
+
if (!preserveVibe) {
|
|
2626
|
+
await this.#enterVibeMode({
|
|
2627
|
+
persistModeChange: false,
|
|
2628
|
+
previousTools: vibeToolsetLostToTeardown
|
|
2629
|
+
? readPersistedToolNames(sessionContext.modeData?.previousTools)
|
|
2630
|
+
: undefined,
|
|
2631
|
+
});
|
|
2632
|
+
}
|
|
2542
2633
|
return;
|
|
2543
2634
|
}
|
|
2544
2635
|
if (!this.session.settings.get("plan.enabled")) {
|
|
@@ -3281,14 +3372,17 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3281
3372
|
}
|
|
3282
3373
|
}
|
|
3283
3374
|
|
|
3284
|
-
async handlePlanModeCommand(
|
|
3375
|
+
async handlePlanModeCommand(
|
|
3376
|
+
initialPrompt?: string,
|
|
3377
|
+
input?: Pick<SubmittedUserInput, "images" | "imageLinks">,
|
|
3378
|
+
): Promise<boolean> {
|
|
3285
3379
|
if (this.goalModeEnabled || this.goalModePaused) {
|
|
3286
3380
|
this.showWarning("Exit goal mode first.");
|
|
3287
|
-
return;
|
|
3381
|
+
return false;
|
|
3288
3382
|
}
|
|
3289
3383
|
if (this.vibeModeEnabled) {
|
|
3290
3384
|
this.showWarning("Exit vibe mode first.");
|
|
3291
|
-
return;
|
|
3385
|
+
return false;
|
|
3292
3386
|
}
|
|
3293
3387
|
if (this.planModeEnabled) {
|
|
3294
3388
|
const planFilePath = this.planModePlanFilePath ?? (await this.#getPlanFilePath());
|
|
@@ -3297,10 +3391,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3297
3391
|
"Exit plan mode?",
|
|
3298
3392
|
"This exits plan mode without approving a plan.",
|
|
3299
3393
|
);
|
|
3300
|
-
if (!confirmed) return;
|
|
3394
|
+
if (!confirmed) return false;
|
|
3301
3395
|
}
|
|
3302
3396
|
await this.#exitPlanMode({ paused: true });
|
|
3303
|
-
return;
|
|
3397
|
+
return false;
|
|
3304
3398
|
}
|
|
3305
3399
|
if (this.planModePaused && !initialPrompt) {
|
|
3306
3400
|
// No-arg third toggle: paused → off. Tools, model, and plan state were
|
|
@@ -3313,16 +3407,28 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3313
3407
|
this.#updatePlanModeStatus();
|
|
3314
3408
|
this.sessionManager.appendModeChange("none");
|
|
3315
3409
|
this.showStatus("Plan mode disabled.");
|
|
3316
|
-
return;
|
|
3410
|
+
return false;
|
|
3317
3411
|
}
|
|
3318
3412
|
if (!this.session.settings.get("plan.enabled")) {
|
|
3319
3413
|
this.showWarning("Plan mode is disabled. Enable it in settings (plan.enabled).");
|
|
3320
|
-
return;
|
|
3414
|
+
return false;
|
|
3321
3415
|
}
|
|
3322
3416
|
await this.#enterPlanMode();
|
|
3323
|
-
if (initialPrompt
|
|
3324
|
-
|
|
3417
|
+
if (!initialPrompt) return false;
|
|
3418
|
+
if (this.session.isStreaming) {
|
|
3419
|
+
const images = input?.images?.length ? input.images : undefined;
|
|
3420
|
+
await this.withLocalSubmission(
|
|
3421
|
+
initialPrompt,
|
|
3422
|
+
() => this.session.prompt(initialPrompt, { streamingBehavior: "steer", images }),
|
|
3423
|
+
{ imageCount: images?.length ?? 0 },
|
|
3424
|
+
);
|
|
3425
|
+
return true;
|
|
3325
3426
|
}
|
|
3427
|
+
if (this.onInputCallback) {
|
|
3428
|
+
this.onInputCallback(this.startPendingSubmission({ text: initialPrompt, ...input }, { preserveDraft: true }));
|
|
3429
|
+
return true;
|
|
3430
|
+
}
|
|
3431
|
+
return false;
|
|
3326
3432
|
}
|
|
3327
3433
|
|
|
3328
3434
|
/**
|
|
@@ -3332,26 +3438,41 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3332
3438
|
* the previous toolset, and kills every worker session so workers cannot
|
|
3333
3439
|
* outlive the mode that directs them.
|
|
3334
3440
|
*/
|
|
3335
|
-
async handleVibeModeCommand(
|
|
3441
|
+
async handleVibeModeCommand(
|
|
3442
|
+
initialPrompt?: string,
|
|
3443
|
+
input?: Pick<SubmittedUserInput, "images" | "imageLinks">,
|
|
3444
|
+
): Promise<boolean> {
|
|
3336
3445
|
if (this.vibeModeEnabled) {
|
|
3337
3446
|
await this.#exitVibeMode();
|
|
3338
|
-
return;
|
|
3447
|
+
return false;
|
|
3339
3448
|
}
|
|
3340
3449
|
if (this.planModeEnabled || this.planModePaused) {
|
|
3341
3450
|
this.showWarning("Exit plan mode first.");
|
|
3342
|
-
return;
|
|
3451
|
+
return false;
|
|
3343
3452
|
}
|
|
3344
3453
|
if (this.goalModeEnabled || this.goalModePaused) {
|
|
3345
3454
|
this.showWarning("Exit goal mode first.");
|
|
3346
|
-
return;
|
|
3455
|
+
return false;
|
|
3347
3456
|
}
|
|
3348
3457
|
await this.#enterVibeMode();
|
|
3349
|
-
if (initialPrompt
|
|
3350
|
-
|
|
3458
|
+
if (!initialPrompt) return false;
|
|
3459
|
+
if (this.session.isStreaming) {
|
|
3460
|
+
const images = input?.images?.length ? input.images : undefined;
|
|
3461
|
+
await this.withLocalSubmission(
|
|
3462
|
+
initialPrompt,
|
|
3463
|
+
() => this.session.prompt(initialPrompt, { streamingBehavior: "steer", images }),
|
|
3464
|
+
{ imageCount: images?.length ?? 0 },
|
|
3465
|
+
);
|
|
3466
|
+
return true;
|
|
3351
3467
|
}
|
|
3468
|
+
if (this.onInputCallback) {
|
|
3469
|
+
this.onInputCallback(this.startPendingSubmission({ text: initialPrompt, ...input }, { preserveDraft: true }));
|
|
3470
|
+
return true;
|
|
3471
|
+
}
|
|
3472
|
+
return false;
|
|
3352
3473
|
}
|
|
3353
3474
|
|
|
3354
|
-
async #enterVibeMode(options?: { persistModeChange?: boolean }): Promise<void> {
|
|
3475
|
+
async #enterVibeMode(options?: { persistModeChange?: boolean; previousTools?: string[] }): Promise<void> {
|
|
3355
3476
|
if (this.vibeModeEnabled) {
|
|
3356
3477
|
return;
|
|
3357
3478
|
}
|
|
@@ -3367,7 +3488,12 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3367
3488
|
const vibeRegistry = VibeSessionRegistry.global();
|
|
3368
3489
|
const ownerScope = vibeRegistry.ownerScope(this.#vibeParentSession());
|
|
3369
3490
|
vibeRegistry.activateScope(ownerScope);
|
|
3370
|
-
|
|
3491
|
+
// When a vibe session switches into another session that is also in vibe
|
|
3492
|
+
// mode, the teardown keeps the live active set, which is by then the reduced
|
|
3493
|
+
// vibe set, so re-snapshotting it here would make the snapshot useless. That
|
|
3494
|
+
// path passes the pre-vibe toolset recorded on the target's own mode_change
|
|
3495
|
+
// entry instead.
|
|
3496
|
+
const previousTools = options?.previousTools ?? this.session.getEnabledToolNames();
|
|
3371
3497
|
const vibeBaseTools = ["read"];
|
|
3372
3498
|
if (this.session.hasBuiltInTool("todo")) vibeBaseTools.push("todo");
|
|
3373
3499
|
await this.session.activateVibeTools(vibeBaseTools);
|
|
@@ -3382,7 +3508,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3382
3508
|
await this.session.sendVibeModeContext({ deliverAs: "steer" });
|
|
3383
3509
|
}
|
|
3384
3510
|
this.#updateVibeModeStatus();
|
|
3385
|
-
if (options?.persistModeChange !== false) this.sessionManager.appendModeChange("vibe");
|
|
3511
|
+
if (options?.persistModeChange !== false) this.sessionManager.appendModeChange("vibe", { previousTools });
|
|
3386
3512
|
this.showStatus(
|
|
3387
3513
|
"Vibe mode enabled. You direct fast/good worker sessions; toolset is read + optional parent Todo + vibe tools.",
|
|
3388
3514
|
);
|
|
@@ -3434,76 +3560,72 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3434
3560
|
this.showStatus(nextBudget === undefined ? "Goal budget cleared." : `Goal budget set to ${nextBudget}.`);
|
|
3435
3561
|
}
|
|
3436
3562
|
|
|
3437
|
-
async handleGoalModeCommand(
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
if (
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
return;
|
|
3460
|
-
}
|
|
3461
|
-
await this.#openGoalMenu("active");
|
|
3462
|
-
return;
|
|
3463
|
-
}
|
|
3464
|
-
const pausedState = this.#getPausedGoalState();
|
|
3465
|
-
if (pausedState) {
|
|
3466
|
-
if (subRest) {
|
|
3467
|
-
this.showWarning("Resume the current goal first, or drop it before setting a new objective.");
|
|
3468
|
-
return;
|
|
3469
|
-
}
|
|
3470
|
-
await this.#openGoalMenu("paused");
|
|
3471
|
-
return;
|
|
3563
|
+
async handleGoalModeCommand(
|
|
3564
|
+
rest?: string,
|
|
3565
|
+
input?: Pick<SubmittedUserInput, "images" | "imageLinks">,
|
|
3566
|
+
): Promise<boolean> {
|
|
3567
|
+
if (this.planModeEnabled || this.planModePaused) {
|
|
3568
|
+
this.showWarning("Exit plan mode first.");
|
|
3569
|
+
return false;
|
|
3570
|
+
}
|
|
3571
|
+
if (this.vibeModeEnabled) {
|
|
3572
|
+
this.showWarning("Exit vibe mode first.");
|
|
3573
|
+
return false;
|
|
3574
|
+
}
|
|
3575
|
+
if (!this.session.settings.get("goal.enabled")) {
|
|
3576
|
+
this.showWarning("Goal mode is disabled. Enable it in settings (goal.enabled).");
|
|
3577
|
+
return false;
|
|
3578
|
+
}
|
|
3579
|
+
const { sub, rest: subRest } = parseGoalSubcommand(rest ?? "");
|
|
3580
|
+
if (sub) return await this.#dispatchGoalSubcommand(sub, subRest, input);
|
|
3581
|
+
if (this.goalModeEnabled) {
|
|
3582
|
+
if (subRest) {
|
|
3583
|
+
this.showStatus("Goal mode is already active. Use /goal to manage it, or /goal drop to start over.");
|
|
3584
|
+
return false;
|
|
3472
3585
|
}
|
|
3586
|
+
await this.#openGoalMenu("active");
|
|
3587
|
+
return false;
|
|
3588
|
+
}
|
|
3589
|
+
const pausedState = this.#getPausedGoalState();
|
|
3590
|
+
if (pausedState) {
|
|
3473
3591
|
if (subRest) {
|
|
3474
|
-
|
|
3475
|
-
return;
|
|
3592
|
+
this.showWarning("Resume the current goal first, or drop it before setting a new objective.");
|
|
3593
|
+
return false;
|
|
3476
3594
|
}
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
)?.trim();
|
|
3480
|
-
if (!objective) return;
|
|
3481
|
-
await this.#startGoalFromObjective(objective);
|
|
3482
|
-
} catch (error) {
|
|
3483
|
-
this.showError(error instanceof Error ? error.message : String(error));
|
|
3595
|
+
await this.#openGoalMenu("paused");
|
|
3596
|
+
return false;
|
|
3484
3597
|
}
|
|
3598
|
+
if (subRest) return await this.#startGoalFromObjective(subRest, input);
|
|
3599
|
+
const objective = (
|
|
3600
|
+
await this.showHookEditor("Goal objective", undefined, undefined, { promptStyle: true })
|
|
3601
|
+
)?.trim();
|
|
3602
|
+
if (!objective) return false;
|
|
3603
|
+
return await this.#startGoalFromObjective(objective, input);
|
|
3485
3604
|
}
|
|
3486
|
-
async handleGuidedGoalCommand(
|
|
3605
|
+
async handleGuidedGoalCommand(
|
|
3606
|
+
rest?: string,
|
|
3607
|
+
input?: Pick<SubmittedUserInput, "images" | "imageLinks">,
|
|
3608
|
+
): Promise<boolean> {
|
|
3487
3609
|
try {
|
|
3488
3610
|
if (this.planModeEnabled || this.planModePaused) {
|
|
3489
3611
|
this.showWarning("Exit plan mode first.");
|
|
3490
|
-
return;
|
|
3612
|
+
return false;
|
|
3491
3613
|
}
|
|
3492
3614
|
if (this.vibeModeEnabled) {
|
|
3493
3615
|
this.showWarning("Exit vibe mode first.");
|
|
3494
|
-
return;
|
|
3616
|
+
return false;
|
|
3495
3617
|
}
|
|
3496
3618
|
if (!this.session.settings.get("goal.enabled")) {
|
|
3497
3619
|
this.showWarning("Goal mode is disabled. Enable it in settings (goal.enabled).");
|
|
3498
|
-
return;
|
|
3620
|
+
return false;
|
|
3499
3621
|
}
|
|
3500
3622
|
if (this.goalModeEnabled) {
|
|
3501
3623
|
this.showStatus("Goal mode is already active. Use /goal to manage it, or /goal drop to start over.");
|
|
3502
|
-
return;
|
|
3624
|
+
return false;
|
|
3503
3625
|
}
|
|
3504
3626
|
if (this.#getPausedGoalState()) {
|
|
3505
3627
|
this.showWarning("Resume the current goal first, or drop it before setting a new objective.");
|
|
3506
|
-
return;
|
|
3628
|
+
return false;
|
|
3507
3629
|
}
|
|
3508
3630
|
|
|
3509
3631
|
// Expose the goal tool for the interview so the agent can finish by
|
|
@@ -3521,51 +3643,57 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3521
3643
|
// assistant turns, and the user answers in the ordinary editor. Queue
|
|
3522
3644
|
// behind an in-flight run instead of aborting it.
|
|
3523
3645
|
const kickoff = prompt.render(guidedGoalInterviewPrompt, { initial: rest?.trim() || undefined });
|
|
3646
|
+
const images = input?.images?.length ? input.images : undefined;
|
|
3524
3647
|
if (this.session.isStreaming) {
|
|
3525
|
-
await this.session.followUp(kickoff,
|
|
3648
|
+
await this.session.followUp(kickoff, images, { synthetic: true });
|
|
3526
3649
|
} else {
|
|
3527
3650
|
try {
|
|
3528
|
-
await this.session.prompt(kickoff, { synthetic: true });
|
|
3651
|
+
await this.session.prompt(kickoff, images ? { synthetic: true, images } : { synthetic: true });
|
|
3529
3652
|
} catch (error) {
|
|
3530
3653
|
if (!(error instanceof AgentBusyError)) throw error;
|
|
3531
|
-
await this.session.followUp(kickoff,
|
|
3654
|
+
await this.session.followUp(kickoff, images, { synthetic: true });
|
|
3532
3655
|
}
|
|
3533
3656
|
}
|
|
3657
|
+
return true;
|
|
3534
3658
|
} catch (error) {
|
|
3535
3659
|
this.showError(error instanceof Error ? error.message : String(error));
|
|
3660
|
+
return false;
|
|
3536
3661
|
}
|
|
3537
3662
|
}
|
|
3538
3663
|
|
|
3539
|
-
async #dispatchGoalSubcommand(
|
|
3664
|
+
async #dispatchGoalSubcommand(
|
|
3665
|
+
sub: GoalSubcommand,
|
|
3666
|
+
rest: string,
|
|
3667
|
+
input?: Pick<SubmittedUserInput, "images" | "imageLinks">,
|
|
3668
|
+
): Promise<boolean> {
|
|
3540
3669
|
switch (sub) {
|
|
3541
3670
|
case "set":
|
|
3542
|
-
await this.#handleGoalSetSubcommand(rest);
|
|
3543
|
-
return;
|
|
3671
|
+
return await this.#handleGoalSetSubcommand(rest, input);
|
|
3544
3672
|
case "show":
|
|
3545
3673
|
this.#showGoalDetails();
|
|
3546
|
-
return;
|
|
3674
|
+
return false;
|
|
3547
3675
|
case "pause":
|
|
3548
3676
|
await this.#pauseGoalAction();
|
|
3549
|
-
return;
|
|
3677
|
+
return false;
|
|
3550
3678
|
case "resume":
|
|
3551
3679
|
await this.#resumeGoalAction();
|
|
3552
|
-
return;
|
|
3680
|
+
return false;
|
|
3553
3681
|
case "drop":
|
|
3554
3682
|
await this.#confirmAndDropGoal();
|
|
3555
|
-
return;
|
|
3683
|
+
return false;
|
|
3556
3684
|
case "budget":
|
|
3557
3685
|
if (!this.goalModeEnabled) {
|
|
3558
3686
|
this.showWarning(
|
|
3559
3687
|
this.#getPausedGoalState() ? "Resume the goal before adjusting the budget." : "No active goal.",
|
|
3560
3688
|
);
|
|
3561
|
-
return;
|
|
3689
|
+
return false;
|
|
3562
3690
|
}
|
|
3563
3691
|
if (!rest) {
|
|
3564
3692
|
await this.#promptGoalBudgetEdit();
|
|
3565
|
-
return;
|
|
3693
|
+
return false;
|
|
3566
3694
|
}
|
|
3567
3695
|
await this.#handleGoalBudgetCommand(rest);
|
|
3568
|
-
return;
|
|
3696
|
+
return false;
|
|
3569
3697
|
}
|
|
3570
3698
|
}
|
|
3571
3699
|
|
|
@@ -3665,15 +3793,32 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3665
3793
|
await this.#exitGoalMode({ reason: "dropped" });
|
|
3666
3794
|
}
|
|
3667
3795
|
|
|
3668
|
-
async #startGoalFromObjective(
|
|
3796
|
+
async #startGoalFromObjective(
|
|
3797
|
+
objective: string,
|
|
3798
|
+
input?: Pick<SubmittedUserInput, "images" | "imageLinks">,
|
|
3799
|
+
): Promise<boolean> {
|
|
3669
3800
|
await this.#enterGoalMode({ objective, silent: true });
|
|
3670
3801
|
this.#resetGoalContinuationSuppression();
|
|
3671
|
-
if (
|
|
3672
|
-
|
|
3802
|
+
if (this.session.isStreaming) {
|
|
3803
|
+
const images = input?.images?.length ? input.images : undefined;
|
|
3804
|
+
await this.withLocalSubmission(
|
|
3805
|
+
objective,
|
|
3806
|
+
() => this.session.prompt(objective, { streamingBehavior: "steer", images }),
|
|
3807
|
+
{ imageCount: images?.length ?? 0 },
|
|
3808
|
+
);
|
|
3809
|
+
return true;
|
|
3673
3810
|
}
|
|
3811
|
+
if (this.onInputCallback) {
|
|
3812
|
+
this.onInputCallback(this.startPendingSubmission({ text: objective, ...input }, { preserveDraft: true }));
|
|
3813
|
+
return true;
|
|
3814
|
+
}
|
|
3815
|
+
return false;
|
|
3674
3816
|
}
|
|
3675
3817
|
|
|
3676
|
-
async #replaceGoalFromObjective(
|
|
3818
|
+
async #replaceGoalFromObjective(
|
|
3819
|
+
objective: string,
|
|
3820
|
+
input?: Pick<SubmittedUserInput, "images" | "imageLinks">,
|
|
3821
|
+
): Promise<boolean> {
|
|
3677
3822
|
const state = await this.session.goalRuntime.replaceGoal({ objective });
|
|
3678
3823
|
this.session.setGoalModeState(state);
|
|
3679
3824
|
this.goalModeEnabled = true;
|
|
@@ -3682,26 +3827,35 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3682
3827
|
this.#updateGoalModeStatus();
|
|
3683
3828
|
if (this.session.isStreaming) {
|
|
3684
3829
|
await this.session.sendGoalModeContext({ deliverAs: "steer" });
|
|
3830
|
+
const images = input?.images?.length ? input.images : undefined;
|
|
3831
|
+
await this.withLocalSubmission(
|
|
3832
|
+
objective,
|
|
3833
|
+
() => this.session.prompt(objective, { streamingBehavior: "steer", images }),
|
|
3834
|
+
{ imageCount: images?.length ?? 0 },
|
|
3835
|
+
);
|
|
3836
|
+
return true;
|
|
3685
3837
|
}
|
|
3686
|
-
if (
|
|
3687
|
-
this.onInputCallback(this.startPendingSubmission({ text: objective }));
|
|
3838
|
+
if (this.onInputCallback) {
|
|
3839
|
+
this.onInputCallback(this.startPendingSubmission({ text: objective, ...input }, { preserveDraft: true }));
|
|
3840
|
+
return true;
|
|
3688
3841
|
}
|
|
3842
|
+
return false;
|
|
3689
3843
|
}
|
|
3690
3844
|
|
|
3691
|
-
async #handleGoalSetSubcommand(
|
|
3845
|
+
async #handleGoalSetSubcommand(
|
|
3846
|
+
rest: string,
|
|
3847
|
+
input?: Pick<SubmittedUserInput, "images" | "imageLinks">,
|
|
3848
|
+
): Promise<boolean> {
|
|
3692
3849
|
if (!this.goalModeEnabled && this.#getPausedGoalState()) {
|
|
3693
3850
|
this.showWarning("Resume the current goal first, or drop it before setting a new objective.");
|
|
3694
|
-
return;
|
|
3851
|
+
return false;
|
|
3695
3852
|
}
|
|
3696
3853
|
const objective = rest.trim()
|
|
3697
3854
|
? rest.trim()
|
|
3698
3855
|
: (await this.showHookEditor("Goal objective", undefined, undefined, { promptStyle: true }))?.trim();
|
|
3699
|
-
if (!objective) return;
|
|
3700
|
-
if (this.goalModeEnabled)
|
|
3701
|
-
|
|
3702
|
-
return;
|
|
3703
|
-
}
|
|
3704
|
-
await this.#startGoalFromObjective(objective);
|
|
3856
|
+
if (!objective) return false;
|
|
3857
|
+
if (this.goalModeEnabled) return await this.#replaceGoalFromObjective(objective, input);
|
|
3858
|
+
return await this.#startGoalFromObjective(objective, input);
|
|
3705
3859
|
}
|
|
3706
3860
|
|
|
3707
3861
|
/** Manually (re-)open the plan-review overlay — bound to `/plan-review`. Lets
|
|
@@ -4148,6 +4302,14 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
4148
4302
|
* flushed there. That is preferred over leaving it queued behind a command
|
|
4149
4303
|
* the user runs during the pause, which mounts immediately and would put the
|
|
4150
4304
|
* older panel out of order.
|
|
4305
|
+
*
|
|
4306
|
+
* The deferral is acknowledged in {@link deferredCommandContainer}, an
|
|
4307
|
+
* anchored container above the editor. Nothing is mounted into the
|
|
4308
|
+
* transcript: a mid-turn transcript mount re-renders rows below the growing
|
|
4309
|
+
* live block and duplicates them in native scrollback (issues #4806/#6767),
|
|
4310
|
+
* which is why the earlier `showStatus` acknowledgment was reverted. An
|
|
4311
|
+
* anchored container is cleared and rebuilt in place, so it costs no
|
|
4312
|
+
* scrollback rows — the same reason the ctrl+p role-cycle track lives there.
|
|
4151
4313
|
*/
|
|
4152
4314
|
presentCommandOutput(content: Component | readonly Component[]): void {
|
|
4153
4315
|
if (!this.session.isStreaming) {
|
|
@@ -4157,14 +4319,35 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
4157
4319
|
const sessionId = this.sessionManager.getSessionId();
|
|
4158
4320
|
if (this.#pendingCommandOutput.length > 0 && this.#pendingCommandOutputSessionId !== sessionId) {
|
|
4159
4321
|
this.#pendingCommandOutput = [];
|
|
4322
|
+
this.#pendingCommandOutputCommands = 0;
|
|
4160
4323
|
}
|
|
4161
4324
|
this.#pendingCommandOutputSessionId = sessionId;
|
|
4162
4325
|
const items = Array.isArray(content) ? content : [content as Component];
|
|
4163
4326
|
this.#pendingCommandOutput.push(...items);
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4327
|
+
this.#pendingCommandOutputCommands += 1;
|
|
4328
|
+
this.#renderDeferredCommandNotice();
|
|
4329
|
+
this.ui.requestRender();
|
|
4330
|
+
}
|
|
4331
|
+
|
|
4332
|
+
/**
|
|
4333
|
+
* Preview the queued panels above the editor so a command answers straight
|
|
4334
|
+
* away, then clear at settle when the real panels enter the transcript.
|
|
4335
|
+
*
|
|
4336
|
+
* Height is capped against the viewport: a `/usage` report with several
|
|
4337
|
+
* providers is tall enough to push the prompt off screen, and the full text
|
|
4338
|
+
* is a moment away in the transcript either way.
|
|
4339
|
+
*/
|
|
4340
|
+
#renderDeferredCommandNotice(): void {
|
|
4341
|
+
this.deferredCommandContainer.clear();
|
|
4342
|
+
if (this.#pendingCommandOutput.length === 0) return;
|
|
4343
|
+
const maxRows = Math.max(
|
|
4344
|
+
DEFERRED_PREVIEW_MIN_ROWS,
|
|
4345
|
+
Math.floor(this.ui.terminal.rows * DEFERRED_PREVIEW_VIEWPORT_FRACTION),
|
|
4346
|
+
);
|
|
4347
|
+
this.deferredCommandContainer.addChild(new Spacer(1));
|
|
4348
|
+
this.deferredCommandContainer.addChild(
|
|
4349
|
+
new DeferredCommandPreview([...this.#pendingCommandOutput], maxRows, this.#pendingCommandOutputCommands),
|
|
4350
|
+
);
|
|
4168
4351
|
}
|
|
4169
4352
|
|
|
4170
4353
|
/** Mount every command panel queued for the current session while the agent was streaming. */
|
|
@@ -4174,6 +4357,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
4174
4357
|
const pendingSessionId = this.#pendingCommandOutputSessionId;
|
|
4175
4358
|
this.#pendingCommandOutput = [];
|
|
4176
4359
|
this.#pendingCommandOutputSessionId = undefined;
|
|
4360
|
+
this.#pendingCommandOutputCommands = 0;
|
|
4361
|
+
this.#renderDeferredCommandNotice();
|
|
4177
4362
|
if (pendingSessionId !== this.sessionManager.getSessionId()) return;
|
|
4178
4363
|
this.present(pending);
|
|
4179
4364
|
}
|
|
@@ -4195,6 +4380,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
4195
4380
|
|
|
4196
4381
|
showError(message: string): void {
|
|
4197
4382
|
this.#pendingSubmittedInput = undefined;
|
|
4383
|
+
this.#pendingSubmissionPreservesDraft = false;
|
|
4198
4384
|
this.clearOptimisticUserMessage();
|
|
4199
4385
|
this.#pendingWorkingMessage = undefined;
|
|
4200
4386
|
if (this.loadingAnimation) {
|
|
@@ -5036,7 +5222,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
5036
5222
|
keybindings: KeybindingsManager,
|
|
5037
5223
|
done: (result: T) => void,
|
|
5038
5224
|
) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>,
|
|
5039
|
-
options?:
|
|
5225
|
+
options?: ExtensionCustomOptions,
|
|
5040
5226
|
): Promise<T> {
|
|
5041
5227
|
return this.#extensionUiController.showHookCustom(factory, options);
|
|
5042
5228
|
}
|