@oh-my-pi/pi-coding-agent 15.3.1 → 15.4.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 (200) hide show
  1. package/CHANGELOG.md +119 -0
  2. package/dist/types/cli/auth-gateway-cli.d.ts +1 -1
  3. package/dist/types/cli/file-processor.d.ts +1 -1
  4. package/dist/types/config/settings-schema.d.ts +45 -3
  5. package/dist/types/config/settings.d.ts +1 -1
  6. package/dist/types/debug/raw-sse.d.ts +2 -0
  7. package/dist/types/edit/file-read-cache.d.ts +15 -4
  8. package/dist/types/edit/index.d.ts +3 -8
  9. package/dist/types/edit/renderer.d.ts +1 -2
  10. package/dist/types/eval/__tests__/shared-executors.test.d.ts +1 -0
  11. package/dist/types/eval/js/shared/local-module-loader.d.ts +16 -0
  12. package/dist/types/eval/js/shared/rewrite-imports.d.ts +4 -0
  13. package/dist/types/eval/js/shared/runtime.d.ts +14 -8
  14. package/dist/types/eval/py/executor.d.ts +1 -2
  15. package/dist/types/eval/py/kernel.d.ts +6 -0
  16. package/dist/types/eval/py/tool-bridge.d.ts +1 -5
  17. package/dist/types/eval/session-id.d.ts +3 -0
  18. package/dist/types/extensibility/extensions/types.d.ts +1 -3
  19. package/dist/types/hashline/anchors.d.ts +15 -9
  20. package/dist/types/hashline/constants.d.ts +0 -2
  21. package/dist/types/hashline/diff.d.ts +1 -2
  22. package/dist/types/hashline/executor.d.ts +52 -0
  23. package/dist/types/hashline/hash.d.ts +44 -93
  24. package/dist/types/hashline/index.d.ts +2 -1
  25. package/dist/types/hashline/input.d.ts +2 -9
  26. package/dist/types/hashline/recovery.d.ts +3 -9
  27. package/dist/types/hashline/tokenizer.d.ts +91 -0
  28. package/dist/types/hashline/types.d.ts +5 -7
  29. package/dist/types/modes/components/extensions/types.d.ts +0 -4
  30. package/dist/types/modes/types.d.ts +1 -0
  31. package/dist/types/modes/utils/ui-helpers.d.ts +1 -0
  32. package/dist/types/sdk.d.ts +2 -0
  33. package/dist/types/session/agent-session.d.ts +11 -15
  34. package/dist/types/session/agent-storage.d.ts +11 -10
  35. package/dist/types/slash-commands/acp-builtins.d.ts +3 -3
  36. package/dist/types/slash-commands/types.d.ts +0 -5
  37. package/dist/types/task/executor.d.ts +2 -0
  38. package/dist/types/task/types.d.ts +8 -0
  39. package/dist/types/tool-discovery/tool-index.d.ts +0 -50
  40. package/dist/types/tools/index.d.ts +2 -8
  41. package/dist/types/tools/match-line-format.d.ts +4 -4
  42. package/dist/types/tools/output-schema-validator.d.ts +64 -0
  43. package/dist/types/tools/review.d.ts +13 -0
  44. package/dist/types/tools/search-tool-bm25.d.ts +1 -1
  45. package/dist/types/tools/search.d.ts +4 -3
  46. package/dist/types/utils/edit-mode.d.ts +1 -1
  47. package/dist/types/web/kagi.d.ts +4 -2
  48. package/dist/types/web/parallel.d.ts +4 -3
  49. package/dist/types/web/scrapers/types.d.ts +2 -1
  50. package/dist/types/web/search/index.d.ts +12 -4
  51. package/dist/types/web/search/provider.d.ts +2 -1
  52. package/dist/types/web/search/providers/anthropic.d.ts +9 -4
  53. package/dist/types/web/search/providers/base.d.ts +34 -2
  54. package/dist/types/web/search/providers/brave.d.ts +8 -1
  55. package/dist/types/web/search/providers/codex.d.ts +13 -9
  56. package/dist/types/web/search/providers/exa.d.ts +10 -1
  57. package/dist/types/web/search/providers/gemini.d.ts +20 -23
  58. package/dist/types/web/search/providers/jina.d.ts +2 -1
  59. package/dist/types/web/search/providers/kagi.d.ts +4 -1
  60. package/dist/types/web/search/providers/kimi.d.ts +10 -1
  61. package/dist/types/web/search/providers/parallel.d.ts +3 -2
  62. package/dist/types/web/search/providers/perplexity.d.ts +5 -2
  63. package/dist/types/web/search/providers/searxng.d.ts +2 -1
  64. package/dist/types/web/search/providers/synthetic.d.ts +5 -8
  65. package/dist/types/web/search/providers/tavily.d.ts +11 -4
  66. package/dist/types/web/search/providers/utils.d.ts +8 -6
  67. package/dist/types/web/search/providers/zai.d.ts +12 -3
  68. package/package.json +7 -7
  69. package/src/cli/auth-gateway-cli.ts +71 -2
  70. package/src/cli/file-processor.ts +12 -2
  71. package/src/cli.ts +0 -8
  72. package/src/commands/auth-gateway.ts +2 -0
  73. package/src/commands/commit.ts +8 -8
  74. package/src/config/prompt-templates.ts +6 -6
  75. package/src/config/settings-schema.ts +47 -3
  76. package/src/config/settings.ts +5 -5
  77. package/src/debug/raw-sse.ts +68 -3
  78. package/src/edit/file-read-cache.ts +68 -25
  79. package/src/edit/index.ts +6 -37
  80. package/src/edit/renderer.ts +9 -47
  81. package/src/edit/streaming.ts +43 -56
  82. package/src/eval/__tests__/shared-executors.test.ts +520 -0
  83. package/src/eval/js/context-manager.ts +64 -53
  84. package/src/eval/js/shared/local-module-loader.ts +265 -0
  85. package/src/eval/js/shared/prelude.txt +4 -0
  86. package/src/eval/js/shared/rewrite-imports.ts +85 -0
  87. package/src/eval/js/shared/runtime.ts +129 -86
  88. package/src/eval/js/worker-core.ts +23 -38
  89. package/src/eval/py/executor.ts +155 -84
  90. package/src/eval/py/kernel.ts +10 -1
  91. package/src/eval/py/prelude.py +22 -24
  92. package/src/eval/py/runner.py +203 -85
  93. package/src/eval/py/tool-bridge.ts +17 -10
  94. package/src/eval/session-id.ts +8 -0
  95. package/src/exec/bash-executor.ts +27 -16
  96. package/src/extensibility/extensions/runner.ts +0 -1
  97. package/src/extensibility/extensions/types.ts +1 -3
  98. package/src/extensibility/plugins/marketplace/manager.ts +20 -1
  99. package/src/hashline/anchors.ts +56 -65
  100. package/src/hashline/apply.ts +29 -31
  101. package/src/hashline/constants.ts +0 -3
  102. package/src/hashline/diff-preview.ts +4 -5
  103. package/src/hashline/diff.ts +30 -4
  104. package/src/hashline/execute.ts +91 -26
  105. package/src/hashline/executor.ts +239 -0
  106. package/src/hashline/grammar.lark +12 -10
  107. package/src/hashline/hash.ts +69 -114
  108. package/src/hashline/index.ts +2 -1
  109. package/src/hashline/input.ts +48 -41
  110. package/src/hashline/prefixes.ts +21 -11
  111. package/src/hashline/recovery.ts +63 -71
  112. package/src/hashline/stream.ts +2 -2
  113. package/src/hashline/tokenizer.ts +467 -0
  114. package/src/hashline/types.ts +6 -8
  115. package/src/internal-urls/docs-index.generated.ts +9 -8
  116. package/src/lsp/config.ts +87 -22
  117. package/src/modes/components/extensions/types.ts +0 -5
  118. package/src/modes/components/session-observer-overlay.ts +11 -2
  119. package/src/modes/components/tree-selector.ts +10 -2
  120. package/src/modes/controllers/command-controller.ts +1 -3
  121. package/src/modes/controllers/extension-ui-controller.ts +10 -11
  122. package/src/modes/controllers/selector-controller.ts +5 -5
  123. package/src/modes/types.ts +4 -1
  124. package/src/modes/utils/ui-helpers.ts +4 -0
  125. package/src/prompts/agents/explore.md +1 -1
  126. package/src/prompts/tools/ast-edit.md +1 -1
  127. package/src/prompts/tools/ast-grep.md +1 -1
  128. package/src/prompts/tools/eval.md +1 -1
  129. package/src/prompts/tools/hashline.md +73 -94
  130. package/src/prompts/tools/read.md +4 -4
  131. package/src/prompts/tools/search.md +3 -3
  132. package/src/sdk.ts +21 -24
  133. package/src/session/agent-session.ts +59 -66
  134. package/src/session/agent-storage.ts +13 -14
  135. package/src/slash-commands/acp-builtins.ts +3 -3
  136. package/src/slash-commands/types.ts +0 -6
  137. package/src/task/executor.ts +55 -57
  138. package/src/task/index.ts +8 -4
  139. package/src/task/render.ts +53 -1
  140. package/src/task/types.ts +8 -0
  141. package/src/tool-discovery/tool-index.ts +0 -134
  142. package/src/tools/ast-edit.ts +36 -13
  143. package/src/tools/ast-grep.ts +45 -4
  144. package/src/tools/browser/tab-worker.ts +3 -2
  145. package/src/tools/eval.ts +2 -1
  146. package/src/tools/fetch.ts +23 -14
  147. package/src/tools/index.ts +2 -8
  148. package/src/tools/irc.ts +59 -5
  149. package/src/tools/jtd-to-json-schema.ts +5 -1
  150. package/src/tools/match-line-format.ts +5 -7
  151. package/src/tools/output-schema-validator.ts +132 -0
  152. package/src/tools/read.ts +142 -63
  153. package/src/tools/review.ts +23 -0
  154. package/src/tools/search-tool-bm25.ts +3 -30
  155. package/src/tools/search.ts +48 -16
  156. package/src/tools/write.ts +3 -3
  157. package/src/tools/yield.ts +32 -41
  158. package/src/utils/edit-mode.ts +1 -2
  159. package/src/utils/file-mentions.ts +2 -2
  160. package/src/web/kagi.ts +15 -6
  161. package/src/web/parallel.ts +9 -6
  162. package/src/web/scrapers/types.ts +7 -1
  163. package/src/web/scrapers/youtube.ts +13 -7
  164. package/src/web/search/index.ts +37 -11
  165. package/src/web/search/provider.ts +5 -3
  166. package/src/web/search/providers/anthropic.ts +30 -21
  167. package/src/web/search/providers/base.ts +35 -2
  168. package/src/web/search/providers/brave.ts +4 -4
  169. package/src/web/search/providers/codex.ts +118 -89
  170. package/src/web/search/providers/exa.ts +3 -2
  171. package/src/web/search/providers/gemini.ts +58 -155
  172. package/src/web/search/providers/jina.ts +4 -4
  173. package/src/web/search/providers/kagi.ts +17 -11
  174. package/src/web/search/providers/kimi.ts +29 -13
  175. package/src/web/search/providers/parallel.ts +171 -23
  176. package/src/web/search/providers/perplexity.ts +38 -37
  177. package/src/web/search/providers/searxng.ts +3 -1
  178. package/src/web/search/providers/synthetic.ts +16 -19
  179. package/src/web/search/providers/tavily.ts +23 -18
  180. package/src/web/search/providers/utils.ts +11 -17
  181. package/src/web/search/providers/zai.ts +16 -8
  182. package/dist/types/hashline/parser.d.ts +0 -7
  183. package/dist/types/mcp/discoverable-tool-metadata.d.ts +0 -7
  184. package/dist/types/tools/vim.d.ts +0 -58
  185. package/dist/types/vim/buffer.d.ts +0 -41
  186. package/dist/types/vim/commands.d.ts +0 -6
  187. package/dist/types/vim/engine.d.ts +0 -47
  188. package/dist/types/vim/parser.d.ts +0 -3
  189. package/dist/types/vim/render.d.ts +0 -25
  190. package/dist/types/vim/types.d.ts +0 -182
  191. package/src/hashline/parser.ts +0 -212
  192. package/src/mcp/discoverable-tool-metadata.ts +0 -24
  193. package/src/prompts/tools/vim.md +0 -98
  194. package/src/tools/vim.ts +0 -949
  195. package/src/vim/buffer.ts +0 -309
  196. package/src/vim/commands.ts +0 -382
  197. package/src/vim/engine.ts +0 -2409
  198. package/src/vim/parser.ts +0 -134
  199. package/src/vim/render.ts +0 -252
  200. package/src/vim/types.ts +0 -197
package/src/lsp/config.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as os from "node:os";
3
3
  import * as path from "node:path";
4
- import { $which, isRecord, logger } from "@oh-my-pi/pi-utils";
4
+ import { $which, isRecord, logger, pathIsWithin } from "@oh-my-pi/pi-utils";
5
5
  import { YAML } from "bun";
6
6
  import { getConfigDirPaths } from "../config";
7
- import { getPreloadedPluginRoots } from "../discovery/helpers";
7
+ import { type ClaudePluginRoot, getPreloadedPluginRoots } from "../discovery/helpers";
8
8
  import { BiomeClient } from "./clients/biome-client";
9
9
  import { SwiftLintClient } from "./clients/swiftlint-client";
10
10
  import DEFAULTS from "./defaults.json" with { type: "json" };
@@ -22,8 +22,13 @@ export interface LspConfig {
22
22
 
23
23
  const PID_TOKEN = "$PID";
24
24
 
25
+ interface RawServerConfig extends Partial<ServerConfig> {
26
+ extensionToLanguage?: unknown;
27
+ initializationOptions?: unknown;
28
+ }
29
+
25
30
  interface NormalizedConfig {
26
- servers: Record<string, Partial<ServerConfig>>;
31
+ servers: Record<string, RawServerConfig>;
27
32
  idleTimeoutMs?: number;
28
33
  }
29
34
 
@@ -42,12 +47,12 @@ function normalizeConfig(value: unknown): NormalizedConfig | null {
42
47
  const rawServers = value.servers;
43
48
 
44
49
  if (isRecord(rawServers)) {
45
- return { servers: rawServers as Record<string, Partial<ServerConfig>>, idleTimeoutMs };
50
+ return { servers: rawServers as Record<string, RawServerConfig>, idleTimeoutMs };
46
51
  }
47
52
 
48
53
  const servers = Object.fromEntries(Object.entries(value).filter(([key]) => key !== "idleTimeoutMs")) as Record<
49
54
  string,
50
- Partial<ServerConfig>
55
+ RawServerConfig
51
56
  >;
52
57
 
53
58
  return { servers, idleTimeoutMs };
@@ -58,11 +63,17 @@ function normalizeStringArray(value: unknown): string[] | null {
58
63
  const items = value.filter((entry): entry is string => typeof entry === "string" && entry.length > 0);
59
64
  return items.length > 0 ? items : null;
60
65
  }
66
+ function normalizeExtensionToFileTypes(value: unknown): string[] | null {
67
+ if (!isRecord(value)) return null;
68
+ const extensions = Object.keys(value).filter(extension => extension.length > 0);
69
+ return extensions.length > 0 ? extensions : null;
70
+ }
61
71
 
62
- function normalizeServerConfig(name: string, config: Partial<ServerConfig>): ServerConfig | null {
72
+ function normalizeServerConfig(name: string, config: RawServerConfig): ServerConfig | null {
63
73
  const command = typeof config.command === "string" && config.command.length > 0 ? config.command : null;
64
- const fileTypes = normalizeStringArray(config.fileTypes);
65
- const rootMarkers = normalizeStringArray(config.rootMarkers);
74
+ const fileTypes =
75
+ normalizeStringArray(config.fileTypes) ?? normalizeExtensionToFileTypes(config.extensionToLanguage);
76
+ const rootMarkers = normalizeStringArray(config.rootMarkers) ?? (config.extensionToLanguage ? ["."] : null);
66
77
 
67
78
  if (!command || !fileTypes || !rootMarkers) {
68
79
  logger.warn("Ignoring invalid LSP server config (missing required fields).", { name });
@@ -72,6 +83,11 @@ function normalizeServerConfig(name: string, config: Partial<ServerConfig>): Ser
72
83
  const args = Array.isArray(config.args)
73
84
  ? config.args.filter((entry): entry is string => typeof entry === "string")
74
85
  : undefined;
86
+ const initOptions = isRecord(config.initOptions)
87
+ ? config.initOptions
88
+ : isRecord(config.initializationOptions)
89
+ ? config.initializationOptions
90
+ : undefined;
75
91
 
76
92
  return {
77
93
  ...config,
@@ -79,6 +95,7 @@ function normalizeServerConfig(name: string, config: Partial<ServerConfig>): Ser
79
95
  args,
80
96
  fileTypes,
81
97
  rootMarkers,
98
+ ...(initOptions ? { initOptions } : {}),
82
99
  };
83
100
  }
84
101
 
@@ -92,7 +109,7 @@ function readConfigFile(filePath: string): NormalizedConfig | null {
92
109
  }
93
110
  }
94
111
 
95
- function coerceServerConfigs(servers: Record<string, Partial<ServerConfig>>): Record<string, ServerConfig> {
112
+ function coerceServerConfigs(servers: Record<string, RawServerConfig>): Record<string, ServerConfig> {
96
113
  const result: Record<string, ServerConfig> = {};
97
114
  for (const [name, config] of Object.entries(servers)) {
98
115
  const normalized = normalizeServerConfig(name, config);
@@ -105,7 +122,7 @@ function coerceServerConfigs(servers: Record<string, Partial<ServerConfig>>): Re
105
122
 
106
123
  function mergeServers(
107
124
  base: Record<string, ServerConfig>,
108
- overrides: Record<string, Partial<ServerConfig>>,
125
+ overrides: Record<string, RawServerConfig>,
109
126
  ): Record<string, ServerConfig> {
110
127
  const merged: Record<string, ServerConfig> = { ...base };
111
128
  for (const [name, config] of Object.entries(overrides)) {
@@ -245,24 +262,71 @@ export function resolveCommand(command: string, cwd: string): string | null {
245
262
  return $which(command);
246
263
  }
247
264
 
265
+ interface ConfigSource {
266
+ read(): NormalizedConfig | null;
267
+ }
268
+
269
+ function fileConfigSource(filePath: string): ConfigSource {
270
+ return {
271
+ read: () => readConfigFile(filePath),
272
+ };
273
+ }
274
+
275
+ function readMarketplaceLspConfig(root: ClaudePluginRoot): NormalizedConfig | null {
276
+ const catalogPaths = [
277
+ path.resolve(root.path, "..", "..", "marketplace.json"),
278
+ path.resolve(root.path, "..", "..", ".claude-plugin", "marketplace.json"),
279
+ ];
280
+
281
+ for (const catalogPath of catalogPaths) {
282
+ try {
283
+ const catalog = JSON.parse(fs.readFileSync(catalogPath, "utf-8")) as unknown;
284
+ if (!isRecord(catalog) || !Array.isArray(catalog.plugins)) continue;
285
+
286
+ for (const plugin of catalog.plugins) {
287
+ if (!isRecord(plugin) || plugin.name !== root.plugin) continue;
288
+
289
+ const lspServers = plugin.lspServers;
290
+ if (typeof lspServers === "string") {
291
+ const configPath = path.resolve(root.path, lspServers);
292
+ if (!pathIsWithin(root.path, configPath)) return null;
293
+ return readConfigFile(configPath);
294
+ }
295
+ if (isRecord(lspServers)) {
296
+ return normalizeConfig({ servers: lspServers });
297
+ }
298
+ return null;
299
+ }
300
+ } catch {}
301
+ }
302
+
303
+ return null;
304
+ }
305
+
306
+ function marketplaceConfigSource(root: ClaudePluginRoot): ConfigSource {
307
+ return {
308
+ read: () => readMarketplaceLspConfig(root),
309
+ };
310
+ }
311
+
248
312
  /**
249
- * Configuration file search paths (in priority order).
313
+ * Configuration sources in priority order.
250
314
  * Supports both visible and hidden variants at each config location.
251
315
  */
252
- function getConfigPaths(cwd: string): string[] {
316
+ function getConfigSources(cwd: string): ConfigSource[] {
253
317
  const filenames = ["lsp.json", ".lsp.json", "lsp.yaml", ".lsp.yaml", "lsp.yml", ".lsp.yml"];
254
- const paths: string[] = [];
318
+ const sources: ConfigSource[] = [];
255
319
 
256
320
  // Project root files (highest priority)
257
321
  for (const filename of filenames) {
258
- paths.push(path.join(cwd, filename));
322
+ sources.push(fileConfigSource(path.join(cwd, filename)));
259
323
  }
260
324
 
261
325
  // Project config directories (.omp/, .pi/, .claude/)
262
326
  const projectDirs = getConfigDirPaths("", { user: false, project: true, cwd });
263
327
  for (const dir of projectDirs) {
264
328
  for (const filename of filenames) {
265
- paths.push(path.join(dir, filename));
329
+ sources.push(fileConfigSource(path.join(dir, filename)));
266
330
  }
267
331
  }
268
332
 
@@ -270,7 +334,7 @@ function getConfigPaths(cwd: string): string[] {
270
334
  const userDirs = getConfigDirPaths("", { user: true, project: false });
271
335
  for (const dir of userDirs) {
272
336
  for (const filename of filenames) {
273
- paths.push(path.join(dir, filename));
337
+ sources.push(fileConfigSource(path.join(dir, filename)));
274
338
  }
275
339
  }
276
340
 
@@ -278,16 +342,17 @@ function getConfigPaths(cwd: string): string[] {
278
342
  const pluginRoots = getPreloadedPluginRoots();
279
343
  for (const root of pluginRoots) {
280
344
  for (const filename of filenames) {
281
- paths.push(path.join(root.path, filename));
345
+ sources.push(fileConfigSource(path.join(root.path, filename)));
282
346
  }
347
+ sources.push(marketplaceConfigSource(root));
283
348
  }
284
349
 
285
350
  // User home root files (lowest priority fallback)
286
351
  for (const filename of filenames) {
287
- paths.push(path.join(os.homedir(), filename));
352
+ sources.push(fileConfigSource(path.join(os.homedir(), filename)));
288
353
  }
289
354
 
290
- return paths;
355
+ return sources;
291
356
  }
292
357
 
293
358
  /**
@@ -324,12 +389,12 @@ function getConfigPaths(cwd: string): string[] {
324
389
  export function loadConfig(cwd: string): LspConfig {
325
390
  let mergedServers = coerceServerConfigs(DEFAULTS);
326
391
 
327
- const configPaths = getConfigPaths(cwd).reverse();
392
+ const configSources = getConfigSources(cwd).reverse();
328
393
  let hasOverrides = false;
329
394
 
330
395
  let idleTimeoutMs: number | undefined;
331
- for (const configPath of configPaths) {
332
- const parsed = readConfigFile(configPath);
396
+ for (const source of configSources) {
397
+ const parsed = source.read();
333
398
  if (!parsed) continue;
334
399
  const hasServerOverrides = Object.keys(parsed.servers).length > 0;
335
400
  if (hasServerOverrides) {
@@ -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": {
@@ -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
  }
@@ -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