@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
@@ -7,7 +7,6 @@ import { getAgentDbPath, getProjectDir, normalizePathForComparison } from "@oh-m
7
7
  import { getRoleInfo } from "../../config/model-registry";
8
8
  import { formatModelSelectorValue } from "../../config/model-resolver";
9
9
  import { settings } from "../../config/settings";
10
- import { DebugSelectorComponent } from "../../debug";
11
10
  import { disableProvider, enableProvider } from "../../discovery";
12
11
  import { clearPluginRootsAndCaches, resolveActiveProjectRegistryPath } from "../../discovery/helpers";
13
12
  import {
@@ -51,6 +50,7 @@ import { SessionObserverOverlayComponent } from "../components/session-observer-
51
50
  import { SessionSelectorComponent } from "../components/session-selector";
52
51
  import { SettingsSelectorComponent } from "../components/settings-selector";
53
52
  import { ToolExecutionComponent } from "../components/tool-execution";
53
+ import { TranscriptBlock } from "../components/transcript-container";
54
54
  import { TreeSelectorComponent } from "../components/tree-selector";
55
55
  import { UserMessageSelectorComponent } from "../components/user-message-selector";
56
56
  import type { SessionObserverRegistry } from "../session-observer-registry";
@@ -932,28 +932,28 @@ export class SelectorController {
932
932
  try {
933
933
  await this.ctx.session.modelRegistry.authStorage.login(providerId as OAuthProvider, {
934
934
  onAuth: (info: { url: string; instructions?: string }) => {
935
- this.ctx.chatContainer.addChild(new Spacer(1));
936
- this.ctx.chatContainer.addChild(new Text(theme.fg("dim", info.url), 1, 0));
935
+ const block = new TranscriptBlock();
936
+ block.addChild(new Text(theme.fg("dim", info.url), 1, 0));
937
937
  const hyperlink = `\x1b]8;;${info.url}\x07Click here to login\x1b]8;;\x07`;
938
- this.ctx.chatContainer.addChild(new Text(theme.fg("accent", hyperlink), 1, 0));
938
+ block.addChild(new Text(theme.fg("accent", hyperlink), 1, 0));
939
939
  if (info.instructions) {
940
- this.ctx.chatContainer.addChild(new Spacer(1));
941
- this.ctx.chatContainer.addChild(new Text(theme.fg("warning", info.instructions), 1, 0));
940
+ block.addChild(new Spacer(1));
941
+ block.addChild(new Text(theme.fg("warning", info.instructions), 1, 0));
942
942
  }
943
943
  if (useManualInput) {
944
- this.ctx.chatContainer.addChild(new Spacer(1));
945
- this.ctx.chatContainer.addChild(new Text(theme.fg("dim", MANUAL_LOGIN_TIP), 1, 0));
944
+ block.addChild(new Spacer(1));
945
+ block.addChild(new Text(theme.fg("dim", MANUAL_LOGIN_TIP), 1, 0));
946
946
  }
947
- this.ctx.ui.requestRender();
947
+ this.ctx.present(block);
948
948
  this.ctx.openInBrowser(info.url);
949
949
  },
950
950
  onPrompt: async (prompt: { message: string; placeholder?: string }) => {
951
- this.ctx.chatContainer.addChild(new Spacer(1));
952
- this.ctx.chatContainer.addChild(new Text(theme.fg("warning", prompt.message), 1, 0));
951
+ const promptBlock = new TranscriptBlock();
952
+ promptBlock.addChild(new Text(theme.fg("warning", prompt.message), 1, 0));
953
953
  if (prompt.placeholder) {
954
- this.ctx.chatContainer.addChild(new Text(theme.fg("dim", prompt.placeholder), 1, 0));
954
+ promptBlock.addChild(new Text(theme.fg("dim", prompt.placeholder), 1, 0));
955
955
  }
956
- this.ctx.ui.requestRender();
956
+ this.ctx.present(promptBlock);
957
957
  const { promise, resolve } = Promise.withResolvers<string>();
958
958
  const codeInput = new Input();
959
959
  codeInput.onSubmit = () => {
@@ -970,18 +970,17 @@ export class SelectorController {
970
970
  return promise;
971
971
  },
972
972
  onProgress: (message: string) => {
973
- this.ctx.chatContainer.addChild(new Text(theme.fg("dim", message), 1, 0));
974
- this.ctx.ui.requestRender();
973
+ this.ctx.present(new Text(theme.fg("dim", message), 1, 0));
975
974
  },
976
975
  onManualCodeInput: useManualInput ? () => manualInput.waitForInput(providerId) : undefined,
977
976
  });
978
977
  await this.ctx.session.modelRegistry.refresh();
979
- this.ctx.chatContainer.addChild(new Spacer(1));
980
- this.ctx.chatContainer.addChild(
978
+ const block = new TranscriptBlock();
979
+ block.addChild(
981
980
  new Text(theme.fg("success", `${theme.status.success} Successfully logged in to ${providerId}`), 1, 0),
982
981
  );
983
- this.ctx.chatContainer.addChild(new Text(theme.fg("dim", `Credentials saved to ${getAgentDbPath()}`), 1, 0));
984
- this.ctx.ui.requestRender();
982
+ block.addChild(new Text(theme.fg("dim", `Credentials saved to ${getAgentDbPath()}`), 1, 0));
983
+ this.ctx.present(block);
985
984
  } catch (error: unknown) {
986
985
  this.ctx.showError(`Login failed: ${error instanceof Error ? error.message : String(error)}`);
987
986
  } finally {
@@ -1003,20 +1002,18 @@ export class SelectorController {
1003
1002
 
1004
1003
  await authStorage.logout(providerId);
1005
1004
  await this.ctx.session.modelRegistry.refresh();
1006
- this.ctx.chatContainer.addChild(new Spacer(1));
1007
- this.ctx.chatContainer.addChild(
1005
+ const block = new TranscriptBlock();
1006
+ block.addChild(
1008
1007
  new Text(theme.fg("success", `${theme.status.success} Successfully logged out of ${providerId}`), 1, 0),
1009
1008
  );
1010
- this.ctx.chatContainer.addChild(
1011
- new Text(theme.fg("dim", `Credentials removed from ${getAgentDbPath()}`), 1, 0),
1012
- );
1009
+ block.addChild(new Text(theme.fg("dim", `Credentials removed from ${getAgentDbPath()}`), 1, 0));
1013
1010
  const remainingSource = authStorage.describeCredentialSource(providerId, this.ctx.session.sessionId);
1014
1011
  if (remainingSource) {
1015
- this.ctx.chatContainer.addChild(
1012
+ block.addChild(
1016
1013
  new Text(theme.fg("warning", `${providerId} is still authenticated via ${remainingSource}`), 1, 0),
1017
1014
  );
1018
1015
  }
1019
- this.ctx.ui.requestRender();
1016
+ this.ctx.present(block);
1020
1017
  } catch (error: unknown) {
1021
1018
  this.ctx.showError(`Logout failed: ${error instanceof Error ? error.message : String(error)}`);
1022
1019
  }
@@ -1080,7 +1077,8 @@ export class SelectorController {
1080
1077
  });
1081
1078
  }
1082
1079
 
1083
- showDebugSelector(): void {
1080
+ async showDebugSelector(): Promise<void> {
1081
+ const { DebugSelectorComponent } = await import("../../debug");
1084
1082
  this.showSelector(done => {
1085
1083
  const selector = new DebugSelectorComponent(this.ctx, done);
1086
1084
  return { component: selector, focus: selector };
@@ -0,0 +1,212 @@
1
+ import type { AssistantMessage } from "@oh-my-pi/pi-ai";
2
+ import { getSegmenter } from "@oh-my-pi/pi-tui";
3
+ import type { AssistantMessageComponent } from "../components/assistant-message";
4
+
5
+ export const STREAMING_REVEAL_FRAME_MS = 1000 / 30;
6
+ export const MIN_STEP = 3;
7
+ export const CATCHUP_FRAMES = 8;
8
+
9
+ type AssistantContentBlock = AssistantMessage["content"][number];
10
+ type StreamingRevealComponent = Pick<AssistantMessageComponent, "updateContent">;
11
+
12
+ type StreamingRevealControllerOptions = {
13
+ getSmoothStreaming(): boolean;
14
+ getHideThinkingBlock(): boolean;
15
+ requestRender(): void;
16
+ };
17
+
18
+ function countGraphemes(text: string): number {
19
+ let count = 0;
20
+ for (const _segment of getSegmenter().segment(text)) {
21
+ count += 1;
22
+ }
23
+ return count;
24
+ }
25
+
26
+ function sliceGraphemes(text: string, units: number): string {
27
+ if (units <= 0 || text.length === 0) return "";
28
+ let count = 0;
29
+ for (const { index, segment } of getSegmenter().segment(text)) {
30
+ count += 1;
31
+ if (count >= units) {
32
+ const end = index + segment.length;
33
+ return end >= text.length ? text : text.slice(0, end);
34
+ }
35
+ }
36
+ return text;
37
+ }
38
+
39
+ export function visibleUnits(message: AssistantMessage, hideThinking: boolean): number {
40
+ let total = 0;
41
+ for (const block of message.content) {
42
+ if (block.type === "text") {
43
+ total += countGraphemes(block.text);
44
+ } else if (block.type === "thinking" && !hideThinking) {
45
+ total += countGraphemes(block.thinking);
46
+ }
47
+ }
48
+ return total;
49
+ }
50
+
51
+ function revealTextBlock(
52
+ block: Extract<AssistantContentBlock, { type: "text" }>,
53
+ remaining: number,
54
+ ): AssistantContentBlock {
55
+ if (remaining <= 0) return block.text.length === 0 ? block : { ...block, text: "" };
56
+ const units = countGraphemes(block.text);
57
+ if (remaining >= units) return block;
58
+ return { ...block, text: sliceGraphemes(block.text, remaining) };
59
+ }
60
+
61
+ function revealThinkingBlock(
62
+ block: Extract<AssistantContentBlock, { type: "thinking" }>,
63
+ remaining: number,
64
+ ): AssistantContentBlock {
65
+ if (remaining <= 0) return block.thinking.length === 0 ? block : { ...block, thinking: "" };
66
+ const units = countGraphemes(block.thinking);
67
+ if (remaining >= units) return block;
68
+ return { ...block, thinking: sliceGraphemes(block.thinking, remaining) };
69
+ }
70
+
71
+ export function buildDisplayMessage(
72
+ target: AssistantMessage,
73
+ revealed: number,
74
+ hideThinking: boolean,
75
+ ): AssistantMessage {
76
+ let remaining = Math.max(0, Math.floor(revealed));
77
+ const content: AssistantContentBlock[] = [];
78
+ for (const block of target.content) {
79
+ if (block.type === "text") {
80
+ content.push(revealTextBlock(block, remaining));
81
+ remaining = Math.max(0, remaining - countGraphemes(block.text));
82
+ } else if (block.type === "thinking" && !hideThinking) {
83
+ content.push(revealThinkingBlock(block, remaining));
84
+ remaining = Math.max(0, remaining - countGraphemes(block.thinking));
85
+ } else {
86
+ content.push(block);
87
+ }
88
+ }
89
+ return { ...target, content };
90
+ }
91
+
92
+ export function nextStep(backlog: number): number {
93
+ return Math.max(MIN_STEP, Math.ceil(Math.max(0, backlog) / CATCHUP_FRAMES));
94
+ }
95
+
96
+ export class StreamingRevealController {
97
+ readonly #getSmoothStreaming: () => boolean;
98
+ readonly #getHideThinkingBlock: () => boolean;
99
+ readonly #requestRender: () => void;
100
+ #target: AssistantMessage | undefined;
101
+ #component: StreamingRevealComponent | undefined;
102
+ #timer: NodeJS.Timeout | undefined;
103
+ #revealed = 0;
104
+ #hideThinkingBlock = false;
105
+ #smoothStreaming = true;
106
+
107
+ constructor(options: StreamingRevealControllerOptions) {
108
+ this.#getSmoothStreaming = options.getSmoothStreaming;
109
+ this.#getHideThinkingBlock = options.getHideThinkingBlock;
110
+ this.#requestRender = options.requestRender;
111
+ }
112
+
113
+ begin(component: StreamingRevealComponent, message: AssistantMessage): void {
114
+ this.stop();
115
+ this.#component = component;
116
+ this.#target = message;
117
+ this.#revealed = 0;
118
+ this.#hideThinkingBlock = this.#getHideThinkingBlock();
119
+ this.#smoothStreaming = this.#getSmoothStreaming();
120
+ if (!this.#smoothStreaming) {
121
+ component.updateContent(message);
122
+ return;
123
+ }
124
+ const total = visibleUnits(message, this.#hideThinkingBlock);
125
+ if (message.content.some(block => block.type === "toolCall")) {
126
+ // A tool call is a transcript-order boundary: finish any leading
127
+ // assistant text before EventController renders the separate tool card.
128
+ this.#revealed = total;
129
+ component.updateContent(buildDisplayMessage(message, this.#revealed, this.#hideThinkingBlock));
130
+ return;
131
+ }
132
+ this.#renderCurrent();
133
+ this.#syncTimer(total);
134
+ }
135
+
136
+ setTarget(message: AssistantMessage): void {
137
+ this.#target = message;
138
+ if (!this.#component) return;
139
+ if (!this.#smoothStreaming) {
140
+ this.#component.updateContent(message);
141
+ return;
142
+ }
143
+ const total = visibleUnits(message, this.#hideThinkingBlock);
144
+ if (message.content.some(block => block.type === "toolCall")) {
145
+ // A tool call is a transcript-order boundary: finish any leading
146
+ // assistant text before EventController renders the separate tool card.
147
+ this.#revealed = total;
148
+ this.#stopTimer();
149
+ this.#component.updateContent(buildDisplayMessage(message, this.#revealed, this.#hideThinkingBlock));
150
+ return;
151
+ }
152
+ if (this.#revealed > total) {
153
+ this.#revealed = total;
154
+ }
155
+ this.#renderCurrent();
156
+ this.#syncTimer(total);
157
+ }
158
+
159
+ stop(): void {
160
+ this.#stopTimer();
161
+ this.#target = undefined;
162
+ this.#component = undefined;
163
+ this.#revealed = 0;
164
+ }
165
+
166
+ #renderCurrent(): void {
167
+ if (!this.#target || !this.#component) return;
168
+ this.#component.updateContent(buildDisplayMessage(this.#target, this.#revealed, this.#hideThinkingBlock));
169
+ }
170
+
171
+ #syncTimer(total = this.#target ? visibleUnits(this.#target, this.#hideThinkingBlock) : 0): void {
172
+ if (!this.#target || !this.#component || this.#revealed >= total) {
173
+ this.#stopTimer();
174
+ return;
175
+ }
176
+ this.#startTimer();
177
+ }
178
+
179
+ #startTimer(): void {
180
+ if (this.#timer) return;
181
+ this.#timer = setInterval(() => {
182
+ this.#tick();
183
+ }, STREAMING_REVEAL_FRAME_MS);
184
+ this.#timer.unref?.();
185
+ }
186
+
187
+ #stopTimer(): void {
188
+ if (!this.#timer) return;
189
+ clearInterval(this.#timer);
190
+ this.#timer = undefined;
191
+ }
192
+
193
+ #tick(): void {
194
+ const target = this.#target;
195
+ const component = this.#component;
196
+ if (!target || !component) {
197
+ this.stop();
198
+ return;
199
+ }
200
+ const total = visibleUnits(target, this.#hideThinkingBlock);
201
+ if (this.#revealed >= total) {
202
+ this.#stopTimer();
203
+ return;
204
+ }
205
+ this.#revealed = Math.min(total, this.#revealed + nextStep(total - this.#revealed));
206
+ component.updateContent(buildDisplayMessage(target, this.#revealed, this.#hideThinkingBlock));
207
+ this.#requestRender();
208
+ if (this.#revealed >= total) {
209
+ this.#stopTimer();
210
+ }
211
+ }
212
+ }
@@ -0,0 +1,173 @@
1
+ import * as fs from "node:fs/promises";
2
+ import type { AssistantMessage } from "@oh-my-pi/pi-ai";
3
+ import { prompt, Snowflake } from "@oh-my-pi/pi-utils";
4
+ import backgroundTanDispatchPrompt from "../../prompts/system/background-tan-dispatch.md" with { type: "text" };
5
+ import { AgentRegistry, MAIN_AGENT_ID } from "../../registry/agent-registry";
6
+ import * as sdk from "../../sdk";
7
+ import type { AgentSession } from "../../session/agent-session";
8
+ import { SessionManager } from "../../session/session-manager";
9
+ import { createMCPProxyTools, createSubagentSettings } from "../../task/executor";
10
+ import type { InteractiveModeContext } from "../types";
11
+
12
+ const TAN_LABEL_PREVIEW_LENGTH = 80;
13
+
14
+ function previewWork(work: string): string {
15
+ const singleLine = work.trim().replace(/\s+/g, " ");
16
+ if (singleLine.length <= TAN_LABEL_PREVIEW_LENGTH) return singleLine;
17
+ return `${singleLine.slice(0, TAN_LABEL_PREVIEW_LENGTH - 1)}…`;
18
+ }
19
+
20
+ function extractAssistantText(message: AssistantMessage | undefined): string {
21
+ if (!message) return "";
22
+ return message.content
23
+ .filter(content => content.type === "text")
24
+ .map(content => content.text)
25
+ .join("")
26
+ .trim();
27
+ }
28
+
29
+ async function removeCloneSession(cloneFile: string): Promise<void> {
30
+ await Promise.allSettled([
31
+ fs.rm(cloneFile, { force: true }),
32
+ fs.rm(cloneFile.slice(0, -6), { recursive: true, force: true }),
33
+ ]);
34
+ }
35
+
36
+ export class TanCommandController {
37
+ constructor(private readonly ctx: InteractiveModeContext) {}
38
+
39
+ async start(work: string): Promise<void> {
40
+ const trimmedWork = work.trim();
41
+ if (!trimmedWork) {
42
+ this.ctx.showStatus("Usage: /tan <work>");
43
+ return;
44
+ }
45
+
46
+ const session = this.ctx.session;
47
+ if (session.isStreaming) {
48
+ this.ctx.showWarning("Wait for the current response to finish or abort it before using /tan.");
49
+ return;
50
+ }
51
+
52
+ const model = session.model;
53
+ if (!model) {
54
+ this.ctx.showError("No active model available for /tan.");
55
+ return;
56
+ }
57
+
58
+ const manager = session.asyncJobManager;
59
+ if (!manager) {
60
+ this.ctx.showError("Background jobs are disabled; enable async jobs to use /tan.");
61
+ return;
62
+ }
63
+
64
+ const parentFile = this.ctx.sessionManager.getSessionFile();
65
+ if (!parentFile) {
66
+ this.ctx.showError("/tan requires a persisted session.");
67
+ return;
68
+ }
69
+
70
+ const parentSessionId = session.sessionId;
71
+ const thinkingLevel = session.configuredThinkingLevel();
72
+ const systemPrompt = [...session.systemPrompt];
73
+ const toolNames = session.getActiveToolNames();
74
+ const modelRegistry = session.modelRegistry;
75
+ const ownerId = session.getAgentId() ?? MAIN_AGENT_ID;
76
+ const mcpManager = this.ctx.mcpManager;
77
+ const cwd = this.ctx.sessionManager.getCwd();
78
+ // Nest the clone inside the parent's artifact directory (like a subagent
79
+ // session) rather than as a top-level sibling, so it shares the parent's
80
+ // artifacts in place — no copy needed.
81
+ const sessionDir = parentFile.slice(0, -6);
82
+ const settings = createSubagentSettings(this.ctx.settings);
83
+ const customTools = mcpManager ? createMCPProxyTools(mcpManager) : undefined;
84
+ const enableLsp = this.ctx.settings.get("task.enableLsp") !== false;
85
+ const agentRegistry = AgentRegistry.global();
86
+ const cloneId = `Tan-${Snowflake.next()}`;
87
+ const label = `/tan ${previewWork(trimmedWork)}`;
88
+
89
+ await this.ctx.sessionManager.ensureOnDisk();
90
+ await this.ctx.sessionManager.flush();
91
+
92
+ let cloneFile = "";
93
+ let jobId = "";
94
+ try {
95
+ const cloneManager = await SessionManager.forkFrom(parentFile, cwd, sessionDir, undefined, {
96
+ suppressBreadcrumb: true,
97
+ });
98
+ cloneFile = cloneManager.getSessionFile() ?? "";
99
+ if (!cloneFile) throw new Error("Forked session did not create a session file.");
100
+
101
+ jobId = manager.register(
102
+ "task",
103
+ label,
104
+ async ({ signal }) => {
105
+ if (signal.aborted) throw new Error("Aborted before execution");
106
+
107
+ let clone: AgentSession | undefined;
108
+ try {
109
+ const created = await sdk.createAgentSession({
110
+ cwd,
111
+ sessionManager: cloneManager,
112
+ model,
113
+ thinkingLevel,
114
+ systemPrompt,
115
+ toolNames,
116
+ providerSessionId: `${parentSessionId}:tan:${Snowflake.next()}`,
117
+ providerPromptCacheKey: parentSessionId,
118
+ modelRegistry,
119
+ authStorage: modelRegistry.authStorage,
120
+ settings,
121
+ hasUI: false,
122
+ enableMCP: false,
123
+ customTools,
124
+ enableLsp,
125
+ agentId: cloneId,
126
+ agentDisplayName: "tan",
127
+ parentTaskPrefix: cloneId,
128
+ agentRegistry,
129
+ disableExtensionDiscovery: true,
130
+ });
131
+ clone = created.session;
132
+ const abortClone = () => {
133
+ void clone?.abort();
134
+ };
135
+ signal.addEventListener("abort", abortClone, { once: true });
136
+ try {
137
+ if (signal.aborted) {
138
+ abortClone();
139
+ throw new Error("Aborted before execution");
140
+ }
141
+ await clone.prompt(trimmedWork, { attribution: "user" });
142
+ await clone.waitForIdle();
143
+ return extractAssistantText(clone.getLastAssistantMessage()) || "(no output)";
144
+ } finally {
145
+ signal.removeEventListener("abort", abortClone);
146
+ }
147
+ } finally {
148
+ await clone?.dispose();
149
+ }
150
+ },
151
+ { ownerId },
152
+ );
153
+ } catch (error) {
154
+ if (cloneFile) await removeCloneSession(cloneFile);
155
+ this.ctx.showError(error instanceof Error ? error.message : String(error));
156
+ return;
157
+ }
158
+
159
+ const content = prompt.render(backgroundTanDispatchPrompt, { jobId, work: trimmedWork });
160
+ await session.sendCustomMessage(
161
+ {
162
+ customType: "background-tan-dispatch",
163
+ content,
164
+ display: true,
165
+ attribution: "user",
166
+ details: { jobId, work: trimmedWork, sessionFile: cloneFile },
167
+ },
168
+ { triggerTurn: false },
169
+ );
170
+ this.ctx.rebuildChatFromMessages();
171
+ this.ctx.showStatus(`Dispatched background tan ${jobId}`);
172
+ }
173
+ }
@@ -2,11 +2,13 @@ import { emergencyTerminalRestore } from "@oh-my-pi/pi-tui";
2
2
  import { postmortem } from "@oh-my-pi/pi-utils";
3
3
 
4
4
  /**
5
- * Run modes for the coding agent.
5
+ * Interactive mode and embeddable RPC client exports for the coding agent.
6
+ *
7
+ * Branch-specific runners live in their concrete modules so importing this
8
+ * barrel does not pull print, RPC server, or ACP server mode into the normal
9
+ * TUI graph.
6
10
  */
7
- export { runAcpMode } from "./acp";
8
11
  export { InteractiveMode, type InteractiveModeOptions } from "./interactive-mode";
9
- export { type PrintModeOptions, runPrintMode } from "./print-mode";
10
12
  export {
11
13
  defineRpcClientTool,
12
14
  type ModelInfo,
@@ -17,7 +19,6 @@ export {
17
19
  type RpcClientToolResult,
18
20
  type RpcEventListener,
19
21
  } from "./rpc/rpc-client";
20
- export { runRpcMode } from "./rpc/rpc-mode";
21
22
  export type {
22
23
  RpcCommand,
23
24
  RpcHostToolCallRequest,