@oh-my-pi/pi-coding-agent 15.3.2 → 15.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. package/CHANGELOG.md +110 -0
  2. package/dist/types/cli/file-processor.d.ts +1 -1
  3. package/dist/types/config/settings-schema.d.ts +45 -3
  4. package/dist/types/config/settings.d.ts +1 -1
  5. package/dist/types/debug/raw-sse.d.ts +2 -0
  6. package/dist/types/edit/file-read-cache.d.ts +15 -4
  7. package/dist/types/edit/index.d.ts +3 -8
  8. package/dist/types/edit/renderer.d.ts +1 -2
  9. package/dist/types/eval/__tests__/shared-executors.test.d.ts +1 -0
  10. package/dist/types/eval/js/shared/local-module-loader.d.ts +16 -0
  11. package/dist/types/eval/js/shared/rewrite-imports.d.ts +4 -0
  12. package/dist/types/eval/js/shared/runtime.d.ts +14 -8
  13. package/dist/types/eval/py/executor.d.ts +1 -2
  14. package/dist/types/eval/py/kernel.d.ts +6 -0
  15. package/dist/types/eval/py/tool-bridge.d.ts +1 -5
  16. package/dist/types/eval/session-id.d.ts +3 -0
  17. package/dist/types/extensibility/extensions/types.d.ts +1 -3
  18. package/dist/types/hashline/anchors.d.ts +15 -9
  19. package/dist/types/hashline/constants.d.ts +0 -2
  20. package/dist/types/hashline/diff.d.ts +1 -2
  21. package/dist/types/hashline/executor.d.ts +52 -0
  22. package/dist/types/hashline/hash.d.ts +44 -93
  23. package/dist/types/hashline/index.d.ts +2 -1
  24. package/dist/types/hashline/input.d.ts +2 -9
  25. package/dist/types/hashline/recovery.d.ts +3 -9
  26. package/dist/types/hashline/tokenizer.d.ts +91 -0
  27. package/dist/types/hashline/types.d.ts +5 -7
  28. package/dist/types/modes/components/extensions/types.d.ts +0 -4
  29. package/dist/types/modes/types.d.ts +1 -0
  30. package/dist/types/modes/utils/ui-helpers.d.ts +1 -0
  31. package/dist/types/sdk.d.ts +2 -0
  32. package/dist/types/session/agent-session.d.ts +11 -15
  33. package/dist/types/session/agent-storage.d.ts +11 -10
  34. package/dist/types/slash-commands/acp-builtins.d.ts +3 -3
  35. package/dist/types/slash-commands/types.d.ts +0 -5
  36. package/dist/types/task/executor.d.ts +2 -0
  37. package/dist/types/tool-discovery/tool-index.d.ts +0 -50
  38. package/dist/types/tools/index.d.ts +2 -8
  39. package/dist/types/tools/match-line-format.d.ts +4 -4
  40. package/dist/types/tools/output-schema-validator.d.ts +64 -0
  41. package/dist/types/tools/review.d.ts +13 -0
  42. package/dist/types/tools/search-tool-bm25.d.ts +1 -1
  43. package/dist/types/tools/search.d.ts +4 -3
  44. package/dist/types/utils/edit-mode.d.ts +1 -1
  45. package/dist/types/web/kagi.d.ts +4 -2
  46. package/dist/types/web/parallel.d.ts +4 -3
  47. package/dist/types/web/scrapers/types.d.ts +2 -1
  48. package/dist/types/web/search/index.d.ts +12 -4
  49. package/dist/types/web/search/provider.d.ts +2 -1
  50. package/dist/types/web/search/providers/anthropic.d.ts +9 -4
  51. package/dist/types/web/search/providers/base.d.ts +34 -2
  52. package/dist/types/web/search/providers/brave.d.ts +8 -1
  53. package/dist/types/web/search/providers/codex.d.ts +13 -9
  54. package/dist/types/web/search/providers/exa.d.ts +10 -1
  55. package/dist/types/web/search/providers/gemini.d.ts +20 -23
  56. package/dist/types/web/search/providers/jina.d.ts +2 -1
  57. package/dist/types/web/search/providers/kagi.d.ts +4 -1
  58. package/dist/types/web/search/providers/kimi.d.ts +10 -1
  59. package/dist/types/web/search/providers/parallel.d.ts +3 -2
  60. package/dist/types/web/search/providers/perplexity.d.ts +5 -2
  61. package/dist/types/web/search/providers/searxng.d.ts +2 -1
  62. package/dist/types/web/search/providers/synthetic.d.ts +5 -8
  63. package/dist/types/web/search/providers/tavily.d.ts +11 -4
  64. package/dist/types/web/search/providers/utils.d.ts +8 -6
  65. package/dist/types/web/search/providers/zai.d.ts +12 -3
  66. package/package.json +7 -7
  67. package/src/cli/file-processor.ts +12 -2
  68. package/src/cli.ts +0 -8
  69. package/src/commands/commit.ts +8 -8
  70. package/src/config/prompt-templates.ts +6 -6
  71. package/src/config/settings-schema.ts +47 -3
  72. package/src/config/settings.ts +5 -5
  73. package/src/debug/raw-sse.ts +68 -3
  74. package/src/edit/file-read-cache.ts +68 -25
  75. package/src/edit/index.ts +6 -37
  76. package/src/edit/renderer.ts +9 -47
  77. package/src/edit/streaming.ts +43 -56
  78. package/src/eval/__tests__/shared-executors.test.ts +520 -0
  79. package/src/eval/js/context-manager.ts +64 -53
  80. package/src/eval/js/shared/local-module-loader.ts +265 -0
  81. package/src/eval/js/shared/prelude.txt +4 -0
  82. package/src/eval/js/shared/rewrite-imports.ts +85 -0
  83. package/src/eval/js/shared/runtime.ts +129 -86
  84. package/src/eval/js/worker-core.ts +23 -38
  85. package/src/eval/py/executor.ts +155 -84
  86. package/src/eval/py/kernel.ts +10 -1
  87. package/src/eval/py/prelude.py +22 -24
  88. package/src/eval/py/runner.py +203 -85
  89. package/src/eval/py/tool-bridge.ts +17 -10
  90. package/src/eval/session-id.ts +8 -0
  91. package/src/exec/bash-executor.ts +27 -16
  92. package/src/extensibility/extensions/runner.ts +0 -1
  93. package/src/extensibility/extensions/types.ts +1 -3
  94. package/src/hashline/anchors.ts +56 -65
  95. package/src/hashline/apply.ts +29 -31
  96. package/src/hashline/constants.ts +0 -3
  97. package/src/hashline/diff-preview.ts +4 -5
  98. package/src/hashline/diff.ts +30 -4
  99. package/src/hashline/execute.ts +91 -26
  100. package/src/hashline/executor.ts +239 -0
  101. package/src/hashline/grammar.lark +12 -10
  102. package/src/hashline/hash.ts +69 -114
  103. package/src/hashline/index.ts +2 -1
  104. package/src/hashline/input.ts +48 -41
  105. package/src/hashline/prefixes.ts +21 -11
  106. package/src/hashline/recovery.ts +63 -71
  107. package/src/hashline/stream.ts +2 -2
  108. package/src/hashline/tokenizer.ts +467 -0
  109. package/src/hashline/types.ts +6 -8
  110. package/src/internal-urls/docs-index.generated.ts +7 -7
  111. package/src/modes/components/extensions/types.ts +0 -5
  112. package/src/modes/components/session-observer-overlay.ts +11 -2
  113. package/src/modes/components/settings-selector.ts +10 -1
  114. package/src/modes/components/tree-selector.ts +10 -2
  115. package/src/modes/controllers/command-controller.ts +1 -3
  116. package/src/modes/controllers/extension-ui-controller.ts +10 -11
  117. package/src/modes/controllers/selector-controller.ts +5 -5
  118. package/src/modes/theme/theme.ts +4 -2
  119. package/src/modes/types.ts +4 -1
  120. package/src/modes/utils/ui-helpers.ts +4 -0
  121. package/src/prompts/agents/explore.md +1 -1
  122. package/src/prompts/tools/ast-edit.md +1 -1
  123. package/src/prompts/tools/ast-grep.md +1 -1
  124. package/src/prompts/tools/eval.md +1 -1
  125. package/src/prompts/tools/hashline.md +73 -94
  126. package/src/prompts/tools/read.md +4 -4
  127. package/src/prompts/tools/search.md +3 -3
  128. package/src/sdk.ts +33 -26
  129. package/src/session/agent-session.ts +59 -66
  130. package/src/session/agent-storage.ts +13 -14
  131. package/src/slash-commands/acp-builtins.ts +3 -3
  132. package/src/slash-commands/types.ts +0 -6
  133. package/src/task/executor.ts +26 -57
  134. package/src/task/index.ts +8 -4
  135. package/src/tool-discovery/tool-index.ts +0 -134
  136. package/src/tools/ast-edit.ts +36 -13
  137. package/src/tools/ast-grep.ts +45 -4
  138. package/src/tools/browser/tab-worker.ts +3 -2
  139. package/src/tools/eval.ts +2 -1
  140. package/src/tools/fetch.ts +23 -14
  141. package/src/tools/index.ts +2 -8
  142. package/src/tools/irc.ts +59 -5
  143. package/src/tools/match-line-format.ts +5 -7
  144. package/src/tools/output-schema-validator.ts +132 -0
  145. package/src/tools/read.ts +142 -31
  146. package/src/tools/review.ts +23 -0
  147. package/src/tools/search-tool-bm25.ts +3 -30
  148. package/src/tools/search.ts +48 -16
  149. package/src/tools/write.ts +3 -3
  150. package/src/tools/yield.ts +32 -41
  151. package/src/utils/edit-mode.ts +1 -2
  152. package/src/utils/file-mentions.ts +2 -2
  153. package/src/web/kagi.ts +15 -6
  154. package/src/web/parallel.ts +9 -6
  155. package/src/web/scrapers/types.ts +7 -1
  156. package/src/web/scrapers/youtube.ts +13 -7
  157. package/src/web/search/index.ts +37 -11
  158. package/src/web/search/provider.ts +5 -3
  159. package/src/web/search/providers/anthropic.ts +30 -21
  160. package/src/web/search/providers/base.ts +35 -2
  161. package/src/web/search/providers/brave.ts +4 -4
  162. package/src/web/search/providers/codex.ts +118 -89
  163. package/src/web/search/providers/exa.ts +3 -2
  164. package/src/web/search/providers/gemini.ts +58 -155
  165. package/src/web/search/providers/jina.ts +4 -4
  166. package/src/web/search/providers/kagi.ts +17 -11
  167. package/src/web/search/providers/kimi.ts +29 -13
  168. package/src/web/search/providers/parallel.ts +171 -23
  169. package/src/web/search/providers/perplexity.ts +38 -37
  170. package/src/web/search/providers/searxng.ts +3 -1
  171. package/src/web/search/providers/synthetic.ts +16 -19
  172. package/src/web/search/providers/tavily.ts +23 -18
  173. package/src/web/search/providers/utils.ts +11 -17
  174. package/src/web/search/providers/zai.ts +16 -8
  175. package/dist/types/hashline/parser.d.ts +0 -7
  176. package/dist/types/mcp/discoverable-tool-metadata.d.ts +0 -7
  177. package/dist/types/tools/vim.d.ts +0 -58
  178. package/dist/types/vim/buffer.d.ts +0 -41
  179. package/dist/types/vim/commands.d.ts +0 -6
  180. package/dist/types/vim/engine.d.ts +0 -47
  181. package/dist/types/vim/parser.d.ts +0 -3
  182. package/dist/types/vim/render.d.ts +0 -25
  183. package/dist/types/vim/types.d.ts +0 -182
  184. package/src/hashline/parser.ts +0 -246
  185. package/src/mcp/discoverable-tool-metadata.ts +0 -24
  186. package/src/prompts/tools/vim.md +0 -98
  187. package/src/tools/vim.ts +0 -949
  188. package/src/vim/buffer.ts +0 -309
  189. package/src/vim/commands.ts +0 -382
  190. package/src/vim/engine.ts +0 -2409
  191. package/src/vim/parser.ts +0 -134
  192. package/src/vim/render.ts +0 -252
  193. package/src/vim/types.ts +0 -197
@@ -143,11 +143,6 @@ export interface DashboardState {
143
143
  selected: Extension | null;
144
144
  }
145
145
 
146
- /**
147
- * @deprecated Use FocusRegion instead
148
- */
149
- export type FocusPane = "sidebar" | "main" | "inspector";
150
-
151
146
  /**
152
147
  * Callbacks from dashboard to parent.
153
148
  */
@@ -22,6 +22,7 @@ import { isSilentAbort } from "../../session/messages";
22
22
  import type { SessionMessageEntry } from "../../session/session-manager";
23
23
  import { parseSessionEntries } from "../../session/session-manager";
24
24
  import { PREVIEW_LIMITS, replaceTabs, TRUNCATE_LENGTHS, truncateToWidth } from "../../tools/render-utils";
25
+ import { toPathList } from "../../tools/search";
25
26
  import type { ObservableSession, SessionObserverRegistry } from "../session-observer-registry";
26
27
  import { getMarkdownTheme, theme } from "../theme/theme";
27
28
  import { DynamicBorder } from "./dynamic-border";
@@ -533,13 +534,21 @@ export class SessionObserverOverlayComponent extends Container {
533
534
  case "write":
534
535
  case "edit":
535
536
  return args.path ? `path: ${args.path}` : "";
536
- case "search":
537
+ case "search": {
538
+ const searchPathsInput =
539
+ typeof args.paths === "string" || Array.isArray(args.paths)
540
+ ? args.paths
541
+ : typeof args.path === "string"
542
+ ? args.path
543
+ : undefined;
544
+ const searchPaths = toPathList(searchPathsInput);
537
545
  return [
538
546
  args.pattern ? `pattern: ${args.pattern}` : "",
539
- Array.isArray(args.paths) ? `paths: ${args.paths.join(", ")}` : "",
547
+ searchPaths.length > 0 ? `paths: ${searchPaths.join(", ")}` : "",
540
548
  ]
541
549
  .filter(Boolean)
542
550
  .join(", ");
551
+ }
543
552
  case "find":
544
553
  return Array.isArray(args.paths) ? `paths: ${args.paths.join(", ")}` : "";
545
554
  case "bash": {
@@ -13,7 +13,7 @@ import {
13
13
  TabBar,
14
14
  Text,
15
15
  } from "@oh-my-pi/pi-tui";
16
- import { type SettingPath, settings } from "../../config/settings";
16
+ import { getDefault, type SettingPath, settings } from "../../config/settings";
17
17
  import type {
18
18
  SettingTab,
19
19
  StatusLinePreset,
@@ -294,6 +294,7 @@ export class SettingsSelectorComponent extends Container {
294
294
  }
295
295
 
296
296
  const currentValue = this.#getCurrentValue(def);
297
+ const changed = this.#isChanged(def, currentValue);
297
298
 
298
299
  switch (def.type) {
299
300
  case "boolean":
@@ -303,6 +304,7 @@ export class SettingsSelectorComponent extends Container {
303
304
  description: def.description,
304
305
  currentValue: currentValue ? "true" : "false",
305
306
  values: ["true", "false"],
307
+ changed,
306
308
  };
307
309
 
308
310
  case "enum":
@@ -312,6 +314,7 @@ export class SettingsSelectorComponent extends Container {
312
314
  description: def.description,
313
315
  currentValue: currentValue as string,
314
316
  values: [...def.values],
317
+ changed,
315
318
  };
316
319
 
317
320
  case "submenu":
@@ -321,6 +324,7 @@ export class SettingsSelectorComponent extends Container {
321
324
  description: def.description,
322
325
  currentValue: this.#getSubmenuCurrentValue(def.path, currentValue),
323
326
  submenu: (cv, done) => this.#createSubmenu(def, cv, done),
327
+ changed,
324
328
  };
325
329
 
326
330
  case "text":
@@ -330,6 +334,7 @@ export class SettingsSelectorComponent extends Container {
330
334
  description: def.description,
331
335
  currentValue: (currentValue as string) ?? "",
332
336
  submenu: (cv, done) => this.#createTextInput(def, cv, done),
337
+ changed,
333
338
  };
334
339
  }
335
340
  }
@@ -341,6 +346,10 @@ export class SettingsSelectorComponent extends Container {
341
346
  return settings.get(def.path);
342
347
  }
343
348
 
349
+ #isChanged(def: SettingDef, currentValue: unknown): boolean {
350
+ return !Object.is(currentValue, getDefault(def.path));
351
+ }
352
+
344
353
  #getSubmenuCurrentValue(path: SettingPath, value: unknown): string {
345
354
  const rawValue = String(value ?? "");
346
355
  if (path === "compaction.thresholdPercent" && (rawValue === "-1" || rawValue === "")) {
@@ -15,6 +15,7 @@ import { theme } from "../../modes/theme/theme";
15
15
  import { matchesAppInterrupt } from "../../modes/utils/keybinding-matchers";
16
16
  import type { SessionTreeNode } from "../../session/session-manager";
17
17
  import { shortenPath } from "../../tools/render-utils";
18
+ import { toPathList } from "../../tools/search";
18
19
  import { DynamicBorder } from "./dynamic-border";
19
20
 
20
21
  /** Gutter info: position (displayIndent where connector was) and whether to show │ */
@@ -690,8 +691,15 @@ class TreeList implements Component {
690
691
  }
691
692
  case "search": {
692
693
  const pattern = String(args.pattern || "");
693
- const paths = Array.isArray(args.paths) ? args.paths.join(", ") : String(args.path || ".");
694
- return `[search: /${pattern}/ in ${shortenPath(paths)}]`;
694
+ const searchPathsInput =
695
+ typeof args.paths === "string" || Array.isArray(args.paths)
696
+ ? args.paths
697
+ : typeof args.path === "string"
698
+ ? args.path
699
+ : undefined;
700
+ const paths = toPathList(searchPathsInput);
701
+ const scope = paths.length > 0 ? paths.join(", ") : ".";
702
+ return `[search: /${pattern}/ in ${shortenPath(scope)}]`;
695
703
  }
696
704
  case "find": {
697
705
  const paths = Array.isArray(args.paths) ? args.paths.join(", ") : String(args.pattern || ".");
@@ -894,8 +894,6 @@ export class CommandController {
894
894
  this.ctx.statusLine.setSessionStartTime(Date.now());
895
895
  this.ctx.updateEditorTopBorder();
896
896
  this.ctx.updateEditorBorderColor();
897
- this.ctx.ui.requestRender();
898
-
899
897
  this.ctx.chatContainer.clear();
900
898
  this.ctx.pendingMessagesContainer.clear();
901
899
  this.ctx.compactionQueuedMessages = [];
@@ -906,7 +904,7 @@ export class CommandController {
906
904
  this.ctx.chatContainer.addChild(new Spacer(1));
907
905
  this.ctx.chatContainer.addChild(new Text(`${theme.fg("accent", `${theme.status.success} ${label}`)}`, 1, 1));
908
906
  await this.ctx.reloadTodos();
909
- this.ctx.ui.requestRender();
907
+ this.ctx.ui.requestRender(true, { clearScrollback: true });
910
908
  }
911
909
 
912
910
  async handleClearCommand(): Promise<void> {
@@ -135,7 +135,7 @@ export class ExtensionUiController {
135
135
  reload: async () => {
136
136
  await this.ctx.session.reload();
137
137
  this.ctx.chatContainer.clear();
138
- this.ctx.renderInitialMessages();
138
+ this.ctx.renderInitialMessages(undefined, { clearTerminalHistory: true });
139
139
  await this.ctx.reloadTodos();
140
140
  this.ctx.showStatus("Reloaded session");
141
141
  },
@@ -180,7 +180,7 @@ export class ExtensionUiController {
180
180
  new Text(`${theme.fg("accent", `${theme.status.success} New session started`)}`, 1, 1),
181
181
  );
182
182
  await this.ctx.reloadTodos();
183
- this.ctx.ui.requestRender();
183
+ this.ctx.ui.requestRender(true, { clearScrollback: true });
184
184
 
185
185
  return { cancelled: false };
186
186
  },
@@ -192,7 +192,7 @@ export class ExtensionUiController {
192
192
 
193
193
  // Update UI
194
194
  this.ctx.chatContainer.clear();
195
- this.ctx.renderInitialMessages();
195
+ this.ctx.renderInitialMessages(undefined, { clearTerminalHistory: true });
196
196
  await this.ctx.reloadTodos();
197
197
  this.ctx.editor.setText(result.selectedText);
198
198
  this.ctx.showStatus("Branched to new session");
@@ -207,7 +207,7 @@ export class ExtensionUiController {
207
207
 
208
208
  // Update UI
209
209
  this.ctx.chatContainer.clear();
210
- this.ctx.renderInitialMessages();
210
+ this.ctx.renderInitialMessages(undefined, { clearTerminalHistory: true });
211
211
  await this.ctx.reloadTodos();
212
212
  if (result.editorText && !this.ctx.editor.getText().trim()) {
213
213
  this.ctx.editor.setText(result.editorText);
@@ -225,7 +225,7 @@ export class ExtensionUiController {
225
225
  }
226
226
  setSessionTerminalTitle(this.ctx.sessionManager.getSessionName(), this.ctx.sessionManager.getCwd());
227
227
  this.ctx.chatContainer.clear();
228
- this.ctx.renderInitialMessages();
228
+ this.ctx.renderInitialMessages(undefined, { clearTerminalHistory: true });
229
229
  await this.ctx.reloadTodos();
230
230
  return { cancelled: false };
231
231
  },
@@ -378,7 +378,7 @@ export class ExtensionUiController {
378
378
  }
379
379
  await this.ctx.session.reload();
380
380
  this.ctx.chatContainer.clear();
381
- this.ctx.renderInitialMessages();
381
+ this.ctx.renderInitialMessages(undefined, { clearTerminalHistory: true });
382
382
  await this.ctx.reloadTodos();
383
383
  this.ctx.showStatus("Reloaded session");
384
384
  },
@@ -419,7 +419,7 @@ export class ExtensionUiController {
419
419
  new Text(`${theme.fg("accent", `${theme.status.success} New session started`)}`, 1, 1),
420
420
  );
421
421
  await this.ctx.reloadTodos();
422
- this.ctx.ui.requestRender();
422
+ this.ctx.ui.requestRender(true, { clearScrollback: true });
423
423
 
424
424
  return { cancelled: false };
425
425
  },
@@ -434,7 +434,7 @@ export class ExtensionUiController {
434
434
 
435
435
  // Update UI
436
436
  this.ctx.chatContainer.clear();
437
- this.ctx.renderInitialMessages();
437
+ this.ctx.renderInitialMessages(undefined, { clearTerminalHistory: true });
438
438
  await this.ctx.reloadTodos();
439
439
  this.ctx.editor.setText(result.selectedText);
440
440
  this.ctx.showStatus("Branched to new session");
@@ -452,7 +452,7 @@ export class ExtensionUiController {
452
452
 
453
453
  // Update UI
454
454
  this.ctx.chatContainer.clear();
455
- this.ctx.renderInitialMessages();
455
+ this.ctx.renderInitialMessages(undefined, { clearTerminalHistory: true });
456
456
  await this.ctx.reloadTodos();
457
457
  if (result.editorText && !this.ctx.editor.getText().trim()) {
458
458
  this.ctx.editor.setText(result.editorText);
@@ -472,7 +472,7 @@ export class ExtensionUiController {
472
472
  return { cancelled: true };
473
473
  }
474
474
  this.ctx.chatContainer.clear();
475
- this.ctx.renderInitialMessages();
475
+ this.ctx.renderInitialMessages(undefined, { clearTerminalHistory: true });
476
476
  await this.ctx.reloadTodos();
477
477
  return { cancelled: false };
478
478
  },
@@ -537,7 +537,6 @@ export class ExtensionUiController {
537
537
  model: this.ctx.session.model,
538
538
  isIdle: () => !this.ctx.session.isStreaming,
539
539
  hasPendingMessages: () => this.ctx.session.queuedMessageCount > 0,
540
- hasQueuedMessages: () => this.ctx.session.queuedMessageCount > 0,
541
540
  abort: () => {
542
541
  this.ctx.session.abort();
543
542
  },
@@ -553,7 +553,7 @@ export class SelectorController {
553
553
  }
554
554
 
555
555
  this.ctx.chatContainer.clear();
556
- this.ctx.renderInitialMessages();
556
+ this.ctx.renderInitialMessages(undefined, { clearTerminalHistory: true });
557
557
  this.ctx.editor.setText(result.selectedText);
558
558
  done();
559
559
  this.ctx.showStatus("Branched to new session");
@@ -664,7 +664,7 @@ export class SelectorController {
664
664
 
665
665
  // Update UI — pass the context built by navigateTree to skip a second O(N) walk.
666
666
  this.ctx.chatContainer.clear();
667
- this.ctx.renderInitialMessages(result.sessionContext);
667
+ this.ctx.renderInitialMessages(result.sessionContext, { clearTerminalHistory: true });
668
668
  await this.ctx.reloadTodos();
669
669
  if (result.editorText && !this.ctx.editor.getText().trim()) {
670
670
  this.ctx.editor.setText(result.editorText);
@@ -772,9 +772,9 @@ export class SelectorController {
772
772
  this.ctx.statusLine.setSessionStartTime(Date.now());
773
773
  this.ctx.updateEditorTopBorder();
774
774
  this.ctx.updateEditorBorderColor();
775
- this.ctx.renderInitialMessages();
775
+ this.ctx.renderInitialMessages(undefined, { clearTerminalHistory: true });
776
776
  await this.ctx.reloadTodos();
777
- this.ctx.ui.requestRender();
777
+ this.ctx.ui.requestRender(true, { clearScrollback: true });
778
778
  return true;
779
779
  }
780
780
 
@@ -788,7 +788,7 @@ export class SelectorController {
788
788
 
789
789
  // Clear and re-render the chat
790
790
  this.ctx.chatContainer.clear();
791
- this.ctx.renderInitialMessages();
791
+ this.ctx.renderInitialMessages(undefined, { clearTerminalHistory: true });
792
792
  await this.ctx.reloadTodos();
793
793
  this.ctx.showStatus("Resumed session");
794
794
  }
@@ -2404,8 +2404,10 @@ export function getEditorTheme(): EditorTheme {
2404
2404
 
2405
2405
  export function getSettingsListTheme(): import("@oh-my-pi/pi-tui").SettingsListTheme {
2406
2406
  return {
2407
- label: (text: string, selected: boolean) => (selected ? theme.fg("accent", text) : text),
2408
- value: (text: string, selected: boolean) => (selected ? theme.fg("accent", text) : theme.fg("muted", text)),
2407
+ label: (text: string, selected: boolean, changed: boolean) =>
2408
+ changed ? theme.fg("statusLineGitDirty", text) : selected ? theme.fg("accent", text) : text,
2409
+ value: (text: string, selected: boolean, changed: boolean) =>
2410
+ selected ? theme.fg("accent", text) : changed ? theme.fg("statusLineGitDirty", text) : theme.fg("muted", text),
2409
2411
  description: (text: string) => theme.fg("dim", text),
2410
2412
  cursor: theme.fg("accent", `${theme.nav.cursor} `),
2411
2413
  hint: (text: string) => theme.fg("dim", text),
@@ -186,7 +186,10 @@ export interface InteractiveModeContext {
186
186
  sessionContext: SessionContext,
187
187
  options?: { updateFooter?: boolean; populateHistory?: boolean },
188
188
  ): void;
189
- renderInitialMessages(prebuiltContext?: SessionContext, options?: { preserveExistingChat?: boolean }): void;
189
+ renderInitialMessages(
190
+ prebuiltContext?: SessionContext,
191
+ options?: { preserveExistingChat?: boolean; clearTerminalHistory?: boolean },
192
+ ): void;
190
193
  getUserMessageText(message: Message): string;
191
194
  findLastAssistantMessage(): AssistantMessage | undefined;
192
195
  extractAssistantText(message: AssistantMessage): string;
@@ -31,6 +31,7 @@ import { formatBytes, formatDuration } from "../../tools/render-utils";
31
31
  type TextBlock = { type: "text"; text: string };
32
32
  interface RenderInitialMessagesOptions {
33
33
  preserveExistingChat?: boolean;
34
+ clearTerminalHistory?: boolean;
34
35
  }
35
36
 
36
37
  type QueuedMessages = {
@@ -490,6 +491,9 @@ export class UiHelpers {
490
491
  const times = compactionCount === 1 ? "1 time" : `${compactionCount} times`;
491
492
  this.ctx.showStatus(`Session compacted ${times}`);
492
493
  }
494
+ if (options.clearTerminalHistory) {
495
+ this.ctx.ui.requestRender(true, { clearScrollback: true });
496
+ }
493
497
  if (preservedChatChildren && preservedChatChildren.length > 0) {
494
498
  for (const child of preservedChatChildren) {
495
499
  this.ctx.chatContainer.addChild(child);
@@ -15,7 +15,7 @@ output:
15
15
  description: Files examined with relevant code references
16
16
  elements:
17
17
  properties:
18
- ref:
18
+ path:
19
19
  metadata:
20
20
  description: Project-relative path or paths to the most relevant code reference(s), optionally suffixed with line ranges like `:12-34` when relevant
21
21
  type: string
@@ -14,7 +14,7 @@ Performs structural AST-aware rewrites via native ast-grep.
14
14
  </instruction>
15
15
 
16
16
  <output>
17
- - Replacement summary, per-file replacement counts, and change diffs as `-LINE+ID|before` / `+LINE+ID|after` lines
17
+ - Replacement summary, per-file replacement counts, and change diffs as `¶src/foo.ts#1a2b`, `-12:before`, `+12:after` lines in hashline mode
18
18
  - Parse issues when files cannot be processed
19
19
  </output>
20
20
 
@@ -18,7 +18,7 @@ Performs structural code search using AST matching via native ast-grep.
18
18
 
19
19
  <output>
20
20
  - Grouped matches with file path, byte range, line/column ranges, metavariable captures
21
- - Match lines are anchor-prefixed: `*LINE+ID|content` for the matched line and ` LINE+ID|content` (leading space) for surrounding context
21
+ - Match lines are numbered under a file-hash header in hashline mode: `¶src/foo.ts#1a2b`, `*42:content` for the matched line, ` 43:content` for context
22
22
  - Summary counts (`totalMatches`, `filesWithMatches`, `filesSearched`) and parse issues when present
23
23
  </output>
24
24
 
@@ -1,7 +1,7 @@
1
1
  Run code in a persistent kernel using a list of cells.
2
2
 
3
3
  <instruction>
4
- Each call submits one or more cells. Cells run in array order. State persists within each language across cells **and across tool calls**.
4
+ Each call submits one or more cells. Cells run in array order. State persists within each language across cells, tool calls, and subagents spawned with `task`; variables a parent or subagent declares are visible to the other on the same shared executor.
5
5
 
6
6
  Cell fields:
7
7
 
@@ -1,130 +1,109 @@
1
1
  Your patch language is a compact, line-anchored edit format.
2
2
 
3
- A patch contains one or more file sections. The first non-blank line of every edit section MUST be `§PATH`.
4
- Operations reference lines in the file by their line number and hash, called "Anchors", e.g. `5th`, `123ab`.
5
- You MUST copy them verbatim from the latest output for the file you're editing.
3
+ A patch contains one or more file sections. Each anchored section starts with `¶PATH#HASH`, copied verbatim from the latest `read`/`search` output. `HASH` is a 4-hex file hash; `¶PATH` without `#HASH` is allowed only for new-file / `BOF` / `EOF` boundary inserts.
6
4
 
7
- Purely textual format. The tool has NO awareness of language, indentation, brackets, fences, or table widths. You MUST emit valid syntax in replacements/insertions.
5
+ Operations reference lines by bare line number (`5`, `123`). Payload text is verbatim — NEVER escape unicode. The tool has NO awareness of language, indentation, brackets, fences, or table widths. Emit valid syntax in replacements/insertions.
8
6
 
9
7
  <ops>
10
- §PATH header: subsequent ops apply to PATH
11
- Each op line is ONE of:
12
- »ANCHOR insert lines AFTER the anchored line (or EOF); payload follows on subsequent lines
13
- «ANCHOR insert lines BEFORE the anchored line (or BOF); payload follows on subsequent lines
14
- A..B replace the inclusive range A..B with payload; delete the range if no payload follows
15
- A shorthand for A..A
8
+ PATH#HASH header: subsequent anchored ops apply to PATH at file hash HASH
9
+ ¶PATH unbound header: only BOF/EOF boundary inserts
10
+ LINE↑PAYLOAD insert ABOVE the anchored line (or BOF)
11
+ LINE↓PAYLOAD insert BELOW the anchored line (or EOF)
12
+ A-B:PAYLOAD replace the inclusive range A..B with PAYLOAD
13
+ A:PAYLOAD shorthand for A-A:PAYLOAD
14
+ A-B! delete the inclusive range A..B (payload forbidden)
15
+ A! shorthand for A-A!
16
16
  </ops>
17
17
 
18
+ <payload>
19
+ - The first payload line is whatever follows the sigil on the op line. Additional payload lines follow on the next lines and append after the first.
20
+ - An empty inline IS an empty first line. So bare `A↓` / `A↑` insert one blank line; bare `A:` / `A-B:` replace with one blank line. `A↓\nfoo` inserts blank-then-`foo`, NOT just `foo`.
21
+ - Payload ends at the next op, next `¶PATH`, envelope marker, or EOF. Blank lines immediately before a next op or `¶PATH` are dropped; blank lines between content lines are preserved.
22
+ </payload>
23
+
18
24
  <rules>
19
- - Payload text is verbatim NEVER escape unicode.
20
- - Payload ends at the next `»`, `«`, `≔`, `§`, envelope marker, or EOF.
21
- - `≔A..B` with no payload deletes the range. To keep a blank line, include one explicit empty payload line.
22
- - **Payload is only what's NEW relative to your range:**
23
- - `≔` replaces inside; NEVER include lines outside.
24
- - `»`/`«` adds at the anchor; NEVER repeat line A or neighbors.
25
- - Payload matching nearby content duplicates — drop it or widen.
26
- - **Pick a self-contained unit first.** Touching a multiline construct? Widen to the whole thing.
27
- - Then smallest op: add → `»`/`«`; delete/replace → `≔`.
25
+ - The sigil tells where content lands: `↑` above, `↓` below, `:` replaces, `!` deletes.
26
+ - **Payload is only what's NEW relative to your range.** `:` replaces inside; `↑`/`↓` add at anchor. NEVER repeat the anchor line or neighbors that duplicates them.
27
+ - **Pick a self-contained unit.** Touching a multiline construct (return, array, brace block, JSX element)? Widen the range to span it. Don't bisect.
28
+ - Smallest op wins: add with `↑`/`↓`; replace with `:`; delete with `!`.
29
+ - Anchors reference the file as last read. ONE patch, ONE coordinate space — later ops still use original line numbers.
28
30
  </rules>
29
31
 
30
- <brace-shapes>
31
- When braces bound your edit, you SHOULD prefer these shapes:
32
- - **Whole block**: range spans `{` through matching `}`.
33
- - **Signature only**: one-line `≔` on the opener; body untouched.
34
- - **Insert inside**: anchor on `{` or last interior line; NEVER repeat the braces.
35
- - **End on `}`**: only when that `}` is part of the change. Otherwise extend or stop earlier.
36
- </brace-shapes>
37
-
38
32
  <common-failures>
39
33
  - **NEVER replay past your range.** Stop before B+1; extend B if it must go.
40
- - **NEVER duplicate chunks inside one payload.** Caught re-emitting? Rewrite.
41
- - **Anchor only inside the visible region.** B+1 truncated? Re-`read` first.
42
- - **You SHOULD prefer the narrowest self-contained edit.** Narrow range beats wide range.
43
- - **Anchors reference the file as last read.** NEVER shift for prior ops.
44
- - **One `»`/`«` op per block, NOT per line.** N lines = ONE op, N payloads. Collapse adjacent ops.
45
- - **NEVER fabricate anchor hashes.** Missing? Re-`read`.
34
+ - **NEVER duplicate chunks inside one payload.**
35
+ - **Read lines look like replace ops.** `84:content` already means "make line 84 equal to content" — don't echo a context line before it.
36
+ - **NEVER fabricate file hashes.** Missing? Re-`read`.
37
+ - **`A!` deletes silently.** Deleting a line that closes/opens a block (`}`, `} else {`, `})`, `*/`) breaks structure with no parse error.
46
38
  </common-failures>
47
39
 
48
40
  <case file="mod.ts">
49
- {{hline 1 "const TITLE = \"Mr\";"}}
50
- {{hline 2 "export function greet(name) {"}}
51
- {{hline 3 "\treturn ["}}
52
- {{hline 4 "\t\tTITLE,"}}
53
- {{hline 5 "\t\tname?.trim() || \"guest\","}}
54
- {{hline 6 "\t].join(\" \");"}}
41
+ ¶mod.ts#1a2b
42
+ {{hline 1 'const TITLE = "Mr";'}}
43
+ {{hline 2 'export function greet(name) {'}}
44
+ {{hline 3 ' return ['}}
45
+ {{hline 4 ' TITLE,'}}
46
+ {{hline 5 ' name?.trim() || "guest",'}}
47
+ {{hline 6 ' ].join(" ");'}}
55
48
  {{hline 7 "}"}}
56
49
  </case>
57
50
 
58
51
  <examples>
59
- # Replace one line (the payload must re-emit the original indentation)
60
- §mod.ts
61
- {{hrefr 1}}
62
- const TITLE = "Mrs";
63
-
64
- # Replace a full multiline statement (widen to a self-contained boundary)
65
- §mod.ts
66
- ≔{{hrefr 3}}..{{hrefr 6}}
67
- return [
52
+ # Replace one line (inline payload preserves original indentation)
53
+ mod.ts#1a2b
54
+ {{hrefr 1}}:const TITLE = "Mrs";
55
+
56
+ # Replace a multiline statement — first line inline, rest below
57
+ ¶mod.ts#1a2b
58
+ {{hrefr 3}}-{{hrefr 6}}: return [
68
59
  "Mrs",
69
60
  name?.trim() || "guest",
70
61
  ].join(" ");
71
62
 
72
- # Insert AFTER/BEFORE a line
73
- §mod.ts
74
- »{{hrefr 4}}
75
- "Dr",
76
- «{{hrefr 5}}
77
- "Dr",
78
-
79
- # Append to file
80
- §mod.ts
81
- »EOF
82
- export const done = true;
83
-
84
- # Delete a line
85
- §mod.ts
86
- ≔{{hrefr 5}}
87
-
88
- # Blank a line (replace with LF: the empty payload is the blank line before `»EOF`)
89
- §mod.ts
90
- ≔{{hrefr 5}}
91
-
92
- »EOF
93
- export const done = true;
63
+ # Insert ABOVE / BELOW a line
64
+ mod.ts#1a2b
65
+ {{hrefr 4}}↓ "Dr",
66
+ {{hrefr 5}}↑ "Dr",
67
+
68
+ # Delete one line / blank a line / insert a blank line
69
+ ¶mod.ts#1a2b
70
+ {{hrefr 5}}!
71
+ {{hrefr 6}}:
72
+ {{hrefr 7}}↑
73
+
74
+ # Create a file / append to one (hash optional for boundary-only inserts)
75
+ ¶new.ts
76
+ BOF↓export const done = true;
77
+ ¶mod.ts
78
+ EOF↓export const done = true;
79
+
80
+ # Multi-file patch
81
+ ¶src/a.ts#1a2b
82
+ 12:const enabled = true;
83
+ ¶src/b.ts#3c4d
84
+ 20!
94
85
  </examples>
95
86
 
96
87
  <anti-pattern>
97
88
  # WRONG — replaces 2 lines just to add one.
98
- §mod.ts
99
- {{hrefr 1}}..{{hrefr 2}}
100
- const TITLE = "Mr";
89
+ mod.ts#1a2b
90
+ {{hrefr 1}}-{{hrefr 2}}:const TITLE = "Mr";
101
91
  const DEBUG = false;
102
92
  export function greet(name) {
103
- # RIGHT — same effect, one-line insert
104
- §mod.ts
105
- »{{hrefr 1}}
106
- const DEBUG = false;
107
93
 
108
- # WRONGreplace from the middle of a larger statement (error-prone)
109
- §mod.ts
110
- {{hrefr 4}}..{{hrefr 5}}
111
- "Dr",
94
+ # RIGHTone-line insert
95
+ mod.ts#1a2b
96
+ {{hrefr 1}}↓const DEBUG = false;
97
+
98
+ # WRONG — bisects a multiline statement
99
+ ¶mod.ts#1a2b
100
+ {{hrefr 4}}-{{hrefr 5}}: "Dr",
112
101
  name?.trim() || "guest",
102
+
113
103
  # RIGHT — widen to the full statement
114
- §mod.ts
115
- {{hrefr 3}}..{{hrefr 6}}
116
- return [
104
+ mod.ts#1a2b
105
+ {{hrefr 3}}-{{hrefr 6}}: return [
117
106
  "Dr",
118
107
  name?.trim() || "guest",
119
108
  ].join(" ");
120
109
  </anti-pattern>
121
-
122
- <critical>
123
- - Copy anchors verbatim (line number + 2-char hash); NEVER include the `|TEXT` body.
124
- - NEVER write unified diff syntax. Headers are `§PATH`; ops are `»`/`«`/`≔`.
125
- - `≔A..B` deletes the range when no payload follows. To keep a blank line, include one explicit empty payload line.
126
- - `≔A..B` with payload writes exactly that payload. Edge line matches just outside? Widen, or it duplicates.
127
- - Multiple ops are cheap. SHOULD prefer two narrow ops over one wide `≔`.
128
- - Before `≔A..B`, mentally delete A..B. Splits an unclosed bracket/brace/string from above, or orphans a closer inside? You're bisecting a construct.
129
- - NEVER use this tool to reformat code (indentation, whitespace, line wrapping, style). Run the project's formatter instead.
130
- </critical>
@@ -28,17 +28,17 @@ Append `:<sel>` to `path`. The bare path falls back to the default mode.
28
28
 
29
29
  - Reading a directory path returns a depth-limited dirent listing.
30
30
  {{#if IS_HL_MODE}}
31
- - Reading a file with an explicit selector returns lines prefixed with `line+hash` anchors: `41th|def alpha():`. The 2-char hash is a content fingerprint that `edit` / `apply_patch` consume — copy it verbatim, NEVER fabricate. The pipe character after the hash is a separator, not part of the file content.
31
+ - Reading a file with an explicit selector emits a file-hash header and numbered lines: `¶src/foo.ts#1a2b` then `41:def alpha():`. Copy the `¶PATH#HASH` header for anchored edits; ops use bare line numbers. NEVER fabricate the hash.
32
32
  {{else}}
33
33
  {{#if IS_LINE_NUMBER_MODE}}
34
34
  - Reading a file with an explicit selector returns lines prefixed with line numbers: `41|def alpha():`.
35
35
  {{/if}}
36
36
  {{/if}}
37
- - Parseable code without a selector returns a **structural summary**: declarations kept, large bodies collapsed to `..` (merged brace pair) or `…` (standalone). Summarized output ends with a footer of the form:
37
+ - Parseable code without a selector returns a **structural summary**: declarations kept, large bodies collapsed to `..` (merged brace pair) or `…` (standalone). Summarized output ends with a footer demonstrating the multi-range selector you can use to recover the elided bodies, e.g.:
38
38
 
39
- `[NN lines across MM elided regions; read <path>:raw or a line range like <path>:1-9999 for verbatim content]`
39
+ `[NN lines elided; re-read needed ranges, e.g. <path>:5-16,40-80]`
40
40
 
41
- If the elided body is what you actually need, re-issue the **exact selector the footer names**. NEVER guess what's inside `..` / `…` — those markers carry no content.
41
+ Re-issue **only the relevant range(s)** using the multi-range selector (e.g. `<path>:5-16,120-200`). NEVER guess what's inside `..` / `…` — those markers carry no content. NEVER re-read the whole file or use `:raw` when targeted ranges suffice.
42
42
 
43
43
  # Documents & Notebooks
44
44
 
@@ -2,14 +2,14 @@ Searches files using powerful regex matching.
2
2
 
3
3
  <instruction>
4
4
  - Supports Rust regex syntax (RE2-style — no lookaround or backreferences). Use line anchors or post-filters instead of (?!…)/(?<!…)
5
- - `paths` is required and accepts an array of files, directories, globs, or internal URLs
6
- - `paths` is an array; do not embed commas or spaces inside a single entry. Pass `["src", "tests"]` not `["src,tests"]`.
5
+ - `paths` is required and accepts either one string or an array of files, directories, globs, or internal URLs
6
+ - For multiple targets, pass an array with one target per element. Do not comma-join targets inside one string: pass `["src", "tests"]`, not `"src,tests"` or `["src,tests"]`.
7
7
  - Cross-line patterns are detected from literal `\n` or escaped `\\n` in `pattern`
8
8
  </instruction>
9
9
 
10
10
  <output>
11
11
  {{#if IS_HL_MODE}}
12
- - Text output is anchor-prefixed: `*5th|content` (match) or ` 9x}|content` (context, leading space). The 2-char suffix is a content fingerprint. The `|` before content is a separator, not part of the file content.
12
+ - Text output emits a file-hash header per matched file plus numbered lines: `¶src/login.ts#3c4d`, `*42:if (user.id) {` (match), ` 43:return user;` (context). Copy the header for anchored edits; ops use bare line numbers.
13
13
  {{else}}
14
14
  {{#if IS_LINE_NUMBER_MODE}}
15
15
  - Text output is line-number-prefixed