@oh-my-pi/pi-coding-agent 13.18.0 → 14.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (235) hide show
  1. package/CHANGELOG.md +316 -1
  2. package/package.json +86 -24
  3. package/scripts/format-prompts.ts +2 -2
  4. package/src/autoresearch/apply-contract-to-state.ts +24 -0
  5. package/src/autoresearch/contract.ts +0 -44
  6. package/src/autoresearch/dashboard.ts +1 -2
  7. package/src/autoresearch/git.ts +116 -30
  8. package/src/autoresearch/helpers.ts +49 -0
  9. package/src/autoresearch/index.ts +28 -187
  10. package/src/autoresearch/prompt.md +26 -9
  11. package/src/autoresearch/state.ts +0 -6
  12. package/src/autoresearch/tools/init-experiment.ts +202 -117
  13. package/src/autoresearch/tools/log-experiment.ts +123 -178
  14. package/src/autoresearch/tools/run-experiment.ts +48 -10
  15. package/src/autoresearch/types.ts +2 -2
  16. package/src/capability/index.ts +4 -2
  17. package/src/cli/file-processor.ts +3 -3
  18. package/src/cli/grep-cli.ts +8 -8
  19. package/src/cli/grievances-cli.ts +78 -0
  20. package/src/cli/read-cli.ts +67 -0
  21. package/src/cli/setup-cli.ts +4 -4
  22. package/src/cli/update-cli.ts +3 -3
  23. package/src/cli.ts +2 -0
  24. package/src/commands/grep.ts +6 -1
  25. package/src/commands/grievances.ts +20 -0
  26. package/src/commands/read.ts +33 -0
  27. package/src/commit/agentic/agent.ts +5 -8
  28. package/src/commit/agentic/index.ts +22 -26
  29. package/src/commit/agentic/tools/analyze-file.ts +3 -3
  30. package/src/commit/agentic/tools/git-file-diff.ts +3 -6
  31. package/src/commit/agentic/tools/git-hunk.ts +3 -3
  32. package/src/commit/agentic/tools/git-overview.ts +6 -9
  33. package/src/commit/agentic/tools/index.ts +6 -8
  34. package/src/commit/agentic/tools/propose-commit.ts +4 -7
  35. package/src/commit/agentic/tools/recent-commits.ts +3 -3
  36. package/src/commit/agentic/tools/split-commit.ts +4 -4
  37. package/src/commit/agentic/validation.ts +1 -1
  38. package/src/commit/analysis/conventional.ts +4 -4
  39. package/src/commit/analysis/summary.ts +3 -3
  40. package/src/commit/changelog/generate.ts +4 -4
  41. package/src/commit/changelog/index.ts +5 -9
  42. package/src/commit/map-reduce/map-phase.ts +4 -4
  43. package/src/commit/map-reduce/reduce-phase.ts +4 -4
  44. package/src/commit/pipeline.ts +13 -16
  45. package/src/config/keybindings.ts +7 -6
  46. package/src/config/prompt-templates.ts +44 -226
  47. package/src/config/resolve-config-value.ts +4 -2
  48. package/src/config/settings-schema.ts +98 -2
  49. package/src/config/settings.ts +25 -26
  50. package/src/dap/client.ts +674 -0
  51. package/src/dap/config.ts +150 -0
  52. package/src/dap/defaults.json +211 -0
  53. package/src/dap/index.ts +4 -0
  54. package/src/dap/session.ts +1255 -0
  55. package/src/dap/types.ts +600 -0
  56. package/src/debug/log-viewer.ts +3 -2
  57. package/src/discovery/builtin.ts +1 -2
  58. package/src/discovery/codex.ts +2 -2
  59. package/src/discovery/github.ts +2 -1
  60. package/src/discovery/helpers.ts +2 -2
  61. package/src/discovery/opencode.ts +2 -2
  62. package/src/edit/diff.ts +818 -0
  63. package/src/edit/index.ts +309 -0
  64. package/src/edit/line-hash.ts +67 -0
  65. package/src/edit/modes/chunk.ts +454 -0
  66. package/src/{patch → edit/modes}/hashline.ts +741 -361
  67. package/src/{patch/applicator.ts → edit/modes/patch.ts} +420 -117
  68. package/src/{patch/fuzzy.ts → edit/modes/replace.ts} +519 -197
  69. package/src/{patch → edit}/normalize.ts +97 -76
  70. package/src/{patch/shared.ts → edit/renderer.ts} +181 -108
  71. package/src/exec/bash-executor.ts +4 -2
  72. package/src/exec/idle-timeout-watchdog.ts +126 -0
  73. package/src/exec/non-interactive-env.ts +5 -0
  74. package/src/extensibility/custom-commands/bundled/ci-green/index.ts +6 -18
  75. package/src/extensibility/custom-commands/bundled/review/index.ts +45 -43
  76. package/src/extensibility/custom-commands/loader.ts +1 -2
  77. package/src/extensibility/custom-tools/loader.ts +34 -11
  78. package/src/extensibility/custom-tools/types.ts +1 -1
  79. package/src/extensibility/extensions/loader.ts +9 -4
  80. package/src/extensibility/extensions/runner.ts +24 -1
  81. package/src/extensibility/extensions/types.ts +4 -2
  82. package/src/extensibility/hooks/loader.ts +5 -6
  83. package/src/extensibility/hooks/types.ts +2 -2
  84. package/src/extensibility/plugins/doctor.ts +2 -1
  85. package/src/extensibility/plugins/marketplace/fetcher.ts +2 -57
  86. package/src/extensibility/plugins/marketplace/source-resolver.ts +4 -4
  87. package/src/extensibility/slash-commands.ts +3 -7
  88. package/src/index.ts +3 -1
  89. package/src/internal-urls/docs-index.generated.ts +11 -11
  90. package/src/ipy/executor.ts +58 -17
  91. package/src/ipy/gateway-coordinator.ts +6 -4
  92. package/src/ipy/kernel.ts +45 -22
  93. package/src/ipy/runtime.ts +2 -2
  94. package/src/lsp/client.ts +7 -4
  95. package/src/lsp/clients/lsp-linter-client.ts +4 -4
  96. package/src/lsp/config.ts +2 -2
  97. package/src/lsp/defaults.json +688 -154
  98. package/src/lsp/index.ts +234 -45
  99. package/src/lsp/lspmux.ts +2 -2
  100. package/src/lsp/startup-events.ts +13 -0
  101. package/src/lsp/types.ts +12 -1
  102. package/src/lsp/utils.ts +8 -1
  103. package/src/main.ts +125 -47
  104. package/src/memories/index.ts +4 -5
  105. package/src/modes/acp/acp-agent.ts +563 -163
  106. package/src/modes/acp/acp-event-mapper.ts +9 -1
  107. package/src/modes/acp/acp-mode.ts +4 -2
  108. package/src/modes/components/agent-dashboard.ts +3 -4
  109. package/src/modes/components/diff.ts +6 -7
  110. package/src/modes/components/footer.ts +9 -29
  111. package/src/modes/components/hook-editor.ts +3 -3
  112. package/src/modes/components/hook-selector.ts +6 -1
  113. package/src/modes/components/read-tool-group.ts +6 -12
  114. package/src/modes/components/session-observer-overlay.ts +472 -0
  115. package/src/modes/components/settings-defs.ts +24 -0
  116. package/src/modes/components/status-line.ts +15 -61
  117. package/src/modes/components/tool-execution.ts +1 -1
  118. package/src/modes/components/welcome.ts +1 -1
  119. package/src/modes/controllers/btw-controller.ts +2 -2
  120. package/src/modes/controllers/command-controller.ts +4 -2
  121. package/src/modes/controllers/event-controller.ts +59 -2
  122. package/src/modes/controllers/extension-ui-controller.ts +1 -0
  123. package/src/modes/controllers/input-controller.ts +15 -8
  124. package/src/modes/controllers/selector-controller.ts +26 -0
  125. package/src/modes/index.ts +20 -2
  126. package/src/modes/interactive-mode.ts +278 -69
  127. package/src/modes/rpc/host-tools.ts +186 -0
  128. package/src/modes/rpc/rpc-client.ts +178 -13
  129. package/src/modes/rpc/rpc-mode.ts +73 -3
  130. package/src/modes/rpc/rpc-types.ts +53 -1
  131. package/src/modes/session-observer-registry.ts +146 -0
  132. package/src/modes/shared.ts +0 -42
  133. package/src/modes/theme/theme.ts +80 -8
  134. package/src/modes/types.ts +4 -2
  135. package/src/modes/utils/keybinding-matchers.ts +9 -0
  136. package/src/prompts/system/custom-system-prompt.md +5 -0
  137. package/src/prompts/system/system-prompt.md +8 -1
  138. package/src/prompts/tools/chunk-edit.md +219 -0
  139. package/src/prompts/tools/debug.md +43 -0
  140. package/src/prompts/tools/grep.md +3 -0
  141. package/src/prompts/tools/lsp.md +5 -5
  142. package/src/prompts/tools/read-chunk.md +17 -0
  143. package/src/prompts/tools/read.md +19 -5
  144. package/src/sdk.ts +216 -165
  145. package/src/secrets/index.ts +1 -1
  146. package/src/secrets/obfuscator.ts +25 -17
  147. package/src/session/agent-session.ts +381 -286
  148. package/src/session/agent-storage.ts +12 -12
  149. package/src/session/compaction/branch-summarization.ts +3 -3
  150. package/src/session/compaction/compaction.ts +5 -6
  151. package/src/session/compaction/utils.ts +3 -3
  152. package/src/session/history-storage.ts +62 -19
  153. package/src/session/messages.ts +3 -3
  154. package/src/session/session-dump-format.ts +203 -0
  155. package/src/session/session-manager.ts +15 -5
  156. package/src/session/session-storage.ts +4 -2
  157. package/src/session/streaming-output.ts +1 -1
  158. package/src/session/tool-choice-queue.ts +213 -0
  159. package/src/slash-commands/builtin-registry.ts +56 -8
  160. package/src/ssh/connection-manager.ts +2 -2
  161. package/src/ssh/sshfs-mount.ts +5 -5
  162. package/src/stt/downloader.ts +4 -4
  163. package/src/stt/recorder.ts +4 -4
  164. package/src/stt/transcriber.ts +2 -2
  165. package/src/system-prompt.ts +25 -13
  166. package/src/task/agents.ts +5 -6
  167. package/src/task/commands.ts +2 -5
  168. package/src/task/executor.ts +32 -4
  169. package/src/task/index.ts +91 -82
  170. package/src/task/template.ts +2 -2
  171. package/src/task/types.ts +25 -0
  172. package/src/task/worktree.ts +131 -149
  173. package/src/tools/ask.ts +2 -3
  174. package/src/tools/ast-edit.ts +7 -7
  175. package/src/tools/ast-grep.ts +7 -7
  176. package/src/tools/auto-generated-guard.ts +36 -41
  177. package/src/tools/await-tool.ts +2 -2
  178. package/src/tools/bash.ts +5 -23
  179. package/src/tools/browser.ts +4 -5
  180. package/src/tools/calculator.ts +2 -3
  181. package/src/tools/cancel-job.ts +2 -2
  182. package/src/tools/checkpoint.ts +3 -3
  183. package/src/tools/debug.ts +1007 -0
  184. package/src/tools/exit-plan-mode.ts +3 -3
  185. package/src/tools/fetch.ts +67 -3
  186. package/src/tools/find.ts +4 -5
  187. package/src/tools/fs-cache-invalidation.ts +5 -0
  188. package/src/tools/gemini-image.ts +13 -5
  189. package/src/tools/gh.ts +130 -308
  190. package/src/tools/grep.ts +57 -9
  191. package/src/tools/index.ts +44 -22
  192. package/src/tools/inspect-image.ts +4 -4
  193. package/src/tools/output-meta.ts +1 -1
  194. package/src/tools/python.ts +19 -6
  195. package/src/tools/read.ts +211 -146
  196. package/src/tools/render-mermaid.ts +2 -3
  197. package/src/tools/render-utils.ts +20 -6
  198. package/src/tools/renderers.ts +3 -1
  199. package/src/tools/report-tool-issue.ts +80 -0
  200. package/src/tools/resolve.ts +70 -39
  201. package/src/tools/search-tool-bm25.ts +2 -2
  202. package/src/tools/ssh.ts +2 -2
  203. package/src/tools/todo-write.ts +2 -2
  204. package/src/tools/tool-timeouts.ts +1 -0
  205. package/src/tools/write.ts +5 -6
  206. package/src/tui/tree-list.ts +3 -1
  207. package/src/utils/clipboard.ts +80 -0
  208. package/src/utils/commit-message-generator.ts +2 -3
  209. package/src/utils/edit-mode.ts +49 -0
  210. package/src/utils/external-editor.ts +11 -5
  211. package/src/utils/file-display-mode.ts +6 -5
  212. package/src/utils/file-mentions.ts +8 -7
  213. package/src/utils/git.ts +1400 -0
  214. package/src/utils/image-loading.ts +98 -0
  215. package/src/utils/title-generator.ts +2 -3
  216. package/src/utils/tools-manager.ts +6 -6
  217. package/src/web/scrapers/choosealicense.ts +1 -1
  218. package/src/web/search/index.ts +3 -3
  219. package/src/web/search/render.ts +6 -4
  220. package/src/autoresearch/command-initialize.md +0 -34
  221. package/src/commit/git/errors.ts +0 -9
  222. package/src/commit/git/index.ts +0 -210
  223. package/src/commit/git/operations.ts +0 -54
  224. package/src/patch/diff.ts +0 -433
  225. package/src/patch/index.ts +0 -888
  226. package/src/patch/parser.ts +0 -532
  227. package/src/patch/types.ts +0 -292
  228. package/src/prompts/agents/oracle.md +0 -77
  229. package/src/tools/gh-cli.ts +0 -125
  230. package/src/tools/pending-action.ts +0 -49
  231. package/src/utils/child-process.ts +0 -88
  232. package/src/utils/frontmatter.ts +0 -117
  233. package/src/utils/image-input.ts +0 -274
  234. package/src/utils/mime.ts +0 -53
  235. package/src/utils/prompt-format.ts +0 -170
package/src/tools/grep.ts CHANGED
@@ -1,18 +1,19 @@
1
1
  import * as path from "node:path";
2
2
  import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
3
3
 
4
- import { type GrepMatch, type GrepResult, grep } from "@oh-my-pi/pi-natives";
4
+ import { type GrepMatch, GrepOutputMode, type GrepResult, grep } from "@oh-my-pi/pi-natives";
5
5
  import type { Component } from "@oh-my-pi/pi-tui";
6
6
  import { Text } from "@oh-my-pi/pi-tui";
7
- import { untilAborted } from "@oh-my-pi/pi-utils";
7
+ import { prompt, untilAborted } from "@oh-my-pi/pi-utils";
8
8
  import { type Static, Type } from "@sinclair/typebox";
9
- import { renderPromptTemplate } from "../config/prompt-templates";
9
+ import { computeLineHash } from "../edit/line-hash";
10
+ import { formatChunkedGrepLine } from "../edit/modes/chunk";
10
11
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
11
- import type { Theme } from "../modes/theme/theme";
12
- import { computeLineHash } from "../patch/hashline";
12
+ import { getLanguageFromPath, type Theme } from "../modes/theme/theme";
13
13
  import grepDescription from "../prompts/tools/grep.md" with { type: "text" };
14
14
  import { DEFAULT_MAX_COLUMN, type TruncationResult, truncateHead } from "../session/streaming-output";
15
15
  import { Ellipsis, Hasher, type RenderCache, renderStatusLine, renderTreeList, truncateToWidth } from "../tui";
16
+ import { resolveEditMode } from "../utils/edit-mode";
16
17
  import { resolveFileDisplayMode } from "../utils/file-display-mode";
17
18
  import type { ToolSession } from ".";
18
19
  import { formatFullOutputReference, type OutputMeta } from "./output-meta";
@@ -72,9 +73,10 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
72
73
 
73
74
  constructor(private readonly session: ToolSession) {
74
75
  const displayMode = resolveFileDisplayMode(session);
75
- this.description = renderPromptTemplate(grepDescription, {
76
+ this.description = prompt.render(grepDescription, {
76
77
  IS_HASHLINE_MODE: displayMode.hashLines,
77
78
  IS_LINE_NUMBER_MODE: !displayMode.hashLines && displayMode.lineNumbers,
79
+ IS_CHUNK_MODE: displayMode.chunked,
78
80
  });
79
81
  }
80
82
 
@@ -89,6 +91,7 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
89
91
 
90
92
  return untilAborted(signal, async () => {
91
93
  const normalizedPattern = pattern.trim();
94
+ const chunkMode = resolveEditMode(this.session) === "chunk";
92
95
  if (!normalizedPattern) {
93
96
  throw new ToolError("Pattern must not be empty");
94
97
  }
@@ -162,7 +165,7 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
162
165
  throw new ToolError(`Path not found: ${scopePath}`);
163
166
  }
164
167
 
165
- const effectiveOutputMode = "content";
168
+ const effectiveOutputMode = GrepOutputMode.Content;
166
169
  const effectiveLimit = normalizedLimit ?? DEFAULT_MATCH_LIMIT;
167
170
  const internalLimit = Math.min(effectiveLimit * 5, 2000);
168
171
 
@@ -270,6 +273,50 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
270
273
  }
271
274
  matchesByFile.get(relativePath)!.push(match);
272
275
  }
276
+ if (chunkMode) {
277
+ const annotatedLines = await Promise.all(
278
+ selectedMatches.map(match => {
279
+ const relativePath = match.path.startsWith("/") ? match.path.slice(1) : match.path;
280
+ const absoluteFilePath = isDirectory ? path.join(searchPath, relativePath) : searchPath;
281
+ const displayPath = formatPath(match.path);
282
+ fileMatchCounts.set(displayPath, (fileMatchCounts.get(displayPath) ?? 0) + 1);
283
+ return formatChunkedGrepLine({
284
+ filePath: absoluteFilePath,
285
+ lineNumber: match.lineNumber,
286
+ line: match.line,
287
+ cwd: this.session.cwd,
288
+ language: getLanguageFromPath(absoluteFilePath),
289
+ });
290
+ }),
291
+ );
292
+ const rawOutput = annotatedLines.join("\n");
293
+ const truncation = truncateHead(rawOutput, { maxLines: Number.MAX_SAFE_INTEGER });
294
+ const truncated = Boolean(matchLimitReached || result.limitReached || truncation.truncated);
295
+ const details: GrepToolDetails = {
296
+ scopePath,
297
+ matchCount: selectedMatches.length,
298
+ fileCount: fileList.length,
299
+ files: fileList,
300
+ fileMatches: fileList.map(path => ({
301
+ path,
302
+ count: fileMatchCounts.get(path) ?? 0,
303
+ })),
304
+ truncated,
305
+ matchLimitReached: matchLimitReached ? effectiveLimit : undefined,
306
+ resultLimitReached: result.limitReached ? internalLimit : undefined,
307
+ };
308
+ if (truncation.truncated) details.truncation = truncation;
309
+ const resultBuilder = toolResult(details)
310
+ .text(truncation.content)
311
+ .limits({
312
+ matchLimit: matchLimitReached ? effectiveLimit : undefined,
313
+ resultLimit: result.limitReached ? internalLimit : undefined,
314
+ });
315
+ if (truncation.truncated) {
316
+ resultBuilder.truncation(truncation, { direction: "head" });
317
+ }
318
+ return resultBuilder.done();
319
+ }
273
320
  const renderMatchesForFile = (relativePath: string) => {
274
321
  const fileMatches = matchesByFile.get(relativePath) ?? [];
275
322
  for (const match of fileMatches) {
@@ -286,12 +333,13 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
286
333
  }
287
334
  const lineWidth = Math.max(...lineNumbers.map(value => value.toString().length));
288
335
  const formatLine = (lineNumber: number, line: string, isMatch: boolean): string => {
336
+ const separator = isMatch ? ":" : "-";
289
337
  if (useHashLines) {
290
338
  const ref = `${lineNumber}#${computeLineHash(lineNumber, line)}`;
291
- return isMatch ? `>>${ref}:${line}` : ` ${ref}:${line}`;
339
+ return `${ref}${separator}${line}`;
292
340
  }
293
341
  const padded = lineNumber.toString().padStart(lineWidth, " ");
294
- return isMatch ? `>>${padded}:${line}` : ` ${padded}:${line}`;
342
+ return `${padded}${separator}${line}`;
295
343
  };
296
344
  if (match.contextBefore) {
297
345
  for (const ctx of match.contextBefore) {
@@ -1,17 +1,20 @@
1
1
  import type { AgentTool } from "@oh-my-pi/pi-agent-core";
2
+ import type { ToolChoice } from "@oh-my-pi/pi-ai";
2
3
  import type { SearchDb } from "@oh-my-pi/pi-natives";
3
4
  import { $env, logger } from "@oh-my-pi/pi-utils";
4
5
  import type { AsyncJobManager } from "../async";
5
6
  import type { PromptTemplate } from "../config/prompt-templates";
6
7
  import type { Settings } from "../config/settings";
8
+ import { EditTool } from "../edit";
7
9
  import type { Skill } from "../extensibility/skills";
8
10
  import type { InternalUrlRouter } from "../internal-urls";
9
11
  import { getPreludeDocs, warmPythonEnvironment } from "../ipy/executor";
10
12
  import { checkPythonKernelAvailability } from "../ipy/kernel";
11
13
  import { LspTool } from "../lsp";
12
14
  import type { DiscoverableMCPSearchIndex, DiscoverableMCPTool } from "../mcp/discoverable-tool-metadata";
13
- import { EditTool } from "../patch";
14
15
  import type { PlanModeState } from "../plan-mode/state";
16
+ import type { CustomMessage } from "../session/messages";
17
+ import type { ToolChoiceQueue } from "../session/tool-choice-queue";
15
18
  import { TaskTool } from "../task";
16
19
  import type { AgentOutputManager } from "../task/output-manager";
17
20
  import type { EventBus } from "../utils/event-bus";
@@ -22,9 +25,11 @@ import { AstGrepTool } from "./ast-grep";
22
25
  import { AwaitTool } from "./await-tool";
23
26
  import { BashTool } from "./bash";
24
27
  import { BrowserTool } from "./browser";
28
+
25
29
  import { CalculatorTool } from "./calculator";
26
30
  import { CancelJobTool } from "./cancel-job";
27
31
  import { type CheckpointState, CheckpointTool, RewindTool } from "./checkpoint";
32
+ import { DebugTool } from "./debug";
28
33
  import { ExitPlanModeTool } from "./exit-plan-mode";
29
34
  import { FindTool } from "./find";
30
35
  import {
@@ -45,6 +50,7 @@ import { wrapToolWithMetaNotice } from "./output-meta";
45
50
  import { PythonTool } from "./python";
46
51
  import { ReadTool } from "./read";
47
52
  import { RenderMermaidTool } from "./render-mermaid";
53
+ import { createReportToolIssueTool, isAutoQaEnabled } from "./report-tool-issue";
48
54
  import { ResolveTool } from "./resolve";
49
55
  import { reportFindingTool } from "./review";
50
56
  import { SearchToolBm25Tool } from "./search-tool-bm25";
@@ -55,10 +61,10 @@ import { WriteTool } from "./write";
55
61
 
56
62
  // Exa MCP tools (22 tools)
57
63
 
64
+ export * from "../edit";
58
65
  export * from "../exa";
59
66
  export type * from "../exa/types";
60
67
  export * from "../lsp";
61
- export * from "../patch";
62
68
  export * from "../session/streaming-output";
63
69
  export * from "../task";
64
70
  export * from "../web/search";
@@ -71,6 +77,7 @@ export * from "./browser";
71
77
  export * from "./calculator";
72
78
  export * from "./cancel-job";
73
79
  export * from "./checkpoint";
80
+ export * from "./debug";
74
81
  export * from "./exit-plan-mode";
75
82
  export * from "./find";
76
83
  export * from "./gemini-image";
@@ -78,10 +85,10 @@ export * from "./gh";
78
85
  export * from "./grep";
79
86
  export * from "./inspect-image";
80
87
  export * from "./notebook";
81
- export * from "./pending-action";
82
88
  export * from "./python";
83
89
  export * from "./read";
84
90
  export * from "./render-mermaid";
91
+ export * from "./report-tool-issue";
85
92
  export * from "./resolve";
86
93
  export * from "./review";
87
94
  export * from "./search-tool-bm25";
@@ -175,12 +182,21 @@ export interface ToolSession {
175
182
  getSelectedMCPToolNames?: () => string[];
176
183
  /** Merge MCP tool selections into the active session tool set. */
177
184
  activateDiscoveredMCPTools?: (toolNames: string[]) => Promise<string[]>;
178
- /** Pending action store for preview/apply workflows */
179
- pendingActionStore?: import("./pending-action").PendingActionStore;
185
+ /** The tool-choice queue used to force forthcoming tool invocations and carry invocation handlers. */
186
+ getToolChoiceQueue?(): ToolChoiceQueue;
187
+ /** Build a model-provider-specific ToolChoice that targets the named tool, or undefined if unsupported. */
188
+ buildToolChoice?(toolName: string): ToolChoice | undefined;
189
+ /** Steer a hidden custom message into the conversation (e.g. a preview reminder). */
190
+ steer?(message: { customType: string; content: string; details?: unknown }): void;
191
+ /** Peek the currently in-flight tool-choice queue directive's invocation handler. Used by the `resolve` tool to dispatch to the pending action. */
192
+ peekQueueInvoker?(): ((input: unknown) => Promise<unknown> | unknown) | undefined;
180
193
  /** Get active checkpoint state if any. */
181
194
  getCheckpointState?: () => CheckpointState | undefined;
182
195
  /** Set or clear active checkpoint state. */
183
196
  setCheckpointState?: (state: CheckpointState | null) => void;
197
+
198
+ /** Queue a hidden message to be injected at the next agent turn. */
199
+ queueDeferredMessage?(message: CustomMessage): void;
184
200
  }
185
201
 
186
202
  type ToolFactory = (session: ToolSession) => Tool | null | Promise<Tool | null>;
@@ -191,6 +207,7 @@ export const BUILTIN_TOOLS: Record<string, ToolFactory> = {
191
207
  render_mermaid: s => new RenderMermaidTool(s),
192
208
  ask: AskTool.createIf,
193
209
  bash: s => new BashTool(s),
210
+ debug: DebugTool.createIf,
194
211
  python: s => new PythonTool(s),
195
212
  calc: s => new CalculatorTool(s),
196
213
  ssh: loadSshTool,
@@ -225,6 +242,7 @@ export const BUILTIN_TOOLS: Record<string, ToolFactory> = {
225
242
  export const HIDDEN_TOOLS: Record<string, ToolFactory> = {
226
243
  submit_result: s => new SubmitResultTool(s),
227
244
  report_finding: () => reportFindingTool,
245
+ report_tool_issue: s => createReportToolIssueTool(s),
228
246
  exit_plan_mode: s => new ExitPlanModeTool(s),
229
247
  resolve: s => new ResolveTool(s),
230
248
  };
@@ -282,11 +300,7 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
282
300
  const isTestEnv = Bun.env.BUN_ENV === "test" || Bun.env.NODE_ENV === "test";
283
301
  const skipPythonWarm = isTestEnv || $env.PI_PYTHON_SKIP_CHECK === "1";
284
302
  if (shouldCheckPython) {
285
- const availability = await logger.timeAsync(
286
- "createTools:pythonCheck",
287
- checkPythonKernelAvailability,
288
- session.cwd,
289
- );
303
+ const availability = await logger.time("createTools:pythonCheck", checkPythonKernelAvailability, session.cwd);
290
304
  pythonAvailable = availability.ok;
291
305
  if (!availability.ok) {
292
306
  logger.warn("Python kernel unavailable, falling back to bash", {
@@ -296,12 +310,13 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
296
310
  const sessionFile = session.getSessionFile?.() ?? undefined;
297
311
  const warmSessionId = sessionFile ? `session:${sessionFile}:cwd:${session.cwd}` : `cwd:${session.cwd}`;
298
312
  try {
299
- await logger.timeAsync(
313
+ await logger.time(
300
314
  "createTools:warmPython",
301
315
  warmPythonEnvironment,
302
316
  session.cwd,
303
317
  warmSessionId,
304
318
  session.settings.get("python.sharedGateway"),
319
+ sessionFile,
305
320
  );
306
321
  } catch (err) {
307
322
  logger.warn("Failed to warm Python environment", {
@@ -343,9 +358,10 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
343
358
  }
344
359
  const allTools: Record<string, ToolFactory> = { ...BUILTIN_TOOLS, ...HIDDEN_TOOLS };
345
360
  const isToolAllowed = (name: string) => {
346
- if (name === "lsp") return enableLsp;
361
+ if (name === "lsp") return enableLsp && session.settings.get("lsp.enabled");
347
362
  if (name === "bash") return allowBash;
348
363
  if (name === "python") return allowPython;
364
+ if (name === "debug") return session.settings.get("debug.enabled");
349
365
  if (name === "todo_write") return !includeSubmitResult && session.settings.get("todo.enabled");
350
366
  if (name === "find") return session.settings.get("find.enabled");
351
367
  if (name === "grep") return session.settings.get("grep.enabled");
@@ -357,7 +373,6 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
357
373
  if (name === "inspect_image") return session.settings.get("inspect_image.enabled");
358
374
  if (name === "web_search") return session.settings.get("web_search.enabled");
359
375
  if (name === "search_tool_bm25") return session.settings.get("mcp.discoveryMode");
360
- if (name === "lsp") return session.settings.get("lsp.enabled");
361
376
  if (name === "calc") return session.settings.get("calc.enabled");
362
377
  if (name === "browser") return session.settings.get("browser.enabled");
363
378
  if (name === "checkpoint" || name === "rewind") return session.settings.get("checkpoint.enabled");
@@ -384,21 +399,28 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
384
399
 
385
400
  const baseResults = await Promise.all(
386
401
  baseEntries.map(async ([name, factory]) => {
387
- const tool = await logger.timeAsync(`createTools:${name}`, factory, session);
402
+ const tool = await logger.time(`createTools:${name}`, factory, session);
388
403
  return tool ? wrapToolWithMetaNotice(tool) : null;
389
404
  }),
390
405
  );
391
406
  const tools = baseResults.filter((r): r is Tool => r !== null);
392
407
  const hasDeferrableTools = tools.some(tool => tool.deferrable === true);
393
- if (!hasDeferrableTools) {
394
- return tools;
395
- }
396
- if (tools.some(tool => tool.name === "resolve")) {
397
- return tools;
408
+ if (hasDeferrableTools && !tools.some(tool => tool.name === "resolve")) {
409
+ const resolveTool = await logger.time("createTools:resolve", HIDDEN_TOOLS.resolve, session);
410
+ if (resolveTool) {
411
+ tools.push(wrapToolWithMetaNotice(resolveTool));
412
+ }
398
413
  }
399
- const resolveTool = await logger.timeAsync("createTools:resolve", HIDDEN_TOOLS.resolve, session);
400
- if (resolveTool) {
401
- tools.push(wrapToolWithMetaNotice(resolveTool));
414
+
415
+ // Auto-inject report_tool_issue when autoqa is enabled (env or setting).
416
+ // Injected unconditionally into every agent, regardless of requested tool list.
417
+ const autoQA = isAutoQaEnabled(session.settings);
418
+ if (autoQA && !tools.some(t => t.name === "report_tool_issue")) {
419
+ const qaTool = await HIDDEN_TOOLS.report_tool_issue(session);
420
+ if (qaTool) {
421
+ tools.push(wrapToolWithMetaNotice(qaTool));
422
+ }
402
423
  }
424
+
403
425
  return tools;
404
426
  }
@@ -1,8 +1,8 @@
1
1
  import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
2
2
  import { type Api, type AssistantMessage, completeSimple, type Model } from "@oh-my-pi/pi-ai";
3
+ import { prompt } from "@oh-my-pi/pi-utils";
3
4
  import { type Static, Type } from "@sinclair/typebox";
4
5
  import { expandRoleAlias, resolveModelFromString } from "../config/model-resolver";
5
- import { renderPromptTemplate } from "../config/prompt-templates";
6
6
  import inspectImageDescription from "../prompts/tools/inspect-image.md" with { type: "text" };
7
7
  import inspectImageSystemPromptTemplate from "../prompts/tools/inspect-image-system.md" with { type: "text" };
8
8
  import {
@@ -10,7 +10,7 @@ import {
10
10
  type LoadedImageInput,
11
11
  loadImageInput,
12
12
  MAX_IMAGE_INPUT_BYTES,
13
- } from "../utils/image-input";
13
+ } from "../utils/image-loading";
14
14
  import type { ToolSession } from "./index";
15
15
  import { ToolError } from "./tool-errors";
16
16
 
@@ -49,7 +49,7 @@ export class InspectImageTool implements AgentTool<typeof inspectImageSchema, In
49
49
  private readonly session: ToolSession,
50
50
  private readonly completeImageRequest: typeof completeSimple = completeSimple,
51
51
  ) {
52
- this.description = renderPromptTemplate(inspectImageDescription);
52
+ this.description = prompt.render(inspectImageDescription);
53
53
  }
54
54
 
55
55
  async execute(
@@ -127,7 +127,7 @@ export class InspectImageTool implements AgentTool<typeof inspectImageSchema, In
127
127
  const response = await this.completeImageRequest(
128
128
  model,
129
129
  {
130
- systemPrompt: renderPromptTemplate(inspectImageSystemPromptTemplate),
130
+ systemPrompt: prompt.render(inspectImageSystemPromptTemplate),
131
131
  messages: [
132
132
  {
133
133
  role: "user",
@@ -337,7 +337,7 @@ export function formatTruncationMetaNotice(truncation: TruncationMeta): string {
337
337
  }
338
338
 
339
339
  if (truncation.nextOffset != null) {
340
- notice += `. Use offset=${truncation.nextOffset} to continue`;
340
+ notice += `. Use sel=L${truncation.nextOffset} to continue`;
341
341
  }
342
342
 
343
343
  if (truncation.artifactId != null) {
@@ -4,11 +4,10 @@ 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";
7
+ import { getProjectDir, prompt } from "@oh-my-pi/pi-utils";
8
8
  import { type Static, Type } from "@sinclair/typebox";
9
- import { renderPromptTemplate } from "../config/prompt-templates";
10
9
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
11
- import { executePython, getPreludeDocs, type PythonExecutorOptions } from "../ipy/executor";
10
+ import { executePython, getPreludeDocs, type PythonExecutorOptions, warmPythonEnvironment } from "../ipy/executor";
12
11
  import type { PreludeHelper, PythonStatusEvent } from "../ipy/kernel";
13
12
  import { truncateToVisualLines } from "../modes/components/visual-truncate";
14
13
  import { getMarkdownTheme, type Theme } from "../modes/theme/theme";
@@ -136,7 +135,7 @@ function renderJsonTree(value: unknown, theme: Theme, expanded: boolean, maxDept
136
135
  export function getPythonToolDescription(): string {
137
136
  const helpers = getPreludeDocs();
138
137
  const categories = groupPreludeHelpers(helpers);
139
- return renderPromptTemplate(pythonDescription, { categories });
138
+ return prompt.render(pythonDescription, { categories });
140
139
  }
141
140
 
142
141
  export interface PythonToolOptions {
@@ -146,7 +145,9 @@ export interface PythonToolOptions {
146
145
  export class PythonTool implements AgentTool<typeof pythonSchema> {
147
146
  readonly name = "python";
148
147
  readonly label = "Python";
149
- readonly description: string;
148
+ get description(): string {
149
+ return getPythonToolDescription();
150
+ }
150
151
  readonly parameters = pythonSchema;
151
152
  readonly concurrency = "exclusive";
152
153
  readonly strict = true;
@@ -158,7 +159,6 @@ export class PythonTool implements AgentTool<typeof pythonSchema> {
158
159
  options?: PythonToolOptions,
159
160
  ) {
160
161
  this.#proxyExecutor = options?.proxyExecutor;
161
- this.description = getPythonToolDescription();
162
162
  }
163
163
 
164
164
  async execute(
@@ -266,6 +266,19 @@ export class PythonTool implements AgentTool<typeof pythonSchema> {
266
266
  },
267
267
  });
268
268
  const sessionId = sessionFile ? `session:${sessionFile}:cwd:${commandCwd}` : `cwd:${commandCwd}`;
269
+
270
+ if (getPreludeDocs().length === 0) {
271
+ const warmup = await warmPythonEnvironment(
272
+ commandCwd,
273
+ sessionId,
274
+ this.session.settings.get("python.sharedGateway"),
275
+ sessionFile ?? undefined,
276
+ );
277
+ if (!warmup.ok) {
278
+ throw new ToolError(warmup.reason ?? "Python prelude helpers unavailable");
279
+ }
280
+ }
281
+
269
282
  const baseExecutorOptions: Omit<PythonExecutorOptions, "reset"> = {
270
283
  cwd: commandCwd,
271
284
  deadlineMs,