@oh-my-pi/pi-coding-agent 15.9.67 → 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.
Files changed (266) hide show
  1. package/CHANGELOG.md +136 -0
  2. package/dist/types/cli/args.d.ts +1 -1
  3. package/dist/types/cli/dry-balance-cli.d.ts +15 -1
  4. package/dist/types/cli/gallery-cli.d.ts +43 -0
  5. package/dist/types/cli/gallery-fixtures/agentic.d.ts +2 -0
  6. package/dist/types/cli/gallery-fixtures/codeintel.d.ts +3 -0
  7. package/dist/types/cli/gallery-fixtures/edit.d.ts +3 -0
  8. package/dist/types/cli/gallery-fixtures/fs.d.ts +2 -0
  9. package/dist/types/cli/gallery-fixtures/index.d.ts +4 -0
  10. package/dist/types/cli/gallery-fixtures/interaction.d.ts +3 -0
  11. package/dist/types/cli/gallery-fixtures/memory.d.ts +2 -0
  12. package/dist/types/cli/gallery-fixtures/misc.d.ts +3 -0
  13. package/dist/types/cli/gallery-fixtures/search.d.ts +3 -0
  14. package/dist/types/cli/gallery-fixtures/shell.d.ts +3 -0
  15. package/dist/types/cli/gallery-fixtures/types.d.ts +44 -0
  16. package/dist/types/cli/gallery-fixtures/web.d.ts +2 -0
  17. package/dist/types/cli/gallery-screenshot.d.ts +35 -0
  18. package/dist/types/commands/gallery.d.ts +47 -0
  19. package/dist/types/commit/analysis/conventional.d.ts +2 -2
  20. package/dist/types/commit/analysis/summary.d.ts +2 -2
  21. package/dist/types/commit/changelog/generate.d.ts +2 -2
  22. package/dist/types/commit/changelog/index.d.ts +2 -2
  23. package/dist/types/commit/map-reduce/index.d.ts +3 -3
  24. package/dist/types/commit/map-reduce/map-phase.d.ts +2 -2
  25. package/dist/types/commit/map-reduce/reduce-phase.d.ts +2 -2
  26. package/dist/types/commit/model-selection.d.ts +10 -4
  27. package/dist/types/config/api-key-resolver.d.ts +34 -0
  28. package/dist/types/config/keybindings.d.ts +6 -1
  29. package/dist/types/config/model-id-affixes.d.ts +2 -0
  30. package/dist/types/config/model-registry.d.ts +25 -2
  31. package/dist/types/config/settings-schema.d.ts +41 -6
  32. package/dist/types/dap/config.d.ts +14 -1
  33. package/dist/types/dap/types.d.ts +10 -0
  34. package/dist/types/extensibility/plugins/marketplace-auto-update.d.ts +8 -0
  35. package/dist/types/lsp/types.d.ts +10 -0
  36. package/dist/types/lsp/utils.d.ts +3 -2
  37. package/dist/types/main.d.ts +3 -2
  38. package/dist/types/memory-backend/index.d.ts +2 -1
  39. package/dist/types/memory-backend/resolve.d.ts +1 -1
  40. package/dist/types/memory-backend/types.d.ts +1 -1
  41. package/dist/types/modes/components/chat-block.d.ts +64 -0
  42. package/dist/types/modes/components/custom-editor.d.ts +5 -1
  43. package/dist/types/modes/components/overlay-box.d.ts +17 -0
  44. package/dist/types/modes/components/plan-review-overlay.d.ts +59 -0
  45. package/dist/types/modes/components/plan-toc.d.ts +41 -0
  46. package/dist/types/modes/components/read-tool-group.d.ts +2 -0
  47. package/dist/types/modes/components/tool-execution.d.ts +18 -0
  48. package/dist/types/modes/components/transcript-container.d.ts +11 -0
  49. package/dist/types/modes/controllers/command-controller.d.ts +1 -0
  50. package/dist/types/modes/controllers/event-controller.d.ts +0 -1
  51. package/dist/types/modes/controllers/extension-ui-controller.d.ts +0 -1
  52. package/dist/types/modes/controllers/input-controller.d.ts +1 -1
  53. package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
  54. package/dist/types/modes/controllers/streaming-reveal.d.ts +22 -0
  55. package/dist/types/modes/controllers/tan-command-controller.d.ts +6 -0
  56. package/dist/types/modes/index.d.ts +5 -4
  57. package/dist/types/modes/interactive-mode.d.ts +16 -6
  58. package/dist/types/modes/setup-version.d.ts +11 -0
  59. package/dist/types/modes/setup-wizard/index.d.ts +2 -1
  60. package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +2 -1
  61. package/dist/types/modes/theme/theme.d.ts +1 -1
  62. package/dist/types/modes/types.d.ts +19 -6
  63. package/dist/types/modes/utils/copy-targets.d.ts +21 -1
  64. package/dist/types/plan-mode/approved-plan.d.ts +27 -8
  65. package/dist/types/plan-mode/plan-protection.d.ts +4 -4
  66. package/dist/types/sdk.d.ts +3 -1
  67. package/dist/types/session/agent-session.d.ts +21 -0
  68. package/dist/types/session/messages.d.ts +12 -0
  69. package/dist/types/session/session-manager.d.ts +3 -1
  70. package/dist/types/slash-commands/types.d.ts +4 -6
  71. package/dist/types/task/executor.d.ts +14 -0
  72. package/dist/types/task/index.d.ts +1 -0
  73. package/dist/types/task/render.d.ts +3 -2
  74. package/dist/types/telemetry-export.d.ts +1 -1
  75. package/dist/types/tools/archive-reader.d.ts +5 -0
  76. package/dist/types/tools/ast-edit.d.ts +3 -0
  77. package/dist/types/tools/ast-grep.d.ts +3 -0
  78. package/dist/types/tools/bash.d.ts +1 -0
  79. package/dist/types/tools/eval-render.d.ts +1 -8
  80. package/dist/types/tools/fetch.d.ts +15 -7
  81. package/dist/types/tools/find.d.ts +8 -4
  82. package/dist/types/tools/grouped-file-output.d.ts +95 -12
  83. package/dist/types/tools/memory-render.d.ts +4 -1
  84. package/dist/types/tools/plan-mode-guard.d.ts +8 -9
  85. package/dist/types/tools/render-utils.d.ts +13 -9
  86. package/dist/types/tools/renderers.d.ts +16 -2
  87. package/dist/types/tools/search.d.ts +5 -1
  88. package/dist/types/tools/sqlite-reader.d.ts +1 -0
  89. package/dist/types/tools/todo.d.ts +3 -2
  90. package/dist/types/tools/write.d.ts +5 -0
  91. package/dist/types/tui/output-block.d.ts +16 -4
  92. package/dist/types/tui/status-line.d.ts +3 -0
  93. package/dist/types/utils/enhanced-paste.d.ts +20 -0
  94. package/dist/types/web/scrapers/github.d.ts +22 -0
  95. package/dist/types/web/search/providers/kimi.d.ts +1 -1
  96. package/dist/types/web/search/providers/perplexity.d.ts +8 -1
  97. package/dist/types/web/search/types.d.ts +1 -1
  98. package/package.json +9 -9
  99. package/scripts/dev-launch +42 -0
  100. package/scripts/dev-launch-preload.ts +19 -0
  101. package/src/auto-thinking/classifier.ts +5 -1
  102. package/src/cli/args.ts +2 -2
  103. package/src/cli/dry-balance-cli.ts +52 -17
  104. package/src/cli/gallery-cli.ts +226 -0
  105. package/src/cli/gallery-fixtures/agentic.ts +292 -0
  106. package/src/cli/gallery-fixtures/codeintel.ts +188 -0
  107. package/src/cli/gallery-fixtures/edit.ts +194 -0
  108. package/src/cli/gallery-fixtures/fs.ts +153 -0
  109. package/src/cli/gallery-fixtures/index.ts +40 -0
  110. package/src/cli/gallery-fixtures/interaction.ts +49 -0
  111. package/src/cli/gallery-fixtures/memory.ts +81 -0
  112. package/src/cli/gallery-fixtures/misc.ts +250 -0
  113. package/src/cli/gallery-fixtures/search.ts +213 -0
  114. package/src/cli/gallery-fixtures/shell.ts +167 -0
  115. package/src/cli/gallery-fixtures/types.ts +41 -0
  116. package/src/cli/gallery-fixtures/web.ts +158 -0
  117. package/src/cli/gallery-screenshot.ts +279 -0
  118. package/src/cli-commands.ts +1 -0
  119. package/src/commands/gallery.ts +52 -0
  120. package/src/commands/launch.ts +1 -1
  121. package/src/commit/analysis/conventional.ts +2 -2
  122. package/src/commit/analysis/summary.ts +2 -2
  123. package/src/commit/changelog/generate.ts +2 -2
  124. package/src/commit/changelog/index.ts +2 -2
  125. package/src/commit/map-reduce/index.ts +3 -3
  126. package/src/commit/map-reduce/map-phase.ts +2 -2
  127. package/src/commit/map-reduce/reduce-phase.ts +2 -2
  128. package/src/commit/model-selection.ts +33 -9
  129. package/src/commit/pipeline.ts +4 -4
  130. package/src/config/api-key-resolver.ts +58 -0
  131. package/src/config/keybindings.ts +15 -6
  132. package/src/config/model-equivalence.ts +35 -12
  133. package/src/config/model-id-affixes.ts +39 -22
  134. package/src/config/model-registry.ts +41 -18
  135. package/src/config/settings-schema.ts +28 -5
  136. package/src/config/settings.ts +31 -2
  137. package/src/dap/client.ts +14 -16
  138. package/src/dap/config.ts +41 -2
  139. package/src/dap/defaults.json +1 -0
  140. package/src/dap/session.ts +1 -0
  141. package/src/dap/types.ts +10 -0
  142. package/src/debug/index.ts +40 -54
  143. package/src/edit/renderer.ts +111 -119
  144. package/src/eval/__tests__/agent-bridge.test.ts +75 -32
  145. package/src/eval/__tests__/llm-bridge.test.ts +90 -31
  146. package/src/eval/agent-bridge.ts +34 -7
  147. package/src/eval/llm-bridge.ts +8 -3
  148. package/src/extensibility/extensions/runner.ts +1 -0
  149. package/src/extensibility/plugins/doctor.ts +0 -1
  150. package/src/extensibility/plugins/marketplace-auto-update.ts +49 -0
  151. package/src/goals/tools/goal-tool.ts +37 -27
  152. package/src/internal-urls/docs-index.generated.ts +10 -10
  153. package/src/lsp/client.ts +104 -55
  154. package/src/lsp/types.ts +10 -0
  155. package/src/lsp/utils.ts +3 -2
  156. package/src/main.ts +53 -56
  157. package/src/memories/index.ts +12 -5
  158. package/src/memory-backend/index.ts +13 -1
  159. package/src/memory-backend/resolve.ts +3 -5
  160. package/src/memory-backend/types.ts +1 -1
  161. package/src/mnemopi/backend.ts +5 -1
  162. package/src/modes/acp/acp-agent.ts +33 -26
  163. package/src/modes/components/assistant-message.ts +2 -9
  164. package/src/modes/components/chat-block.ts +111 -0
  165. package/src/modes/components/copy-selector.ts +1 -44
  166. package/src/modes/components/custom-editor.ts +33 -1
  167. package/src/modes/components/custom-message.ts +1 -3
  168. package/src/modes/components/execution-shared.ts +1 -2
  169. package/src/modes/components/hook-message.ts +1 -3
  170. package/src/modes/components/overlay-box.ts +108 -0
  171. package/src/modes/components/plan-review-overlay.ts +799 -0
  172. package/src/modes/components/plan-toc.ts +138 -0
  173. package/src/modes/components/read-tool-group.ts +20 -4
  174. package/src/modes/components/skill-message.ts +0 -1
  175. package/src/modes/components/status-line.ts +3 -5
  176. package/src/modes/components/tips.txt +1 -0
  177. package/src/modes/components/todo-reminder.ts +0 -2
  178. package/src/modes/components/tool-execution.ts +115 -90
  179. package/src/modes/components/transcript-container.ts +84 -24
  180. package/src/modes/components/user-message.ts +1 -2
  181. package/src/modes/controllers/command-controller-shared.ts +7 -6
  182. package/src/modes/controllers/command-controller.ts +70 -57
  183. package/src/modes/controllers/event-controller.ts +41 -40
  184. package/src/modes/controllers/extension-ui-controller.ts +10 -73
  185. package/src/modes/controllers/input-controller.ts +135 -122
  186. package/src/modes/controllers/mcp-command-controller.ts +69 -60
  187. package/src/modes/controllers/selector-controller.ts +25 -27
  188. package/src/modes/controllers/streaming-reveal.ts +212 -0
  189. package/src/modes/controllers/tan-command-controller.ts +173 -0
  190. package/src/modes/index.ts +5 -4
  191. package/src/modes/interactive-mode.ts +171 -82
  192. package/src/modes/setup-version.ts +11 -0
  193. package/src/modes/setup-wizard/index.ts +3 -2
  194. package/src/modes/setup-wizard/scenes/web-search.ts +3 -2
  195. package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
  196. package/src/modes/theme/theme-schema.json +1 -1
  197. package/src/modes/theme/theme.ts +8 -4
  198. package/src/modes/types.ts +19 -8
  199. package/src/modes/utils/context-usage.ts +10 -6
  200. package/src/modes/utils/copy-targets.ts +133 -27
  201. package/src/modes/utils/hotkeys-markdown.ts +1 -0
  202. package/src/modes/utils/ui-helpers.ts +44 -46
  203. package/src/plan-mode/approved-plan.ts +66 -43
  204. package/src/plan-mode/plan-protection.ts +4 -4
  205. package/src/prompts/system/background-tan-dispatch.md +8 -0
  206. package/src/prompts/system/plan-mode-active.md +67 -58
  207. package/src/prompts/system/plan-mode-approved.md +1 -1
  208. package/src/sdk.ts +32 -60
  209. package/src/session/agent-session.ts +89 -13
  210. package/src/session/messages.ts +26 -0
  211. package/src/session/session-manager.ts +13 -5
  212. package/src/slash-commands/builtin-registry.ts +37 -10
  213. package/src/slash-commands/helpers/usage-report.ts +2 -0
  214. package/src/slash-commands/types.ts +4 -6
  215. package/src/task/executor.ts +25 -4
  216. package/src/task/index.ts +4 -0
  217. package/src/task/render.ts +212 -148
  218. package/src/telemetry-export.ts +25 -7
  219. package/src/tools/archive-reader.ts +64 -0
  220. package/src/tools/ask.ts +119 -164
  221. package/src/tools/ast-edit.ts +98 -71
  222. package/src/tools/ast-grep.ts +37 -43
  223. package/src/tools/bash.ts +50 -6
  224. package/src/tools/debug.ts +20 -8
  225. package/src/tools/eval-backends.ts +6 -17
  226. package/src/tools/eval-render.ts +21 -18
  227. package/src/tools/eval.ts +5 -4
  228. package/src/tools/fetch.ts +391 -91
  229. package/src/tools/find.ts +44 -30
  230. package/src/tools/gh-renderer.ts +81 -42
  231. package/src/tools/grouped-file-output.ts +272 -48
  232. package/src/tools/image-gen.ts +150 -103
  233. package/src/tools/inspect-image-renderer.ts +63 -41
  234. package/src/tools/inspect-image.ts +8 -1
  235. package/src/tools/job.ts +3 -4
  236. package/src/tools/memory-render.ts +4 -1
  237. package/src/tools/plan-mode-guard.ts +21 -39
  238. package/src/tools/read.ts +23 -16
  239. package/src/tools/render-utils.ts +38 -40
  240. package/src/tools/renderers.ts +16 -1
  241. package/src/tools/report-tool-issue.ts +1 -1
  242. package/src/tools/resolve.ts +14 -0
  243. package/src/tools/search-tool-bm25.ts +36 -23
  244. package/src/tools/search.ts +189 -95
  245. package/src/tools/sqlite-reader.ts +9 -12
  246. package/src/tools/todo.ts +138 -59
  247. package/src/tools/write.ts +100 -60
  248. package/src/tui/output-block.ts +60 -13
  249. package/src/tui/status-line.ts +5 -1
  250. package/src/utils/commit-message-generator.ts +9 -1
  251. package/src/utils/enhanced-paste.ts +202 -0
  252. package/src/utils/title-generator.ts +2 -1
  253. package/src/web/scrapers/github.ts +255 -3
  254. package/src/web/scrapers/youtube.ts +3 -2
  255. package/src/web/search/providers/anthropic.ts +25 -19
  256. package/src/web/search/providers/exa.ts +11 -3
  257. package/src/web/search/providers/kimi.ts +28 -17
  258. package/src/web/search/providers/parallel.ts +35 -24
  259. package/src/web/search/providers/perplexity.ts +199 -51
  260. package/src/web/search/providers/synthetic.ts +8 -6
  261. package/src/web/search/providers/tavily.ts +9 -8
  262. package/src/web/search/providers/zai.ts +8 -6
  263. package/src/web/search/render.ts +39 -54
  264. package/src/web/search/types.ts +5 -1
  265. package/dist/types/eval/__tests__/shared-executors.test.d.ts +0 -1
  266. package/src/eval/__tests__/shared-executors.test.ts +0 -609
@@ -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, Text } from "@oh-my-pi/pi-tui";
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
- #lastThinkingCount = 0;
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.#resetReadGroup();
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.updateContent(this.ctx.streamingMessage);
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.ctx.streamingComponent.updateContent(this.ctx.streamingMessage);
372
+ this.#streamingReveal.setTarget(this.ctx.streamingMessage);
359
373
 
360
- const thinkingCount = this.ctx.streamingMessage.content.filter(
361
- content => content.type === "thinking" && content.thinking.trim(),
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 (thinkingCount > this.#lastThinkingCount) {
379
+ if (visibleBlockCount > this.#lastVisibleBlockCount) {
364
380
  this.#resetReadGroup();
365
- this.#lastThinkingCount = thinkingCount;
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
- // Real user-cancel / network / provider abort: surface the standard
465
- // operator-facing label. AgentSession.#handleAgentEvent already stamped
466
- // SILENT_ABORT_MARKER for the plan-compact transition before this
467
- // controller ran, so reaching this branch implies the abort was NOT a
468
- // silent internal transition.
469
- const retryAttempt = this.ctx.session.retryAttempt;
470
- errorMessage =
471
- retryAttempt > 0
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.chatContainer.addChild(component);
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.chatContainer.addChild(component);
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
  }
@@ -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.chatContainer.addChild(new Spacer(1));
180
- this.ctx.chatContainer.addChild(
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.chatContainer.addChild(new Spacer(1));
419
- this.ctx.chatContainer.addChild(
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: !this.ctx.isBackgrounded,
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.chatContainer.addChild(errorText);
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.chatContainer.addChild(errorText);
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 (!this.ctx.isBackgrounded && !wasStreaming && shouldDisplay) {
832
+ if (!wasStreaming && shouldDisplay) {
896
833
  this.ctx.rebuildChatFromMessages();
897
834
  }
898
835
  }