@kolisachint/hoocode-agent 0.4.156 → 0.4.158
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 +91 -0
- package/dist/core/agent-session-stats.d.ts +17 -1
- package/dist/core/agent-session-stats.d.ts.map +1 -1
- package/dist/core/agent-session-stats.js +20 -0
- package/dist/core/agent-session-stats.js.map +1 -1
- package/dist/core/context-files.d.ts +14 -4
- package/dist/core/context-files.d.ts.map +1 -1
- package/dist/core/context-files.js +7 -4
- package/dist/core/context-files.js.map +1 -1
- package/dist/core/format-tokens.d.ts +9 -0
- package/dist/core/format-tokens.d.ts.map +1 -0
- package/dist/core/format-tokens.js +19 -0
- package/dist/core/format-tokens.js.map +1 -0
- package/dist/core/mcp-status.d.ts +32 -0
- package/dist/core/mcp-status.d.ts.map +1 -0
- package/dist/core/mcp-status.js +27 -0
- package/dist/core/mcp-status.js.map +1 -0
- package/dist/core/resource-loader.d.ts +6 -16
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/settings-defaults.d.ts +1 -0
- package/dist/core/settings-defaults.d.ts.map +1 -1
- package/dist/core/settings-defaults.js +2 -1
- package/dist/core/settings-defaults.js.map +1 -1
- package/dist/core/settings-manager.d.ts +2 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +8 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/settings-types.d.ts +1 -0
- package/dist/core/settings-types.d.ts.map +1 -1
- package/dist/core/settings-types.js.map +1 -1
- package/dist/core/tools/todo.d.ts +24 -0
- package/dist/core/tools/todo.d.ts.map +1 -1
- package/dist/core/tools/todo.js +50 -9
- package/dist/core/tools/todo.js.map +1 -1
- package/dist/core/wordmark.d.ts +2 -2
- package/dist/core/wordmark.d.ts.map +1 -1
- package/dist/core/wordmark.js +4 -4
- package/dist/core/wordmark.js.map +1 -1
- package/dist/extensions/core/hoo-core.d.ts +3 -0
- package/dist/extensions/core/hoo-core.d.ts.map +1 -1
- package/dist/extensions/core/hoo-core.js +3 -0
- package/dist/extensions/core/hoo-core.js.map +1 -1
- package/dist/extensions/core/mcp-loader.d.ts.map +1 -1
- package/dist/extensions/core/mcp-loader.js +27 -3
- package/dist/extensions/core/mcp-loader.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +7 -26
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +2 -0
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +15 -2
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/task-panel.d.ts +20 -12
- package/dist/modes/interactive/components/task-panel.d.ts.map +1 -1
- package/dist/modes/interactive/components/task-panel.js +171 -233
- package/dist/modes/interactive/components/task-panel.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +56 -7
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +174 -20
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/model-controller.d.ts +6 -0
- package/dist/modes/interactive/model-controller.d.ts.map +1 -1
- package/dist/modes/interactive/model-controller.js +33 -14
- package/dist/modes/interactive/model-controller.js.map +1 -1
- package/dist/modes/interactive/resource-display.d.ts +22 -0
- package/dist/modes/interactive/resource-display.d.ts.map +1 -1
- package/dist/modes/interactive/resource-display.js +73 -16
- package/dist/modes/interactive/resource-display.js.map +1 -1
- package/docs/settings.md +2 -1
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +4 -4
|
@@ -10,14 +10,19 @@ import { createCompactionSummaryMessage } from "@kolisachint/hoocode-agent-core"
|
|
|
10
10
|
import { CombinedAutocompleteProvider, Container, fuzzyFilter, getCapabilities, hyperlink, Loader, Markdown, matchesKey, ProcessTerminal, Spacer, setKeybindings, Text, TUI, } from "@kolisachint/hoocode-tui";
|
|
11
11
|
import { spawnSync } from "child_process";
|
|
12
12
|
import { APP_NAME, APP_TITLE, VERSION } from "../../config.js";
|
|
13
|
+
import { loadAgentRegistry } from "../../core/agent-registry.js";
|
|
13
14
|
import { parseSkillBlock } from "../../core/agent-session.js";
|
|
15
|
+
import { sumAssistantUsage } from "../../core/agent-session-stats.js";
|
|
14
16
|
import { FooterDataProvider } from "../../core/footer-data-provider.js";
|
|
17
|
+
import { formatDurationSecs } from "../../core/format-duration.js";
|
|
18
|
+
import { formatTokens } from "../../core/format-tokens.js";
|
|
15
19
|
import { KeybindingsManager } from "../../core/keybindings.js";
|
|
16
20
|
import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../core/session-cwd.js";
|
|
17
21
|
import { SessionManager } from "../../core/session-manager.js";
|
|
18
22
|
import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
|
|
19
23
|
import { startupProgress } from "../../core/startup-progress.js";
|
|
20
24
|
import { taskStore } from "../../core/task-store.js";
|
|
25
|
+
import { settleDanglingMainTasks } from "../../core/tools/todo.js";
|
|
21
26
|
import { buildCompactWordmark } from "../../core/wordmark.js";
|
|
22
27
|
import { extensionForImageMimeType, readClipboardImage } from "../../utils/clipboard-image.js";
|
|
23
28
|
import { parseGitUrl } from "../../utils/git.js";
|
|
@@ -25,6 +30,7 @@ import { killTrackedDetachedChildren } from "../../utils/shell.js";
|
|
|
25
30
|
import { ensureTool } from "../../utils/tools-manager.js";
|
|
26
31
|
import { checkForNewHooCodeVersion } from "../../utils/version-check.js";
|
|
27
32
|
import { BashExecutionController } from "./bash-execution-controller.js";
|
|
33
|
+
import { SEGMENT_SEP } from "./brand.js";
|
|
28
34
|
import { CommandExecutor } from "./command-executor.js";
|
|
29
35
|
import { BELL, CompletionChime } from "./completion-chime.js";
|
|
30
36
|
import { AssistantMessageComponent } from "./components/assistant-message.js";
|
|
@@ -50,7 +56,7 @@ import { ExtensionDialogs } from "./extension-dialogs.js";
|
|
|
50
56
|
import { LoginController } from "./login-controller.js";
|
|
51
57
|
import { MessageQueueController } from "./message-queue-controller.js";
|
|
52
58
|
import { ModelController } from "./model-controller.js";
|
|
53
|
-
import { ExpandableText, formatDisplayPath, isExpandable, showLoadedResources as renderLoadedResources, } from "./resource-display.js";
|
|
59
|
+
import { ExpandableText, formatDisplayPath, isExpandable, showLoadedResources as renderLoadedResources, willShowResourceListing, } from "./resource-display.js";
|
|
54
60
|
import { checkForPackageUpdates, checkTmuxKeyboardSetup, getChangelogForDisplay } from "./startup-checks.js";
|
|
55
61
|
import { TeamFocusController } from "./team-focus.js";
|
|
56
62
|
import { getAvailableThemes, getAvailableThemesWithPaths, getEditorTheme, getMarkdownTheme, getThemeByName, initTheme, onThemeChange, setRegisteredThemes, setTheme, setThemeInstance, stopThemeWatcher, Theme, theme, } from "./theme/theme.js";
|
|
@@ -151,6 +157,8 @@ export class InteractiveMode {
|
|
|
151
157
|
lastEscapeTime = 0;
|
|
152
158
|
changelogMarkdown = undefined;
|
|
153
159
|
startupNoticesShown = false;
|
|
160
|
+
/** Billing caveat folded into the startup state line, once resolved. */
|
|
161
|
+
startupAuthNote = undefined;
|
|
154
162
|
// Status line tracking (for mutating immediately-sequential status updates)
|
|
155
163
|
lastStatusSpacer = undefined;
|
|
156
164
|
lastStatusText = undefined;
|
|
@@ -199,9 +207,17 @@ export class InteractiveMode {
|
|
|
199
207
|
// True between auto_retry_start and the next agent_start: the turn is not done,
|
|
200
208
|
// it is about to re-run, so the deferred completion check must not fire the chime.
|
|
201
209
|
chimePendingRetry = false;
|
|
202
|
-
//
|
|
203
|
-
//
|
|
204
|
-
|
|
210
|
+
// Stop reason of the request's latest assistant message, reset when the user
|
|
211
|
+
// starts a new one. Read at settle time by two consumers that ask the same
|
|
212
|
+
// question — did this request end cleanly? An "aborted" turn means the user is
|
|
213
|
+
// present, so no chime; anything other than a clean "stop" means dangling plan
|
|
214
|
+
// items must not be claimed as completed.
|
|
215
|
+
turnStopReason = undefined;
|
|
216
|
+
// Cumulative usage + wall clock captured at the first agent_start of a request,
|
|
217
|
+
// diffed at agent_end to print that request's own cost into the transcript.
|
|
218
|
+
// Anchored on the FIRST start so a retried request reports one span, not one
|
|
219
|
+
// per attempt (same reason the chime anchors its duration there).
|
|
220
|
+
turnCostAnchor;
|
|
205
221
|
// Shutdown state
|
|
206
222
|
shutdownRequested = false;
|
|
207
223
|
// Extension UI state
|
|
@@ -328,11 +344,12 @@ export class InteractiveMode {
|
|
|
328
344
|
const editorPaddingX = this.settingsManager.getEditorPaddingX();
|
|
329
345
|
const autocompleteMaxVisible = this.settingsManager.getAutocompleteMaxVisible();
|
|
330
346
|
this.defaultEditor = new CustomEditor(this.ui, getEditorTheme(), this.keybindings, {
|
|
347
|
+
border: this.settingsManager.getEditorBorder(),
|
|
331
348
|
paddingX: editorPaddingX,
|
|
332
349
|
autocompleteMaxVisible,
|
|
333
350
|
});
|
|
334
351
|
this.editor = this.defaultEditor;
|
|
335
|
-
this.editor.promptPrefix = "
|
|
352
|
+
this.editor.promptPrefix = "❯";
|
|
336
353
|
this.editorContainer = new Container();
|
|
337
354
|
this.editorContainer.addChild(this.editor);
|
|
338
355
|
this.footerDataProvider = new FooterDataProvider(this.sessionManager.getCwd());
|
|
@@ -819,12 +836,48 @@ export class InteractiveMode {
|
|
|
819
836
|
getExtensionRunner: () => this.session.extensionRunner,
|
|
820
837
|
getActiveMode: () => this.footerDataProvider.getActiveMode(),
|
|
821
838
|
getSubagentEnabled: () => this.footerDataProvider.getSubagentEnabled(),
|
|
839
|
+
getAgentCount: () => this.getDispatchableAgentCount(),
|
|
840
|
+
getStateLine: () => this.buildSessionStateLine(),
|
|
822
841
|
quietStartup: () => this.settingsManager.getQuietStartup(),
|
|
823
842
|
verbose: this.options.verbose ?? false,
|
|
824
843
|
isExpanded: () => this.getStartupExpansionState(),
|
|
825
844
|
getBuiltInCommandConflictDiagnostics: (runner) => this.getBuiltInCommandConflictDiagnostics(runner),
|
|
826
845
|
}, options);
|
|
827
846
|
}
|
|
847
|
+
/**
|
|
848
|
+
* Agents the model can actually dispatch. Zero when the Task tool is off, so
|
|
849
|
+
* the summary never advertises a capability the session cannot use.
|
|
850
|
+
*/
|
|
851
|
+
getDispatchableAgentCount() {
|
|
852
|
+
if (!this.footerDataProvider.getSubagentEnabled())
|
|
853
|
+
return 0;
|
|
854
|
+
try {
|
|
855
|
+
return loadAgentRegistry({ cwd: this.sessionManager.getCwd() }).list().length;
|
|
856
|
+
}
|
|
857
|
+
catch {
|
|
858
|
+
return 0;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
/**
|
|
862
|
+
* One line of session state for the startup summary: the model, its thinking
|
|
863
|
+
* level, and any billing caveat. The footer carries the same facts for the
|
|
864
|
+
* rest of the session, so this replaces the separate status lines that used
|
|
865
|
+
* to be emitted for each of them.
|
|
866
|
+
*/
|
|
867
|
+
buildSessionStateLine() {
|
|
868
|
+
const model = this.session.model;
|
|
869
|
+
if (!model)
|
|
870
|
+
return undefined;
|
|
871
|
+
const segments = [theme.fg("muted", model.id)];
|
|
872
|
+
const thinkingLevel = this.session.thinkingLevel;
|
|
873
|
+
if (model.reasoning && thinkingLevel && thinkingLevel !== "off") {
|
|
874
|
+
segments.push(theme.fg("dim", `thinking ${thinkingLevel}`));
|
|
875
|
+
}
|
|
876
|
+
if (this.startupAuthNote) {
|
|
877
|
+
segments.push(theme.fg("warning", this.startupAuthNote));
|
|
878
|
+
}
|
|
879
|
+
return segments.join(theme.fg("muted", ` ${SEGMENT_SEP} `));
|
|
880
|
+
}
|
|
828
881
|
/**
|
|
829
882
|
* Initialize the extension system with TUI-based UI context.
|
|
830
883
|
*/
|
|
@@ -906,6 +959,15 @@ export class InteractiveMode {
|
|
|
906
959
|
this.setupAutocompleteProvider();
|
|
907
960
|
const extensionRunner = this.session.extensionRunner;
|
|
908
961
|
this.setupExtensionShortcuts(extensionRunner);
|
|
962
|
+
// Resolved before the summary renders so the billing caveat lands on the
|
|
963
|
+
// state line instead of arriving as a late, separate warning. Skipped on a
|
|
964
|
+
// quiet startup, where the full warning still fires from run().
|
|
965
|
+
if (willShowResourceListing({
|
|
966
|
+
verbose: this.options.verbose ?? false,
|
|
967
|
+
quietStartup: () => this.settingsManager.getQuietStartup(),
|
|
968
|
+
})) {
|
|
969
|
+
this.startupAuthNote = await this.modelController.getAnthropicSubscriptionNote();
|
|
970
|
+
}
|
|
909
971
|
this.showLoadedResources({ force: false, showDiagnosticsWhenQuiet: true });
|
|
910
972
|
this.showStartupNoticesIfNeeded();
|
|
911
973
|
}
|
|
@@ -916,11 +978,14 @@ export class InteractiveMode {
|
|
|
916
978
|
this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
|
|
917
979
|
this.ui.setShowHardwareCursor(this.settingsManager.getShowHardwareCursor());
|
|
918
980
|
this.ui.setClearOnShrink(this.settingsManager.getClearOnShrink());
|
|
981
|
+
const editorBorder = this.settingsManager.getEditorBorder();
|
|
919
982
|
const editorPaddingX = this.settingsManager.getEditorPaddingX();
|
|
920
983
|
const autocompleteMaxVisible = this.settingsManager.getAutocompleteMaxVisible();
|
|
984
|
+
this.defaultEditor.setBorder(editorBorder);
|
|
921
985
|
this.defaultEditor.setPaddingX(editorPaddingX);
|
|
922
986
|
this.defaultEditor.setAutocompleteMaxVisible(autocompleteMaxVisible);
|
|
923
987
|
if (this.editor !== this.defaultEditor) {
|
|
988
|
+
this.editor.setBorder?.(editorBorder);
|
|
924
989
|
this.editor.setPaddingX?.(editorPaddingX);
|
|
925
990
|
this.editor.setAutocompleteMaxVisible?.(autocompleteMaxVisible);
|
|
926
991
|
}
|
|
@@ -1616,20 +1681,94 @@ export class InteractiveMode {
|
|
|
1616
1681
|
});
|
|
1617
1682
|
}
|
|
1618
1683
|
/**
|
|
1619
|
-
* Deferred
|
|
1620
|
-
* tick so the streaming flag settles and any retry has had a chance to arm,
|
|
1621
|
-
*
|
|
1622
|
-
* and no retry is pending
|
|
1623
|
-
*
|
|
1684
|
+
* Deferred end-of-request settle. Called from the agent_end handler, it waits
|
|
1685
|
+
* one tick so the streaming flag settles and any retry has had a chance to arm,
|
|
1686
|
+
* then acts only when the session is genuinely idle (not streaming, not
|
|
1687
|
+
* compacting) and no retry is pending — so a retried or auto-continued request
|
|
1688
|
+
* neither rings a premature "it's done" nor prints a partial cost line.
|
|
1689
|
+
*
|
|
1690
|
+
* The completion chime, the transcript cost line, and settling dangling plan
|
|
1691
|
+
* items all hang off this single check because they answer the same question:
|
|
1692
|
+
* has the request actually ended?
|
|
1624
1693
|
*/
|
|
1625
|
-
|
|
1694
|
+
settleRequestOnIdle() {
|
|
1626
1695
|
setTimeout(() => {
|
|
1627
1696
|
if (this.chimePendingRetry || this.session.isStreaming || this.session.isCompacting) {
|
|
1628
1697
|
return;
|
|
1629
1698
|
}
|
|
1630
|
-
this.
|
|
1699
|
+
this.showTurnCost();
|
|
1700
|
+
this.settleDanglingPlanItems();
|
|
1701
|
+
this.chime?.onTurnComplete({ aborted: this.turnStopReason === "aborted" });
|
|
1631
1702
|
}, 0);
|
|
1632
1703
|
}
|
|
1704
|
+
/**
|
|
1705
|
+
* Flip main-plan rows the model left at in_progress to a settled status now the
|
|
1706
|
+
* request is over, instead of letting them claim live work until the next user
|
|
1707
|
+
* message wipes the pane.
|
|
1708
|
+
*
|
|
1709
|
+
* The model marks its own TodoWrite items and routinely drops the final call
|
|
1710
|
+
* that completes the last one, so the row outlives the work. A clean "stop" is
|
|
1711
|
+
* taken as the model believing it was finished — it chose to stop talking, and
|
|
1712
|
+
* its closing message is the report — so those items settle to done. An abort,
|
|
1713
|
+
* error, or length cutoff says the opposite, so they settle to cancelled: an
|
|
1714
|
+
* honest "never finished" rather than a fabricated completion.
|
|
1715
|
+
*
|
|
1716
|
+
* Skipped while messages are queued: a follow-up/steer arriving during the run
|
|
1717
|
+
* means the request continues, and the plan with it.
|
|
1718
|
+
*/
|
|
1719
|
+
settleDanglingPlanItems() {
|
|
1720
|
+
if (this.session.pendingMessageCount > 0)
|
|
1721
|
+
return;
|
|
1722
|
+
const settled = settleDanglingMainTasks(this.turnStopReason === "stop" ? "done" : "cancelled");
|
|
1723
|
+
if (settled > 0)
|
|
1724
|
+
this.ui.requestRender();
|
|
1725
|
+
}
|
|
1726
|
+
/**
|
|
1727
|
+
* Append this request's own token/time/cost to the transcript.
|
|
1728
|
+
*
|
|
1729
|
+
* This is the honest home for the number. The task panel is a live instrument —
|
|
1730
|
+
* present tense, wiped on the next user message — and the footer carries
|
|
1731
|
+
* cumulative session vitals, so after the fact neither can answer "what did that
|
|
1732
|
+
* request cost". Fired at agent_end rather than turn_end because one request is
|
|
1733
|
+
* commonly tens of turns; per-turn would wedge a number between every tool block.
|
|
1734
|
+
*
|
|
1735
|
+
* Delegated runs are reported separately rather than folded into the totals:
|
|
1736
|
+
* subagents bill against their own sessions, so their tokens never appear in this
|
|
1737
|
+
* session's entries and adding them to ↑/↓ would misreport what the parent spent.
|
|
1738
|
+
*/
|
|
1739
|
+
showTurnCost() {
|
|
1740
|
+
const anchor = this.turnCostAnchor;
|
|
1741
|
+
this.turnCostAnchor = undefined;
|
|
1742
|
+
if (!anchor)
|
|
1743
|
+
return;
|
|
1744
|
+
const now = sumAssistantUsage(this.session.sessionManager.getEntries());
|
|
1745
|
+
const input = now.input - anchor.totals.input;
|
|
1746
|
+
const output = now.output - anchor.totals.output;
|
|
1747
|
+
const cost = now.cost - anchor.totals.cost;
|
|
1748
|
+
// Nothing was accounted (e.g. aborted before the first response landed): stay
|
|
1749
|
+
// silent rather than print a row of zeroes.
|
|
1750
|
+
if (input <= 0 && output <= 0)
|
|
1751
|
+
return;
|
|
1752
|
+
const segs = [
|
|
1753
|
+
theme.fg("dim", "↑") +
|
|
1754
|
+
theme.fg("muted", formatTokens(input)) +
|
|
1755
|
+
theme.fg("dim", " ↓") +
|
|
1756
|
+
theme.fg("muted", formatTokens(output)),
|
|
1757
|
+
theme.fg("muted", formatDurationSecs((Date.now() - anchor.at) / 1000)),
|
|
1758
|
+
];
|
|
1759
|
+
if (cost > 0)
|
|
1760
|
+
segs.push(theme.fg("muted", `$${cost.toFixed(3)}`));
|
|
1761
|
+
const runs = taskStore.list().filter((task) => task.source === "subagent");
|
|
1762
|
+
if (runs.length > 0) {
|
|
1763
|
+
const delegatedTokens = runs.reduce((sum, r) => sum + (r.usage ? r.usage.input + r.usage.output : 0), 0);
|
|
1764
|
+
const label = runs.length === 1 ? (runs[0]?.subagentMode ?? "subagent") : "subagents";
|
|
1765
|
+
const text = `◇${runs.length} ${label}${delegatedTokens > 0 ? ` ${formatTokens(delegatedTokens)}` : ""}`;
|
|
1766
|
+
segs.push(theme.fg("dim", text));
|
|
1767
|
+
}
|
|
1768
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
1769
|
+
this.chatContainer.addChild(new Text(segs.join(theme.fg("dim", " · ")), 1, 0));
|
|
1770
|
+
this.ui.requestRender();
|
|
1771
|
+
}
|
|
1633
1772
|
async handleEvent(event) {
|
|
1634
1773
|
if (!this.isInitialized) {
|
|
1635
1774
|
await this.init();
|
|
@@ -1642,6 +1781,10 @@ export class InteractiveMode {
|
|
|
1642
1781
|
// first start and clear the pending-retry gate now that it has resumed.
|
|
1643
1782
|
this.chime?.onTurnStart();
|
|
1644
1783
|
this.chimePendingRetry = false;
|
|
1784
|
+
this.turnCostAnchor ??= {
|
|
1785
|
+
totals: sumAssistantUsage(this.session.sessionManager.getEntries()),
|
|
1786
|
+
at: Date.now(),
|
|
1787
|
+
};
|
|
1645
1788
|
if (this.settingsManager.getShowTerminalProgress()) {
|
|
1646
1789
|
this.ui.terminal.setProgress(true);
|
|
1647
1790
|
}
|
|
@@ -1696,7 +1839,7 @@ export class InteractiveMode {
|
|
|
1696
1839
|
// turn, drop any lingering bars/notices (e.g. an unavailable-index line)
|
|
1697
1840
|
// so they don't sit in the footer for the rest of the session.
|
|
1698
1841
|
startupProgress.clear();
|
|
1699
|
-
this.
|
|
1842
|
+
this.turnStopReason = undefined;
|
|
1700
1843
|
this.addMessageToChat(event.message);
|
|
1701
1844
|
this.messageQueue.updatePendingMessagesDisplay();
|
|
1702
1845
|
this.ui.requestRender();
|
|
@@ -1740,9 +1883,9 @@ export class InteractiveMode {
|
|
|
1740
1883
|
if (event.message.role === "user")
|
|
1741
1884
|
break;
|
|
1742
1885
|
if (event.message.role === "assistant") {
|
|
1743
|
-
// Remember
|
|
1744
|
-
//
|
|
1745
|
-
this.
|
|
1886
|
+
// Remember how the turn's latest assistant message ended, so the deferred
|
|
1887
|
+
// settle can tell a clean finish from an abort/error.
|
|
1888
|
+
this.turnStopReason = event.message.stopReason;
|
|
1746
1889
|
}
|
|
1747
1890
|
if (this.streamingComponent && event.message.role === "assistant") {
|
|
1748
1891
|
this.streamingMessage = event.message;
|
|
@@ -1832,10 +1975,10 @@ export class InteractiveMode {
|
|
|
1832
1975
|
await this.checkShutdownRequested();
|
|
1833
1976
|
// agent_end fires before the streaming flag clears and before the retry
|
|
1834
1977
|
// decision is made (both happen after this listener returns), so defer a
|
|
1835
|
-
// tick and re-check: only
|
|
1978
|
+
// tick and re-check: only settle once the session is genuinely idle and no
|
|
1836
1979
|
// retry is pending — otherwise a retried or auto-continued turn would ring
|
|
1837
|
-
// a premature "it's done".
|
|
1838
|
-
this.
|
|
1980
|
+
// a premature "it's done" and print a partial cost line.
|
|
1981
|
+
this.settleRequestOnIdle();
|
|
1839
1982
|
this.ui.requestRender();
|
|
1840
1983
|
break;
|
|
1841
1984
|
case "compaction_start": {
|
|
@@ -2323,7 +2466,7 @@ export class InteractiveMode {
|
|
|
2323
2466
|
this.editor.promptColor = theme.getBashModeBorderColor();
|
|
2324
2467
|
}
|
|
2325
2468
|
else {
|
|
2326
|
-
this.editor.promptPrefix = "
|
|
2469
|
+
this.editor.promptPrefix = "❯";
|
|
2327
2470
|
this.editor.promptColor = (s) => s;
|
|
2328
2471
|
}
|
|
2329
2472
|
this.ui.requestRender();
|
|
@@ -2542,6 +2685,7 @@ export class InteractiveMode {
|
|
|
2542
2685
|
doubleEscapeAction: this.settingsManager.getDoubleEscapeAction(),
|
|
2543
2686
|
treeFilterMode: this.settingsManager.getTreeFilterMode(),
|
|
2544
2687
|
showHardwareCursor: this.settingsManager.getShowHardwareCursor(),
|
|
2688
|
+
editorBorder: this.settingsManager.getEditorBorder(),
|
|
2545
2689
|
editorPaddingX: this.settingsManager.getEditorPaddingX(),
|
|
2546
2690
|
autocompleteMaxVisible: this.settingsManager.getAutocompleteMaxVisible(),
|
|
2547
2691
|
quietStartup: this.settingsManager.getQuietStartup(),
|
|
@@ -2711,6 +2855,13 @@ export class InteractiveMode {
|
|
|
2711
2855
|
this.settingsManager.setShowHardwareCursor(enabled);
|
|
2712
2856
|
this.ui.setShowHardwareCursor(enabled);
|
|
2713
2857
|
},
|
|
2858
|
+
onEditorBorderChange: (border) => {
|
|
2859
|
+
this.settingsManager.setEditorBorder(border);
|
|
2860
|
+
this.defaultEditor.setBorder(border);
|
|
2861
|
+
if (this.editor !== this.defaultEditor && this.editor.setBorder !== undefined) {
|
|
2862
|
+
this.editor.setBorder(border);
|
|
2863
|
+
}
|
|
2864
|
+
},
|
|
2714
2865
|
onEditorPaddingXChange: (padding) => {
|
|
2715
2866
|
this.settingsManager.setEditorPaddingX(padding);
|
|
2716
2867
|
this.defaultEditor.setPaddingX(padding);
|
|
@@ -2994,11 +3145,14 @@ export class InteractiveMode {
|
|
|
2994
3145
|
if (!themeResult.success) {
|
|
2995
3146
|
this.showError(`Failed to load theme "${themeName}": ${themeResult.error}\nFell back to dark theme.`);
|
|
2996
3147
|
}
|
|
3148
|
+
const editorBorder = this.settingsManager.getEditorBorder();
|
|
2997
3149
|
const editorPaddingX = this.settingsManager.getEditorPaddingX();
|
|
2998
3150
|
const autocompleteMaxVisible = this.settingsManager.getAutocompleteMaxVisible();
|
|
3151
|
+
this.defaultEditor.setBorder(editorBorder);
|
|
2999
3152
|
this.defaultEditor.setPaddingX(editorPaddingX);
|
|
3000
3153
|
this.defaultEditor.setAutocompleteMaxVisible(autocompleteMaxVisible);
|
|
3001
3154
|
if (this.editor !== this.defaultEditor) {
|
|
3155
|
+
this.editor.setBorder?.(editorBorder);
|
|
3002
3156
|
this.editor.setPaddingX?.(editorPaddingX);
|
|
3003
3157
|
this.editor.setAutocompleteMaxVisible?.(autocompleteMaxVisible);
|
|
3004
3158
|
}
|