@oh-my-pi/pi-coding-agent 15.10.0 → 15.10.1
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 +75 -1
- package/dist/types/cli/dry-balance-cli.d.ts +15 -1
- package/dist/types/commit/analysis/conventional.d.ts +2 -2
- package/dist/types/commit/analysis/summary.d.ts +2 -2
- package/dist/types/commit/changelog/generate.d.ts +2 -2
- package/dist/types/commit/changelog/index.d.ts +2 -2
- package/dist/types/commit/map-reduce/index.d.ts +3 -3
- package/dist/types/commit/map-reduce/map-phase.d.ts +2 -2
- package/dist/types/commit/map-reduce/reduce-phase.d.ts +2 -2
- package/dist/types/commit/model-selection.d.ts +10 -4
- package/dist/types/config/api-key-resolver.d.ts +34 -0
- package/dist/types/config/model-registry.d.ts +17 -1
- package/dist/types/config/settings-schema.d.ts +9 -0
- package/dist/types/dap/config.d.ts +14 -1
- package/dist/types/dap/types.d.ts +10 -0
- package/dist/types/lsp/utils.d.ts +3 -2
- package/dist/types/modes/components/chat-block.d.ts +64 -0
- package/dist/types/modes/components/custom-editor.d.ts +3 -0
- package/dist/types/modes/components/overlay-box.d.ts +17 -0
- package/dist/types/modes/components/plan-review-overlay.d.ts +59 -0
- package/dist/types/modes/components/plan-toc.d.ts +41 -0
- package/dist/types/modes/components/read-tool-group.d.ts +2 -0
- package/dist/types/modes/components/transcript-container.d.ts +11 -0
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/controllers/event-controller.d.ts +0 -1
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +0 -1
- package/dist/types/modes/controllers/input-controller.d.ts +1 -1
- package/dist/types/modes/controllers/streaming-reveal.d.ts +22 -0
- package/dist/types/modes/controllers/tan-command-controller.d.ts +6 -0
- package/dist/types/modes/interactive-mode.d.ts +15 -5
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +18 -5
- package/dist/types/modes/utils/copy-targets.d.ts +21 -1
- package/dist/types/plan-mode/approved-plan.d.ts +27 -8
- package/dist/types/plan-mode/plan-protection.d.ts +4 -4
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +21 -0
- package/dist/types/session/messages.d.ts +12 -0
- package/dist/types/session/session-manager.d.ts +3 -1
- package/dist/types/slash-commands/types.d.ts +4 -6
- package/dist/types/task/executor.d.ts +7 -0
- package/dist/types/task/index.d.ts +1 -0
- package/dist/types/task/render.d.ts +3 -2
- package/dist/types/tools/archive-reader.d.ts +5 -0
- package/dist/types/tools/ast-edit.d.ts +3 -0
- package/dist/types/tools/ast-grep.d.ts +3 -0
- package/dist/types/tools/bash.d.ts +1 -0
- package/dist/types/tools/find.d.ts +8 -4
- package/dist/types/tools/grouped-file-output.d.ts +95 -12
- package/dist/types/tools/memory-render.d.ts +4 -1
- package/dist/types/tools/plan-mode-guard.d.ts +8 -9
- package/dist/types/tools/render-utils.d.ts +5 -9
- package/dist/types/tools/search.d.ts +4 -0
- package/dist/types/tools/sqlite-reader.d.ts +1 -0
- package/dist/types/tools/todo.d.ts +3 -2
- package/dist/types/tools/write.d.ts +3 -0
- package/dist/types/tui/output-block.d.ts +16 -4
- package/dist/types/tui/status-line.d.ts +3 -0
- package/dist/types/utils/enhanced-paste.d.ts +20 -0
- package/dist/types/web/search/providers/kimi.d.ts +1 -1
- package/package.json +9 -9
- package/src/auto-thinking/classifier.ts +5 -1
- package/src/cli/dry-balance-cli.ts +52 -17
- package/src/cli/gallery-cli.ts +4 -1
- package/src/cli/gallery-fixtures/misc.ts +29 -0
- package/src/commit/analysis/conventional.ts +2 -2
- package/src/commit/analysis/summary.ts +2 -2
- package/src/commit/changelog/generate.ts +2 -2
- package/src/commit/changelog/index.ts +2 -2
- package/src/commit/map-reduce/index.ts +3 -3
- package/src/commit/map-reduce/map-phase.ts +2 -2
- package/src/commit/map-reduce/reduce-phase.ts +2 -2
- package/src/commit/model-selection.ts +33 -9
- package/src/commit/pipeline.ts +4 -4
- package/src/config/api-key-resolver.ts +58 -0
- package/src/config/model-registry.ts +25 -2
- package/src/config/settings-schema.ts +10 -0
- package/src/config/settings.ts +20 -2
- package/src/dap/config.ts +41 -2
- package/src/dap/defaults.json +1 -0
- package/src/dap/session.ts +1 -0
- package/src/dap/types.ts +10 -0
- package/src/debug/index.ts +40 -54
- package/src/edit/renderer.ts +82 -78
- package/src/eval/__tests__/llm-bridge.test.ts +90 -31
- package/src/eval/llm-bridge.ts +8 -3
- package/src/goals/tools/goal-tool.ts +36 -26
- package/src/internal-urls/docs-index.generated.ts +6 -6
- package/src/lsp/utils.ts +3 -2
- package/src/main.ts +9 -7
- package/src/memories/index.ts +12 -5
- package/src/mnemopi/backend.ts +5 -1
- package/src/modes/acp/acp-agent.ts +33 -26
- package/src/modes/components/assistant-message.ts +2 -9
- package/src/modes/components/chat-block.ts +111 -0
- package/src/modes/components/copy-selector.ts +1 -44
- package/src/modes/components/custom-editor.ts +23 -0
- package/src/modes/components/custom-message.ts +1 -3
- package/src/modes/components/execution-shared.ts +1 -2
- package/src/modes/components/hook-message.ts +1 -3
- package/src/modes/components/overlay-box.ts +108 -0
- package/src/modes/components/plan-review-overlay.ts +799 -0
- package/src/modes/components/plan-toc.ts +138 -0
- package/src/modes/components/read-tool-group.ts +20 -4
- package/src/modes/components/skill-message.ts +0 -1
- package/src/modes/components/tips.txt +1 -0
- package/src/modes/components/todo-reminder.ts +0 -2
- package/src/modes/components/tool-execution.ts +68 -88
- package/src/modes/components/transcript-container.ts +84 -24
- package/src/modes/components/user-message.ts +1 -2
- package/src/modes/controllers/command-controller-shared.ts +7 -6
- package/src/modes/controllers/command-controller.ts +57 -55
- package/src/modes/controllers/event-controller.ts +41 -40
- package/src/modes/controllers/extension-ui-controller.ts +10 -73
- package/src/modes/controllers/input-controller.ts +124 -119
- package/src/modes/controllers/mcp-command-controller.ts +69 -60
- package/src/modes/controllers/selector-controller.ts +23 -25
- package/src/modes/controllers/streaming-reveal.ts +212 -0
- package/src/modes/controllers/tan-command-controller.ts +173 -0
- package/src/modes/interactive-mode.ts +169 -94
- package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
- package/src/modes/theme/theme-schema.json +1 -1
- package/src/modes/theme/theme.ts +8 -4
- package/src/modes/types.ts +18 -7
- package/src/modes/utils/copy-targets.ts +133 -27
- package/src/modes/utils/ui-helpers.ts +44 -46
- package/src/plan-mode/approved-plan.ts +66 -43
- package/src/plan-mode/plan-protection.ts +4 -4
- package/src/prompts/system/background-tan-dispatch.md +8 -0
- package/src/prompts/system/plan-mode-active.md +67 -58
- package/src/prompts/system/plan-mode-approved.md +1 -1
- package/src/sdk.ts +11 -37
- package/src/session/agent-session.ts +82 -6
- package/src/session/messages.ts +26 -0
- package/src/session/session-manager.ts +13 -5
- package/src/slash-commands/builtin-registry.ts +36 -9
- package/src/slash-commands/types.ts +4 -6
- package/src/task/executor.ts +5 -2
- package/src/task/index.ts +4 -0
- package/src/task/render.ts +212 -147
- package/src/tools/archive-reader.ts +64 -0
- package/src/tools/ask.ts +119 -164
- package/src/tools/ast-edit.ts +98 -71
- package/src/tools/ast-grep.ts +37 -43
- package/src/tools/bash.ts +50 -6
- package/src/tools/debug.ts +20 -8
- package/src/tools/fetch.ts +297 -7
- package/src/tools/find.ts +44 -30
- package/src/tools/gh-renderer.ts +81 -42
- package/src/tools/grouped-file-output.ts +272 -48
- package/src/tools/image-gen.ts +150 -103
- package/src/tools/inspect-image-renderer.ts +63 -41
- package/src/tools/inspect-image.ts +8 -1
- package/src/tools/job.ts +3 -4
- package/src/tools/memory-render.ts +4 -1
- package/src/tools/plan-mode-guard.ts +21 -39
- package/src/tools/read.ts +23 -16
- package/src/tools/render-utils.ts +21 -37
- package/src/tools/resolve.ts +14 -0
- package/src/tools/search-tool-bm25.ts +36 -23
- package/src/tools/search.ts +80 -78
- package/src/tools/sqlite-reader.ts +9 -12
- package/src/tools/todo.ts +118 -52
- package/src/tools/write.ts +81 -62
- package/src/tui/output-block.ts +60 -13
- package/src/tui/status-line.ts +5 -1
- package/src/utils/commit-message-generator.ts +9 -1
- package/src/utils/enhanced-paste.ts +202 -0
- package/src/utils/title-generator.ts +2 -1
- package/src/web/search/providers/anthropic.ts +25 -19
- package/src/web/search/providers/exa.ts +11 -3
- package/src/web/search/providers/kimi.ts +28 -17
- package/src/web/search/providers/parallel.ts +35 -24
- package/src/web/search/providers/synthetic.ts +8 -6
- package/src/web/search/providers/tavily.ts +9 -8
- package/src/web/search/providers/zai.ts +8 -6
|
@@ -39,6 +39,26 @@ function isBlockAppendOnly(child: Component): boolean {
|
|
|
39
39
|
return fn ? fn.call(child) : false;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
// A "plain blank" row is empty or whitespace-only with no ANSI bytes. It marks
|
|
43
|
+
// separation padding (a `Spacer`, or a no-background `paddingY` row) as opposed
|
|
44
|
+
// to a background-colored padding row, whose escape sequences contain `\S` and
|
|
45
|
+
// are therefore preserved as part of a block's visual design.
|
|
46
|
+
const NON_WHITESPACE = /\S/;
|
|
47
|
+
function isPlainBlank(line: string): boolean {
|
|
48
|
+
return !NON_WHITESPACE.test(line);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Strip leading/trailing plain-blank rows so each block contributes only its
|
|
52
|
+
// visible body; the container owns the gaps between blocks. Returns the input
|
|
53
|
+
// array unchanged when there is nothing to trim (no allocation on the hot path).
|
|
54
|
+
function stripPlainBlankEdges(lines: string[]): string[] {
|
|
55
|
+
let start = 0;
|
|
56
|
+
let end = lines.length;
|
|
57
|
+
while (start < end && isPlainBlank(lines[start]!)) start++;
|
|
58
|
+
while (end > start && isPlainBlank(lines[end - 1]!)) end--;
|
|
59
|
+
return start === 0 && end === lines.length ? lines : lines.slice(start, end);
|
|
60
|
+
}
|
|
61
|
+
|
|
42
62
|
/**
|
|
43
63
|
* Transcript container that freezes the rendered output of every block except
|
|
44
64
|
* the bottom-most (live) one on terminals where committed native scrollback is
|
|
@@ -118,9 +138,13 @@ export class TranscriptContainer extends Container implements NativeScrollbackLi
|
|
|
118
138
|
width = Math.max(1, width);
|
|
119
139
|
this.#nativeScrollbackLiveRegionStart = undefined;
|
|
120
140
|
this.#nativeScrollbackCommitSafeEnd = undefined;
|
|
121
|
-
if (!TERMINAL.eagerEraseScrollbackRisk) return super.render(width);
|
|
122
141
|
|
|
142
|
+
// Freezing/snapshotting only applies on ED3-risk terminals; elsewhere every
|
|
143
|
+
// block renders live. Inter-block spacing applies on BOTH paths so the gap
|
|
144
|
+
// between blocks is identical regardless of terminal.
|
|
145
|
+
const risk = TERMINAL.eagerEraseScrollbackRisk;
|
|
123
146
|
const count = this.children.length;
|
|
147
|
+
|
|
124
148
|
// The live region spans from the earliest still-mutating block through the
|
|
125
149
|
// bottom. A block that has not finalized must stay repaintable: out-of-band
|
|
126
150
|
// inserts (TTSR/todo cards) can append a finalized block *below* a tool that
|
|
@@ -137,45 +161,81 @@ export class TranscriptContainer extends Container implements NativeScrollbackLi
|
|
|
137
161
|
// recompute them so they freeze at their final content. Everything below
|
|
138
162
|
// the lower of the two cutoffs was already frozen last frame and replays.
|
|
139
163
|
const replayCutoff = Math.min(liveStartIndex, this.#prevLiveStartIndex);
|
|
140
|
-
this.#prevLiveStartIndex = liveStartIndex;
|
|
164
|
+
if (risk) this.#prevLiveStartIndex = liveStartIndex;
|
|
141
165
|
|
|
142
166
|
const lines: string[] = [];
|
|
143
167
|
// Tracks whether we are still inside the leading run of append-only live
|
|
144
|
-
// blocks. The first non-append-only live block
|
|
145
|
-
// the live region's start, which cannot happen for a leading run) closes it.
|
|
168
|
+
// blocks. The first non-append-only live block closes it.
|
|
146
169
|
let commitSafeOpen = true;
|
|
170
|
+
// The live-region start is recorded at the first visible row at/after the
|
|
171
|
+
// cutoff; empty leading blocks (or a separator) must not claim it early.
|
|
172
|
+
let liveRecorded = false;
|
|
147
173
|
for (let i = 0; i < count; i++) {
|
|
148
174
|
const child = this.children[i]! as Component & SnapshotCarrier;
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
175
|
+
|
|
176
|
+
// Resolve this child's contribution — its visible body with plain-blank
|
|
177
|
+
// top/bottom edges stripped (the container owns inter-block gaps). On
|
|
178
|
+
// ED3-risk terminals a frozen, scrolled-off block replays its snapshot
|
|
179
|
+
// instead of recomputing; a stale generation (post-thaw) or width
|
|
180
|
+
// mismatch (resize) recomputes, as does a block still live last frame.
|
|
181
|
+
let contribution: string[] | undefined;
|
|
182
|
+
if (risk && i < liveStartIndex && i < replayCutoff) {
|
|
152
183
|
const snapshot = child[kSnapshot];
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
// instead, as does a block that was still live last frame (i >= cutoff).
|
|
156
|
-
if (i < replayCutoff && snapshot && snapshot.generation === this.#generation && snapshot.width === width) {
|
|
157
|
-
lines.push(...snapshot.lines);
|
|
158
|
-
continue;
|
|
184
|
+
if (snapshot && snapshot.generation === this.#generation && snapshot.width === width) {
|
|
185
|
+
contribution = snapshot.lines;
|
|
159
186
|
}
|
|
160
187
|
}
|
|
161
|
-
|
|
188
|
+
if (contribution === undefined) {
|
|
189
|
+
const rendered = child.render(width);
|
|
190
|
+
contribution = stripPlainBlankEdges(rendered);
|
|
191
|
+
// Cache every block's latest contribution. While a block is in the
|
|
192
|
+
// live region this keeps its snapshot current; on the frame it crosses
|
|
193
|
+
// out, the recompute above refreshes it before it freezes.
|
|
194
|
+
if (risk) child[kSnapshot] = { width, lines: contribution, generation: this.#generation };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Empty (or stripped-to-nothing) children contribute nothing and never
|
|
198
|
+
// affect spacing or the live-region offsets.
|
|
199
|
+
if (contribution.length === 0) continue;
|
|
200
|
+
|
|
201
|
+
// Every block is separated from preceding visible content by exactly one
|
|
202
|
+
// blank row — skipped when it opens the transcript or the prior row is
|
|
203
|
+
// already a plain blank (a fragment's own trailing pad), never doubling.
|
|
204
|
+
const sep = lines.length > 0 && !isPlainBlank(lines[lines.length - 1]!) ? 1 : 0;
|
|
205
|
+
|
|
206
|
+
// The separator before the first live block stays in the committed prefix
|
|
207
|
+
// (it is deterministic and never changes once the prior block is frozen),
|
|
208
|
+
// so the live region begins at the block's first content row.
|
|
209
|
+
if (risk && !liveRecorded && i >= liveStartIndex) {
|
|
210
|
+
this.#nativeScrollbackLiveRegionStart = lines.length + sep;
|
|
211
|
+
liveRecorded = true;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (sep) lines.push("");
|
|
215
|
+
for (let j = 0; j < contribution.length; j++) lines.push(contribution[j]!);
|
|
216
|
+
|
|
162
217
|
// Extend the commit-safe boundary through each leading append-only live
|
|
163
|
-
// block.
|
|
164
|
-
//
|
|
165
|
-
|
|
166
|
-
if (i >= liveStartIndex && commitSafeOpen) {
|
|
218
|
+
// block. The first volatile live block closes the run so its mutable
|
|
219
|
+
// rows stay deferred.
|
|
220
|
+
if (risk && i >= liveStartIndex && commitSafeOpen) {
|
|
167
221
|
if (isBlockAppendOnly(child)) {
|
|
168
|
-
this.#nativeScrollbackCommitSafeEnd = lines.length
|
|
222
|
+
this.#nativeScrollbackCommitSafeEnd = lines.length;
|
|
169
223
|
} else {
|
|
170
224
|
commitSafeOpen = false;
|
|
171
225
|
}
|
|
172
226
|
}
|
|
173
|
-
// Cache every block's latest render. While a block is in the live region
|
|
174
|
-
// this keeps its snapshot current; on the frame it crosses out, the
|
|
175
|
-
// recompute above refreshes it to the final state before it freezes.
|
|
176
|
-
child[kSnapshot] = { width, lines: rendered, generation: this.#generation };
|
|
177
|
-
lines.push(...rendered);
|
|
178
227
|
}
|
|
179
228
|
return lines;
|
|
180
229
|
}
|
|
181
230
|
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Groups a run of sibling rows (an IRC card's header + body, a file-mention
|
|
234
|
+
* list, a bordered command/version panel) into a single transcript child so the
|
|
235
|
+
* container spaces it as one block — one blank line above, none injected between
|
|
236
|
+
* its rows. Without this wrapper the rows would be top-level children and the
|
|
237
|
+
* container would put a blank line between each (and inside any border box).
|
|
238
|
+
* It is a plain {@link Container}; the named subclass documents intent and makes
|
|
239
|
+
* every manual block grouping greppable.
|
|
240
|
+
*/
|
|
241
|
+
export class TranscriptBlock extends Container {}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Container, Markdown
|
|
1
|
+
import { Container, Markdown } from "@oh-my-pi/pi-tui";
|
|
2
2
|
import { getMarkdownTheme, theme } from "../../modes/theme/theme";
|
|
3
3
|
import { imageReferenceHyperlink, renderImageReferences } from "../image-references";
|
|
4
4
|
import { highlightMagicKeywords } from "../magic-keywords";
|
|
@@ -30,7 +30,6 @@ export class UserMessageComponent extends Container {
|
|
|
30
30
|
renderText: baseText,
|
|
31
31
|
renderReference: (label, index) => imageReferenceHyperlink(label, index, imageLinks, imageLabel),
|
|
32
32
|
});
|
|
33
|
-
this.addChild(new Spacer(1));
|
|
34
33
|
this.addChild(
|
|
35
34
|
new Markdown(text, 1, 1, getMarkdownTheme(), {
|
|
36
35
|
bgColor,
|
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
* wording, and add-flow logic stay in the per-controller files because they
|
|
8
8
|
* diverge in workflow.
|
|
9
9
|
*/
|
|
10
|
-
import {
|
|
10
|
+
import { Text } from "@oh-my-pi/pi-tui";
|
|
11
11
|
import type { SourceMeta } from "../../capability/types";
|
|
12
12
|
import { shortenPath } from "../../tools/render-utils";
|
|
13
13
|
import { DynamicBorder } from "../components/dynamic-border";
|
|
14
|
+
import { TranscriptBlock } from "../components/transcript-container";
|
|
14
15
|
import { parseCommandArgs } from "../shared";
|
|
15
16
|
import type { InteractiveModeContext } from "../types";
|
|
16
17
|
|
|
@@ -100,9 +101,9 @@ export function* groupBySource<T>(
|
|
|
100
101
|
* container and request a render.
|
|
101
102
|
*/
|
|
102
103
|
export function showCommandMessage(ctx: InteractiveModeContext, text: string): void {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
ctx.
|
|
104
|
+
const block = new TranscriptBlock();
|
|
105
|
+
block.addChild(new DynamicBorder());
|
|
106
|
+
block.addChild(new Text(text, 1, 1));
|
|
107
|
+
block.addChild(new DynamicBorder());
|
|
108
|
+
ctx.present(block);
|
|
108
109
|
}
|
|
@@ -28,6 +28,7 @@ import { BashExecutionComponent } from "../../modes/components/bash-execution";
|
|
|
28
28
|
import { BorderedLoader } from "../../modes/components/bordered-loader";
|
|
29
29
|
import { DynamicBorder } from "../../modes/components/dynamic-border";
|
|
30
30
|
import { EvalExecutionComponent } from "../../modes/components/eval-execution";
|
|
31
|
+
import { TranscriptBlock } from "../../modes/components/transcript-container";
|
|
31
32
|
import { getMarkdownTheme, getSymbolTheme, theme } from "../../modes/theme/theme";
|
|
32
33
|
import type { InteractiveModeContext } from "../../modes/types";
|
|
33
34
|
import { computeContextBreakdown, renderContextUsage } from "../../modes/utils/context-usage";
|
|
@@ -46,13 +47,13 @@ import { openPath } from "../../utils/open";
|
|
|
46
47
|
import { setSessionTerminalTitle } from "../../utils/title-generator";
|
|
47
48
|
|
|
48
49
|
function showMarkdownPanel(ctx: InteractiveModeContext, title: string, markdown: string): void {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
ctx.
|
|
50
|
+
const block = new TranscriptBlock();
|
|
51
|
+
block.addChild(new DynamicBorder());
|
|
52
|
+
block.addChild(new Text(theme.bold(theme.fg("accent", title)), 1, 0));
|
|
53
|
+
block.addChild(new Spacer(1));
|
|
54
|
+
block.addChild(new Markdown(markdown.trim(), 1, 1, getMarkdownTheme()));
|
|
55
|
+
block.addChild(new DynamicBorder());
|
|
56
|
+
ctx.present(block);
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
export class CommandController {
|
|
@@ -334,9 +335,7 @@ export class CommandController {
|
|
|
334
335
|
}
|
|
335
336
|
}
|
|
336
337
|
|
|
337
|
-
this.ctx.
|
|
338
|
-
this.ctx.chatContainer.addChild(new Text(info, 1, 0));
|
|
339
|
-
this.ctx.ui.requestRender();
|
|
338
|
+
this.ctx.present([new Spacer(1), new Text(info, 1, 0)]);
|
|
340
339
|
}
|
|
341
340
|
|
|
342
341
|
async handleJobsCommand(): Promise<void> {
|
|
@@ -353,9 +352,7 @@ export class CommandController {
|
|
|
353
352
|
|
|
354
353
|
if (snapshot.running.length === 0 && snapshot.recent.length === 0) {
|
|
355
354
|
info += `\n${theme.fg("dim", "No async jobs yet.")}\n`;
|
|
356
|
-
this.ctx.
|
|
357
|
-
this.ctx.chatContainer.addChild(new Text(info, 1, 0));
|
|
358
|
-
this.ctx.ui.requestRender();
|
|
355
|
+
this.ctx.present([new Spacer(1), new Text(info, 1, 0)]);
|
|
359
356
|
return;
|
|
360
357
|
}
|
|
361
358
|
|
|
@@ -375,9 +372,7 @@ export class CommandController {
|
|
|
375
372
|
}
|
|
376
373
|
}
|
|
377
374
|
|
|
378
|
-
this.ctx.
|
|
379
|
-
this.ctx.chatContainer.addChild(new Text(info.trimEnd(), 1, 0));
|
|
380
|
-
this.ctx.ui.requestRender();
|
|
375
|
+
this.ctx.present([new Spacer(1), new Text(info.trimEnd(), 1, 0)]);
|
|
381
376
|
}
|
|
382
377
|
|
|
383
378
|
async handleUsageCommand(reports?: UsageReport[] | null): Promise<void> {
|
|
@@ -403,9 +398,7 @@ export class CommandController {
|
|
|
403
398
|
|
|
404
399
|
const availableWidth = Math.max(40, (this.ctx.ui.terminal.columns ?? 100) - 2);
|
|
405
400
|
const output = renderUsageReports(usageReports, theme, Date.now(), availableWidth);
|
|
406
|
-
this.ctx.
|
|
407
|
-
this.ctx.chatContainer.addChild(new Text(output, 1, 0));
|
|
408
|
-
this.ctx.ui.requestRender();
|
|
401
|
+
this.ctx.present([new Spacer(1), new Text(output, 1, 0)]);
|
|
409
402
|
}
|
|
410
403
|
|
|
411
404
|
async handleChangelogCommand(showFull = false): Promise<void> {
|
|
@@ -426,13 +419,13 @@ export class CommandController {
|
|
|
426
419
|
? ""
|
|
427
420
|
: `\n\n${theme.fg("dim", "Use")} ${theme.bold("/changelog full")} ${theme.fg("dim", "to view the complete changelog.")}`;
|
|
428
421
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
this.ctx.
|
|
422
|
+
const block = new TranscriptBlock();
|
|
423
|
+
block.addChild(new DynamicBorder());
|
|
424
|
+
block.addChild(new Text(theme.bold(theme.fg("accent", title)), 1, 0));
|
|
425
|
+
block.addChild(new Spacer(1));
|
|
426
|
+
block.addChild(new Markdown(changelogMarkdown + hint, 1, 1, getMarkdownTheme()));
|
|
427
|
+
block.addChild(new DynamicBorder());
|
|
428
|
+
this.ctx.present(block);
|
|
436
429
|
}
|
|
437
430
|
|
|
438
431
|
handleHotkeysCommand(): void {
|
|
@@ -452,13 +445,13 @@ export class CommandController {
|
|
|
452
445
|
return;
|
|
453
446
|
}
|
|
454
447
|
const output = renderContextUsage(breakdown, theme);
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
this.ctx.
|
|
448
|
+
const block = new TranscriptBlock();
|
|
449
|
+
block.addChild(new DynamicBorder());
|
|
450
|
+
block.addChild(new Text(theme.bold(theme.fg("accent", "Context Usage")), 1, 0));
|
|
451
|
+
block.addChild(new Spacer(1));
|
|
452
|
+
block.addChild(new Text(output, 1, 0));
|
|
453
|
+
block.addChild(new DynamicBorder());
|
|
454
|
+
this.ctx.present(block);
|
|
462
455
|
}
|
|
463
456
|
|
|
464
457
|
async handleMemoryCommand(text: string): Promise<void> {
|
|
@@ -473,13 +466,13 @@ export class CommandController {
|
|
|
473
466
|
this.ctx.showWarning("Memory payload is empty (memory backend off, disabled, or no memory available).");
|
|
474
467
|
return;
|
|
475
468
|
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
this.ctx.
|
|
469
|
+
const block = new TranscriptBlock();
|
|
470
|
+
block.addChild(new DynamicBorder());
|
|
471
|
+
block.addChild(new Text(theme.bold(theme.fg("accent", "Memory Injection Payload")), 1, 0));
|
|
472
|
+
block.addChild(new Spacer(1));
|
|
473
|
+
block.addChild(new Markdown(payload, 1, 1, getMarkdownTheme()));
|
|
474
|
+
block.addChild(new DynamicBorder());
|
|
475
|
+
this.ctx.present(block);
|
|
483
476
|
return;
|
|
484
477
|
}
|
|
485
478
|
|
|
@@ -800,8 +793,7 @@ export class CommandController {
|
|
|
800
793
|
this.ctx.streamingMessage = undefined;
|
|
801
794
|
this.ctx.pendingTools.clear();
|
|
802
795
|
|
|
803
|
-
this.ctx.
|
|
804
|
-
this.ctx.chatContainer.addChild(new Text(`${theme.fg("accent", `${theme.status.success} ${label}`)}`, 1, 1));
|
|
796
|
+
this.ctx.present([new Spacer(1), new Text(`${theme.fg("accent", `${theme.status.success} ${label}`)}`, 1, 1)]);
|
|
805
797
|
await this.ctx.reloadTodos();
|
|
806
798
|
this.ctx.ui.requestRender(true, { clearScrollback: true });
|
|
807
799
|
}
|
|
@@ -810,6 +802,18 @@ export class CommandController {
|
|
|
810
802
|
await this.#runNewSessionFlow();
|
|
811
803
|
}
|
|
812
804
|
|
|
805
|
+
async handleFreshCommand(): Promise<void> {
|
|
806
|
+
const result = this.ctx.session.freshSession();
|
|
807
|
+
if (!result) {
|
|
808
|
+
this.ctx.showWarning("Wait for the current response to finish or abort it before refreshing provider state.");
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
const stateLabel = result.closedProviderSessions === 1 ? "provider state" : "provider states";
|
|
812
|
+
this.ctx.statusLine.invalidate();
|
|
813
|
+
this.ctx.updateEditorTopBorder();
|
|
814
|
+
this.ctx.showStatus(`Fresh provider session started (${result.closedProviderSessions} ${stateLabel} pruned).`);
|
|
815
|
+
}
|
|
816
|
+
|
|
813
817
|
async handleDropCommand(): Promise<void> {
|
|
814
818
|
if (!this.ctx.sessionManager.getSessionFile()) {
|
|
815
819
|
this.ctx.showError("Nothing to drop (in-memory session)");
|
|
@@ -840,11 +844,10 @@ export class CommandController {
|
|
|
840
844
|
|
|
841
845
|
const sessionFile = this.ctx.session.sessionFile;
|
|
842
846
|
const shortPath = sessionFile ? sessionFile.split("/").pop() : "new session";
|
|
843
|
-
this.ctx.
|
|
844
|
-
|
|
847
|
+
this.ctx.present([
|
|
848
|
+
new Spacer(1),
|
|
845
849
|
new Text(`${theme.fg("accent", `${theme.status.success} Session forked to ${shortPath}`)}`, 1, 1),
|
|
846
|
-
);
|
|
847
|
-
this.ctx.ui.requestRender();
|
|
850
|
+
]);
|
|
848
851
|
}
|
|
849
852
|
|
|
850
853
|
async handleMoveCommand(targetPath: string): Promise<void> {
|
|
@@ -878,11 +881,10 @@ export class CommandController {
|
|
|
878
881
|
await this.ctx.sessionManager.moveTo(resolvedPath);
|
|
879
882
|
await this.ctx.applyCwdChange(resolvedPath);
|
|
880
883
|
|
|
881
|
-
this.ctx.
|
|
882
|
-
|
|
884
|
+
this.ctx.present([
|
|
885
|
+
new Spacer(1),
|
|
883
886
|
new Text(`${theme.fg("accent", `${theme.status.success} Session moved to ${resolvedPath}`)}`, 1, 1),
|
|
884
|
-
);
|
|
885
|
-
this.ctx.ui.requestRender();
|
|
887
|
+
]);
|
|
886
888
|
} catch (err) {
|
|
887
889
|
this.ctx.showError(`Move failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
888
890
|
}
|
|
@@ -913,7 +915,7 @@ export class CommandController {
|
|
|
913
915
|
this.ctx.pendingMessagesContainer.addChild(this.ctx.bashComponent);
|
|
914
916
|
this.ctx.pendingBashComponents.push(this.ctx.bashComponent);
|
|
915
917
|
} else {
|
|
916
|
-
this.ctx.
|
|
918
|
+
this.ctx.present(this.ctx.bashComponent);
|
|
917
919
|
}
|
|
918
920
|
this.ctx.ui.requestRender();
|
|
919
921
|
|
|
@@ -954,7 +956,7 @@ export class CommandController {
|
|
|
954
956
|
this.ctx.pendingMessagesContainer.addChild(this.ctx.pythonComponent);
|
|
955
957
|
this.ctx.pendingPythonComponents.push(this.ctx.pythonComponent);
|
|
956
958
|
} else {
|
|
957
|
-
this.ctx.
|
|
959
|
+
this.ctx.present(this.ctx.pythonComponent);
|
|
958
960
|
}
|
|
959
961
|
this.ctx.ui.requestRender();
|
|
960
962
|
|
|
@@ -1143,10 +1145,10 @@ export class CommandController {
|
|
|
1143
1145
|
this.ctx.updateEditorBorderColor();
|
|
1144
1146
|
await this.ctx.reloadTodos();
|
|
1145
1147
|
|
|
1146
|
-
this.ctx.
|
|
1147
|
-
|
|
1148
|
+
this.ctx.present([
|
|
1149
|
+
new Spacer(1),
|
|
1148
1150
|
new Text(`${theme.fg("accent", `${theme.status.success} New session started with handoff context`)}`, 1, 1),
|
|
1149
|
-
);
|
|
1151
|
+
]);
|
|
1150
1152
|
if (result.savedPath) {
|
|
1151
1153
|
this.ctx.showStatus(`Handoff document saved to: ${result.savedPath}`);
|
|
1152
1154
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { INTENT_FIELD } from "@oh-my-pi/pi-agent-core";
|
|
2
2
|
import { calculatePromptTokens } from "@oh-my-pi/pi-agent-core/compaction/compaction";
|
|
3
3
|
import type { AssistantMessage, ImageContent } from "@oh-my-pi/pi-ai";
|
|
4
|
-
import { type Component, Loader, TERMINAL
|
|
4
|
+
import { type Component, Loader, TERMINAL } from "@oh-my-pi/pi-tui";
|
|
5
5
|
import { settings } from "../../config/settings";
|
|
6
6
|
import { getFileSnapshotStore } from "../../edit/file-snapshot-store";
|
|
7
7
|
import { AssistantMessageComponent } from "../../modes/components/assistant-message";
|
|
@@ -17,9 +17,10 @@ import { getSymbolTheme, theme } from "../../modes/theme/theme";
|
|
|
17
17
|
import type { InteractiveModeContext, TodoPhase } from "../../modes/types";
|
|
18
18
|
import type { PlanApprovalDetails } from "../../plan-mode/approved-plan";
|
|
19
19
|
import type { AgentSessionEvent } from "../../session/agent-session";
|
|
20
|
-
import { isSilentAbort, readPendingDisplayTag } from "../../session/messages";
|
|
20
|
+
import { isSilentAbort, readPendingDisplayTag, resolveAbortLabel } from "../../session/messages";
|
|
21
21
|
import type { ResolveToolDetails } from "../../tools/resolve";
|
|
22
22
|
import { interruptHint } from "../shared";
|
|
23
|
+
import { StreamingRevealController } from "./streaming-reveal";
|
|
23
24
|
|
|
24
25
|
type AgentSessionEventKind = AgentSessionEvent["type"];
|
|
25
26
|
|
|
@@ -44,7 +45,13 @@ type AgentSessionEventHandlers = {
|
|
|
44
45
|
|
|
45
46
|
export class EventController {
|
|
46
47
|
#lastReadGroup: ReadToolGroupComponent | undefined = undefined;
|
|
47
|
-
|
|
48
|
+
// Count of visible assistant content blocks (rendered non-empty text/thinking)
|
|
49
|
+
// already seen in the current streaming message. A newly appearing one breaks
|
|
50
|
+
// the read run: the rendered reasoning/answer is a visual separator, so reads
|
|
51
|
+
// after it start a fresh group. Empty/absent thinking — common when a model
|
|
52
|
+
// emits one read per completion — does not break it, so a run of consecutive
|
|
53
|
+
// reads collapses into one group even across completion boundaries.
|
|
54
|
+
#lastVisibleBlockCount = 0;
|
|
48
55
|
#renderedCustomMessages = new Set<string>();
|
|
49
56
|
#lastIntent: string | undefined = undefined;
|
|
50
57
|
#backgroundToolCallIds = new Set<string>();
|
|
@@ -60,9 +67,15 @@ export class EventController {
|
|
|
60
67
|
#pinnedErrorComponent: AssistantMessageComponent | undefined = undefined;
|
|
61
68
|
#idleCompactionTimer?: NodeJS.Timeout;
|
|
62
69
|
#ircExpiryTimers = new Map<string, NodeJS.Timeout>();
|
|
70
|
+
#streamingReveal: StreamingRevealController;
|
|
63
71
|
#handlers: AgentSessionEventHandlers;
|
|
64
72
|
|
|
65
73
|
constructor(private ctx: InteractiveModeContext) {
|
|
74
|
+
this.#streamingReveal = new StreamingRevealController({
|
|
75
|
+
getSmoothStreaming: () => this.ctx.settings.get("display.smoothStreaming"),
|
|
76
|
+
getHideThinkingBlock: () => this.ctx.hideThinkingBlock,
|
|
77
|
+
requestRender: () => this.ctx.ui.requestRender(),
|
|
78
|
+
});
|
|
66
79
|
this.#handlers = {
|
|
67
80
|
agent_start: e => this.#handleAgentStart(e),
|
|
68
81
|
agent_end: e => this.#handleAgentEnd(e),
|
|
@@ -95,6 +108,7 @@ export class EventController {
|
|
|
95
108
|
}
|
|
96
109
|
|
|
97
110
|
dispose(): void {
|
|
111
|
+
this.#streamingReveal.stop();
|
|
98
112
|
this.#cancelIdleCompaction();
|
|
99
113
|
for (const timer of this.#ircExpiryTimers.values()) {
|
|
100
114
|
clearTimeout(timer);
|
|
@@ -103,12 +117,12 @@ export class EventController {
|
|
|
103
117
|
}
|
|
104
118
|
|
|
105
119
|
#resetReadGroup(): void {
|
|
120
|
+
this.#lastReadGroup?.finalize();
|
|
106
121
|
this.#lastReadGroup = undefined;
|
|
107
122
|
}
|
|
108
123
|
|
|
109
124
|
#getReadGroup(): ReadToolGroupComponent {
|
|
110
125
|
if (!this.#lastReadGroup) {
|
|
111
|
-
this.ctx.chatContainer.addChild(new Text("", 0, 0));
|
|
112
126
|
const group = new ReadToolGroupComponent({
|
|
113
127
|
showContentPreview: this.ctx.settings.get("read.toolResultPreview"),
|
|
114
128
|
});
|
|
@@ -209,6 +223,7 @@ export class EventController {
|
|
|
209
223
|
this.#lastIntent = undefined;
|
|
210
224
|
this.#readToolCallArgs.clear();
|
|
211
225
|
this.#readToolCallAssistantComponents.clear();
|
|
226
|
+
this.#resetReadGroup();
|
|
212
227
|
this.#assistantMessageStreaming = false;
|
|
213
228
|
this.#lastAssistantComponent = undefined;
|
|
214
229
|
// Restore the previous turn's inline error in the transcript before dropping
|
|
@@ -299,9 +314,8 @@ export class EventController {
|
|
|
299
314
|
this.ctx.addMessageToChat(event.message);
|
|
300
315
|
this.ctx.ui.requestRender();
|
|
301
316
|
} else if (event.message.role === "assistant") {
|
|
302
|
-
this.#lastThinkingCount = 0;
|
|
303
317
|
this.#assistantMessageStreaming = true;
|
|
304
|
-
this.#
|
|
318
|
+
this.#lastVisibleBlockCount = 0;
|
|
305
319
|
this.ctx.streamingComponent = new AssistantMessageComponent(
|
|
306
320
|
undefined,
|
|
307
321
|
this.ctx.hideThinkingBlock,
|
|
@@ -311,7 +325,7 @@ export class EventController {
|
|
|
311
325
|
);
|
|
312
326
|
this.ctx.streamingMessage = event.message;
|
|
313
327
|
this.ctx.chatContainer.addChild(this.ctx.streamingComponent);
|
|
314
|
-
this.ctx.streamingComponent
|
|
328
|
+
this.#streamingReveal.begin(this.ctx.streamingComponent, this.ctx.streamingMessage);
|
|
315
329
|
this.ctx.ui.requestRender();
|
|
316
330
|
}
|
|
317
331
|
}
|
|
@@ -355,16 +369,17 @@ export class EventController {
|
|
|
355
369
|
async #handleMessageUpdate(event: Extract<AgentSessionEvent, { type: "message_update" }>): Promise<void> {
|
|
356
370
|
if (this.ctx.streamingComponent && event.message.role === "assistant") {
|
|
357
371
|
this.ctx.streamingMessage = event.message;
|
|
358
|
-
this.
|
|
372
|
+
this.#streamingReveal.setTarget(this.ctx.streamingMessage);
|
|
359
373
|
|
|
360
|
-
const
|
|
361
|
-
content =>
|
|
374
|
+
const visibleBlockCount = this.ctx.streamingMessage.content.filter(
|
|
375
|
+
content =>
|
|
376
|
+
(content.type === "text" && content.text.trim().length > 0) ||
|
|
377
|
+
(content.type === "thinking" && content.thinking.trim().length > 0),
|
|
362
378
|
).length;
|
|
363
|
-
if (
|
|
379
|
+
if (visibleBlockCount > this.#lastVisibleBlockCount) {
|
|
364
380
|
this.#resetReadGroup();
|
|
365
|
-
this.#
|
|
381
|
+
this.#lastVisibleBlockCount = visibleBlockCount;
|
|
366
382
|
}
|
|
367
|
-
|
|
368
383
|
for (const content of this.ctx.streamingMessage.content) {
|
|
369
384
|
if (content.type !== "toolCall") continue;
|
|
370
385
|
if (content.name === "read") {
|
|
@@ -397,7 +412,6 @@ export class EventController {
|
|
|
397
412
|
: content.arguments;
|
|
398
413
|
if (!this.ctx.pendingTools.has(content.id)) {
|
|
399
414
|
this.#resetReadGroup();
|
|
400
|
-
this.ctx.chatContainer.addChild(new Text("", 0, 0));
|
|
401
415
|
const tool = this.ctx.session.getToolByName(content.name);
|
|
402
416
|
const component = new ToolExecutionComponent(
|
|
403
417
|
content.name,
|
|
@@ -456,21 +470,20 @@ export class EventController {
|
|
|
456
470
|
}
|
|
457
471
|
if (this.ctx.streamingComponent && event.message.role === "assistant") {
|
|
458
472
|
this.ctx.streamingMessage = event.message;
|
|
473
|
+
this.#streamingReveal.stop();
|
|
459
474
|
let errorMessage: string | undefined;
|
|
460
475
|
const aborted = this.ctx.streamingMessage.stopReason === "aborted";
|
|
461
476
|
const silentlyAborted = aborted && isSilentAbort(this.ctx.streamingMessage.errorMessage);
|
|
462
477
|
const ttsrSilenced = aborted && this.ctx.session.isTtsrAbortPending;
|
|
463
478
|
if (aborted && !silentlyAborted && !ttsrSilenced) {
|
|
464
|
-
//
|
|
465
|
-
//
|
|
466
|
-
//
|
|
467
|
-
//
|
|
468
|
-
//
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
? `Aborted after ${retryAttempt} retry attempt${retryAttempt > 1 ? "s" : ""}`
|
|
473
|
-
: "Operation aborted";
|
|
479
|
+
// Resolve the operator-facing label: a user-interrupt (Esc) abort
|
|
480
|
+
// carries USER_INTERRUPT_LABEL on errorMessage (threaded through the
|
|
481
|
+
// AbortController), which is preserved verbatim; any other abort with
|
|
482
|
+
// no threaded reason falls back to the retry-aware generic label.
|
|
483
|
+
// AgentSession.#handleAgentEvent already stamped SILENT_ABORT_MARKER for
|
|
484
|
+
// the plan-compact transition before this controller ran, so reaching
|
|
485
|
+
// this branch implies the abort was NOT a silent internal transition.
|
|
486
|
+
errorMessage = resolveAbortLabel(this.ctx.streamingMessage.errorMessage, this.ctx.session.retryAttempt);
|
|
474
487
|
this.ctx.streamingMessage.errorMessage = errorMessage;
|
|
475
488
|
}
|
|
476
489
|
if (silentlyAborted || ttsrSilenced) {
|
|
@@ -663,6 +676,7 @@ export class EventController {
|
|
|
663
676
|
async #handleAgentEnd(_event: Extract<AgentSessionEvent, { type: "agent_end" }>): Promise<void> {
|
|
664
677
|
this.#agentTurnActive = false;
|
|
665
678
|
this.#assistantMessageStreaming = false;
|
|
679
|
+
this.#streamingReveal.stop();
|
|
666
680
|
if (this.ctx.loadingAnimation) {
|
|
667
681
|
this.ctx.loadingAnimation.stop();
|
|
668
682
|
this.ctx.loadingAnimation = undefined;
|
|
@@ -689,6 +703,7 @@ export class EventController {
|
|
|
689
703
|
);
|
|
690
704
|
this.#readToolCallArgs.clear();
|
|
691
705
|
this.#readToolCallAssistantComponents.clear();
|
|
706
|
+
this.#resetReadGroup();
|
|
692
707
|
this.#lastAssistantComponent = undefined;
|
|
693
708
|
this.ctx.ui.requestRender();
|
|
694
709
|
this.#scheduleIdleCompaction();
|
|
@@ -832,14 +847,12 @@ export class EventController {
|
|
|
832
847
|
async #handleTtsrTriggered(event: Extract<AgentSessionEvent, { type: "ttsr_triggered" }>): Promise<void> {
|
|
833
848
|
const component = new TtsrNotificationComponent(event.rules);
|
|
834
849
|
component.setExpanded(this.ctx.toolOutputExpanded);
|
|
835
|
-
this.ctx.
|
|
836
|
-
this.ctx.ui.requestRender();
|
|
850
|
+
this.ctx.present(component);
|
|
837
851
|
}
|
|
838
852
|
|
|
839
853
|
async #handleTodoReminder(event: Extract<AgentSessionEvent, { type: "todo_reminder" }>): Promise<void> {
|
|
840
854
|
const component = new TodoReminderComponent(event.todos, event.attempt, event.maxAttempts);
|
|
841
|
-
this.ctx.
|
|
842
|
-
this.ctx.ui.requestRender();
|
|
855
|
+
this.ctx.present(component);
|
|
843
856
|
}
|
|
844
857
|
|
|
845
858
|
async #handleTodoAutoClear(_event: Extract<AgentSessionEvent, { type: "todo_auto_clear" }>): Promise<void> {
|
|
@@ -892,7 +905,6 @@ export class EventController {
|
|
|
892
905
|
}
|
|
893
906
|
|
|
894
907
|
sendCompletionNotification(): void {
|
|
895
|
-
if (this.ctx.isBackgrounded === false) return;
|
|
896
908
|
const notify = settings.get("completion.notify");
|
|
897
909
|
if (notify === "off") return;
|
|
898
910
|
|
|
@@ -911,15 +923,4 @@ export class EventController {
|
|
|
911
923
|
actions: "focus",
|
|
912
924
|
});
|
|
913
925
|
}
|
|
914
|
-
|
|
915
|
-
async handleBackgroundEvent(event: AgentSessionEvent): Promise<void> {
|
|
916
|
-
if (event.type !== "agent_end") {
|
|
917
|
-
return;
|
|
918
|
-
}
|
|
919
|
-
if (this.ctx.session.queuedMessageCount > 0 || this.ctx.session.isStreaming) {
|
|
920
|
-
return;
|
|
921
|
-
}
|
|
922
|
-
this.sendCompletionNotification();
|
|
923
|
-
await this.ctx.shutdown();
|
|
924
|
-
}
|
|
925
926
|
}
|