@oh-my-pi/pi-coding-agent 13.1.2 → 13.2.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 (235) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/package.json +7 -7
  3. package/scripts/format-prompts.ts +33 -14
  4. package/src/async/job-manager.ts +43 -1
  5. package/src/capability/index.ts +1 -2
  6. package/src/capability/tool.ts +1 -1
  7. package/src/cli/args.ts +1 -2
  8. package/src/cli/config-cli.ts +1 -1
  9. package/src/cli/file-processor.ts +1 -2
  10. package/src/cli/grep-cli.ts +1 -1
  11. package/src/cli/jupyter-cli.ts +1 -1
  12. package/src/cli/plugin-cli.ts +1 -1
  13. package/src/cli/setup-cli.ts +1 -1
  14. package/src/cli/shell-cli.ts +1 -1
  15. package/src/cli/ssh-cli.ts +1 -1
  16. package/src/cli/stats-cli.ts +1 -2
  17. package/src/cli/update-cli.ts +1 -2
  18. package/src/cli/web-search-cli.ts +1 -1
  19. package/src/cli.ts +1 -1
  20. package/src/commands/launch.ts +2 -1
  21. package/src/commit/agentic/agent.ts +2 -1
  22. package/src/commit/agentic/index.ts +1 -2
  23. package/src/commit/agentic/prompts/system.md +3 -3
  24. package/src/commit/agentic/tools/propose-changelog.ts +30 -19
  25. package/src/commit/changelog/generate.ts +16 -6
  26. package/src/commit/changelog/index.ts +2 -1
  27. package/src/commit/pipeline.ts +1 -2
  28. package/src/commit/prompts/reduce-system.md +1 -1
  29. package/src/commit/types.ts +10 -1
  30. package/src/config/keybindings.ts +1 -2
  31. package/src/config/model-registry.ts +1 -1
  32. package/src/config/prompt-templates.ts +14 -2
  33. package/src/config/settings-schema.ts +10 -0
  34. package/src/config/settings.ts +25 -2
  35. package/src/config.ts +1 -2
  36. package/src/debug/index.ts +1 -1
  37. package/src/debug/report-bundle.ts +1 -2
  38. package/src/debug/system-info.ts +1 -2
  39. package/src/discovery/agents.ts +2 -2
  40. package/src/discovery/builtin.ts +24 -14
  41. package/src/discovery/claude-plugins.ts +3 -2
  42. package/src/discovery/claude.ts +9 -9
  43. package/src/discovery/codex.ts +3 -3
  44. package/src/discovery/cursor.ts +5 -4
  45. package/src/discovery/gemini.ts +5 -5
  46. package/src/discovery/helpers.ts +47 -69
  47. package/src/discovery/mcp-json.ts +3 -3
  48. package/src/discovery/opencode.ts +7 -8
  49. package/src/discovery/ssh.ts +3 -3
  50. package/src/discovery/vscode.ts +3 -2
  51. package/src/discovery/windsurf.ts +3 -2
  52. package/src/exa/company.ts +1 -1
  53. package/src/exa/factory.ts +1 -6
  54. package/src/exa/linkedin.ts +1 -1
  55. package/src/exa/mcp-client.ts +19 -8
  56. package/src/exa/search.ts +2 -2
  57. package/src/exa/types.ts +3 -3
  58. package/src/exec/bash-executor.ts +2 -1
  59. package/src/exec/non-interactive-env.ts +43 -0
  60. package/src/export/custom-share.ts +1 -1
  61. package/src/export/html/index.ts +1 -2
  62. package/src/extensibility/custom-commands/loader.ts +1 -2
  63. package/src/extensibility/plugins/installer.ts +1 -2
  64. package/src/extensibility/plugins/loader.ts +1 -2
  65. package/src/extensibility/plugins/manager.ts +3 -2
  66. package/src/extensibility/skills.ts +59 -115
  67. package/src/index.ts +1 -3
  68. package/src/internal-urls/docs-index.generated.ts +1 -1
  69. package/src/ipy/executor.ts +1 -2
  70. package/src/ipy/gateway-coordinator.ts +1 -2
  71. package/src/ipy/modules.ts +1 -1
  72. package/src/ipy/runtime.ts +1 -3
  73. package/src/main.ts +1 -2
  74. package/src/mcp/config.ts +1 -1
  75. package/src/mcp/transports/stdio.ts +1 -2
  76. package/src/memories/index.ts +1 -2
  77. package/src/modes/components/diff.ts +49 -19
  78. package/src/modes/components/extensions/extension-dashboard.ts +1 -1
  79. package/src/modes/components/extensions/inspector-panel.ts +8 -2
  80. package/src/modes/components/footer.ts +1 -2
  81. package/src/modes/components/status-line/segments.ts +1 -2
  82. package/src/modes/components/tool-execution.ts +3 -10
  83. package/src/modes/components/welcome.ts +1 -1
  84. package/src/modes/controllers/command-controller.ts +1 -2
  85. package/src/modes/controllers/mcp-command-controller.ts +1 -1
  86. package/src/modes/controllers/selector-controller.ts +1 -1
  87. package/src/modes/controllers/ssh-command-controller.ts +1 -1
  88. package/src/modes/interactive-mode.ts +2 -3
  89. package/src/modes/shared.ts +1 -2
  90. package/src/modes/theme/theme.ts +1 -2
  91. package/src/patch/index.ts +1 -25
  92. package/src/prompts/agents/designer.md +7 -10
  93. package/src/prompts/agents/explore.md +15 -23
  94. package/src/prompts/agents/init.md +23 -23
  95. package/src/prompts/agents/plan.md +14 -77
  96. package/src/prompts/agents/reviewer.md +6 -5
  97. package/src/prompts/agents/task.md +13 -11
  98. package/src/prompts/compaction/branch-summary.md +3 -3
  99. package/src/prompts/compaction/compaction-short-summary.md +7 -7
  100. package/src/prompts/compaction/compaction-summary-context.md +1 -1
  101. package/src/prompts/compaction/compaction-summary.md +5 -5
  102. package/src/prompts/compaction/compaction-turn-prefix.md +3 -3
  103. package/src/prompts/compaction/compaction-update-summary.md +11 -11
  104. package/src/prompts/memories/consolidation.md +5 -5
  105. package/src/prompts/memories/read-path.md +6 -6
  106. package/src/prompts/memories/stage_one_input.md +1 -1
  107. package/src/prompts/memories/stage_one_system.md +5 -5
  108. package/src/prompts/review-request.md +4 -4
  109. package/src/prompts/system/agent-creation-architect.md +17 -17
  110. package/src/prompts/system/agent-creation-user.md +2 -2
  111. package/src/prompts/system/custom-system-prompt.md +4 -4
  112. package/src/prompts/system/plan-mode-active.md +20 -20
  113. package/src/prompts/system/plan-mode-approved.md +7 -7
  114. package/src/prompts/system/plan-mode-reference.md +2 -2
  115. package/src/prompts/system/plan-mode-subagent.md +8 -8
  116. package/src/prompts/system/subagent-submit-reminder.md +5 -5
  117. package/src/prompts/system/subagent-system-prompt.md +29 -22
  118. package/src/prompts/system/subagent-user-prompt.md +7 -3
  119. package/src/prompts/system/summarization-system.md +1 -1
  120. package/src/prompts/system/system-prompt.md +201 -226
  121. package/src/prompts/system/title-system.md +2 -2
  122. package/src/prompts/system/ttsr-interrupt.md +1 -1
  123. package/src/prompts/system/web-search.md +16 -16
  124. package/src/prompts/tools/ask.md +1 -3
  125. package/src/prompts/tools/await.md +2 -4
  126. package/src/prompts/tools/bash.md +5 -7
  127. package/src/prompts/tools/browser.md +4 -6
  128. package/src/prompts/tools/calculator.md +1 -3
  129. package/src/prompts/tools/cancel-job.md +2 -4
  130. package/src/prompts/tools/exit-plan-mode.md +7 -7
  131. package/src/prompts/tools/fetch.md +0 -2
  132. package/src/prompts/tools/find.md +3 -5
  133. package/src/prompts/tools/gemini-image.md +6 -22
  134. package/src/prompts/tools/grep.md +4 -6
  135. package/src/prompts/tools/hashline.md +12 -15
  136. package/src/prompts/tools/lsp.md +1 -3
  137. package/src/prompts/tools/patch.md +7 -9
  138. package/src/prompts/tools/python.md +10 -14
  139. package/src/prompts/tools/read.md +0 -2
  140. package/src/prompts/tools/replace.md +5 -7
  141. package/src/prompts/tools/ssh.md +3 -5
  142. package/src/prompts/tools/task.md +6 -8
  143. package/src/prompts/tools/todo-write.md +7 -9
  144. package/src/prompts/tools/web-search.md +3 -5
  145. package/src/prompts/tools/write.md +3 -5
  146. package/src/sdk.ts +1 -2
  147. package/src/session/agent-session.ts +10 -26
  148. package/src/session/agent-storage.ts +1 -2
  149. package/src/session/history-storage.ts +1 -2
  150. package/src/session/session-manager.ts +10 -2
  151. package/src/ssh/connection-manager.ts +11 -2
  152. package/src/ssh/sshfs-mount.ts +7 -1
  153. package/src/system-prompt.ts +25 -103
  154. package/src/task/agents.ts +1 -1
  155. package/src/task/worktree.ts +1 -2
  156. package/src/tools/ask.ts +0 -1
  157. package/src/tools/await-tool.ts +6 -3
  158. package/src/tools/bash-interactive.ts +2 -45
  159. package/src/tools/bash.ts +5 -5
  160. package/src/tools/browser.ts +1 -2
  161. package/src/tools/gemini-image.ts +8 -28
  162. package/src/tools/json-tree.ts +2 -1
  163. package/src/tools/python.ts +1 -1
  164. package/src/tools/read.ts +1 -2
  165. package/src/tools/render-utils.ts +5 -2
  166. package/src/tools/todo-write.ts +11 -9
  167. package/src/utils/tools-manager.ts +1 -2
  168. package/src/web/scrapers/artifacthub.ts +2 -1
  169. package/src/web/scrapers/aur.ts +2 -1
  170. package/src/web/scrapers/biorxiv.ts +2 -1
  171. package/src/web/scrapers/bluesky.ts +2 -1
  172. package/src/web/scrapers/chocolatey.ts +2 -1
  173. package/src/web/scrapers/cisa-kev.ts +2 -1
  174. package/src/web/scrapers/clojars.ts +2 -1
  175. package/src/web/scrapers/coingecko.ts +2 -1
  176. package/src/web/scrapers/crates-io.ts +2 -1
  177. package/src/web/scrapers/crossref.ts +2 -1
  178. package/src/web/scrapers/discogs.ts +3 -1
  179. package/src/web/scrapers/discourse.ts +2 -1
  180. package/src/web/scrapers/dockerhub.ts +2 -1
  181. package/src/web/scrapers/fdroid.ts +2 -1
  182. package/src/web/scrapers/firefox-addons.ts +2 -1
  183. package/src/web/scrapers/flathub.ts +2 -1
  184. package/src/web/scrapers/gitlab.ts +1 -1
  185. package/src/web/scrapers/go-pkg.ts +2 -1
  186. package/src/web/scrapers/hackage.ts +2 -1
  187. package/src/web/scrapers/hackernews.ts +2 -1
  188. package/src/web/scrapers/hex.ts +2 -1
  189. package/src/web/scrapers/huggingface.ts +2 -1
  190. package/src/web/scrapers/jetbrains-marketplace.ts +2 -1
  191. package/src/web/scrapers/lemmy.ts +2 -1
  192. package/src/web/scrapers/lobsters.ts +2 -1
  193. package/src/web/scrapers/mastodon.ts +2 -1
  194. package/src/web/scrapers/maven.ts +2 -1
  195. package/src/web/scrapers/mdn.ts +2 -1
  196. package/src/web/scrapers/metacpan.ts +2 -1
  197. package/src/web/scrapers/musicbrainz.ts +3 -1
  198. package/src/web/scrapers/npm.ts +2 -1
  199. package/src/web/scrapers/nuget.ts +2 -1
  200. package/src/web/scrapers/nvd.ts +2 -1
  201. package/src/web/scrapers/ollama.ts +2 -1
  202. package/src/web/scrapers/open-vsx.ts +2 -1
  203. package/src/web/scrapers/opencorporates.ts +2 -1
  204. package/src/web/scrapers/openlibrary.ts +2 -1
  205. package/src/web/scrapers/orcid.ts +3 -1
  206. package/src/web/scrapers/osv.ts +2 -1
  207. package/src/web/scrapers/packagist.ts +2 -1
  208. package/src/web/scrapers/pub-dev.ts +2 -1
  209. package/src/web/scrapers/pubmed.ts +2 -1
  210. package/src/web/scrapers/pypi.ts +2 -1
  211. package/src/web/scrapers/rawg.ts +2 -8
  212. package/src/web/scrapers/reddit.ts +2 -1
  213. package/src/web/scrapers/repology.ts +2 -1
  214. package/src/web/scrapers/rfc.ts +2 -1
  215. package/src/web/scrapers/rubygems.ts +2 -1
  216. package/src/web/scrapers/searchcode.ts +2 -1
  217. package/src/web/scrapers/sec-edgar.ts +2 -1
  218. package/src/web/scrapers/semantic-scholar.ts +2 -1
  219. package/src/web/scrapers/snapcraft.ts +2 -1
  220. package/src/web/scrapers/sourcegraph.ts +2 -1
  221. package/src/web/scrapers/spdx.ts +2 -1
  222. package/src/web/scrapers/stackoverflow.ts +2 -1
  223. package/src/web/scrapers/terraform.ts +2 -1
  224. package/src/web/scrapers/types.ts +0 -11
  225. package/src/web/scrapers/vimeo.ts +2 -1
  226. package/src/web/scrapers/vscode-marketplace.ts +2 -1
  227. package/src/web/scrapers/w3c.ts +2 -1
  228. package/src/web/scrapers/wikidata.ts +2 -1
  229. package/src/web/search/index.ts +10 -14
  230. package/src/web/search/provider.ts +2 -2
  231. package/src/web/search/providers/codex.ts +1 -2
  232. package/src/web/search/providers/exa.ts +1 -6
  233. package/src/web/search/providers/gemini.ts +1 -1
  234. package/src/web/search/providers/perplexity.ts +1 -2
  235. package/src/web/search/providers/utils.ts +1 -1
@@ -1,6 +1,6 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
- import { getRemoteDir } from "@oh-my-pi/pi-utils/dirs";
3
+ import { getRemoteDir, postmortem } from "@oh-my-pi/pi-utils";
4
4
  import { $ } from "bun";
5
5
  import { getControlDir, getControlPathTemplate, type SSHConnectionTarget } from "./connection-manager";
6
6
  import { buildSshTarget, sanitizeHostName } from "./utils";
@@ -83,6 +83,8 @@ export async function isMounted(path: string): Promise<boolean> {
83
83
  return result.exitCode === 0;
84
84
  }
85
85
 
86
+ let registered = false;
87
+
86
88
  export async function mountRemote(host: SSHConnectionTarget, remotePath = "/"): Promise<string | undefined> {
87
89
  if (!hasSshfs()) return undefined;
88
90
 
@@ -90,6 +92,10 @@ export async function mountRemote(host: SSHConnectionTarget, remotePath = "/"):
90
92
  await Promise.all([ensureDir(REMOTE_DIR), ensureDir(CONTROL_DIR), ensureDir(mountPath)]);
91
93
 
92
94
  if (await isMounted(mountPath)) {
95
+ if (!registered) {
96
+ registered = true;
97
+ postmortem.register("sshfs-cleanup", unmountAll);
98
+ }
93
99
  mountedPaths.add(mountPath);
94
100
  return mountPath;
95
101
  }
@@ -5,8 +5,7 @@
5
5
  import * as fs from "node:fs";
6
6
  import * as os from "node:os";
7
7
  import * as path from "node:path";
8
- import { $env, hasFsCode, isEnoent, logger } from "@oh-my-pi/pi-utils";
9
- import { getGpuCachePath, getProjectDir } from "@oh-my-pi/pi-utils/dirs";
8
+ import { $env, getGpuCachePath, getProjectDir, hasFsCode, isEnoent, logger } from "@oh-my-pi/pi-utils";
10
9
  import { $ } from "bun";
11
10
  import { contextFileCapability } from "./capability/context-file";
12
11
  import { systemPromptCapability } from "./capability/system-prompt";
@@ -16,7 +15,6 @@ import { type ContextFile, loadCapability, type SystemPrompt as SystemPromptFile
16
15
  import { loadSkills, type Skill } from "./extensibility/skills";
17
16
  import customSystemPromptTemplate from "./prompts/system/custom-system-prompt.md" with { type: "text" };
18
17
  import systemPromptTemplate from "./prompts/system/system-prompt.md" with { type: "text" };
19
- import type { ToolName } from "./tools";
20
18
 
21
19
  type PreloadedSkill = { name: string; content: string };
22
20
 
@@ -206,65 +204,6 @@ function getTerminalName(): string | undefined {
206
204
  return term ?? undefined;
207
205
  }
208
206
 
209
- function normalizeDesktopValue(value: string): string | undefined {
210
- const trimmed = value.trim();
211
- if (!trimmed) return undefined;
212
- const parts = trimmed
213
- .split(":")
214
- .map(part => part.trim())
215
- .filter(Boolean);
216
- return parts[0] ?? trimmed;
217
- }
218
-
219
- function getDesktopEnvironment(): string | undefined {
220
- if (Bun.env.KDE_FULL_SESSION === "true") return "KDE";
221
- const raw = firstNonEmpty(
222
- Bun.env.XDG_CURRENT_DESKTOP,
223
- Bun.env.DESKTOP_SESSION,
224
- Bun.env.XDG_SESSION_DESKTOP,
225
- Bun.env.GDMSESSION,
226
- );
227
- return raw ? normalizeDesktopValue(raw) : undefined;
228
- }
229
-
230
- function matchKnownWindowManager(value: string): string | null {
231
- const normalized = value.toLowerCase();
232
- const candidates = [
233
- "sway",
234
- "i3",
235
- "i3wm",
236
- "bspwm",
237
- "openbox",
238
- "awesome",
239
- "herbstluftwm",
240
- "fluxbox",
241
- "icewm",
242
- "dwm",
243
- "hyprland",
244
- "wayfire",
245
- "river",
246
- "labwc",
247
- "qtile",
248
- ];
249
- for (const candidate of candidates) {
250
- if (normalized.includes(candidate)) return candidate;
251
- }
252
- return null;
253
- }
254
-
255
- function getWindowManager(): string | undefined {
256
- const explicit = firstNonEmpty(Bun.env.WINDOWMANAGER);
257
- if (explicit) return explicit;
258
-
259
- const desktop = firstNonEmpty(Bun.env.XDG_CURRENT_DESKTOP, Bun.env.DESKTOP_SESSION);
260
- if (desktop) {
261
- const matched = matchKnownWindowManager(desktop);
262
- if (matched) return matched;
263
- }
264
-
265
- return undefined;
266
- }
267
-
268
207
  /** Cached system info structure */
269
208
  interface GpuCache {
270
209
  gpu: string;
@@ -308,13 +247,11 @@ async function getEnvironmentInfo(): Promise<Array<{ label: string; value: strin
308
247
  { label: "Distro", value: os.type() },
309
248
  { label: "Kernel", value: os.version() },
310
249
  { label: "Arch", value: os.arch() },
311
- { label: "CPU", value: `${cpus.length}x ${cpus[0]?.model}` },
250
+ { label: "CPU", value: `${cpus[0]?.model}` },
312
251
  { label: "GPU", value: gpu },
313
252
  { label: "Terminal", value: getTerminalName() },
314
- { label: "DE", value: getDesktopEnvironment() },
315
- { label: "WM", value: getWindowManager() },
316
253
  ];
317
- return entries.filter((e): e is { label: string; value: string } => e.value != null && e.value !== "unknown");
254
+ return entries.filter((e): e is { label: string; value: string } => !!e.value);
318
255
  }
319
256
 
320
257
  /** Resolve input as file path or literal string */
@@ -435,7 +372,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
435
372
  appendSystemPrompt,
436
373
  repeatToolDescriptions = false,
437
374
  skillsSettings,
438
- toolNames,
375
+ toolNames: providedToolNames,
439
376
  cwd,
440
377
  contextFiles: providedContextFiles,
441
378
  skills: providedSkills,
@@ -554,25 +491,24 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
554
491
  timeZoneName: "short",
555
492
  });
556
493
 
557
- // Build tool descriptions array
558
- // Priority: toolNames (explicit list) > tools (Map) > defaults
494
+ // Build tool metadata for system prompt rendering
495
+ // Priority: explicit list > tools map > defaults
559
496
  // Default includes both bash and python; actual availability determined by settings in createTools
560
- const defaultToolNames: ToolName[] = ["read", "bash", "python", "edit", "write"];
561
- let toolNamesArray: string[];
562
- if (toolNames !== undefined) {
563
- // Explicit toolNames list provided (could be empty)
564
- toolNamesArray = toolNames;
565
- } else if (tools !== undefined) {
566
- // Tools map provided
567
- toolNamesArray = Array.from(tools.keys());
568
- } else {
569
- // Use defaults
570
- toolNamesArray = defaultToolNames;
497
+ let toolNames = providedToolNames;
498
+ if (!toolNames) {
499
+ if (tools) {
500
+ // Tools map provided
501
+ toolNames = Array.from(tools.keys());
502
+ } else {
503
+ // Use defaults
504
+ toolNames = ["read", "bash", "python", "edit", "write"]; // TODO: Why?
505
+ }
571
506
  }
572
507
 
573
508
  // Build tool descriptions for system prompt rendering
574
- const toolDescriptions = toolNamesArray.map(name => ({
509
+ const toolInfo = toolNames.map(name => ({
575
510
  name,
511
+ label: tools?.get(name)?.label ?? "",
576
512
  description: tools?.get(name)?.description ?? "",
577
513
  }));
578
514
 
@@ -580,29 +516,15 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
580
516
  const hasRead = tools?.has("read");
581
517
  const filteredSkills = preloadedSkills === undefined && hasRead ? skills : [];
582
518
 
583
- if (resolvedCustomPrompt) {
584
- return renderPromptTemplate(customSystemPromptTemplate, {
585
- systemPromptCustomization: systemPromptCustomization ?? "",
586
- customPrompt: resolvedCustomPrompt,
587
- appendPrompt: resolvedAppendPrompt ?? "",
588
- contextFiles,
589
- agentsMdSearch,
590
- skills: filteredSkills,
591
- preloadedSkills: preloadedSkillContents,
592
- rules: rules ?? [],
593
- date,
594
- dateTime,
595
- cwd: resolvedCwd,
596
- });
597
- }
598
-
599
519
  const environment = await logger.timeAsync("getEnvironmentInfo", getEnvironmentInfo);
600
- return renderPromptTemplate(systemPromptTemplate, {
601
- tools: toolNamesArray,
602
- toolDescriptions,
520
+ const data = {
521
+ systemPromptCustomization: systemPromptCustomization ?? "",
522
+ customPrompt: resolvedCustomPrompt,
523
+ appendPrompt: resolvedAppendPrompt ?? "",
524
+ tools: toolNames,
525
+ toolInfo,
603
526
  repeatToolDescriptions,
604
527
  environment,
605
- systemPromptCustomization: systemPromptCustomization ?? "",
606
528
  contextFiles,
607
529
  agentsMdSearch,
608
530
  skills: filteredSkills,
@@ -611,8 +533,8 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
611
533
  date,
612
534
  dateTime,
613
535
  cwd: resolvedCwd,
614
- appendSystemPrompt: resolvedAppendPrompt ?? "",
615
536
  intentTracing: !!intentField,
616
537
  intentField: intentField ?? "",
617
- });
538
+ };
539
+ return renderPromptTemplate(resolvedCustomPrompt ? customSystemPromptTemplate : systemPromptTemplate, data);
618
540
  }
@@ -108,7 +108,7 @@ export function parseAgent(
108
108
  });
109
109
  const fields = parseAgentFields(frontmatter);
110
110
  if (!fields) {
111
- throw new AgentParsingError(new Error("Invalid agent fields"), filePath);
111
+ throw new AgentParsingError(new Error(`Invalid agent field: ${filePath}\n${content}`), filePath);
112
112
  }
113
113
  return {
114
114
  ...fields,
@@ -1,8 +1,7 @@
1
1
  import * as fs from "node:fs/promises";
2
2
  import * as os from "node:os";
3
3
  import path from "node:path";
4
- import { isEnoent, Snowflake } from "@oh-my-pi/pi-utils";
5
- import { getWorktreeDir } from "@oh-my-pi/pi-utils/dirs";
4
+ import { getWorktreeDir, isEnoent, Snowflake } from "@oh-my-pi/pi-utils";
6
5
  import { $ } from "bun";
7
6
 
8
7
  export interface WorktreeBaseline {
package/src/tools/ask.ts CHANGED
@@ -59,7 +59,6 @@ export interface QuestionResult {
59
59
  }
60
60
 
61
61
  export interface AskToolDetails {
62
- /** Single question mode (backwards compatible) */
63
62
  question?: string;
64
63
  options?: string[];
65
64
  multi?: boolean;
@@ -79,7 +79,7 @@ export class AwaitTool implements AgentTool<typeof awaitSchema, AwaitToolDetails
79
79
  // If all watched jobs are already done, return immediately
80
80
  const runningJobs = jobsToWatch.filter(j => j.status === "running");
81
81
  if (runningJobs.length === 0) {
82
- return this.#buildResult(jobsToWatch);
82
+ return this.#buildResult(manager, jobsToWatch);
83
83
  }
84
84
 
85
85
  // Block until at least one running job finishes or the call is aborted
@@ -100,13 +100,14 @@ export class AwaitTool implements AgentTool<typeof awaitSchema, AwaitToolDetails
100
100
  }
101
101
 
102
102
  if (signal?.aborted) {
103
- return this.#buildResult(jobsToWatch);
103
+ return this.#buildResult(manager, jobsToWatch);
104
104
  }
105
105
 
106
- return this.#buildResult(jobsToWatch);
106
+ return this.#buildResult(manager, jobsToWatch);
107
107
  }
108
108
 
109
109
  #buildResult(
110
+ manager: NonNullable<ToolSession["asyncJobManager"]>,
110
111
  jobs: {
111
112
  id: string;
112
113
  type: "bash" | "task";
@@ -128,6 +129,8 @@ export class AwaitTool implements AgentTool<typeof awaitSchema, AwaitToolDetails
128
129
  ...(j.errorText ? { errorText: j.errorText } : {}),
129
130
  }));
130
131
 
132
+ manager.acknowledgeDeliveries(jobResults.filter(j => j.status !== "running").map(j => j.id));
133
+
131
134
  const completed = jobResults.filter(j => j.status !== "running");
132
135
  const running = jobResults.filter(j => j.status === "running");
133
136
 
@@ -11,6 +11,7 @@ import {
11
11
  } from "@oh-my-pi/pi-tui";
12
12
  import type { Terminal as XtermTerminalType } from "@xterm/headless";
13
13
  import xterm from "@xterm/headless";
14
+ import { NON_INTERACTIVE_ENV } from "../exec/non-interactive-env";
14
15
  import type { Theme } from "../modes/theme/theme";
15
16
  import { OutputSink, type OutputSummary } from "../session/streaming-output";
16
17
  import { formatStatusIcon, replaceTabs } from "./render-utils";
@@ -275,50 +276,6 @@ class BashInteractiveOverlayComponent implements Component {
275
276
  }
276
277
  }
277
278
 
278
- export const NO_PAGER_ENV = {
279
- // Disable pagers so commands don't block on interactive views.
280
- PAGER: "cat",
281
- GIT_PAGER: "cat",
282
- MANPAGER: "cat",
283
- SYSTEMD_PAGER: "cat",
284
- BAT_PAGER: "cat",
285
- DELTA_PAGER: "cat",
286
- GH_PAGER: "cat",
287
- GLAB_PAGER: "cat",
288
- PSQL_PAGER: "cat",
289
- MYSQL_PAGER: "cat",
290
- AWS_PAGER: "",
291
- HOMEBREW_PAGER: "cat",
292
- LESS: "FRX",
293
- // Disable editor and terminal credential prompts.
294
- GIT_EDITOR: "true",
295
- VISUAL: "true",
296
- EDITOR: "true",
297
- GIT_TERMINAL_PROMPT: "0",
298
- SSH_ASKPASS: "/usr/bin/false",
299
- CI: "1",
300
- // Package manager defaults for unattended execution.
301
- npm_config_yes: "true",
302
- npm_config_update_notifier: "false",
303
- npm_config_fund: "false",
304
- npm_config_audit: "false",
305
- npm_config_progress: "false",
306
- PNPM_DISABLE_SELF_UPDATE_CHECK: "true",
307
- PNPM_UPDATE_NOTIFIER: "false",
308
- YARN_ENABLE_TELEMETRY: "0",
309
- YARN_ENABLE_PROGRESS_BARS: "0",
310
- // Cross-language/tooling non-interactive defaults.
311
- CARGO_TERM_PROGRESS_WHEN: "never",
312
- DEBIAN_FRONTEND: "noninteractive",
313
- PIP_NO_INPUT: "1",
314
- PIP_DISABLE_PIP_VERSION_CHECK: "1",
315
- TF_INPUT: "0",
316
- TF_IN_AUTOMATION: "1",
317
- GH_PROMPT_DISABLED: "1",
318
- COMPOSER_NO_INTERACTION: "1",
319
- CLOUDSDK_CORE_DISABLE_PROMPTS: "1",
320
- };
321
-
322
279
  export async function runInteractiveBashPty(
323
280
  ui: NonNullable<AgentToolContext["ui"]>,
324
281
  options: {
@@ -388,8 +345,8 @@ export async function runInteractiveBashPty(
388
345
  cwd: options.cwd,
389
346
  timeoutMs: options.timeoutMs,
390
347
  env: {
348
+ ...NON_INTERACTIVE_ENV,
391
349
  ...options.env,
392
- ...NO_PAGER_ENV,
393
350
  },
394
351
  signal: options.signal,
395
352
  cols,
package/src/tools/bash.ts CHANGED
@@ -3,11 +3,11 @@ import * as path from "node:path";
3
3
  import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
4
4
  import type { Component } from "@oh-my-pi/pi-tui";
5
5
  import { Text } from "@oh-my-pi/pi-tui";
6
- import { $env, isEnoent } from "@oh-my-pi/pi-utils";
7
- import { getProjectDir } from "@oh-my-pi/pi-utils/dirs";
6
+ import { $env, getProjectDir, isEnoent } from "@oh-my-pi/pi-utils";
8
7
  import { Type } from "@sinclair/typebox";
9
8
  import { renderPromptTemplate } from "../config/prompt-templates";
10
9
  import { type BashResult, executeBash } from "../exec/bash-executor";
10
+ import { NON_INTERACTIVE_ENV } from "../exec/non-interactive-env";
11
11
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
12
12
  import { truncateToVisualLines } from "../modes/components/visual-truncate";
13
13
  import type { Theme } from "../modes/theme/theme";
@@ -16,7 +16,7 @@ import { DEFAULT_MAX_BYTES, TailBuffer } from "../session/streaming-output";
16
16
  import { renderStatusLine } from "../tui";
17
17
  import { CachedOutputBlock } from "../tui/output-block";
18
18
  import type { ToolSession } from ".";
19
- import { type BashInteractiveResult, NO_PAGER_ENV, runInteractiveBashPty } from "./bash-interactive";
19
+ import { type BashInteractiveResult, runInteractiveBashPty } from "./bash-interactive";
20
20
  import { checkBashInterception } from "./bash-interceptor";
21
21
  import { applyHeadTail } from "./bash-normalize";
22
22
  import { expandInternalUrls, type InternalUrlExpansionOptions } from "./bash-skill-urls";
@@ -222,7 +222,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
222
222
  sessionKey: `${this.session.getSessionId?.() ?? ""}:async:${jobId}`,
223
223
  timeout: timeoutMs,
224
224
  signal: runSignal,
225
- env: NO_PAGER_ENV,
225
+ env: NON_INTERACTIVE_ENV,
226
226
  artifactPath,
227
227
  artifactId,
228
228
  onChunk: chunk => {
@@ -273,7 +273,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
273
273
  sessionKey: this.session.getSessionId?.() ?? undefined,
274
274
  timeout: timeoutMs,
275
275
  signal,
276
- env: NO_PAGER_ENV,
276
+ env: NON_INTERACTIVE_ENV,
277
277
  artifactPath,
278
278
  artifactId,
279
279
  onChunk: chunk => {
@@ -3,8 +3,7 @@ import * as path from "node:path";
3
3
  import { Readability } from "@mozilla/readability";
4
4
  import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
5
5
  import { StringEnum } from "@oh-my-pi/pi-ai";
6
- import { logger, Snowflake, untilAborted } from "@oh-my-pi/pi-utils";
7
- import { getPuppeteerDir } from "@oh-my-pi/pi-utils/dirs";
6
+ import { getPuppeteerDir, logger, Snowflake, untilAborted } from "@oh-my-pi/pi-utils";
8
7
  import { type Static, Type } from "@sinclair/typebox";
9
8
  import { type HTMLElement, parseHTML } from "linkedom";
10
9
  import type {
@@ -13,8 +13,8 @@ import { resolveReadPath } from "./path-utils";
13
13
  const DEFAULT_MODEL = "gemini-3-pro-image-preview";
14
14
  const DEFAULT_OPENROUTER_MODEL = "google/gemini-3-pro-image-preview";
15
15
  const DEFAULT_ANTIGRAVITY_MODEL = "gemini-3-pro-image";
16
- const DEFAULT_TIMEOUT_SECONDS = 120;
17
- const MAX_IMAGE_SIZE = 20 * 1024 * 1024;
16
+ const IMAGE_TIMEOUT = 3 * 60 * 1000; // 3 minutes
17
+ const MAX_IMAGE_SIZE = 35 * 1024 * 1024;
18
18
 
19
19
  const ANTIGRAVITY_ENDPOINT = "https://daily-cloudcode-pa.sandbox.googleapis.com";
20
20
  const IMAGE_SYSTEM_INSTRUCTION =
@@ -76,13 +76,7 @@ const baseImageSchema = Type.Object(
76
76
  style: Type.Optional(
77
77
  Type.String({
78
78
  description:
79
- "Artistic style, mood, color grading (e.g., 'film noir mood, cinematic color grading', 'Studio Ghibli watercolor', 'photorealistic').",
80
- }),
81
- ),
82
- camera: Type.Optional(
83
- Type.String({
84
- description:
85
- "Lens and camera specs (e.g., 'Shot on 35mm, f/1.8', 'macro lens, extreme close-up', '85mm portrait lens').",
79
+ "Artistic style, mood, color grading, camera (e.g., 'film noir mood, cinematic color grading', 'Studio Ghibli watercolor', 'photorealistic').",
86
80
  }),
87
81
  ),
88
82
  text: Type.Optional(
@@ -94,23 +88,16 @@ const baseImageSchema = Type.Object(
94
88
  changes: Type.Optional(
95
89
  Type.Array(Type.String(), {
96
90
  description:
97
- "For edits: specific changes to make (e.g., ['Change the tie to green', 'Remove the car in background']). Use with input_images.",
98
- }),
99
- ),
100
- preserve: Type.Optional(
101
- Type.String({
102
- description:
103
- "For edits: what to keep unchanged (e.g., 'identity, face, hairstyle, lighting'). Use with input_images and changes.",
91
+ "For edits: specific changes to make, as well as, what to keep unchanged (e.g., ['Change the tie to green', 'Remove the car in background']). Use with input_images.",
104
92
  }),
105
93
  ),
106
94
  aspect_ratio: Type.Optional(aspectRatioSchema),
107
95
  image_size: Type.Optional(imageSizeSchema),
108
- input_images: Type.Optional(
96
+ input: Type.Optional(
109
97
  Type.Array(inputImageSchema, {
110
98
  description: "Optional input images for edits or variations.",
111
99
  }),
112
100
  ),
113
- timeout: Type.Optional(Type.Number({ description: "Timeout in seconds (default: 120)" })),
114
101
  },
115
102
  { additionalProperties: false },
116
103
  );
@@ -136,7 +123,6 @@ function assemblePrompt(params: GeminiImageParams): string {
136
123
  // Technical details as separate sentences
137
124
  if (params.composition) parts.push(params.composition);
138
125
  if (params.lighting) parts.push(params.lighting);
139
- if (params.camera) parts.push(params.camera);
140
126
  if (params.style) parts.push(params.style);
141
127
 
142
128
  // Join with periods for sentence structure
@@ -150,9 +136,6 @@ function assemblePrompt(params: GeminiImageParams): string {
150
136
  // Edit mode: changes and preserve directives
151
137
  if (params.changes?.length) {
152
138
  prompt += `\n\nChanges:\n${params.changes.map(c => `- ${c}`).join("\n")}`;
153
- if (params.preserve) {
154
- prompt += `\n\nPreserve: ${params.preserve}`;
155
- }
156
139
  }
157
140
 
158
141
  return prompt;
@@ -638,16 +621,13 @@ export const geminiImageTool: CustomTool<typeof geminiImageSchema, GeminiImageTo
638
621
  const cwd = ctx.sessionManager.getCwd();
639
622
 
640
623
  const resolvedImages: InlineImageData[] = [];
641
- if (params.input_images?.length) {
642
- for (const input of params.input_images) {
624
+ if (params.input?.length) {
625
+ for (const input of params.input) {
643
626
  resolvedImages.push(await resolveInputImage(input, cwd));
644
627
  }
645
628
  }
646
629
 
647
- const { timeout: rawTimeout = DEFAULT_TIMEOUT_SECONDS } = params;
648
- // Clamp to reasonable range: 1s - 600s (10 min)
649
- const timeoutSeconds = Math.max(1, Math.min(600, rawTimeout));
650
- const requestSignal = ptree.combineSignals(signal, timeoutSeconds * 1000);
630
+ const requestSignal = ptree.combineSignals(signal, IMAGE_TIMEOUT);
651
631
 
652
632
  if (provider === "antigravity") {
653
633
  if (!apiKey.projectId) {
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * JSON tree rendering utilities shared across tool renderers.
3
3
  */
4
+ import { INTENT_FIELD } from "@oh-my-pi/pi-agent-core";
4
5
  import type { Theme } from "../modes/theme/theme";
5
6
  import { truncateToWidth } from "./render-utils";
6
7
 
@@ -13,7 +14,7 @@ export const JSON_TREE_SCALAR_LEN_COLLAPSED = 60;
13
14
  export const JSON_TREE_SCALAR_LEN_EXPANDED = 2000;
14
15
 
15
16
  /** Keys injected by the harness that should not be displayed to users */
16
- const HIDDEN_ARG_KEYS = new Set(["agent__intent"]);
17
+ const HIDDEN_ARG_KEYS = new Set([INTENT_FIELD]);
17
18
 
18
19
  /** Strip harness-internal keys from tool args for display */
19
20
  export function stripInternalArgs(args: Record<string, unknown>): Record<string, unknown> {
@@ -4,7 +4,7 @@ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallb
4
4
  import type { ImageContent } from "@oh-my-pi/pi-ai";
5
5
  import type { Component } from "@oh-my-pi/pi-tui";
6
6
  import { Markdown, Text } from "@oh-my-pi/pi-tui";
7
- import { getProjectDir } from "@oh-my-pi/pi-utils/dirs";
7
+ import { getProjectDir } from "@oh-my-pi/pi-utils";
8
8
  import { type Static, Type } from "@sinclair/typebox";
9
9
  import { renderPromptTemplate } from "../config/prompt-templates";
10
10
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
package/src/tools/read.ts CHANGED
@@ -5,8 +5,7 @@ import type { ImageContent, TextContent } from "@oh-my-pi/pi-ai";
5
5
  import { FileType, glob } from "@oh-my-pi/pi-natives";
6
6
  import type { Component } from "@oh-my-pi/pi-tui";
7
7
  import { Text } from "@oh-my-pi/pi-tui";
8
- import { ptree, untilAborted } from "@oh-my-pi/pi-utils";
9
- import { getRemoteDir } from "@oh-my-pi/pi-utils/dirs";
8
+ import { getRemoteDir, ptree, untilAborted } from "@oh-my-pi/pi-utils";
10
9
  import { type Static, Type } from "@sinclair/typebox";
11
10
  import { renderPromptTemplate } from "../config/prompt-templates";
12
11
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
@@ -6,11 +6,14 @@
6
6
  */
7
7
  import * as os from "node:os";
8
8
  import { type Ellipsis, truncateToWidth } from "@oh-my-pi/pi-tui";
9
- import { pluralize } from "@oh-my-pi/pi-utils";
9
+ import { getIndentation, pluralize } from "@oh-my-pi/pi-utils";
10
10
  import type { Theme } from "../modes/theme/theme";
11
11
 
12
- export { Ellipsis, replaceTabs, truncateToWidth } from "@oh-my-pi/pi-tui";
12
+ export { Ellipsis, truncateToWidth } from "@oh-my-pi/pi-tui";
13
13
 
14
+ export function replaceTabs(text: string, file?: string): string {
15
+ return text.replaceAll("\t", getIndentation(file));
16
+ }
14
17
  // =============================================================================
15
18
  // Standardized Display Constants
16
19
  // =============================================================================
@@ -41,16 +41,18 @@ export interface TodoWriteToolDetails {
41
41
  // Schema
42
42
  // =============================================================================
43
43
 
44
- const StatusEnum = StringEnum(["pending", "in_progress", "completed", "abandoned"] as const);
44
+ const StatusEnum = StringEnum(["pending", "in_progress", "completed", "abandoned"] as const, {
45
+ description: "Task status",
46
+ });
45
47
 
46
48
  const InputTask = Type.Object({
47
- content: Type.String(),
49
+ content: Type.String({ description: "Task description" }),
48
50
  status: Type.Optional(StatusEnum),
49
- notes: Type.Optional(Type.String()),
51
+ notes: Type.Optional(Type.String({ description: "Additional context or notes" })),
50
52
  });
51
53
 
52
54
  const InputPhase = Type.Object({
53
- name: Type.String(),
55
+ name: Type.String({ description: "Phase name" }),
54
56
  tasks: Type.Optional(Type.Array(InputTask)),
55
57
  });
56
58
 
@@ -63,21 +65,21 @@ const todoWriteSchema = Type.Object({
63
65
  }),
64
66
  Type.Object({
65
67
  op: Type.Literal("add_phase"),
66
- name: Type.String(),
68
+ name: Type.String({ description: "Phase name" }),
67
69
  tasks: Type.Optional(Type.Array(InputTask)),
68
70
  }),
69
71
  Type.Object({
70
72
  op: Type.Literal("add_task"),
71
73
  phase: Type.String({ description: "Phase ID, e.g. phase-1" }),
72
- content: Type.String(),
73
- notes: Type.Optional(Type.String()),
74
+ content: Type.String({ description: "Task description" }),
75
+ notes: Type.Optional(Type.String({ description: "Additional context or notes" })),
74
76
  }),
75
77
  Type.Object({
76
78
  op: Type.Literal("update"),
77
79
  id: Type.String({ description: "Task ID, e.g. task-3" }),
78
80
  status: Type.Optional(StatusEnum),
79
- content: Type.Optional(Type.String()),
80
- notes: Type.Optional(Type.String()),
81
+ content: Type.Optional(Type.String({ description: "Updated task description" })),
82
+ notes: Type.Optional(Type.String({ description: "Additional context or notes" })),
81
83
  }),
82
84
  Type.Object({
83
85
  op: Type.Literal("remove_task"),
@@ -1,8 +1,7 @@
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 { logger, ptree, TempDir } from "@oh-my-pi/pi-utils";
5
- import { APP_NAME, getToolsDir } from "@oh-my-pi/pi-utils/dirs";
4
+ import { APP_NAME, getToolsDir, logger, ptree, TempDir } from "@oh-my-pi/pi-utils";
6
5
 
7
6
  const TOOLS_DIR = getToolsDir();
8
7
  const TOOL_DOWNLOAD_TIMEOUT_MS = 15000;
@@ -1,5 +1,6 @@
1
+ import { tryParseJson } from "@oh-my-pi/pi-utils";
1
2
  import type { RenderResult, SpecialHandler } from "./types";
2
- import { buildResult, formatIsoDate, formatNumber, loadPage, tryParseJson } from "./types";
3
+ import { buildResult, formatIsoDate, formatNumber, loadPage } from "./types";
3
4
 
4
5
  interface ArtifactHubMaintainer {
5
6
  name: string;
@@ -1,5 +1,6 @@
1
+ import { tryParseJson } from "@oh-my-pi/pi-utils";
1
2
  import type { RenderResult, SpecialHandler } from "./types";
2
- import { buildResult, formatIsoDate, formatNumber, loadPage, tryParseJson } from "./types";
3
+ import { buildResult, formatIsoDate, formatNumber, loadPage } from "./types";
3
4
 
4
5
  interface AurPackage {
5
6
  Name: string;
@@ -1,5 +1,6 @@
1
+ import { tryParseJson } from "@oh-my-pi/pi-utils";
1
2
  import type { RenderResult, SpecialHandler } from "./types";
2
- import { buildResult, loadPage, tryParseJson } from "./types";
3
+ import { buildResult, loadPage } from "./types";
3
4
 
4
5
  interface BiorxivPaper {
5
6
  biorxiv_doi?: string;
@@ -1,5 +1,6 @@
1
+ import { tryParseJson } from "@oh-my-pi/pi-utils";
1
2
  import type { RenderResult, SpecialHandler } from "./types";
2
- import { buildResult, formatNumber, loadPage, tryParseJson } from "./types";
3
+ import { buildResult, formatNumber, loadPage } from "./types";
3
4
 
4
5
  const API_BASE = "https://public.api.bsky.app/xrpc";
5
6
 
@@ -1,5 +1,6 @@
1
+ import { tryParseJson } from "@oh-my-pi/pi-utils";
1
2
  import type { RenderResult, SpecialHandler } from "./types";
2
- import { buildResult, formatIsoDate, formatNumber, loadPage, tryParseJson } from "./types";
3
+ import { buildResult, formatIsoDate, formatNumber, loadPage } from "./types";
3
4
 
4
5
  interface NuGetODataEntry {
5
6
  Id: string;