@oh-my-pi/pi-coding-agent 15.10.0 → 15.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +142 -1
- package/dist/types/cli/dry-balance-cli.d.ts +15 -1
- package/dist/types/cli/startup-cwd.d.ts +2 -0
- package/dist/types/commands/launch.d.ts +3 -0
- package/dist/types/commit/analysis/conventional.d.ts +2 -2
- package/dist/types/commit/analysis/summary.d.ts +2 -2
- package/dist/types/commit/changelog/generate.d.ts +2 -2
- package/dist/types/commit/changelog/index.d.ts +2 -2
- package/dist/types/commit/map-reduce/index.d.ts +3 -3
- package/dist/types/commit/map-reduce/map-phase.d.ts +2 -2
- package/dist/types/commit/map-reduce/reduce-phase.d.ts +2 -2
- package/dist/types/commit/model-selection.d.ts +10 -4
- package/dist/types/config/api-key-resolver.d.ts +34 -0
- package/dist/types/config/keybindings.d.ts +2 -2
- package/dist/types/config/model-provider-priority.d.ts +1 -0
- package/dist/types/config/model-registry.d.ts +17 -1
- package/dist/types/config/model-resolver.d.ts +4 -1
- package/dist/types/config/settings-schema.d.ts +9 -0
- package/dist/types/config/settings.d.ts +7 -2
- package/dist/types/dap/config.d.ts +14 -1
- package/dist/types/dap/types.d.ts +10 -0
- package/dist/types/debug/report-bundle.d.ts +3 -0
- package/dist/types/edit/file-snapshot-store.d.ts +18 -10
- package/dist/types/eval/py/__tests__/prelude.test.d.ts +1 -0
- package/dist/types/extensibility/extensions/types.d.ts +4 -1
- package/dist/types/lsp/client.d.ts +10 -0
- package/dist/types/lsp/utils.d.ts +3 -2
- package/dist/types/main.d.ts +3 -9
- package/dist/types/mcp/tool-bridge.d.ts +2 -0
- package/dist/types/modes/components/chat-block.d.ts +64 -0
- package/dist/types/modes/components/custom-editor.d.ts +4 -1
- package/dist/types/modes/components/overlay-box.d.ts +17 -0
- package/dist/types/modes/components/plan-review-overlay.d.ts +59 -0
- package/dist/types/modes/components/plan-toc.d.ts +41 -0
- package/dist/types/modes/components/read-tool-group.d.ts +2 -0
- package/dist/types/modes/components/status-line.d.ts +2 -0
- package/dist/types/modes/components/transcript-container.d.ts +11 -0
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/controllers/event-controller.d.ts +17 -1
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +0 -1
- package/dist/types/modes/controllers/input-controller.d.ts +1 -1
- package/dist/types/modes/controllers/streaming-reveal.d.ts +22 -0
- package/dist/types/modes/controllers/tan-command-controller.d.ts +6 -0
- package/dist/types/modes/interactive-mode.d.ts +16 -5
- package/dist/types/modes/magic-keywords.d.ts +1 -1
- package/dist/types/modes/markdown-prose.d.ts +1 -1
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +21 -5
- package/dist/types/modes/utils/copy-targets.d.ts +21 -1
- package/dist/types/modes/workflow.d.ts +3 -3
- package/dist/types/plan-mode/approved-plan.d.ts +27 -8
- package/dist/types/plan-mode/plan-protection.d.ts +4 -4
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +21 -0
- package/dist/types/session/auth-storage.d.ts +1 -1
- package/dist/types/session/messages.d.ts +12 -0
- package/dist/types/session/session-manager.d.ts +8 -3
- package/dist/types/slash-commands/types.d.ts +4 -6
- package/dist/types/task/executor.d.ts +17 -0
- package/dist/types/task/index.d.ts +1 -0
- package/dist/types/task/render.d.ts +3 -2
- package/dist/types/tools/archive-reader.d.ts +5 -0
- package/dist/types/tools/ast-edit.d.ts +3 -0
- package/dist/types/tools/ast-grep.d.ts +3 -0
- package/dist/types/tools/bash.d.ts +1 -0
- package/dist/types/tools/eval.d.ts +8 -0
- package/dist/types/tools/find.d.ts +8 -4
- package/dist/types/tools/gh-cache-invalidation.d.ts +6 -0
- package/dist/types/tools/github-cache.d.ts +12 -0
- package/dist/types/tools/grouped-file-output.d.ts +95 -12
- package/dist/types/tools/memory-render.d.ts +4 -1
- package/dist/types/tools/path-utils.d.ts +8 -0
- package/dist/types/tools/plan-mode-guard.d.ts +8 -9
- package/dist/types/tools/render-utils.d.ts +5 -9
- package/dist/types/tools/search.d.ts +6 -2
- package/dist/types/tools/sqlite-reader.d.ts +1 -0
- package/dist/types/tools/todo.d.ts +3 -2
- package/dist/types/tools/write.d.ts +3 -0
- package/dist/types/tools/yield.d.ts +8 -0
- package/dist/types/tui/output-block.d.ts +16 -4
- package/dist/types/tui/status-line.d.ts +3 -0
- package/dist/types/utils/enhanced-paste.d.ts +20 -0
- package/dist/types/web/search/providers/kimi.d.ts +1 -1
- package/package.json +9 -9
- package/src/auto-thinking/classifier.ts +5 -1
- package/src/cli/args.ts +3 -1
- package/src/cli/dry-balance-cli.ts +54 -21
- package/src/cli/gallery-cli.ts +4 -1
- package/src/cli/gallery-fixtures/misc.ts +29 -0
- package/src/cli/startup-cwd.ts +68 -0
- package/src/commands/launch.ts +3 -0
- package/src/commit/analysis/conventional.ts +2 -2
- package/src/commit/analysis/summary.ts +2 -2
- package/src/commit/changelog/generate.ts +2 -2
- package/src/commit/changelog/index.ts +2 -2
- package/src/commit/map-reduce/index.ts +3 -3
- package/src/commit/map-reduce/map-phase.ts +2 -2
- package/src/commit/map-reduce/reduce-phase.ts +2 -2
- package/src/commit/model-selection.ts +36 -11
- package/src/commit/pipeline.ts +4 -4
- package/src/config/api-key-resolver.ts +58 -0
- package/src/config/model-provider-priority.ts +55 -0
- package/src/config/model-registry.ts +29 -24
- package/src/config/model-resolver.ts +39 -7
- package/src/config/settings-schema.ts +10 -0
- package/src/config/settings.ts +106 -43
- package/src/dap/config.ts +41 -2
- package/src/dap/defaults.json +1 -0
- package/src/dap/session.ts +1 -0
- package/src/dap/types.ts +10 -0
- package/src/debug/index.ts +47 -53
- package/src/debug/raw-sse-buffer.ts +7 -4
- package/src/debug/report-bundle.ts +9 -0
- package/src/edit/file-snapshot-store.ts +33 -1
- package/src/edit/hashline/filesystem.ts +2 -1
- package/src/edit/renderer.ts +82 -78
- package/src/eval/__tests__/llm-bridge.test.ts +110 -31
- package/src/eval/js/context-manager.ts +32 -15
- package/src/eval/llm-bridge.ts +22 -6
- package/src/eval/py/__tests__/prelude.test.ts +19 -0
- package/src/eval/py/executor.ts +23 -11
- package/src/eval/py/prelude.py +1 -1
- package/src/extensibility/extensions/types.ts +10 -1
- package/src/goals/tools/goal-tool.ts +36 -26
- package/src/internal-urls/docs-index.generated.ts +8 -8
- package/src/lsp/client.ts +23 -11
- package/src/lsp/config.ts +11 -1
- package/src/lsp/index.ts +61 -9
- package/src/lsp/utils.ts +3 -2
- package/src/main.ts +100 -72
- package/src/mcp/tool-bridge.ts +2 -0
- package/src/memories/index.ts +14 -7
- package/src/mnemopi/backend.ts +5 -1
- package/src/modes/acp/acp-agent.ts +33 -26
- package/src/modes/components/assistant-message.ts +2 -9
- package/src/modes/components/chat-block.ts +111 -0
- package/src/modes/components/copy-selector.ts +1 -44
- package/src/modes/components/custom-editor.ts +164 -109
- package/src/modes/components/custom-message.ts +1 -3
- package/src/modes/components/execution-shared.ts +1 -2
- package/src/modes/components/hook-message.ts +1 -3
- package/src/modes/components/model-selector.ts +59 -13
- package/src/modes/components/oauth-selector.ts +33 -7
- package/src/modes/components/overlay-box.ts +108 -0
- package/src/modes/components/plan-review-overlay.ts +799 -0
- package/src/modes/components/plan-toc.ts +138 -0
- package/src/modes/components/read-tool-group.ts +20 -4
- package/src/modes/components/skill-message.ts +0 -1
- package/src/modes/components/status-line.ts +19 -4
- package/src/modes/components/tips.txt +2 -1
- package/src/modes/components/todo-reminder.ts +0 -2
- package/src/modes/components/tool-execution.ts +68 -88
- package/src/modes/components/transcript-container.ts +84 -24
- package/src/modes/components/user-message.ts +2 -3
- package/src/modes/controllers/command-controller-shared.ts +7 -6
- package/src/modes/controllers/command-controller.ts +57 -55
- package/src/modes/controllers/event-controller.ts +67 -40
- package/src/modes/controllers/extension-ui-controller.ts +10 -73
- package/src/modes/controllers/input-controller.ts +170 -126
- package/src/modes/controllers/mcp-command-controller.ts +69 -60
- package/src/modes/controllers/selector-controller.ts +23 -25
- package/src/modes/controllers/streaming-reveal.ts +212 -0
- package/src/modes/controllers/tan-command-controller.ts +173 -0
- package/src/modes/interactive-mode.ts +274 -112
- package/src/modes/magic-keywords.ts +1 -1
- package/src/modes/markdown-prose.ts +1 -1
- package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
- package/src/modes/theme/shimmer.ts +20 -9
- package/src/modes/theme/theme-schema.json +1 -1
- package/src/modes/theme/theme.ts +8 -4
- package/src/modes/types.ts +21 -7
- package/src/modes/utils/copy-targets.ts +133 -27
- package/src/modes/utils/ui-helpers.ts +44 -46
- package/src/modes/workflow.ts +10 -10
- package/src/plan-mode/approved-plan.ts +66 -43
- package/src/plan-mode/plan-protection.ts +4 -4
- package/src/prompts/system/background-tan-dispatch.md +8 -0
- package/src/prompts/system/plan-mode-active.md +67 -58
- package/src/prompts/system/plan-mode-approved.md +1 -1
- package/src/prompts/system/workflow-notice.md +1 -1
- package/src/prompts/tools/bash.md +9 -0
- package/src/prompts/tools/browser.md +1 -1
- package/src/prompts/tools/eval.md +2 -1
- package/src/prompts/tools/read.md +2 -2
- package/src/sdk.ts +37 -46
- package/src/session/agent-session.ts +119 -18
- package/src/session/auth-storage.ts +2 -0
- package/src/session/messages.ts +26 -0
- package/src/session/session-manager.ts +109 -28
- package/src/slash-commands/builtin-registry.ts +36 -9
- package/src/slash-commands/types.ts +4 -6
- package/src/task/executor.ts +76 -38
- package/src/task/index.ts +4 -0
- package/src/task/render.ts +211 -147
- package/src/tools/archive-reader.ts +64 -0
- package/src/tools/ask.ts +119 -164
- package/src/tools/ast-edit.ts +98 -71
- package/src/tools/ast-grep.ts +37 -43
- package/src/tools/bash.ts +57 -6
- package/src/tools/browser/tab-supervisor.ts +13 -1
- package/src/tools/browser/tab-worker.ts +33 -4
- package/src/tools/debug.ts +20 -8
- package/src/tools/eval.ts +13 -2
- package/src/tools/fetch.ts +297 -7
- package/src/tools/find.ts +51 -30
- package/src/tools/gh-cache-invalidation.ts +200 -0
- package/src/tools/gh-renderer.ts +81 -42
- package/src/tools/github-cache.ts +25 -0
- package/src/tools/grouped-file-output.ts +272 -48
- package/src/tools/image-gen.ts +150 -103
- package/src/tools/inspect-image-renderer.ts +63 -41
- package/src/tools/inspect-image.ts +10 -3
- package/src/tools/job.ts +3 -4
- package/src/tools/memory-render.ts +4 -1
- package/src/tools/path-utils.ts +28 -2
- package/src/tools/plan-mode-guard.ts +66 -39
- package/src/tools/read.ts +48 -28
- package/src/tools/render-utils.ts +21 -37
- package/src/tools/resolve.ts +14 -0
- package/src/tools/search-tool-bm25.ts +36 -23
- package/src/tools/search.ts +118 -81
- package/src/tools/sqlite-reader.ts +9 -12
- package/src/tools/todo.ts +118 -52
- package/src/tools/write.ts +83 -64
- package/src/tools/yield.ts +10 -1
- package/src/tui/output-block.ts +60 -13
- package/src/tui/status-line.ts +5 -1
- package/src/utils/commit-message-generator.ts +11 -3
- package/src/utils/enhanced-paste.ts +230 -0
- package/src/utils/title-generator.ts +2 -1
- package/src/web/search/providers/anthropic.ts +25 -19
- package/src/web/search/providers/codex.ts +37 -8
- package/src/web/search/providers/exa.ts +11 -3
- package/src/web/search/providers/kimi.ts +28 -17
- package/src/web/search/providers/parallel.ts +35 -24
- package/src/web/search/providers/synthetic.ts +8 -6
- package/src/web/search/providers/tavily.ts +9 -8
- package/src/web/search/providers/zai.ts +8 -6
|
@@ -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,14 +17,25 @@ 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
|
|
|
26
27
|
const IRC_MESSAGE_VISIBLE_TTL_MS = 10_000;
|
|
27
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Loader label shown the instant a user interrupt (Esc) is requested, kept until
|
|
31
|
+
* the agent turn fully unwinds. Esc fires the abort synchronously, but the loop
|
|
32
|
+
* only stops the spinner at `agent_end`, which it cannot reach until every
|
|
33
|
+
* in-flight tool settles its abort in `executeToolCalls` (`Promise.allSettled`).
|
|
34
|
+
* Swapping the steady "Working…" for this acknowledges the keypress instead of
|
|
35
|
+
* reading as an ignored Esc for the seconds a slow tool takes to tear down.
|
|
36
|
+
*/
|
|
37
|
+
export const INTERRUPTING_WORKING_MESSAGE = "Interrupting…";
|
|
38
|
+
|
|
28
39
|
// Events that change foreground streaming state, or that reset a turn. The TUI
|
|
29
40
|
// eager native-scrollback rebuild mode is recomputed only on these so unrelated
|
|
30
41
|
// IRC/notices/status refreshes do not toggle scrollback replay policy.
|
|
@@ -44,12 +55,19 @@ type AgentSessionEventHandlers = {
|
|
|
44
55
|
|
|
45
56
|
export class EventController {
|
|
46
57
|
#lastReadGroup: ReadToolGroupComponent | undefined = undefined;
|
|
47
|
-
|
|
58
|
+
// Count of visible assistant content blocks (rendered non-empty text/thinking)
|
|
59
|
+
// already seen in the current streaming message. A newly appearing one breaks
|
|
60
|
+
// the read run: the rendered reasoning/answer is a visual separator, so reads
|
|
61
|
+
// after it start a fresh group. Empty/absent thinking — common when a model
|
|
62
|
+
// emits one read per completion — does not break it, so a run of consecutive
|
|
63
|
+
// reads collapses into one group even across completion boundaries.
|
|
64
|
+
#lastVisibleBlockCount = 0;
|
|
48
65
|
#renderedCustomMessages = new Set<string>();
|
|
49
66
|
#lastIntent: string | undefined = undefined;
|
|
50
67
|
#backgroundToolCallIds = new Set<string>();
|
|
51
68
|
#assistantMessageStreaming = false;
|
|
52
69
|
#agentTurnActive = false;
|
|
70
|
+
#interrupting = false;
|
|
53
71
|
#readToolCallArgs = new Map<string, Record<string, unknown>>();
|
|
54
72
|
#readToolCallAssistantComponents = new Map<string, AssistantMessageComponent>();
|
|
55
73
|
#lastAssistantComponent: AssistantMessageComponent | undefined = undefined;
|
|
@@ -60,9 +78,15 @@ export class EventController {
|
|
|
60
78
|
#pinnedErrorComponent: AssistantMessageComponent | undefined = undefined;
|
|
61
79
|
#idleCompactionTimer?: NodeJS.Timeout;
|
|
62
80
|
#ircExpiryTimers = new Map<string, NodeJS.Timeout>();
|
|
81
|
+
#streamingReveal: StreamingRevealController;
|
|
63
82
|
#handlers: AgentSessionEventHandlers;
|
|
64
83
|
|
|
65
84
|
constructor(private ctx: InteractiveModeContext) {
|
|
85
|
+
this.#streamingReveal = new StreamingRevealController({
|
|
86
|
+
getSmoothStreaming: () => this.ctx.settings.get("display.smoothStreaming"),
|
|
87
|
+
getHideThinkingBlock: () => this.ctx.hideThinkingBlock,
|
|
88
|
+
requestRender: () => this.ctx.ui.requestRender(),
|
|
89
|
+
});
|
|
66
90
|
this.#handlers = {
|
|
67
91
|
agent_start: e => this.#handleAgentStart(e),
|
|
68
92
|
agent_end: e => this.#handleAgentEnd(e),
|
|
@@ -95,6 +119,7 @@ export class EventController {
|
|
|
95
119
|
}
|
|
96
120
|
|
|
97
121
|
dispose(): void {
|
|
122
|
+
this.#streamingReveal.stop();
|
|
98
123
|
this.#cancelIdleCompaction();
|
|
99
124
|
for (const timer of this.#ircExpiryTimers.values()) {
|
|
100
125
|
clearTimeout(timer);
|
|
@@ -103,12 +128,12 @@ export class EventController {
|
|
|
103
128
|
}
|
|
104
129
|
|
|
105
130
|
#resetReadGroup(): void {
|
|
131
|
+
this.#lastReadGroup?.finalize();
|
|
106
132
|
this.#lastReadGroup = undefined;
|
|
107
133
|
}
|
|
108
134
|
|
|
109
135
|
#getReadGroup(): ReadToolGroupComponent {
|
|
110
136
|
if (!this.#lastReadGroup) {
|
|
111
|
-
this.ctx.chatContainer.addChild(new Text("", 0, 0));
|
|
112
137
|
const group = new ReadToolGroupComponent({
|
|
113
138
|
showContentPreview: this.ctx.settings.get("read.toolResultPreview"),
|
|
114
139
|
});
|
|
@@ -153,6 +178,7 @@ export class EventController {
|
|
|
153
178
|
return true;
|
|
154
179
|
}
|
|
155
180
|
#updateWorkingMessageFromIntent(intent: unknown): void {
|
|
181
|
+
if (this.#interrupting) return;
|
|
156
182
|
// Streamed JSON can deliver non-string `_i` (object, number, boolean) before
|
|
157
183
|
// schema validation; `?.` only guards null/undefined, so guard the type too.
|
|
158
184
|
if (typeof intent !== "string") return;
|
|
@@ -162,6 +188,19 @@ export class EventController {
|
|
|
162
188
|
this.ctx.setWorkingMessage(`${trimmed}${interruptHint()}`);
|
|
163
189
|
}
|
|
164
190
|
|
|
191
|
+
/**
|
|
192
|
+
* Acknowledge a user interrupt (Esc) immediately: switch the loader to
|
|
193
|
+
* `INTERRUPTING_WORKING_MESSAGE` and freeze intent-driven working-message
|
|
194
|
+
* updates for the rest of the turn so a late `tool_execution_start` intent
|
|
195
|
+
* cannot repaint a "Working…/<intent>" line over the acknowledgment. Reset at
|
|
196
|
+
* the next `agent_start`. No-op outside an active turn or if already set.
|
|
197
|
+
*/
|
|
198
|
+
notifyInterrupting(): void {
|
|
199
|
+
if (!this.#agentTurnActive || this.#interrupting) return;
|
|
200
|
+
this.#interrupting = true;
|
|
201
|
+
this.ctx.setWorkingMessage(INTERRUPTING_WORKING_MESSAGE);
|
|
202
|
+
}
|
|
203
|
+
|
|
165
204
|
subscribeToAgent(): void {
|
|
166
205
|
this.ctx.unsubscribe = this.ctx.session.subscribe(async (event: AgentSessionEvent) => {
|
|
167
206
|
await this.handleEvent(event);
|
|
@@ -206,9 +245,11 @@ export class EventController {
|
|
|
206
245
|
|
|
207
246
|
async #handleAgentStart(_event: Extract<AgentSessionEvent, { type: "agent_start" }>): Promise<void> {
|
|
208
247
|
this.#agentTurnActive = true;
|
|
248
|
+
this.#interrupting = false;
|
|
209
249
|
this.#lastIntent = undefined;
|
|
210
250
|
this.#readToolCallArgs.clear();
|
|
211
251
|
this.#readToolCallAssistantComponents.clear();
|
|
252
|
+
this.#resetReadGroup();
|
|
212
253
|
this.#assistantMessageStreaming = false;
|
|
213
254
|
this.#lastAssistantComponent = undefined;
|
|
214
255
|
// Restore the previous turn's inline error in the transcript before dropping
|
|
@@ -299,9 +340,8 @@ export class EventController {
|
|
|
299
340
|
this.ctx.addMessageToChat(event.message);
|
|
300
341
|
this.ctx.ui.requestRender();
|
|
301
342
|
} else if (event.message.role === "assistant") {
|
|
302
|
-
this.#lastThinkingCount = 0;
|
|
303
343
|
this.#assistantMessageStreaming = true;
|
|
304
|
-
this.#
|
|
344
|
+
this.#lastVisibleBlockCount = 0;
|
|
305
345
|
this.ctx.streamingComponent = new AssistantMessageComponent(
|
|
306
346
|
undefined,
|
|
307
347
|
this.ctx.hideThinkingBlock,
|
|
@@ -311,7 +351,7 @@ export class EventController {
|
|
|
311
351
|
);
|
|
312
352
|
this.ctx.streamingMessage = event.message;
|
|
313
353
|
this.ctx.chatContainer.addChild(this.ctx.streamingComponent);
|
|
314
|
-
this.ctx.streamingComponent
|
|
354
|
+
this.#streamingReveal.begin(this.ctx.streamingComponent, this.ctx.streamingMessage);
|
|
315
355
|
this.ctx.ui.requestRender();
|
|
316
356
|
}
|
|
317
357
|
}
|
|
@@ -355,16 +395,17 @@ export class EventController {
|
|
|
355
395
|
async #handleMessageUpdate(event: Extract<AgentSessionEvent, { type: "message_update" }>): Promise<void> {
|
|
356
396
|
if (this.ctx.streamingComponent && event.message.role === "assistant") {
|
|
357
397
|
this.ctx.streamingMessage = event.message;
|
|
358
|
-
this.
|
|
398
|
+
this.#streamingReveal.setTarget(this.ctx.streamingMessage);
|
|
359
399
|
|
|
360
|
-
const
|
|
361
|
-
content =>
|
|
400
|
+
const visibleBlockCount = this.ctx.streamingMessage.content.filter(
|
|
401
|
+
content =>
|
|
402
|
+
(content.type === "text" && content.text.trim().length > 0) ||
|
|
403
|
+
(content.type === "thinking" && content.thinking.trim().length > 0),
|
|
362
404
|
).length;
|
|
363
|
-
if (
|
|
405
|
+
if (visibleBlockCount > this.#lastVisibleBlockCount) {
|
|
364
406
|
this.#resetReadGroup();
|
|
365
|
-
this.#
|
|
407
|
+
this.#lastVisibleBlockCount = visibleBlockCount;
|
|
366
408
|
}
|
|
367
|
-
|
|
368
409
|
for (const content of this.ctx.streamingMessage.content) {
|
|
369
410
|
if (content.type !== "toolCall") continue;
|
|
370
411
|
if (content.name === "read") {
|
|
@@ -397,7 +438,6 @@ export class EventController {
|
|
|
397
438
|
: content.arguments;
|
|
398
439
|
if (!this.ctx.pendingTools.has(content.id)) {
|
|
399
440
|
this.#resetReadGroup();
|
|
400
|
-
this.ctx.chatContainer.addChild(new Text("", 0, 0));
|
|
401
441
|
const tool = this.ctx.session.getToolByName(content.name);
|
|
402
442
|
const component = new ToolExecutionComponent(
|
|
403
443
|
content.name,
|
|
@@ -456,21 +496,20 @@ export class EventController {
|
|
|
456
496
|
}
|
|
457
497
|
if (this.ctx.streamingComponent && event.message.role === "assistant") {
|
|
458
498
|
this.ctx.streamingMessage = event.message;
|
|
499
|
+
this.#streamingReveal.stop();
|
|
459
500
|
let errorMessage: string | undefined;
|
|
460
501
|
const aborted = this.ctx.streamingMessage.stopReason === "aborted";
|
|
461
502
|
const silentlyAborted = aborted && isSilentAbort(this.ctx.streamingMessage.errorMessage);
|
|
462
503
|
const ttsrSilenced = aborted && this.ctx.session.isTtsrAbortPending;
|
|
463
504
|
if (aborted && !silentlyAborted && !ttsrSilenced) {
|
|
464
|
-
//
|
|
465
|
-
//
|
|
466
|
-
//
|
|
467
|
-
//
|
|
468
|
-
//
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
? `Aborted after ${retryAttempt} retry attempt${retryAttempt > 1 ? "s" : ""}`
|
|
473
|
-
: "Operation aborted";
|
|
505
|
+
// Resolve the operator-facing label: a user-interrupt (Esc) abort
|
|
506
|
+
// carries USER_INTERRUPT_LABEL on errorMessage (threaded through the
|
|
507
|
+
// AbortController), which is preserved verbatim; any other abort with
|
|
508
|
+
// no threaded reason falls back to the retry-aware generic label.
|
|
509
|
+
// AgentSession.#handleAgentEvent already stamped SILENT_ABORT_MARKER for
|
|
510
|
+
// the plan-compact transition before this controller ran, so reaching
|
|
511
|
+
// this branch implies the abort was NOT a silent internal transition.
|
|
512
|
+
errorMessage = resolveAbortLabel(this.ctx.streamingMessage.errorMessage, this.ctx.session.retryAttempt);
|
|
474
513
|
this.ctx.streamingMessage.errorMessage = errorMessage;
|
|
475
514
|
}
|
|
476
515
|
if (silentlyAborted || ttsrSilenced) {
|
|
@@ -663,6 +702,7 @@ export class EventController {
|
|
|
663
702
|
async #handleAgentEnd(_event: Extract<AgentSessionEvent, { type: "agent_end" }>): Promise<void> {
|
|
664
703
|
this.#agentTurnActive = false;
|
|
665
704
|
this.#assistantMessageStreaming = false;
|
|
705
|
+
this.#streamingReveal.stop();
|
|
666
706
|
if (this.ctx.loadingAnimation) {
|
|
667
707
|
this.ctx.loadingAnimation.stop();
|
|
668
708
|
this.ctx.loadingAnimation = undefined;
|
|
@@ -689,6 +729,7 @@ export class EventController {
|
|
|
689
729
|
);
|
|
690
730
|
this.#readToolCallArgs.clear();
|
|
691
731
|
this.#readToolCallAssistantComponents.clear();
|
|
732
|
+
this.#resetReadGroup();
|
|
692
733
|
this.#lastAssistantComponent = undefined;
|
|
693
734
|
this.ctx.ui.requestRender();
|
|
694
735
|
this.#scheduleIdleCompaction();
|
|
@@ -832,14 +873,12 @@ export class EventController {
|
|
|
832
873
|
async #handleTtsrTriggered(event: Extract<AgentSessionEvent, { type: "ttsr_triggered" }>): Promise<void> {
|
|
833
874
|
const component = new TtsrNotificationComponent(event.rules);
|
|
834
875
|
component.setExpanded(this.ctx.toolOutputExpanded);
|
|
835
|
-
this.ctx.
|
|
836
|
-
this.ctx.ui.requestRender();
|
|
876
|
+
this.ctx.present(component);
|
|
837
877
|
}
|
|
838
878
|
|
|
839
879
|
async #handleTodoReminder(event: Extract<AgentSessionEvent, { type: "todo_reminder" }>): Promise<void> {
|
|
840
880
|
const component = new TodoReminderComponent(event.todos, event.attempt, event.maxAttempts);
|
|
841
|
-
this.ctx.
|
|
842
|
-
this.ctx.ui.requestRender();
|
|
881
|
+
this.ctx.present(component);
|
|
843
882
|
}
|
|
844
883
|
|
|
845
884
|
async #handleTodoAutoClear(_event: Extract<AgentSessionEvent, { type: "todo_auto_clear" }>): Promise<void> {
|
|
@@ -892,7 +931,6 @@ export class EventController {
|
|
|
892
931
|
}
|
|
893
932
|
|
|
894
933
|
sendCompletionNotification(): void {
|
|
895
|
-
if (this.ctx.isBackgrounded === false) return;
|
|
896
934
|
const notify = settings.get("completion.notify");
|
|
897
935
|
if (notify === "off") return;
|
|
898
936
|
|
|
@@ -911,15 +949,4 @@ export class EventController {
|
|
|
911
949
|
actions: "focus",
|
|
912
950
|
});
|
|
913
951
|
}
|
|
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
952
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { Component, OverlayHandle, TUI } from "@oh-my-pi/pi-tui";
|
|
2
2
|
import { Container, Spacer, Text } from "@oh-my-pi/pi-tui";
|
|
3
|
-
import { logger } from "@oh-my-pi/pi-utils";
|
|
4
3
|
import { KeybindingsManager } from "../../config/keybindings";
|
|
5
4
|
import type {
|
|
6
5
|
CompactOptions,
|
|
@@ -176,10 +175,10 @@ export class ExtensionUiController {
|
|
|
176
175
|
this.ctx.streamingMessage = undefined;
|
|
177
176
|
this.ctx.pendingTools.clear();
|
|
178
177
|
|
|
179
|
-
this.ctx.
|
|
180
|
-
|
|
178
|
+
this.ctx.present([
|
|
179
|
+
new Spacer(1),
|
|
181
180
|
new Text(`${theme.fg("accent", `${theme.status.success} New session started`)}`, 1, 1),
|
|
182
|
-
);
|
|
181
|
+
]);
|
|
183
182
|
await this.ctx.reloadTodos();
|
|
184
183
|
this.ctx.ui.requestRender(true, { clearScrollback: true });
|
|
185
184
|
|
|
@@ -326,10 +325,6 @@ export class ExtensionUiController {
|
|
|
326
325
|
.then(() => this.#applyCustomMessageDisplay(wasStreaming, message.display))
|
|
327
326
|
.catch((err: unknown) => {
|
|
328
327
|
const errorText = `Extension sendMessage failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
329
|
-
if (this.ctx.isBackgrounded) {
|
|
330
|
-
logger.error(errorText);
|
|
331
|
-
return;
|
|
332
|
-
}
|
|
333
328
|
this.ctx.showError(errorText);
|
|
334
329
|
});
|
|
335
330
|
},
|
|
@@ -374,9 +369,6 @@ export class ExtensionUiController {
|
|
|
374
369
|
getContextUsage: () => this.ctx.session.getContextUsage(),
|
|
375
370
|
waitForIdle: () => this.ctx.session.agent.waitForIdle(),
|
|
376
371
|
reload: async () => {
|
|
377
|
-
if (this.ctx.isBackgrounded) {
|
|
378
|
-
return;
|
|
379
|
-
}
|
|
380
372
|
await this.ctx.session.reload();
|
|
381
373
|
this.ctx.chatContainer.clear();
|
|
382
374
|
this.ctx.renderInitialMessages(undefined, { clearTerminalHistory: true });
|
|
@@ -384,9 +376,6 @@ export class ExtensionUiController {
|
|
|
384
376
|
this.ctx.showStatus("Reloaded session");
|
|
385
377
|
},
|
|
386
378
|
newSession: async options => {
|
|
387
|
-
if (this.ctx.isBackgrounded) {
|
|
388
|
-
return { cancelled: true };
|
|
389
|
-
}
|
|
390
379
|
// Stop any loading animation
|
|
391
380
|
if (this.ctx.loadingAnimation) {
|
|
392
381
|
this.ctx.loadingAnimation.stop();
|
|
@@ -415,19 +404,16 @@ export class ExtensionUiController {
|
|
|
415
404
|
this.ctx.streamingMessage = undefined;
|
|
416
405
|
this.ctx.pendingTools.clear();
|
|
417
406
|
|
|
418
|
-
this.ctx.
|
|
419
|
-
|
|
407
|
+
this.ctx.present([
|
|
408
|
+
new Spacer(1),
|
|
420
409
|
new Text(`${theme.fg("accent", `${theme.status.success} New session started`)}`, 1, 1),
|
|
421
|
-
);
|
|
410
|
+
]);
|
|
422
411
|
await this.ctx.reloadTodos();
|
|
423
412
|
this.ctx.ui.requestRender(true, { clearScrollback: true });
|
|
424
413
|
|
|
425
414
|
return { cancelled: false };
|
|
426
415
|
},
|
|
427
416
|
branch: async entryId => {
|
|
428
|
-
if (this.ctx.isBackgrounded) {
|
|
429
|
-
return { cancelled: true };
|
|
430
|
-
}
|
|
431
417
|
const result = await this.ctx.session.branch(entryId);
|
|
432
418
|
if (result.cancelled) {
|
|
433
419
|
return { cancelled: true };
|
|
@@ -443,9 +429,6 @@ export class ExtensionUiController {
|
|
|
443
429
|
return { cancelled: false };
|
|
444
430
|
},
|
|
445
431
|
navigateTree: async (targetId, options) => {
|
|
446
|
-
if (this.ctx.isBackgrounded) {
|
|
447
|
-
return { cancelled: true };
|
|
448
|
-
}
|
|
449
432
|
const result = await this.ctx.session.navigateTree(targetId, { summarize: options?.summarize });
|
|
450
433
|
if (result.cancelled) {
|
|
451
434
|
return { cancelled: true };
|
|
@@ -464,9 +447,6 @@ export class ExtensionUiController {
|
|
|
464
447
|
},
|
|
465
448
|
compact: async instructionsOrOptions => this.#handleInteractiveCompact(instructionsOrOptions),
|
|
466
449
|
switchSession: async sessionPath => {
|
|
467
|
-
if (this.ctx.isBackgrounded) {
|
|
468
|
-
return { cancelled: true };
|
|
469
|
-
}
|
|
470
450
|
this.clearHookWidgets();
|
|
471
451
|
const result = await this.ctx.session.switchSession(sessionPath);
|
|
472
452
|
if (!result) {
|
|
@@ -482,36 +462,6 @@ export class ExtensionUiController {
|
|
|
482
462
|
extensionRunner.initialize(actions, contextActions, commandActions, uiContext);
|
|
483
463
|
}
|
|
484
464
|
|
|
485
|
-
createBackgroundUiContext(): ExtensionUIContext {
|
|
486
|
-
return {
|
|
487
|
-
select: async (_title: string, _options: ExtensionUISelectItem[], _dialogOptions) => undefined,
|
|
488
|
-
confirm: async (_title: string, _message: string, _dialogOptions) => false,
|
|
489
|
-
input: async (_title: string, _placeholder?: string, _dialogOptions?: unknown) => undefined,
|
|
490
|
-
notify: () => {},
|
|
491
|
-
onTerminalInput: () => () => {},
|
|
492
|
-
setStatus: () => {},
|
|
493
|
-
setWorkingMessage: () => {},
|
|
494
|
-
setWidget: () => {},
|
|
495
|
-
setTitle: () => {},
|
|
496
|
-
custom: async () => undefined as never,
|
|
497
|
-
setEditorText: () => {},
|
|
498
|
-
pasteToEditor: () => {},
|
|
499
|
-
getEditorText: () => "",
|
|
500
|
-
editor: async () => undefined,
|
|
501
|
-
get theme() {
|
|
502
|
-
return theme;
|
|
503
|
-
},
|
|
504
|
-
getAllThemes: () => Promise.resolve([]),
|
|
505
|
-
getTheme: () => Promise.resolve(undefined),
|
|
506
|
-
setTheme: () => Promise.resolve({ success: false, error: "Background mode" }),
|
|
507
|
-
setFooter: () => {},
|
|
508
|
-
setHeader: () => {},
|
|
509
|
-
setEditorComponent: () => {},
|
|
510
|
-
getToolsExpanded: () => false,
|
|
511
|
-
setToolsExpanded: () => {},
|
|
512
|
-
};
|
|
513
|
-
}
|
|
514
|
-
|
|
515
465
|
/**
|
|
516
466
|
* Emit session event to all extension tools.
|
|
517
467
|
*/
|
|
@@ -531,7 +481,7 @@ export class ExtensionUiController {
|
|
|
531
481
|
ui: uiContext,
|
|
532
482
|
getContextUsage: () => this.ctx.session.getContextUsage(),
|
|
533
483
|
compact: instructionsOrOptions => this.#compactSession(instructionsOrOptions),
|
|
534
|
-
hasUI:
|
|
484
|
+
hasUI: true,
|
|
535
485
|
cwd: this.ctx.sessionManager.getCwd(),
|
|
536
486
|
sessionManager: this.ctx.session.sessionManager,
|
|
537
487
|
modelRegistry: this.ctx.session.modelRegistry,
|
|
@@ -557,22 +507,14 @@ export class ExtensionUiController {
|
|
|
557
507
|
* Show a tool error in the chat.
|
|
558
508
|
*/
|
|
559
509
|
showToolError(toolName: string, error: string): void {
|
|
560
|
-
if (this.ctx.isBackgrounded) {
|
|
561
|
-
logger.error(`Tool "${toolName}" error: ${error}`);
|
|
562
|
-
return;
|
|
563
|
-
}
|
|
564
510
|
const errorText = new Text(theme.fg("error", `Tool "${toolName}" error: ${error}`), 1, 0);
|
|
565
|
-
this.ctx.
|
|
566
|
-
this.ctx.ui.requestRender();
|
|
511
|
+
this.ctx.present(errorText);
|
|
567
512
|
}
|
|
568
513
|
|
|
569
514
|
/**
|
|
570
515
|
* Set hook status text in the footer.
|
|
571
516
|
*/
|
|
572
517
|
setHookStatus(key: string, text: string | undefined): void {
|
|
573
|
-
if (this.ctx.isBackgrounded) {
|
|
574
|
-
return;
|
|
575
|
-
}
|
|
576
518
|
this.ctx.statusLine.setHookStatus(key, text);
|
|
577
519
|
this.ctx.ui.requestRender();
|
|
578
520
|
}
|
|
@@ -860,14 +802,9 @@ export class ExtensionUiController {
|
|
|
860
802
|
|
|
861
803
|
showExtensionError(extensionPath: string, error: string): void {
|
|
862
804
|
const errorText = new Text(theme.fg("error", `Extension "${extensionPath}" error: ${error}`), 1, 0);
|
|
863
|
-
this.ctx.
|
|
864
|
-
this.ctx.ui.requestRender();
|
|
805
|
+
this.ctx.present(errorText);
|
|
865
806
|
}
|
|
866
807
|
async #handleInteractiveCompact(instructionsOrOptions: string | CompactOptions | undefined): Promise<void> {
|
|
867
|
-
if (this.ctx.isBackgrounded) {
|
|
868
|
-
await this.#compactSession(instructionsOrOptions);
|
|
869
|
-
return;
|
|
870
|
-
}
|
|
871
808
|
await this.ctx.executeCompaction(instructionsOrOptions, false);
|
|
872
809
|
}
|
|
873
810
|
|
|
@@ -892,7 +829,7 @@ export class ExtensionUiController {
|
|
|
892
829
|
#applyCustomMessageDisplay(wasStreaming: boolean, shouldDisplay: boolean | undefined): void {
|
|
893
830
|
// For non-streaming cases with display=true, update UI
|
|
894
831
|
// (streaming cases update via message_end event)
|
|
895
|
-
if (!
|
|
832
|
+
if (!wasStreaming && shouldDisplay) {
|
|
896
833
|
this.ctx.rebuildChatFromMessages();
|
|
897
834
|
}
|
|
898
835
|
}
|