@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
@@ -14,7 +14,7 @@ import { Ellipsis, fileHyperlink, renderStatusLine, renderTreeList, truncateToWi
14
14
  import { resolveFileDisplayMode } from "../utils/file-display-mode";
15
15
  import type { ToolSession } from ".";
16
16
  import { createFileRecorder, formatResultPath } from "./file-recorder";
17
- import { formatGroupedFiles } from "./grouped-file-output";
17
+ import { classifyGroupedLines, formatGroupedFiles, groupLineIndicesByBlank } from "./grouped-file-output";
18
18
  import { formatMatchLine } from "./match-line-format";
19
19
  import type { OutputMeta } from "./output-meta";
20
20
  import { resolveToolSearchScope } from "./path-utils";
@@ -29,7 +29,6 @@ import {
29
29
  formatParseErrors,
30
30
  formatParseErrorsCountLabel,
31
31
  PREVIEW_LIMITS,
32
- splitGroupsByBlankLine,
33
32
  } from "./render-utils";
34
33
  import { ToolError } from "./tool-errors";
35
34
  import { toolResult } from "./tool-result";
@@ -118,6 +117,9 @@ export interface AstGrepToolDetails {
118
117
  /** Absolute base directory used during search. Used by the renderer to resolve
119
118
  * display-relative paths to absolute paths for OSC 8 hyperlinks. */
120
119
  searchPath?: string;
120
+ /** Session cwd at search time. Display header/match paths are cwd-relative, so
121
+ * the renderer resolves them against this; `searchPath` is the scope target. */
122
+ cwd?: string;
121
123
  }
122
124
 
123
125
  export class AstGrepTool implements AgentTool<typeof astGrepSchema, AstGrepToolDetails> {
@@ -207,6 +209,7 @@ export class AstGrepTool implements AgentTool<typeof astGrepSchema, AstGrepToolD
207
209
  ...(cappedParseErrors.length > 0 ? { parseErrors: cappedParseErrors, parseErrorsTotal } : {}),
208
210
  scopePath,
209
211
  searchPath: resolvedSearchPath,
212
+ cwd: this.session.cwd,
210
213
  files: fileList,
211
214
  fileMatches: [],
212
215
  };
@@ -381,15 +384,41 @@ export const astGrepToolRenderer = {
381
384
  if (limitReached) meta.push(uiTheme.fg("warning", "limit reached"));
382
385
  const description = args?.pat;
383
386
  const header = renderStatusLine(
384
- { icon: limitReached ? "warning" : "success", title: "AST Grep", description, meta },
387
+ {
388
+ ...(limitReached
389
+ ? { icon: "warning" as const }
390
+ : { iconOverride: uiTheme.fg("accent", uiTheme.symbol("icon.search")) }),
391
+ title: "AST Grep",
392
+ description,
393
+ meta,
394
+ },
385
395
  uiTheme,
386
396
  );
387
397
 
388
398
  const textContent = result.details?.displayContent ?? result.content?.find(c => c.type === "text")?.text ?? "";
389
- const allGroups = splitGroupsByBlankLine(textContent.split("\n"));
390
- const matchGroups = allGroups.filter(
391
- group => !group[0]?.startsWith("Result limit reached") && !group[0]?.startsWith("Parse issues:"),
392
- );
399
+ const allLines = textContent.split("\n");
400
+ // Resolve hyperlinks over the whole output so nested directory headers
401
+ // reconstruct across the blank-line groups the tree list collapses by.
402
+ const contexts = classifyGroupedLines(allLines, details?.cwd ?? details?.searchPath, details?.searchPath);
403
+ const styledLines = allLines.map((line, index) => {
404
+ const ctx = contexts[index]!;
405
+ if (ctx.kind === "dir") {
406
+ const styled = uiTheme.fg("accent", line);
407
+ return ctx.headerPath ? fileHyperlink(ctx.headerPath, styled) : styled;
408
+ }
409
+ if (ctx.kind === "file") {
410
+ const styled = uiTheme.fg(ctx.depth === 1 ? "accent" : "dim", line);
411
+ return ctx.headerPath ? fileHyperlink(ctx.headerPath, styled) : styled;
412
+ }
413
+ if (line.startsWith(" meta:")) return uiTheme.fg("dim", line);
414
+ return uiTheme.fg("toolOutput", line);
415
+ });
416
+ const matchGroups = groupLineIndicesByBlank(allLines)
417
+ .filter(indices => {
418
+ const first = allLines[indices[0]!]!;
419
+ return !first.startsWith("Result limit reached") && !first.startsWith("Parse issues:");
420
+ })
421
+ .map(indices => indices.map(index => styledLines[index]!));
393
422
 
394
423
  const extraLines: string[] = [];
395
424
  if (limitReached) {
@@ -404,7 +433,6 @@ export const astGrepToolRenderer = {
404
433
  return createCachedComponent(
405
434
  () => options.expanded,
406
435
  width => {
407
- const searchBase = details?.searchPath;
408
436
  const matchLines = renderTreeList(
409
437
  {
410
438
  items: matchGroups,
@@ -412,41 +440,7 @@ export const astGrepToolRenderer = {
412
440
  maxCollapsed: matchGroups.length,
413
441
  maxCollapsedLines: COLLAPSED_MATCH_LIMIT,
414
442
  itemType: "match",
415
- renderItem: group => {
416
- let contextDir = searchBase ?? "";
417
- return group.map(line => {
418
- if (line.startsWith("## ")) {
419
- const fileName = line
420
- .slice(3)
421
- .trimEnd()
422
- .replace(/\s+\([^)]*\)\s*$/, "")
423
- .replace(/#[0-9a-f]+$/, "");
424
- const absPath = contextDir && fileName ? path.join(contextDir, fileName) : undefined;
425
- const styled = uiTheme.fg("dim", line);
426
- return absPath ? fileHyperlink(absPath, styled) : styled;
427
- }
428
- if (line.startsWith("# ")) {
429
- const raw = line
430
- .slice(2)
431
- .trimEnd()
432
- .replace(/\s+\([^)]*\)\s*$/, "");
433
- const isDirectory = raw.endsWith("/");
434
- const name = isDirectory ? raw.replace(/\/$/, "") : raw.replace(/#[0-9a-f]+$/, "");
435
- if (isDirectory) {
436
- if (searchBase) {
437
- contextDir = name === "." ? searchBase : path.join(searchBase, name);
438
- }
439
- return uiTheme.fg("accent", line);
440
- }
441
- // Root-level file (single # without trailing slash) from formatGroupedFiles.
442
- const absPath = searchBase && name ? path.join(searchBase, name) : undefined;
443
- const styled = uiTheme.fg("accent", line);
444
- return absPath ? fileHyperlink(absPath, styled) : styled;
445
- }
446
- if (line.startsWith(" meta:")) return uiTheme.fg("dim", line);
447
- return uiTheme.fg("toolOutput", line);
448
- });
449
- },
443
+ renderItem: group => group,
450
444
  },
451
445
  uiTheme,
452
446
  );
package/src/tools/bash.ts CHANGED
@@ -287,6 +287,35 @@ function formatExitCodeNotice(exitCode: number): string {
287
287
  return `Command exited with code ${exitCode}`;
288
288
  }
289
289
 
290
+ const RAW_OUTPUT_ARTIFACT_PREFIX = "[raw output: artifact://";
291
+ const RAW_OUTPUT_ARTIFACT_SUFFIX = "]";
292
+
293
+ function stripRawOutputArtifactNotice(text: string): { text: string; artifactId?: string } {
294
+ const trimmed = text.trimEnd();
295
+ const lineStart = trimmed.lastIndexOf("\n");
296
+ const candidateStart = lineStart === -1 ? 0 : lineStart + 1;
297
+ if (
298
+ !trimmed.startsWith(RAW_OUTPUT_ARTIFACT_PREFIX, candidateStart) ||
299
+ !trimmed.endsWith(RAW_OUTPUT_ARTIFACT_SUFFIX)
300
+ ) {
301
+ return { text };
302
+ }
303
+
304
+ const idStart = candidateStart + RAW_OUTPUT_ARTIFACT_PREFIX.length;
305
+ const idEnd = trimmed.length - RAW_OUTPUT_ARTIFACT_SUFFIX.length;
306
+ if (idStart === idEnd) return { text };
307
+ for (let i = idStart; i < idEnd; i++) {
308
+ const code = trimmed.charCodeAt(i);
309
+ if (code < 48 || code > 57) return { text };
310
+ }
311
+
312
+ const artifactId = trimmed.slice(idStart, idEnd);
313
+ return {
314
+ text: trimmed.slice(0, lineStart === -1 ? 0 : lineStart).trimEnd(),
315
+ artifactId,
316
+ };
317
+ }
318
+
290
319
  /**
291
320
  * Strip the trailing occurrence of `notice` (plus a single surrounding newline
292
321
  * on each side) so the TUI can echo the value via a styled footer label
@@ -1028,6 +1057,7 @@ export interface ShellRendererConfig<TArgs> {
1028
1057
  resolveCommand?: (args: TArgs | undefined) => string | undefined;
1029
1058
  resolveCwd?: (args: TArgs | undefined) => string | undefined;
1030
1059
  resolveEnv?: (args: TArgs | undefined) => Record<string, string> | undefined;
1060
+ showHeader?: boolean;
1031
1061
  }
1032
1062
 
1033
1063
  function getPartialJson<TArgs>(args: TArgs | undefined): string | undefined {
@@ -1079,9 +1109,11 @@ export function createShellRenderer<TArgs>(config: ShellRendererConfig<TArgs>) {
1079
1109
  return {
1080
1110
  renderCall(args: TArgs, options: RenderResultOptions, uiTheme: Theme): Component {
1081
1111
  const renderArgs = toBashRenderArgs(args, config);
1082
- const title = config.resolveTitle(args, options);
1083
1112
  const cmdLines = formatBashCommandLines(renderArgs, uiTheme);
1084
- const header = renderStatusLine({ icon: "pending", title }, uiTheme);
1113
+ const header =
1114
+ config.showHeader === false
1115
+ ? undefined
1116
+ : renderStatusLine({ icon: "pending", title: config.resolveTitle(args, options) }, uiTheme);
1085
1117
  const outputBlock = new CachedOutputBlock();
1086
1118
  return markFramedBlockComponent({
1087
1119
  render: (width: number): string[] =>
@@ -1115,8 +1147,10 @@ export function createShellRenderer<TArgs>(config: ShellRendererConfig<TArgs>) {
1115
1147
  const cmdLines = args ? formatBashCommandLines(renderArgs, uiTheme) : undefined;
1116
1148
  const isError = result.isError === true;
1117
1149
  const icon = options.isPartial ? "pending" : isError ? "error" : "success";
1118
- const title = config.resolveTitle(args, options);
1119
- const header = renderStatusLine({ icon, title }, uiTheme);
1150
+ const header =
1151
+ config.showHeader === false
1152
+ ? undefined
1153
+ : renderStatusLine({ icon, title: config.resolveTitle(args, options) }, uiTheme);
1120
1154
  const details = result.details;
1121
1155
  const outputBlock = new CachedOutputBlock();
1122
1156
 
@@ -1133,7 +1167,9 @@ export function createShellRenderer<TArgs>(config: ShellRendererConfig<TArgs>) {
1133
1167
  const rawOutput = renderContext?.output ?? result.content?.find(c => c.type === "text")?.text ?? "";
1134
1168
  const strippedOutput = stripOutputNotice(rawOutput, details?.meta);
1135
1169
  const withoutExit = stripExitCodeNotice(strippedOutput, details?.exitCode);
1136
- const output = stripWallTimeNotice(withoutExit, details?.wallTimeMs);
1170
+ const withoutWall = stripWallTimeNotice(withoutExit, details?.wallTimeMs);
1171
+ const rawOutputArtifact = stripRawOutputArtifactNotice(withoutWall);
1172
+ const output = rawOutputArtifact.text;
1137
1173
  const displayOutput = output.trimEnd();
1138
1174
  const showingFullOutput = expanded && renderContext?.isFullOutput === true;
1139
1175
 
@@ -1152,6 +1188,9 @@ export function createShellRenderer<TArgs>(config: ShellRendererConfig<TArgs>) {
1152
1188
  : `Timeout: ${timeoutSeconds}s`,
1153
1189
  );
1154
1190
  }
1191
+ if (rawOutputArtifact.artifactId) {
1192
+ statsParts.push(`Artifact: ${rawOutputArtifact.artifactId}`);
1193
+ }
1155
1194
  if (isError && typeof details?.exitCode === "number") {
1156
1195
  statsParts.push(`Exit: ${details.exitCode}`);
1157
1196
  }
@@ -1215,7 +1254,11 @@ export function createShellRenderer<TArgs>(config: ShellRendererConfig<TArgs>) {
1215
1254
  { label: uiTheme.fg("toolTitle", "Output"), lines: outputLines },
1216
1255
  ],
1217
1256
  width,
1218
- animate: options.isPartial && shimmerEnabled(),
1257
+ // Don't animate once the command has been backgrounded: the block
1258
+ // gets committed to scrollback and finalizes later via the async
1259
+ // update path, so a mid-sweep frame would freeze a stray dark
1260
+ // border segment.
1261
+ animate: options.isPartial && shimmerEnabled() && details?.async?.state !== "running",
1219
1262
  },
1220
1263
  uiTheme,
1221
1264
  );
@@ -1235,4 +1278,5 @@ export const bashToolRenderer = createShellRenderer<BashRenderArgs>({
1235
1278
  resolveCommand: args => args?.command,
1236
1279
  resolveCwd: args => args?.cwd,
1237
1280
  resolveEnv: args => args?.env,
1281
+ showHeader: false,
1238
1282
  });
@@ -22,6 +22,7 @@ import {
22
22
  type DapFunctionBreakpointRecord,
23
23
  type DapInstructionBreakpointRecord,
24
24
  type DapModule,
25
+ type DapResolvedAdapter,
25
26
  type DapScope,
26
27
  type DapSessionSummary,
27
28
  type DapSource,
@@ -30,6 +31,8 @@ import {
30
31
  type DapVariable,
31
32
  dapSessionManager,
32
33
  getAvailableAdapters,
34
+ type LaunchProgramKind,
35
+ resolveLaunchOverrides,
33
36
  selectAttachAdapter,
34
37
  selectLaunchAdapter,
35
38
  } from "../dap";
@@ -489,16 +492,23 @@ function getConfiguredAdapters(cwd: string): string {
489
492
  const adapters = getAvailableAdapters(cwd).map(adapter => adapter.name);
490
493
  return adapters.length > 0 ? adapters.join(", ") : "none";
491
494
  }
492
- async function validateLaunchProgram(program: string, cwd: string): Promise<void> {
493
- let isDirectory: boolean;
495
+
496
+ async function classifyLaunchProgram(program: string): Promise<LaunchProgramKind> {
494
497
  try {
495
- isDirectory = (await fs.stat(program)).isDirectory();
498
+ return (await fs.stat(program)).isDirectory() ? "directory" : "file";
496
499
  } catch (error) {
497
- if (isEnoent(error)) return;
500
+ if (isEnoent(error)) return "missing";
498
501
  throw error;
499
502
  }
500
- if (!isDirectory) return;
503
+ }
501
504
 
505
+ function validateLaunchProgram(
506
+ program: string,
507
+ cwd: string,
508
+ programKind: LaunchProgramKind,
509
+ adapter: DapResolvedAdapter,
510
+ ): void {
511
+ if (programKind !== "directory" || adapter.acceptsDirectoryProgram) return;
502
512
  const displayPath = formatPathRelativeToCwd(program, cwd, { trailingSlash: true });
503
513
  throw new ToolError(
504
514
  `launch program resolves to a directory: ${displayPath}. Pass an executable file path, or for Python use adapter "debugpy" with program set to the .py file.`,
@@ -676,8 +686,8 @@ export class DebugTool implements AgentTool<typeof debugSchema, DebugToolDetails
676
686
  }
677
687
  const commandCwd = params.cwd ? resolveToCwd(params.cwd, this.session.cwd) : this.session.cwd;
678
688
  const program = resolveToCwd(params.program, commandCwd);
679
- await validateLaunchProgram(program, commandCwd);
680
- const adapter = selectLaunchAdapter(program, commandCwd, params.adapter);
689
+ const programKind = await classifyLaunchProgram(program);
690
+ const adapter = selectLaunchAdapter(program, commandCwd, params.adapter, programKind);
681
691
  if (!adapter) {
682
692
  if (params.adapter === "debugpy") {
683
693
  throw new ToolError("adapter 'debugpy' is not available: python not found in PATH");
@@ -686,8 +696,10 @@ export class DebugTool implements AgentTool<typeof debugSchema, DebugToolDetails
686
696
  `No debugger adapter available. Installed adapters: ${getConfiguredAdapters(commandCwd)}`,
687
697
  );
688
698
  }
699
+ validateLaunchProgram(program, commandCwd, programKind, adapter);
700
+ const extraLaunchArguments = resolveLaunchOverrides(adapter, program, programKind);
689
701
  const snapshot = await dapSessionManager.launch(
690
- { adapter, program, args: params.args, cwd: commandCwd },
702
+ { adapter, program, args: params.args, cwd: commandCwd, extraLaunchArguments },
691
703
  combinedSignal,
692
704
  timeoutSec * 1000,
693
705
  );
@@ -1,4 +1,4 @@
1
- import { $env, $flag } from "@oh-my-pi/pi-utils";
1
+ import { $flag } from "@oh-my-pi/pi-utils";
2
2
  import type { ToolSession } from ".";
3
3
 
4
4
  export interface EvalBackendsAllowance {
@@ -6,21 +6,6 @@ export interface EvalBackendsAllowance {
6
6
  js: boolean;
7
7
  }
8
8
 
9
- /**
10
- * Parse PI_PY / PI_JS environment variables. Each is a boolean flag; unset
11
- * means "not specified, defer to settings". Returns null when neither is set
12
- * so the caller can fall through to `readEvalBackendsAllowance` per key.
13
- */
14
- function getEvalBackendsFromEnv(): EvalBackendsAllowance | null {
15
- const pyEnv = $env.PI_PY;
16
- const jsEnv = $env.PI_JS;
17
- if (pyEnv === undefined && jsEnv === undefined) return null;
18
- return {
19
- python: pyEnv === undefined ? true : $flag("PI_PY"),
20
- js: jsEnv === undefined ? true : $flag("PI_JS"),
21
- };
22
- }
23
-
24
9
  /** Read per-backend allowance from settings (defaults true). */
25
10
  export function readEvalBackendsAllowance(session: ToolSession): EvalBackendsAllowance {
26
11
  return {
@@ -34,5 +19,9 @@ export function readEvalBackendsAllowance(session: ToolSession): EvalBackendsAll
34
19
  * override the per-key settings; otherwise settings (defaults true) win.
35
20
  */
36
21
  export function resolveEvalBackends(session: ToolSession): EvalBackendsAllowance {
37
- return getEvalBackendsFromEnv() ?? readEvalBackendsAllowance(session);
22
+ const settings = readEvalBackendsAllowance(session);
23
+ return {
24
+ python: $flag("PI_PY", settings.python),
25
+ js: $flag("PI_JS", settings.js),
26
+ };
38
27
  }
@@ -40,14 +40,6 @@ import {
40
40
  wrapBrackets,
41
41
  } from "./render-utils";
42
42
  export const EVAL_DEFAULT_PREVIEW_LINES = 10;
43
- /**
44
- * Rows of source kept in the *pending* eval preview. The window follows the
45
- * streaming edge (newest lines pinned to the bottom) so you can watch the code
46
- * being written, while staying bounded — a volatile tool block taller than the
47
- * viewport would otherwise strand its scrolled-off head out of native scrollback
48
- * on ED3-risk terminals. Matches the streaming windows used by edit/write.
49
- */
50
- export const EVAL_STREAMING_PREVIEW_LINES = 12;
51
43
 
52
44
  function languageForHighlighter(language: EvalLanguage | undefined): "python" | "javascript" {
53
45
  return language === "js" ? "javascript" : "python";
@@ -517,15 +509,12 @@ export const evalToolRenderer = {
517
509
  title: cell.title,
518
510
  status: "pending",
519
511
  width,
520
- codeMaxLines: EVAL_STREAMING_PREVIEW_LINES,
521
- // Follow the streaming edge with a bounded tail window so the
522
- // newest source stays visible as it is written, instead of
523
- // rendering every line of a >100-line `code` which would
524
- // overflow the viewport and, because a tool block is volatile
525
- // (it collapses to a capped result), strand its scrolled-off head
526
- // out of native scrollback, cutting the box top. `Ctrl+O` lifts
527
- // the window via `expanded` for a deliberate full view.
528
- codeTail: true,
512
+ // Always render the full source: the code is fixed input, not the
513
+ // streaming part, so it is never compacted. While still pending
514
+ // (args streaming) the block is not yet committed to native
515
+ // scrollback its head is only committed once a result exists and
516
+ // the code has finalized (see `isStreamingPreviewAppendOnly`).
517
+ codeMaxLines: Number.POSITIVE_INFINITY,
529
518
  expanded: options.expanded,
530
519
  animate,
531
520
  },
@@ -628,7 +617,9 @@ export const evalToolRenderer = {
628
617
  duration: cell.durationMs,
629
618
  output: outputLines.length > 0 ? outputLines.join("\n") : undefined,
630
619
  outputMaxLines: outputLines.length,
631
- codeMaxLines: expanded ? Number.POSITIVE_INFINITY : EVAL_DEFAULT_PREVIEW_LINES,
620
+ // Code is fixed input — always shown in full, never compacted.
621
+ // Only `output` honors the collapsed preview cap above.
622
+ codeMaxLines: Number.POSITIVE_INFINITY,
632
623
  expanded,
633
624
  width,
634
625
  animate,
@@ -760,6 +751,18 @@ export const evalToolRenderer = {
760
751
  },
761
752
  };
762
753
  },
754
+
755
+ // Append-only once a result exists (args complete → code finalized). The code
756
+ // is rendered in full as a fixed top-anchored prefix, and the streamed stdout
757
+ // below it only appends rows at the bottom, so the scrolled-off head commits
758
+ // to native scrollback instead of being yanked — collapsed or expanded, since
759
+ // the collapsed output cap keeps its sliding tail in the bottom live region.
760
+ // Returns false while still pending: the code is mid-stream (args incomplete)
761
+ // and its header still reads "pending", so committing it would strand a stale
762
+ // pending preview in history.
763
+ isStreamingPreviewAppendOnly(_args: EvalRenderArgs, _options: RenderResultOptions, result?: unknown): boolean {
764
+ return result != null;
765
+ },
763
766
  mergeCallAndResult: true,
764
767
  inline: true,
765
768
  };
package/src/tools/eval.ts CHANGED
@@ -130,11 +130,12 @@ function timeoutSecondsFromMs(timeoutMs: number): number {
130
130
  }
131
131
 
132
132
  async function resolveBackend(session: ToolSession, language: EvalLanguage): Promise<ResolvedBackend> {
133
- const allowPy = (session.settings.get("eval.py") as boolean | undefined) ?? true;
134
- const allowJs = (session.settings.get("eval.js") as boolean | undefined) ?? true;
133
+ const backends = resolveEvalBackends(session);
134
+ const allowPy = backends.python;
135
+ const allowJs = backends.js;
135
136
 
136
137
  if (language === "python") {
137
- if (!allowPy) throw new ToolError("Python backend is disabled (eval.py = false).");
138
+ if (!allowPy) throw new ToolError("Python backend is disabled (PI_PY=0 or eval.py = false).");
138
139
  if (!(await pythonBackend.isAvailable(session))) {
139
140
  throw new ToolError(
140
141
  'Python backend is unavailable in this session. Pass language: "js" or install the python kernel.',
@@ -142,7 +143,7 @@ async function resolveBackend(session: ToolSession, language: EvalLanguage): Pro
142
143
  }
143
144
  return { backend: pythonBackend };
144
145
  }
145
- if (!allowJs) throw new ToolError("JavaScript backend is disabled (eval.js = false).");
146
+ if (!allowJs) throw new ToolError("JavaScript backend is disabled (PI_JS=0 or eval.js = false).");
146
147
  return { backend: jsBackend };
147
148
  }
148
149