@oh-my-pi/pi-coding-agent 15.10.11 → 15.11.0

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 (217) hide show
  1. package/CHANGELOG.md +103 -2
  2. package/dist/cli.js +5790 -5731
  3. package/dist/types/async/index.d.ts +0 -1
  4. package/dist/types/cli/args.d.ts +1 -0
  5. package/dist/types/cli/gallery-fixtures/types.d.ts +5 -0
  6. package/dist/types/cli-commands.d.ts +12 -0
  7. package/dist/types/commands/launch.d.ts +4 -0
  8. package/dist/types/config/api-key-resolver.d.ts +3 -0
  9. package/dist/types/config/keybindings.d.ts +6 -1
  10. package/dist/types/config/model-registry.d.ts +1 -0
  11. package/dist/types/config/model-resolver.d.ts +18 -0
  12. package/dist/types/config/settings-schema.d.ts +85 -34
  13. package/dist/types/config/settings.d.ts +7 -0
  14. package/dist/types/edit/hashline/noop-loop-guard.d.ts +72 -0
  15. package/dist/types/eval/py/executor.d.ts +5 -0
  16. package/dist/types/eval/py/kernel.d.ts +6 -1
  17. package/dist/types/eval/py/runtime.d.ts +9 -0
  18. package/dist/types/exec/bash-executor.d.ts +2 -0
  19. package/dist/types/export/html/template.generated.d.ts +1 -1
  20. package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
  21. package/dist/types/extensibility/extensions/runner.d.ts +3 -2
  22. package/dist/types/extensibility/extensions/types.d.ts +3 -0
  23. package/dist/types/extensibility/shared-events.d.ts +2 -2
  24. package/dist/types/internal-urls/history-protocol.d.ts +14 -0
  25. package/dist/types/internal-urls/index.d.ts +1 -0
  26. package/dist/types/internal-urls/types.d.ts +1 -1
  27. package/dist/types/irc/bus.d.ts +66 -0
  28. package/dist/types/memory-backend/index.d.ts +1 -0
  29. package/dist/types/memory-backend/runtime.d.ts +4 -0
  30. package/dist/types/memory-backend/types.d.ts +66 -1
  31. package/dist/types/modes/components/agent-hub.d.ts +30 -0
  32. package/dist/types/modes/components/compaction-summary-message.d.ts +10 -4
  33. package/dist/types/modes/components/custom-editor.d.ts +2 -0
  34. package/dist/types/modes/components/tool-execution.d.ts +8 -0
  35. package/dist/types/modes/components/ttsr-notification.d.ts +5 -1
  36. package/dist/types/modes/components/welcome.d.ts +3 -9
  37. package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
  38. package/dist/types/modes/index.d.ts +3 -3
  39. package/dist/types/modes/interactive-mode.d.ts +10 -4
  40. package/dist/types/modes/oauth-manual-input.d.ts +7 -0
  41. package/dist/types/modes/rpc/rpc-client.d.ts +39 -2
  42. package/dist/types/modes/rpc/rpc-mode.d.ts +31 -2
  43. package/dist/types/modes/rpc/rpc-subagents.d.ts +24 -0
  44. package/dist/types/modes/rpc/rpc-types.d.ts +75 -1
  45. package/dist/types/modes/setup-wizard/index.d.ts +5 -1
  46. package/dist/types/modes/setup-wizard/lazy.d.ts +2 -0
  47. package/dist/types/modes/theme/theme.d.ts +2 -1
  48. package/dist/types/modes/types.d.ts +5 -2
  49. package/dist/types/modes/utils/ui-helpers.d.ts +1 -1
  50. package/dist/types/registry/agent-lifecycle.d.ts +51 -0
  51. package/dist/types/registry/agent-registry.d.ts +16 -5
  52. package/dist/types/secrets/index.d.ts +1 -1
  53. package/dist/types/secrets/obfuscator.d.ts +8 -2
  54. package/dist/types/session/agent-session.d.ts +49 -32
  55. package/dist/types/session/messages.d.ts +2 -4
  56. package/dist/types/session/session-history-format.d.ts +12 -0
  57. package/dist/types/session/session-manager.d.ts +21 -3
  58. package/dist/types/session/streaming-output.d.ts +46 -0
  59. package/dist/types/slash-commands/acp-builtins.d.ts +16 -0
  60. package/dist/types/slash-commands/builtin-registry.d.ts +1 -0
  61. package/dist/types/slash-commands/types.d.ts +1 -1
  62. package/dist/types/system-prompt.d.ts +2 -0
  63. package/dist/types/task/executor.d.ts +12 -2
  64. package/dist/types/task/index.d.ts +13 -6
  65. package/dist/types/task/output-manager.d.ts +0 -7
  66. package/dist/types/task/repair-args.d.ts +8 -7
  67. package/dist/types/task/types.d.ts +63 -51
  68. package/dist/types/thinking.d.ts +4 -0
  69. package/dist/types/tiny/title-client.d.ts +11 -0
  70. package/dist/types/tiny/title-protocol.d.ts +1 -0
  71. package/dist/types/tools/browser/tab-worker.d.ts +3 -1
  72. package/dist/types/tools/find.d.ts +0 -11
  73. package/dist/types/tools/grouped-file-output.d.ts +0 -49
  74. package/dist/types/tools/index.d.ts +7 -3
  75. package/dist/types/tools/irc.d.ts +76 -38
  76. package/dist/types/tools/job.d.ts +7 -1
  77. package/dist/types/utils/git.d.ts +15 -2
  78. package/dist/types/utils/title-generator.d.ts +3 -2
  79. package/examples/extensions/with-deps/package.json +1 -0
  80. package/package.json +11 -10
  81. package/scripts/bundle-dist.ts +28 -19
  82. package/src/async/index.ts +0 -1
  83. package/src/auto-thinking/classifier.ts +1 -0
  84. package/src/cli/args.ts +3 -0
  85. package/src/cli/gallery-cli.ts +1 -1
  86. package/src/cli/gallery-fixtures/agentic.ts +230 -115
  87. package/src/cli/gallery-fixtures/types.ts +5 -0
  88. package/src/cli-commands.ts +29 -0
  89. package/src/cli.ts +28 -15
  90. package/src/commands/launch.ts +4 -0
  91. package/src/commit/agentic/tools/analyze-file.ts +38 -19
  92. package/src/commit/model-selection.ts +3 -2
  93. package/src/config/api-key-resolver.ts +8 -6
  94. package/src/config/keybindings.ts +6 -1
  95. package/src/config/model-registry.ts +97 -30
  96. package/src/config/model-resolver.ts +60 -0
  97. package/src/config/settings-schema.ts +99 -55
  98. package/src/config/settings.ts +68 -3
  99. package/src/edit/hashline/execute.ts +39 -2
  100. package/src/edit/hashline/noop-loop-guard.ts +99 -0
  101. package/src/eval/__tests__/agent-bridge.test.ts +5 -3
  102. package/src/eval/agent-bridge.ts +3 -16
  103. package/src/eval/completion-bridge.ts +1 -0
  104. package/src/eval/js/shared/prelude.txt +1 -1
  105. package/src/eval/py/executor.ts +29 -7
  106. package/src/eval/py/index.ts +6 -1
  107. package/src/eval/py/kernel.ts +31 -11
  108. package/src/eval/py/prelude.py +5 -6
  109. package/src/eval/py/runtime.ts +37 -0
  110. package/src/exec/bash-executor.ts +82 -3
  111. package/src/export/html/template.generated.ts +1 -1
  112. package/src/export/html/template.js +38 -13
  113. package/src/extensibility/custom-tools/types.ts +2 -2
  114. package/src/extensibility/extensions/get-commands-handler.ts +2 -1
  115. package/src/extensibility/extensions/runner.ts +6 -1
  116. package/src/extensibility/extensions/types.ts +3 -0
  117. package/src/extensibility/shared-events.ts +2 -2
  118. package/src/hindsight/bank.ts +17 -2
  119. package/src/internal-urls/docs-index.generated.ts +11 -11
  120. package/src/internal-urls/history-protocol.ts +113 -0
  121. package/src/internal-urls/index.ts +1 -0
  122. package/src/internal-urls/router.ts +3 -1
  123. package/src/internal-urls/types.ts +1 -1
  124. package/src/irc/bus.ts +292 -0
  125. package/src/main.ts +26 -66
  126. package/src/memories/index.ts +2 -0
  127. package/src/memory-backend/index.ts +1 -0
  128. package/src/memory-backend/local-backend.ts +9 -0
  129. package/src/memory-backend/off-backend.ts +9 -0
  130. package/src/memory-backend/runtime.ts +66 -0
  131. package/src/memory-backend/types.ts +81 -1
  132. package/src/mnemopi/backend.ts +151 -4
  133. package/src/modes/acp/acp-agent.ts +119 -11
  134. package/src/modes/components/{session-observer-overlay.ts → agent-hub.ts} +586 -367
  135. package/src/modes/components/assistant-message.ts +19 -21
  136. package/src/modes/components/compaction-summary-message.ts +68 -32
  137. package/src/modes/components/custom-editor.ts +10 -0
  138. package/src/modes/components/footer.ts +3 -1
  139. package/src/modes/components/status-line/component.ts +118 -34
  140. package/src/modes/components/tool-execution.ts +31 -1
  141. package/src/modes/components/ttsr-notification.ts +72 -30
  142. package/src/modes/components/welcome.ts +9 -33
  143. package/src/modes/controllers/command-controller.ts +1 -1
  144. package/src/modes/controllers/event-controller.ts +65 -0
  145. package/src/modes/controllers/extension-ui-controller.ts +8 -8
  146. package/src/modes/controllers/input-controller.ts +19 -2
  147. package/src/modes/controllers/mcp-command-controller.ts +38 -3
  148. package/src/modes/controllers/selector-controller.ts +21 -17
  149. package/src/modes/index.ts +3 -21
  150. package/src/modes/interactive-mode.ts +47 -22
  151. package/src/modes/oauth-manual-input.ts +30 -3
  152. package/src/modes/rpc/rpc-client.ts +154 -3
  153. package/src/modes/rpc/rpc-mode.ts +97 -12
  154. package/src/modes/rpc/rpc-subagents.ts +265 -0
  155. package/src/modes/rpc/rpc-types.ts +81 -1
  156. package/src/modes/setup-wizard/index.ts +12 -2
  157. package/src/modes/setup-wizard/lazy.ts +16 -0
  158. package/src/modes/theme/theme.ts +18 -5
  159. package/src/modes/types.ts +5 -5
  160. package/src/modes/utils/hotkeys-markdown.ts +1 -0
  161. package/src/modes/utils/ui-helpers.ts +51 -49
  162. package/src/prompts/system/irc-incoming.md +3 -4
  163. package/src/prompts/system/orchestrate-notice.md +2 -2
  164. package/src/prompts/system/subagent-system-prompt.md +0 -5
  165. package/src/prompts/system/system-prompt.md +1 -0
  166. package/src/prompts/system/workflow-notice.md +2 -2
  167. package/src/prompts/tools/eval.md +3 -3
  168. package/src/prompts/tools/irc.md +29 -19
  169. package/src/prompts/tools/read.md +2 -2
  170. package/src/prompts/tools/task-summary.md +5 -16
  171. package/src/prompts/tools/task.md +38 -29
  172. package/src/registry/agent-lifecycle.ts +218 -0
  173. package/src/registry/agent-registry.ts +16 -5
  174. package/src/sdk.ts +37 -10
  175. package/src/secrets/index.ts +8 -1
  176. package/src/secrets/obfuscator.ts +39 -18
  177. package/src/session/agent-session.ts +422 -291
  178. package/src/session/messages.ts +11 -78
  179. package/src/session/session-history-format.ts +246 -0
  180. package/src/session/session-manager.ts +59 -5
  181. package/src/session/streaming-output.ts +226 -10
  182. package/src/slash-commands/acp-builtins.ts +24 -0
  183. package/src/slash-commands/builtin-registry.ts +20 -0
  184. package/src/slash-commands/types.ts +1 -1
  185. package/src/system-prompt.ts +14 -0
  186. package/src/task/executor.ts +851 -461
  187. package/src/task/index.ts +721 -796
  188. package/src/task/output-manager.ts +0 -11
  189. package/src/task/render.ts +148 -63
  190. package/src/task/repair-args.ts +21 -9
  191. package/src/task/types.ts +82 -66
  192. package/src/thinking.ts +7 -0
  193. package/src/tiny/title-client.ts +34 -5
  194. package/src/tiny/title-protocol.ts +1 -1
  195. package/src/tiny/worker.ts +6 -4
  196. package/src/tools/ask.ts +4 -2
  197. package/src/tools/bash.ts +61 -10
  198. package/src/tools/browser/tab-worker.ts +26 -7
  199. package/src/tools/browser.ts +28 -1
  200. package/src/tools/find.ts +2 -27
  201. package/src/tools/grouped-file-output.ts +1 -118
  202. package/src/tools/image-gen.ts +11 -4
  203. package/src/tools/index.ts +17 -13
  204. package/src/tools/inspect-image.ts +1 -0
  205. package/src/tools/irc.ts +596 -171
  206. package/src/tools/job.ts +41 -7
  207. package/src/tools/read.ts +57 -1
  208. package/src/tools/renderers.ts +2 -0
  209. package/src/tools/resolve.ts +4 -1
  210. package/src/utils/commit-message-generator.ts +1 -0
  211. package/src/utils/git.ts +267 -13
  212. package/src/utils/title-generator.ts +24 -5
  213. package/dist/types/async/support.d.ts +0 -2
  214. package/dist/types/modes/components/session-observer-overlay.d.ts +0 -11
  215. package/dist/types/task/simple-mode.d.ts +0 -8
  216. package/src/async/support.ts +0 -5
  217. package/src/task/simple-mode.ts +0 -27
@@ -129,6 +129,8 @@ export type SymbolKey =
129
129
  | "icon.extensionInstruction"
130
130
  // STT
131
131
  | "icon.mic"
132
+ // Compaction divider
133
+ | "icon.camera"
132
134
  // Thinking Levels
133
135
  | "thinking.minimal"
134
136
  | "thinking.low"
@@ -220,7 +222,8 @@ export type SymbolKey =
220
222
  | "tool.resolve"
221
223
  | "tool.review"
222
224
  | "tool.inspectImage"
223
- | "tool.goal";
225
+ | "tool.goal"
226
+ | "tool.irc";
224
227
 
225
228
  type SymbolMap = Record<SymbolKey, string>;
226
229
 
@@ -322,13 +325,15 @@ const UNICODE_SYMBOLS: SymbolMap = {
322
325
  "icon.extensionInstruction": "📘",
323
326
  // STT
324
327
  "icon.mic": "🎤",
328
+ // Compaction divider
329
+ "icon.camera": "📷",
325
330
  // Thinking levels
326
331
  "thinking.minimal": "◔ min",
327
332
  "thinking.low": "◑ low",
328
333
  "thinking.medium": "◒ med",
329
334
  "thinking.high": "◕ high",
330
335
  "thinking.xhigh": "◉ xhigh",
331
- "thinking.autoPending": "▣?",
336
+ "thinking.autoPending": "",
332
337
  // Checkboxes
333
338
  "checkbox.checked": "☑",
334
339
  "checkbox.unchecked": "☐",
@@ -414,6 +419,7 @@ const UNICODE_SYMBOLS: SymbolMap = {
414
419
  "tool.review": "◉",
415
420
  "tool.inspectImage": "🖼",
416
421
  "tool.goal": "◎",
422
+ "tool.irc": "✉",
417
423
  };
418
424
 
419
425
  const NERD_SYMBOLS: SymbolMap = {
@@ -599,6 +605,8 @@ const NERD_SYMBOLS: SymbolMap = {
599
605
  "icon.extensionInstruction": "\uf02d",
600
606
  // STT - fa-microphone
601
607
  "icon.mic": "\uf130",
608
+ // Compaction divider - fa-camera-retro
609
+ "icon.camera": "\uf083",
602
610
  // Thinking Levels - emoji labels
603
611
  // pick: 🤨 min | alt:  min  min
604
612
  "thinking.minimal": "\u{F0E7} min",
@@ -610,8 +618,8 @@ const NERD_SYMBOLS: SymbolMap = {
610
618
  "thinking.high": "\u{F111} high",
611
619
  // pick: 🧠 xhi | alt:  xhi  xhi
612
620
  "thinking.xhigh": "\u{F06D} xhi",
613
- // pick: 󰞋 (nf-md-help_box) | alt: [?]
614
- "thinking.autoPending": "\u{f078b}",
621
+ // pick: (fa-circle-o-notch) | alt: 󰂼 (nf-md-cached) ⟳
622
+ "thinking.autoPending": "\uf1ce",
615
623
  // Checkboxes
616
624
  // pick:  | alt:  
617
625
  "checkbox.checked": "\uf14a",
@@ -708,6 +716,7 @@ const NERD_SYMBOLS: SymbolMap = {
708
716
  "tool.review": "\uEA70",
709
717
  "tool.inspectImage": "\uEAEA",
710
718
  "tool.goal": "\uEBF8",
719
+ "tool.irc": "\uF086",
711
720
  };
712
721
 
713
722
  const ASCII_SYMBOLS: SymbolMap = {
@@ -808,13 +817,15 @@ const ASCII_SYMBOLS: SymbolMap = {
808
817
  "icon.extensionInstruction": "IN",
809
818
  // STT
810
819
  "icon.mic": "MIC",
820
+ // Compaction divider
821
+ "icon.camera": "[o]",
811
822
  // Thinking Levels
812
823
  "thinking.minimal": "[min]",
813
824
  "thinking.low": "[low]",
814
825
  "thinking.medium": "[med]",
815
826
  "thinking.high": "[high]",
816
827
  "thinking.xhigh": "[xhi]",
817
- "thinking.autoPending": "[?]",
828
+ "thinking.autoPending": "[~]",
818
829
  // Checkboxes
819
830
  "checkbox.checked": "[x]",
820
831
  "checkbox.unchecked": "[ ]",
@@ -898,6 +909,7 @@ const ASCII_SYMBOLS: SymbolMap = {
898
909
  "tool.review": "rev",
899
910
  "tool.inspectImage": "[i]",
900
911
  "tool.goal": "(o)",
912
+ "tool.irc": "irc",
901
913
  };
902
914
 
903
915
  const SYMBOL_PRESETS: Record<SymbolPreset, SymbolMap> = {
@@ -1686,6 +1698,7 @@ export class Theme {
1686
1698
  extensionContextFile: this.#symbols["icon.extensionContextFile"],
1687
1699
  extensionInstruction: this.#symbols["icon.extensionInstruction"],
1688
1700
  mic: this.#symbols["icon.mic"],
1701
+ camera: this.#symbols["icon.camera"],
1689
1702
  };
1690
1703
  }
1691
1704
 
@@ -99,6 +99,7 @@ export interface InteractiveModeContext {
99
99
  historyStorage?: HistoryStorage;
100
100
  mcpManager?: MCPManager;
101
101
  lspServers?: LspStartupServerInfo[];
102
+ titleSystemPrompt?: string;
102
103
 
103
104
  // State
104
105
  isInitialized: boolean;
@@ -135,6 +136,7 @@ export interface InteractiveModeContext {
135
136
  locallySubmittedUserSignatures: Set<string>;
136
137
  lastSigintTime: number;
137
138
  lastEscapeTime: number;
139
+ lastLeftTapTime: number;
138
140
  shutdownRequested: boolean;
139
141
  hookSelector: HookSelectorComponent | undefined;
140
142
  hookInput: HookInputComponent | undefined;
@@ -224,10 +226,7 @@ export interface InteractiveModeContext {
224
226
  sessionContext: SessionContext,
225
227
  options?: { updateFooter?: boolean; populateHistory?: boolean },
226
228
  ): void;
227
- renderInitialMessages(
228
- prebuiltContext?: SessionContext,
229
- options?: { preserveExistingChat?: boolean; clearTerminalHistory?: boolean },
230
- ): void;
229
+ renderInitialMessages(options?: { preserveExistingChat?: boolean; clearTerminalHistory?: boolean }): void;
231
230
  getUserMessageText(message: Message): string;
232
231
  findLastAssistantMessage(): AssistantMessage | undefined;
233
232
  extractAssistantText(message: AssistantMessage): string;
@@ -288,9 +287,10 @@ export interface InteractiveModeContext {
288
287
  handleResumeSession(sessionPath: string): Promise<void>;
289
288
  handleSessionDeleteCommand(): Promise<void>;
290
289
  showOAuthSelector(mode: "login" | "logout", providerId?: string): Promise<void>;
290
+ showProviderSetup(): Promise<void>;
291
291
  showHookConfirm(title: string, message: string): Promise<boolean>;
292
292
  showDebugSelector(): Promise<void>;
293
- showSessionObserver(): void;
293
+ showAgentHub(): void;
294
294
  resetObserverRegistry(): void;
295
295
 
296
296
  // Input handling
@@ -50,6 +50,7 @@ export function buildHotkeysMarkdown(bindings: HotkeysMarkdownBindings): string
50
50
  `| \`${appKey(bindings, "app.editor.external")}\` | Edit message in external editor |`,
51
51
  `| \`${appKey(bindings, "app.clipboard.pasteImage")}\` | Paste image from clipboard |`,
52
52
  `| \`${appKey(bindings, "app.stt.toggle")}\` | Toggle speech-to-text recording |`,
53
+ `| \`${appKey(bindings, "app.agents.hub")}\` / \`${appKey(bindings, "app.session.observe")}\` / double-tap \`←\` (empty editor) | Open the agent hub |`,
53
54
  "| `#` | Open prompt actions |",
54
55
  "| `/` | Slash commands |",
55
56
  "| `!` | Run bash command |",
@@ -35,6 +35,7 @@ import {
35
35
  type SkillPromptDetails,
36
36
  } from "../../session/messages";
37
37
  import type { SessionContext } from "../../session/session-manager";
38
+ import { createIrcMessageCard } from "../../tools/irc";
38
39
  import { formatBytes, formatDuration } from "../../tools/render-utils";
39
40
 
40
41
  type TextBlock = { type: "text"; text: string };
@@ -190,49 +191,31 @@ export class UiHelpers {
190
191
  this.ctx.chatContainer.addChild(component);
191
192
  break;
192
193
  }
193
- if (
194
- message.customType === "irc:incoming" ||
195
- message.customType === "irc:autoreply" ||
196
- message.customType === "irc:relay"
197
- ) {
194
+ if (message.customType === "irc:incoming" || message.customType === "irc:relay") {
198
195
  const details = (
199
196
  message as CustomMessage<{
200
197
  from?: string;
201
198
  to?: string;
202
199
  message?: string;
203
- reply?: string;
204
200
  body?: string;
205
- kind?: "message" | "reply";
201
+ replyTo?: string;
206
202
  }>
207
203
  ).details;
208
- let arrow: string;
209
- let body: string;
210
- if (message.customType === "irc:incoming") {
211
- const peer = details?.from ?? "?";
212
- body = details?.message ?? "";
213
- arrow = `⇦ ${peer}`;
214
- } else if (message.customType === "irc:autoreply") {
215
- const peer = details?.to ?? "?";
216
- body = details?.reply ?? "";
217
- arrow = `⇨ ${peer}`;
218
- } else {
219
- const from = details?.from ?? "?";
220
- const to = details?.to ?? "?";
221
- body = details?.body ?? "";
222
- arrow = `${from} ⇨ ${to}`;
223
- }
224
- const block = new TranscriptBlock();
225
- const header = `${theme.fg("accent", `[IRC] ${arrow}`)}`;
226
- const headerComponent = new Text(header, 1, 0);
227
- block.addChild(headerComponent);
228
- if (body) {
229
- for (const line of body.split("\n")) {
230
- const lineComponent = new Text(theme.fg("muted", ` ${line}`), 0, 0);
231
- block.addChild(lineComponent);
232
- }
233
- }
234
- this.ctx.chatContainer.addChild(block);
235
- return [block];
204
+ const incoming = message.customType === "irc:incoming";
205
+ const card = createIrcMessageCard(
206
+ {
207
+ kind: incoming ? "incoming" : "relay",
208
+ from: details?.from,
209
+ to: details?.to,
210
+ body: incoming ? details?.message : details?.body,
211
+ replyTo: details?.replyTo,
212
+ timestamp: message.timestamp,
213
+ },
214
+ () => this.ctx.toolOutputExpanded,
215
+ theme,
216
+ );
217
+ this.ctx.chatContainer.addChild(card);
218
+ return [card];
236
219
  }
237
220
  const renderer = this.ctx.session.extensionRunner?.getMessageRenderer(message.customType);
238
221
  // Both HookMessage and CustomMessage have the same structure, cast for compatibility
@@ -337,13 +320,23 @@ export class UiHelpers {
337
320
  let readGroup: ReadToolGroupComponent | null = null;
338
321
  const readToolCallArgs = new Map<string, Record<string, unknown>>();
339
322
  const readToolCallAssistantComponents = new Map<string, AssistantMessageComponent>();
340
- const deferredMessages: AgentMessage[] = [];
341
- for (const message of sessionContext.messages) {
342
- // Defer compaction summaries so they render at the bottom (visible after scroll)
343
- if (message.role === "compactionSummary") {
344
- deferredMessages.push(message);
345
- continue;
323
+ // Rebuild-time mirror of the event controller's displaceable-poll
324
+ // bookkeeping: a `job` poll that found every watched job still running is
325
+ // superseded by the next `job` call, so a rebuilt transcript collapses a
326
+ // repeated-poll run to its final snapshot instead of replaying the spam.
327
+ let waitingPoll: ToolExecutionComponent | null = null;
328
+ const resolveWaitingPoll = (nextToolName?: string) => {
329
+ const previous = waitingPoll;
330
+ if (!previous) return;
331
+ waitingPoll = null;
332
+ if (nextToolName === "job" && previous.isDisplaceableBlock()) {
333
+ this.ctx.chatContainer.removeChild(previous);
346
334
  }
335
+ // Sealing freezes the block and stops the waiting-poll spinner that
336
+ // updateResult armed.
337
+ previous.seal();
338
+ };
339
+ for (const message of sessionContext.messages) {
347
340
  // Assistant messages need special handling for tool calls
348
341
  if (message.role === "assistant") {
349
342
  this.ctx.addMessageToChat(message);
@@ -379,6 +372,7 @@ export class UiHelpers {
379
372
  if (content.type !== "toolCall") {
380
373
  continue;
381
374
  }
375
+ resolveWaitingPoll(content.name);
382
376
 
383
377
  if (
384
378
  content.name === "read" &&
@@ -493,8 +487,17 @@ export class UiHelpers {
493
487
  if (component) {
494
488
  component.updateResult(message, false, message.toolCallId);
495
489
  this.ctx.pendingTools.delete(message.toolCallId);
490
+ if (
491
+ message.toolName === "job" &&
492
+ component instanceof ToolExecutionComponent &&
493
+ component.isDisplaceableBlock()
494
+ ) {
495
+ waitingPoll = component;
496
+ }
496
497
  }
497
498
  } else {
499
+ // A user prompt closes the displacement window, same as the live path.
500
+ if (message.role === "user") resolveWaitingPoll();
498
501
  // All other messages use standard rendering
499
502
  this.ctx.addMessageToChat(message, options);
500
503
  }
@@ -504,17 +507,15 @@ export class UiHelpers {
504
507
  // rebuilt group freezes (even with a never-persisted result) and commits to
505
508
  // native scrollback like every other historical block.
506
509
  readGroup?.seal();
507
-
508
- // Render deferred messages (compaction summaries) at the bottom so they're visible
509
- for (const message of deferredMessages) {
510
- this.ctx.addMessageToChat(message, options);
511
- }
510
+ // A trailing waiting poll is final history on rebuild; seal it so it
511
+ // freezes (and its spinner timer stops) like every other block.
512
+ resolveWaitingPoll();
512
513
 
513
514
  this.ctx.pendingTools.clear();
514
515
  this.ctx.ui.requestRender();
515
516
  }
516
517
 
517
- renderInitialMessages(prebuiltContext?: SessionContext, options: RenderInitialMessagesOptions = {}): void {
518
+ renderInitialMessages(options: RenderInitialMessagesOptions = {}): void {
518
519
  // This path is used to rebuild the visible chat transcript (e.g. after custom/debug UI).
519
520
  // Clear existing rendered chat first to avoid duplicating the full session in the container.
520
521
  // On a non-preserving rebuild the existing blocks are discarded for good, so
@@ -530,8 +531,9 @@ export class UiHelpers {
530
531
  this.ctx.pendingBashComponents = [];
531
532
  this.ctx.pendingPythonComponents = [];
532
533
 
533
- // Reuse a pre-built context when available (e.g. from navigateTree) to avoid a second O(N) walk.
534
- const context = prebuiltContext ?? this.ctx.sessionManager.buildSessionContext();
534
+ // Display always uses the full-history transcript: compactions show as
535
+ // inline dividers instead of restarting the visible conversation.
536
+ const context = this.ctx.session.buildTranscriptSessionContext();
535
537
  this.ctx.renderSessionContext(context, {
536
538
  updateFooter: true,
537
539
  populateHistory: true,
@@ -1,8 +1,7 @@
1
1
  <irc>
2
- You received an IRC message from agent `{{from}}`.
2
+ Incoming IRC message from agent `{{from}}`{{#if replyTo}} (replying to {{replyTo}}){{/if}}:
3
3
 
4
- Reply briefly and directly using the conversation context already available to you. NEVER call tools. The reply you write is delivered back to `{{from}}` as your answer.
5
-
6
- Message:
7
4
  {{message}}
5
+
6
+ If a response is expected, reply with the `irc` tool (`op: "send"`, `to: "{{from}}"`) — you may finish your current step first. Nobody replies on your behalf.
8
7
  </irc>
@@ -8,7 +8,7 @@ You decompose, dispatch, verify, and iterate. Substantial and parallelizable wor
8
8
  <rules>
9
9
  1. **NEVER yield until everything is closed.** A phase finishing is *not* a yield point — launch the next phase in the same turn. Stop only when every requested item is verifiably done, or you hit a concrete [blocked] state that genuinely requires the user.
10
10
  2. **Enumerate the full surface before dispatching.** If the request references audits, plans, checklists, phase lists, or file lists, expand them into a flat set of items in `todo`. "Most of them" or "the important ones" is failure. Re-read the source documents — NEVER work from memory.
11
- 3. **Parallelize maximally; NEVER launch a one-off task.** Every set of edits with disjoint file scope MUST ship as one `task` batch — fan the work as wide as it decomposes. A single-task batch for divisible work is a failure: split it. If you are about to dispatch exactly one subagent, stop — either there is more to run alongside it (find it and batch them) or the change is small enough to make inline yourself (do it). Serialize only when one subagent produces a contract (types, schema, shared module) the next consumes — and state the dependency when you do.
11
+ 3. **Parallelize maximally; NEVER launch a one-off task.** Every set of edits with disjoint file scope MUST ship as parallel `task` calls in one message — fan the work as wide as it decomposes. Dispatching divisible work one call at a time, serially, is a failure: split it and dispatch together. If you are about to dispatch exactly one subagent, stop — either there is more to run alongside it (find it and dispatch them together) or the change is small enough to make inline yourself (do it). Serialize only when one subagent produces a contract (types, schema, shared module) the next consumes — and state the dependency when you do.
12
12
  4. **Each `task` assignment is self-contained.** Subagents have no shared context. Spell out: target files (≤3–5 explicit paths, no globs), the change with APIs and patterns, edge cases, and observable acceptance criteria. NEVER assume they read the same plan you did.
13
13
  5. **Verify after every phase before launching the next.** Run the appropriate gate: `bun check` for types, package-scoped `bun test` for behavior, `lsp diagnostics` for changed files. If a phase introduced breakage, dispatch fix-up subagents *before* moving on. NEVER declare a phase done on a red tree.
14
14
  6. **Commit policy.** If the request asks for commits or the repo workflow expects them, commit after each green phase with a focused message. NEVER commit a red tree. NEVER commit work the user did not ask to commit.
@@ -21,7 +21,7 @@ You decompose, dispatch, verify, and iterate. Substantial and parallelizable wor
21
21
  <workflow>
22
22
  1. **Ingest.** Read every referenced file (audits, plans, prior agent output, current branch state). Run `git status` to see uncommitted changes.
23
23
  2. **Plan.** Materialize the full work surface in `todo` as ordered phases. Within each phase, list the parallelizable units.
24
- 3. **Dispatch phase.** Launch all parallel `task` subagents in one call. Wait for the batch.
24
+ 3. **Dispatch phase.** Launch all parallel `task` subagents in one message, then collect every result (async results / `job poll`) before moving on.
25
25
  4. **Verify phase.** Run the gates. On failure, dispatch fix-up subagents and re-verify. Do not advance with a red gate.
26
26
  5. **Commit phase** (if applicable). Focused message naming the phase.
27
27
  6. **Advance.** Mark the phase done in `todo`, immediately start the next phase. No summary message between phases — keep going.
@@ -32,11 +32,6 @@ You are working in an isolated working tree at `{{worktree}}` for this sub-task.
32
32
  You NEVER modify files outside this tree or in the original repository.
33
33
  {{/if}}
34
34
 
35
- {{#if contextFile}}
36
- # Conversation Context
37
- If you need additional information, your conversation with the user is in {{contextFile}} — `read` its tail or `search` it for relevant terms.
38
- {{/if}}
39
-
40
35
  {{#if ircPeers}}
41
36
  # IRC Peers
42
37
  You can reach other live agents via the `irc` tool. Your id is `{{ircSelfId}}`. Currently visible peers:
@@ -149,6 +149,7 @@ With most FS/bash-like tools, static references to them will automatically resol
149
149
  - `agent://<id>`: full agent output artifact
150
150
  - `/<path>`: JSON field extraction
151
151
  - `artifact://<id>`: Artifact content
152
+ - `history://<agentId>`: agent transcript as concise markdown; bare `history://` lists agents
152
153
  - `local://<name>.md`: Plan artifacts and shared content with subagents
153
154
  {{#if hasObsidian}}
154
155
  - `vault://<vault>/<path>`: Obsidian vault content (read/edit). `vault://` lists vaults; `vault://_/…` targets the active vault. File-scoped `?op=outline|backlinks|links|tags|properties|tasks|base|…`; vault-scoped `?op=search&q=…|daily|tasks|orphans|unresolved|bases|…`.
@@ -13,8 +13,8 @@ Worth it when the task benefits from decomposition + parallel coverage, or from
13
13
  <helpers>
14
14
  State persists across cells, so scout in one cell and fan out in the next. Every cell has:
15
15
 
16
- - `agent(prompt, *, agent_type="task", model=None, context=None, label=None, schema=None)` — run ONE subagent; returns its final text, or the validated object when `schema` (a JSON Schema dict) is given. With `schema` the subagent is forced to emit structured output that is validated for you — branch on the object, not on parsed prose. `agent_type` picks a discovered agent ("explore", "reviewer", "oracle", …); `context` is shared background; `label` names the artifact. Subagents are told their final text IS the return value, so they hand back raw data. `agent()` blocks until the subagent finishes; eval-spawned agents nest at most 3 deep.
17
- - `parallel(thunks)` — run zero-arg callables concurrently through a bounded pool, preserving input order; returns once all finish. The pool runs as wide as a `task` tool batch — don't hand-tune it; fan out as wide as the work divides. A thunk that raises propagates — wrap risky work in `try/except` inside the thunk to keep partial results. In a loop, bind each closure's value with a default arg (`lambda d=d: …`) or every thunk captures the last one.
16
+ - `agent(prompt, *, agent_type="task", model=None, label=None, schema=None)` — run ONE subagent; returns its final text, or the validated object when `schema` (a JSON Schema dict) is given. With `schema` the subagent is forced to emit structured output that is validated for you — branch on the object, not on parsed prose. `agent_type` picks a discovered agent ("explore", "reviewer", "oracle", …); `label` names the artifact. Shared background goes in a `local://` file referenced from each prompt, not a parameter. Subagents are told their final text IS the return value, so they hand back raw data. `agent()` blocks until the subagent finishes; eval-spawned agents nest at most 3 deep.
17
+ - `parallel(thunks)` — run zero-arg callables concurrently through a bounded pool, preserving input order; returns once all finish. The pool is bounded by the session's `task` concurrency — don't hand-tune it; fan out as wide as the work divides. A thunk that raises propagates — wrap risky work in `try/except` inside the thunk to keep partial results. In a loop, bind each closure's value with a default arg (`lambda d=d: …`) or every thunk captures the last one.
18
18
  - `pipeline(items, *stages)` — map items through `stages` left-to-right. There is a BARRIER between stages: ALL items clear stage N before stage N+1 begins. Each stage is a one-arg callable; stage 1 gets the original item, later stages get the previous result. Same pool width as `parallel()`.
19
19
  - `completion(prompt, *, model="default", system=None, schema=None)` — oneshot, stateless model call (no tools, no history). Tiers: "smol", "default", "slow". Cheap classification/scoring inside a fan-out.
20
20
  - `log(message)` — emit a progress line above the status tree. `phase(title)` — start a phase; the status lines that follow group under it.
@@ -46,9 +46,9 @@ tool.<name>(args) → unknown
46
46
  Invoke any session tool by name. `args` is the tool's parameter object.
47
47
  completion(prompt, model?="default", system?=None, schema?=None) → str | dict
48
48
  Oneshot, stateless completion (no history, no tools). `model` picks a tier: "smol" (fast), "default" (this session's model), "slow" (most capable). Pass `system` for a system prompt. Pass a JSON-Schema `schema` to force structured output and get the parsed object back; otherwise returns the completion text.
49
- {{#if spawns}}agent(prompt, agent_type?="task", model?=None, context?=None, label?=None, schema?=None) → str | dict
50
- Run a subagent and return its final output. Defaults to the bundled "task" agent; pass `agent_type`/`agentType` for another discovered agent. Pass a JSON-Schema `schema` to force structured output and get the parsed object back.
51
- {{#if js}} In JS, pass options as one trailing object — never positional: agent(prompt, { agentType, context, schema }).
49
+ {{#if spawns}}agent(prompt, agent_type?="task", model?=None, label?=None, schema?=None) → str | dict
50
+ Run a subagent and return its final output. Defaults to the bundled "task" agent; pass `agent_type`/`agentType` for another discovered agent. Pass a JSON-Schema `schema` to force structured output and get the parsed object back. Share background by writing a `local://` file and referencing it in the prompt.
51
+ {{#if js}} In JS, pass options as one trailing object — never positional: agent(prompt, { agentType, schema }).
52
52
  {{/if}}
53
53
  {{/if}}
54
54
  parallel(thunks) → list
@@ -1,11 +1,15 @@
1
- Sends short text messages to other live agents in this process and receives their prose replies.
1
+ Sends short text messages to other agents in this process and receives theirs.
2
2
 
3
3
  <instruction>
4
4
  - The main agent is addressable as `Main`. Subagents reuse their task id (e.g. `AuthLoader`, or `AuthLoader-2` when the name repeats).
5
- - `op: "list"` returns the current set of visible peers. Use it before sending if you are not sure who is live.
6
- - `op: "send"` delivers `message` to `to`. `to` may be a specific id or `"all"` to broadcast.
7
- - Replies are generated on a side channel that does not wait for the recipient's main loop, so it is safe to IRC an agent that is mid tool call.
8
- - The exchange (question + auto-reply) is injected into the recipient's history; they see it on their next turn and can follow up.
5
+ - `op: "list"` every addressable peer with status (`running` | `idle` | `parked`), unread count, parent, and last activity. Use it before sending if you are not sure who exists.
6
+ - `op: "send"` fire-and-forget delivery of `message` to `to` (a peer id, or `"all"` to broadcast to live peers). Returns per-recipient receipts immediately; it NEVER waits for the recipient to act. Receipt outcomes: `injected` (recipient was mid-turn; message folded in at their next step boundary), `woken` (idle recipient started a turn), `revived` (parked recipient was brought back and woken), `failed`.
7
+ - Messaging an `idle` or `parked` peer is how you wake it there is no separate revive call.
8
+ - `send` with `await: true` — convenience round-trip: send, then block until the next message from that peer arrives (or the timeout passes). Invalid with `to: "all"`.
9
+ - `op: "wait"` — block until a message arrives (optionally only `from` a specific peer); consumes and returns it. A timeout is a clean "no message" result, not an error.
10
+ - `op: "inbox"` — drain pending messages without blocking (`peek: true` to leave them unread).
11
+ - `replyTo` — set it to the id of the message you are answering so the sender can correlate.
12
+ - Nobody answers on a peer's behalf anymore: a reply only arrives when the recipient actually sends one. For background on what a peer has been doing, `read` `history://<id>` instead of interrogating them.
9
13
  </instruction>
10
14
 
11
15
  <when_to_use>
@@ -21,29 +25,35 @@ NEVER use `irc` for: routine progress updates, things a tool call can verify, or
21
25
  <etiquette>
22
26
  These rules apply to both sending and replying.
23
27
  - **Plain prose only.** NEVER send structured JSON status payloads (e.g. `{"type":"task_completed",…}`). Write a normal sentence: "Done with the auth refactor — left a TODO in `src/server/auth.ts` for the rate limiter."
24
- - **NEVER quote the message you are replying to.** Lead with the answer.
25
- - **Use IRC, not terminal tools, to learn about peers.** NEVER `grep` artifacts, read other sessions' JSONL files, or shell-poke to figure out what another agent is doing. DM them.
26
- - **One round-trip is enough.** Replies arrive synchronously when the recipient is reachable. NEVER follow up with "did you get my message?". If `delivered` is empty or the result was `failed`, the peer is unavailable — move on or report the blocker; NEVER retry in a loop.
28
+ - **NEVER quote the message you are replying to.** Lead with the answer; set `replyTo` instead.
29
+ - **Use IRC, not terminal tools, to learn about peers.** NEVER `grep` artifacts, read other sessions' JSONL files, or shell-poke to figure out what another agent is doing. DM them, or `read` `history://<id>`.
30
+ - **Send, then keep working.** `send` returns immediately — only `wait` (or `await: true`) when you genuinely cannot proceed without the answer. NEVER follow up with "did you get my message?"; a `failed` receipt means the peer is unreachable — move on or report the blocker; NEVER retry in a loop.
31
+ - **Answer when a response is expected.** When an incoming message asks something, reply with `irc send` to the sender (you may finish your current step first).
27
32
  - **Stay terse.** A DM is a chat message, not a memo. One question per send. Share file paths and artifacts via `local://` / `memory://` / `artifact://` URLs instead of pasting blobs.
28
33
  - **Address peers by id.** Use the exact id from `op: "list"` (e.g. `AuthLoader`, `Main`). NEVER invent friendly names.
29
34
  - **NEVER IRC for things a tool would answer.** If a `read`, `grep`, or build command resolves the question, do that first.
30
- - **Answer incoming IRC messages before continuing.** Address the question directly; do not repeat it back to the user.
31
35
  </etiquette>
32
36
 
33
37
  <output>
34
- - `send`: returns each recipient that received the message and any prose replies that arrived.
35
- - `list`: returns peers and channels visible to the caller.
38
+ - `send`: per-recipient delivery receipts (`injected` / `woken` / `revived` / `failed`); with `await: true`, also the reply (or a timeout notice).
39
+ - `wait`: the consumed message, or a clean timeout notice.
40
+ - `inbox`: pending messages, oldest first.
41
+ - `list`: peers with status, unread count, parent, and last activity.
36
42
  </output>
37
43
 
38
44
  <examples>
39
45
  # List peers
40
46
  `{"op": "list"}`
41
- # Direct message to the main agent (waits for prose reply)
42
- `{"op": "send", "to": "Main", "message": "Should I prefer JWT or session cookies for the auth flow?"}`
43
- # Unexpected state ask the originator
44
- `{"op": "send", "to": "Main", "message": "Assignment says edit src/auth/jwt.ts but the file does not exist. Is the new path src/server/auth/jwt.ts?"}`
45
- # Blocked by a peerask them directly
46
- `{"op": "send", "to": "AuthLoader", "message": "Are you still touching src/server/auth.ts? I need to add a 401 path; OK to proceed or should I wait?"}`
47
- # Broadcast to discover who owns something (no replies, just informs them)
48
- `{"op": "send", "to": "all", "message": "About to refactor src/server/middleware/*. Anyone already in there?", "awaitReply": false}`
47
+ # Fire-and-forget DM keep working, check inbox later
48
+ `{"op": "send", "to": "AuthLoader", "message": "Are you still touching src/server/auth.ts? I need to add a 401 path."}`
49
+ # Round-trip when you cannot proceed without the answer
50
+ `{"op": "send", "to": "Main", "message": "Should I prefer JWT or session cookies for the auth flow?", "await": true}`
51
+ # Wake a parked agent (same send the bus revives it)
52
+ `{"op": "send", "to": "SchemaMigrator", "message": "The users table changed again; please re-check your migration."}`
53
+ # Block until a specific peer answers
54
+ `{"op": "wait", "from": "AuthLoader", "timeoutMs": 60000}`
55
+ # Drain pending messages
56
+ `{"op": "inbox"}`
57
+ # Broadcast to live peers (no replies expected)
58
+ `{"op": "send", "to": "all", "message": "About to refactor src/server/middleware/*. Anyone already in there?"}`
49
59
  </examples>
@@ -8,7 +8,7 @@ Read files, directories, archives, SQLite databases, images, documents, internal
8
8
 
9
9
  ## Parameters
10
10
 
11
- - `path` — required. Local path, internal URI (`skill://`, `agent://`, `artifact://`, `memory://`, `rule://`, `local://`, `vault://`, `mcp://`, `omp://`, `issue://`, `pr://`), or URL. Append `:<sel>` for line ranges, raw mode, or special modes (e.g. `src/foo.ts:50-200`, `src/foo.ts:raw`, `db.sqlite:users:42`).
11
+ - `path` — required. Local path, internal URI (`skill://`, `agent://`, `artifact://`, `history://`, `memory://`, `rule://`, `local://`, `vault://`, `mcp://`, `omp://`, `issue://`, `pr://`), or URL. Append `:<sel>` for line ranges, raw mode, or special modes (e.g. `src/foo.ts:50-200`, `src/foo.ts:raw`, `db.sqlite:users:42`).
12
12
 
13
13
  ## Selectors
14
14
 
@@ -74,7 +74,7 @@ For `.sqlite`, `.sqlite3`, `.db`, `.db3`:
74
74
 
75
75
  # Internal URIs
76
76
 
77
- `skill://<name>`, `agent://<id>`, `artifact://<id>`, `memory://root`, `rule://<name>`, `local://<name>.md`, `vault://<vault>/<path>`, `mcp://<uri>`, `omp://<doc>.md`, `issue://<N>`, and `pr://<N>` resolve transparently and accept the same line selectors as filesystem paths. Use `artifact://<id>` to recover full output that a previous bash/eval/tool result spilled or truncated.
77
+ `skill://<name>`, `agent://<id>`, `artifact://<id>`, `history://<agentId>`, `memory://root`, `rule://<name>`, `local://<name>.md`, `vault://<vault>/<path>`, `mcp://<uri>`, `omp://<doc>.md`, `issue://<N>`, and `pr://<N>` resolve transparently and accept the same line selectors as filesystem paths. Use `artifact://<id>` to recover full output that a previous bash/eval/tool result spilled or truncated. `history://<agentId>` is an agent's transcript as concise markdown; bare `history://` lists agents.
78
78
 
79
79
  <critical>
80
80
  - You MUST use `read` for every file, directory, archive, and URL inspection. `cat`, `head`, `tail`, `less`, `more`, `ls`, `tar`, `unzip`, `curl`, `wget` are FORBIDDEN — any such bash call is a bug, regardless of how short or convenient it looks.
@@ -1,28 +1,17 @@
1
- <task-summary>
2
- <header>{{successCount}}/{{totalCount}} succeeded{{#if hasCancelledNote}} ({{cancelledCount}} cancelled){{/if}} [{{duration}}]</header>
3
-
4
- {{#each summaries}}
5
- <agent id="{{id}}" agent="{{agent}}">
6
- <status>{{status}}</status>
1
+ <task-result id="{{id}}" agent="{{agentName}}" status="{{status}}" duration="{{duration}}">
7
2
  {{#if meta}}<meta lines="{{meta.lineCount}}" size="{{meta.charSize}}" />{{/if}}
8
3
  {{#if truncated}}
9
- <preview full-path="agent://{{id}}">
4
+ <preview full-output="agent://{{id}}">
10
5
  {{preview}}
11
6
  </preview>
12
7
  {{else}}
13
- <result>
8
+ <output>
14
9
  {{preview}}
15
- </result>
10
+ </output>
16
11
  {{/if}}
17
- </agent>
18
- {{#unless @last}}
19
- ---
20
- {{/unless}}
21
- {{/each}}
22
-
23
12
  {{#if mergeSummary}}
24
13
  <merge-summary>
25
14
  {{mergeSummary}}
26
15
  </merge-summary>
27
16
  {{/if}}
28
- </task-summary>
17
+ </task-result>