@oh-my-pi/pi-coding-agent 13.19.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 (202) hide show
  1. package/CHANGELOG.md +266 -1
  2. package/package.json +86 -20
  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 +91 -0
  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 +83 -125
  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 -5
  28. package/src/commit/agentic/index.ts +3 -4
  29. package/src/commit/agentic/tools/analyze-file.ts +3 -3
  30. package/src/commit/agentic/validation.ts +1 -1
  31. package/src/commit/analysis/conventional.ts +4 -4
  32. package/src/commit/analysis/summary.ts +3 -3
  33. package/src/commit/changelog/generate.ts +4 -4
  34. package/src/commit/map-reduce/map-phase.ts +4 -4
  35. package/src/commit/map-reduce/reduce-phase.ts +4 -4
  36. package/src/commit/pipeline.ts +3 -4
  37. package/src/config/prompt-templates.ts +44 -226
  38. package/src/config/resolve-config-value.ts +4 -2
  39. package/src/config/settings-schema.ts +54 -2
  40. package/src/config/settings.ts +25 -26
  41. package/src/dap/client.ts +674 -0
  42. package/src/dap/config.ts +150 -0
  43. package/src/dap/defaults.json +211 -0
  44. package/src/dap/index.ts +4 -0
  45. package/src/dap/session.ts +1255 -0
  46. package/src/dap/types.ts +600 -0
  47. package/src/debug/log-viewer.ts +3 -2
  48. package/src/discovery/builtin.ts +1 -2
  49. package/src/discovery/codex.ts +2 -2
  50. package/src/discovery/github.ts +2 -1
  51. package/src/discovery/helpers.ts +2 -2
  52. package/src/discovery/opencode.ts +2 -2
  53. package/src/edit/diff.ts +818 -0
  54. package/src/edit/index.ts +309 -0
  55. package/src/edit/line-hash.ts +67 -0
  56. package/src/edit/modes/chunk.ts +454 -0
  57. package/src/{patch → edit/modes}/hashline.ts +741 -361
  58. package/src/{patch/applicator.ts → edit/modes/patch.ts} +420 -117
  59. package/src/{patch/fuzzy.ts → edit/modes/replace.ts} +519 -197
  60. package/src/{patch → edit}/normalize.ts +97 -76
  61. package/src/{patch/shared.ts → edit/renderer.ts} +181 -108
  62. package/src/exec/bash-executor.ts +4 -2
  63. package/src/exec/idle-timeout-watchdog.ts +126 -0
  64. package/src/exec/non-interactive-env.ts +5 -0
  65. package/src/extensibility/custom-commands/bundled/ci-green/index.ts +2 -2
  66. package/src/extensibility/custom-commands/bundled/review/index.ts +2 -2
  67. package/src/extensibility/custom-commands/loader.ts +1 -2
  68. package/src/extensibility/custom-tools/loader.ts +34 -11
  69. package/src/extensibility/extensions/loader.ts +9 -4
  70. package/src/extensibility/extensions/runner.ts +24 -1
  71. package/src/extensibility/extensions/types.ts +1 -1
  72. package/src/extensibility/hooks/loader.ts +5 -6
  73. package/src/extensibility/hooks/types.ts +1 -1
  74. package/src/extensibility/plugins/doctor.ts +2 -1
  75. package/src/extensibility/slash-commands.ts +3 -7
  76. package/src/index.ts +2 -1
  77. package/src/internal-urls/docs-index.generated.ts +11 -11
  78. package/src/ipy/executor.ts +58 -17
  79. package/src/ipy/gateway-coordinator.ts +6 -4
  80. package/src/ipy/kernel.ts +45 -22
  81. package/src/ipy/runtime.ts +2 -2
  82. package/src/lsp/client.ts +7 -4
  83. package/src/lsp/clients/lsp-linter-client.ts +4 -4
  84. package/src/lsp/config.ts +2 -2
  85. package/src/lsp/defaults.json +688 -154
  86. package/src/lsp/index.ts +234 -45
  87. package/src/lsp/lspmux.ts +2 -2
  88. package/src/lsp/startup-events.ts +13 -0
  89. package/src/lsp/types.ts +12 -1
  90. package/src/lsp/utils.ts +8 -1
  91. package/src/main.ts +102 -46
  92. package/src/memories/index.ts +4 -5
  93. package/src/modes/acp/acp-agent.ts +563 -163
  94. package/src/modes/acp/acp-event-mapper.ts +9 -1
  95. package/src/modes/acp/acp-mode.ts +4 -2
  96. package/src/modes/components/agent-dashboard.ts +3 -4
  97. package/src/modes/components/diff.ts +6 -7
  98. package/src/modes/components/read-tool-group.ts +6 -12
  99. package/src/modes/components/settings-defs.ts +5 -0
  100. package/src/modes/components/tool-execution.ts +1 -1
  101. package/src/modes/components/welcome.ts +1 -1
  102. package/src/modes/controllers/btw-controller.ts +2 -2
  103. package/src/modes/controllers/command-controller.ts +3 -2
  104. package/src/modes/controllers/input-controller.ts +12 -8
  105. package/src/modes/index.ts +20 -2
  106. package/src/modes/interactive-mode.ts +94 -37
  107. package/src/modes/rpc/host-tools.ts +186 -0
  108. package/src/modes/rpc/rpc-client.ts +178 -13
  109. package/src/modes/rpc/rpc-mode.ts +73 -3
  110. package/src/modes/rpc/rpc-types.ts +53 -1
  111. package/src/modes/theme/theme.ts +80 -8
  112. package/src/modes/types.ts +2 -2
  113. package/src/prompts/system/system-prompt.md +2 -1
  114. package/src/prompts/tools/chunk-edit.md +219 -0
  115. package/src/prompts/tools/debug.md +43 -0
  116. package/src/prompts/tools/grep.md +3 -0
  117. package/src/prompts/tools/lsp.md +5 -5
  118. package/src/prompts/tools/read-chunk.md +17 -0
  119. package/src/prompts/tools/read.md +19 -5
  120. package/src/sdk.ts +190 -154
  121. package/src/secrets/obfuscator.ts +1 -1
  122. package/src/session/agent-session.ts +306 -256
  123. package/src/session/agent-storage.ts +12 -12
  124. package/src/session/compaction/branch-summarization.ts +3 -3
  125. package/src/session/compaction/compaction.ts +5 -6
  126. package/src/session/compaction/utils.ts +3 -3
  127. package/src/session/history-storage.ts +62 -19
  128. package/src/session/messages.ts +3 -3
  129. package/src/session/session-dump-format.ts +203 -0
  130. package/src/session/session-storage.ts +4 -2
  131. package/src/session/streaming-output.ts +1 -1
  132. package/src/session/tool-choice-queue.ts +213 -0
  133. package/src/slash-commands/builtin-registry.ts +56 -8
  134. package/src/ssh/connection-manager.ts +2 -2
  135. package/src/ssh/sshfs-mount.ts +5 -5
  136. package/src/stt/downloader.ts +4 -4
  137. package/src/stt/recorder.ts +4 -4
  138. package/src/stt/transcriber.ts +2 -2
  139. package/src/system-prompt.ts +21 -13
  140. package/src/task/agents.ts +5 -6
  141. package/src/task/commands.ts +2 -5
  142. package/src/task/executor.ts +4 -4
  143. package/src/task/index.ts +3 -4
  144. package/src/task/template.ts +2 -2
  145. package/src/task/worktree.ts +4 -4
  146. package/src/tools/ask.ts +2 -3
  147. package/src/tools/ast-edit.ts +7 -7
  148. package/src/tools/ast-grep.ts +7 -7
  149. package/src/tools/auto-generated-guard.ts +36 -41
  150. package/src/tools/await-tool.ts +2 -2
  151. package/src/tools/bash.ts +5 -23
  152. package/src/tools/browser.ts +4 -5
  153. package/src/tools/calculator.ts +2 -3
  154. package/src/tools/cancel-job.ts +2 -2
  155. package/src/tools/checkpoint.ts +3 -3
  156. package/src/tools/debug.ts +1007 -0
  157. package/src/tools/exit-plan-mode.ts +2 -3
  158. package/src/tools/fetch.ts +67 -3
  159. package/src/tools/find.ts +4 -5
  160. package/src/tools/fs-cache-invalidation.ts +5 -0
  161. package/src/tools/gemini-image.ts +13 -5
  162. package/src/tools/gh.ts +10 -11
  163. package/src/tools/grep.ts +57 -9
  164. package/src/tools/index.ts +44 -22
  165. package/src/tools/inspect-image.ts +4 -4
  166. package/src/tools/output-meta.ts +1 -1
  167. package/src/tools/python.ts +19 -6
  168. package/src/tools/read.ts +198 -67
  169. package/src/tools/render-mermaid.ts +2 -3
  170. package/src/tools/render-utils.ts +20 -6
  171. package/src/tools/renderers.ts +3 -1
  172. package/src/tools/report-tool-issue.ts +80 -0
  173. package/src/tools/resolve.ts +70 -39
  174. package/src/tools/search-tool-bm25.ts +2 -2
  175. package/src/tools/ssh.ts +2 -2
  176. package/src/tools/todo-write.ts +2 -2
  177. package/src/tools/tool-timeouts.ts +1 -0
  178. package/src/tools/write.ts +5 -6
  179. package/src/tui/tree-list.ts +3 -1
  180. package/src/utils/clipboard.ts +80 -0
  181. package/src/utils/commit-message-generator.ts +2 -3
  182. package/src/utils/edit-mode.ts +49 -0
  183. package/src/utils/file-display-mode.ts +6 -5
  184. package/src/utils/file-mentions.ts +8 -7
  185. package/src/utils/git.ts +4 -4
  186. package/src/utils/image-loading.ts +98 -0
  187. package/src/utils/title-generator.ts +2 -3
  188. package/src/utils/tools-manager.ts +6 -6
  189. package/src/web/scrapers/choosealicense.ts +1 -1
  190. package/src/web/search/index.ts +3 -3
  191. package/src/autoresearch/command-initialize.md +0 -34
  192. package/src/patch/diff.ts +0 -433
  193. package/src/patch/index.ts +0 -888
  194. package/src/patch/parser.ts +0 -532
  195. package/src/patch/types.ts +0 -292
  196. package/src/prompts/agents/oracle.md +0 -77
  197. package/src/tools/pending-action.ts +0 -49
  198. package/src/utils/child-process.ts +0 -88
  199. package/src/utils/frontmatter.ts +0 -117
  200. package/src/utils/image-input.ts +0 -274
  201. package/src/utils/mime.ts +0 -53
  202. package/src/utils/prompt-format.ts +0 -170
@@ -0,0 +1,126 @@
1
+ export type ExecutionAbortReason = "idle-timeout" | "signal";
2
+
3
+ export interface IdleTimeoutWatchdogOptions {
4
+ timeoutMs?: number;
5
+ signal?: AbortSignal;
6
+ hardTimeoutGraceMs: number;
7
+ onAbort?: (reason: ExecutionAbortReason) => void;
8
+ }
9
+
10
+ export class IdleTimeoutWatchdog {
11
+ #abortController = new AbortController();
12
+ #abortReason?: ExecutionAbortReason;
13
+ #hardTimeoutDeferred = Promise.withResolvers<"hard-timeout">();
14
+ #hardTimeoutGraceMs: number;
15
+ #hardTimeoutTimer?: NodeJS.Timeout;
16
+ #idleTimer?: NodeJS.Timeout;
17
+ #onAbort?: (reason: ExecutionAbortReason) => void;
18
+ #signal?: AbortSignal;
19
+ #signalAbortHandler?: () => void;
20
+ #timeoutMs?: number;
21
+
22
+ constructor(options: IdleTimeoutWatchdogOptions) {
23
+ this.#timeoutMs = options.timeoutMs;
24
+ this.#hardTimeoutGraceMs = options.hardTimeoutGraceMs;
25
+ this.#onAbort = options.onAbort;
26
+ this.#signal = options.signal;
27
+
28
+ if (this.#signal) {
29
+ if (this.#signal.aborted) {
30
+ this.#abort("signal");
31
+ return;
32
+ }
33
+
34
+ this.#signalAbortHandler = () => {
35
+ this.#abort("signal");
36
+ };
37
+ this.#signal.addEventListener("abort", this.#signalAbortHandler, { once: true });
38
+ }
39
+
40
+ this.touch();
41
+ }
42
+
43
+ get abortedBySignal(): boolean {
44
+ return this.#abortReason === "signal";
45
+ }
46
+
47
+ get hardTimeoutPromise(): Promise<"hard-timeout"> {
48
+ return this.#hardTimeoutDeferred.promise;
49
+ }
50
+
51
+ get signal(): AbortSignal {
52
+ return this.#abortController.signal;
53
+ }
54
+
55
+ get timedOut(): boolean {
56
+ return this.#abortReason === "idle-timeout";
57
+ }
58
+
59
+ touch(): void {
60
+ if (this.#abortReason || this.#timeoutMs === undefined || this.#timeoutMs <= 0) {
61
+ return;
62
+ }
63
+
64
+ if (this.#idleTimer) {
65
+ clearTimeout(this.#idleTimer);
66
+ }
67
+
68
+ this.#idleTimer = setTimeout(() => {
69
+ this.#abort("idle-timeout");
70
+ }, this.#timeoutMs);
71
+ }
72
+
73
+ dispose(): void {
74
+ if (this.#idleTimer) {
75
+ clearTimeout(this.#idleTimer);
76
+ this.#idleTimer = undefined;
77
+ }
78
+ if (this.#hardTimeoutTimer) {
79
+ clearTimeout(this.#hardTimeoutTimer);
80
+ this.#hardTimeoutTimer = undefined;
81
+ }
82
+ if (this.#signal && this.#signalAbortHandler) {
83
+ this.#signal.removeEventListener("abort", this.#signalAbortHandler);
84
+ this.#signalAbortHandler = undefined;
85
+ }
86
+ }
87
+
88
+ #abort(reason: ExecutionAbortReason): void {
89
+ if (this.#abortReason) {
90
+ return;
91
+ }
92
+
93
+ this.#abortReason = reason;
94
+
95
+ if (this.#idleTimer) {
96
+ clearTimeout(this.#idleTimer);
97
+ this.#idleTimer = undefined;
98
+ }
99
+
100
+ if (!this.#abortController.signal.aborted) {
101
+ this.#abortController.abort(reason);
102
+ }
103
+
104
+ this.#onAbort?.(reason);
105
+ this.#armHardTimeout();
106
+ }
107
+
108
+ #armHardTimeout(): void {
109
+ if (this.#hardTimeoutTimer || this.#hardTimeoutGraceMs <= 0) {
110
+ return;
111
+ }
112
+
113
+ this.#hardTimeoutTimer = setTimeout(() => {
114
+ this.#hardTimeoutDeferred.resolve("hard-timeout");
115
+ }, this.#hardTimeoutGraceMs);
116
+ }
117
+ }
118
+
119
+ export function formatIdleTimeoutMessage(timeoutMs?: number): string {
120
+ if (timeoutMs === undefined) {
121
+ return "Command timed out without output";
122
+ }
123
+
124
+ const seconds = Math.max(1, Math.round(timeoutMs / 1000));
125
+ return `Command timed out after ${seconds} seconds without output`;
126
+ }
@@ -13,6 +13,11 @@ export const NON_INTERACTIVE_ENV: Readonly<Record<string, string>> = {
13
13
  AWS_PAGER: "",
14
14
  HOMEBREW_PAGER: "cat",
15
15
  LESS: "FRX",
16
+ // Disable terminal features that can block the process.
17
+ TERM: "dumb",
18
+ GPG_TTY: "not a tty",
19
+ NO_COLOR: "1",
20
+ PYTHONUNBUFFERED: "1",
16
21
  // Disable editor and terminal credential prompts.
17
22
  GIT_EDITOR: "true",
18
23
  VISUAL: "true",
@@ -1,4 +1,4 @@
1
- import { renderPromptTemplate } from "../../../../config/prompt-templates";
1
+ import { prompt } from "@oh-my-pi/pi-utils";
2
2
  import type { CustomCommand, CustomCommandAPI } from "../../../../extensibility/custom-commands/types";
3
3
  import type { HookCommandContext } from "../../../../extensibility/hooks/types";
4
4
  import ciGreenRequestTemplate from "../../../../prompts/ci-green-request.md" with { type: "text" };
@@ -20,6 +20,6 @@ export class GreenCommand implements CustomCommand {
20
20
 
21
21
  async execute(_args: string[], _ctx: HookCommandContext): Promise<string> {
22
22
  const headTag = await getHeadTag(this.api);
23
- return renderPromptTemplate(ciGreenRequestTemplate, { headTag });
23
+ return prompt.render(ciGreenRequestTemplate, { headTag });
24
24
  }
25
25
  }
@@ -11,7 +11,7 @@
11
11
  * rich context for the orchestrating agent to distribute work across
12
12
  * multiple reviewer agents based on diff weight and locality.
13
13
  */
14
- import { renderPromptTemplate } from "../../../../config/prompt-templates";
14
+ import { prompt } from "@oh-my-pi/pi-utils";
15
15
  import type { CustomCommand, CustomCommandAPI } from "../../../../extensibility/custom-commands/types";
16
16
  import type { HookCommandContext } from "../../../../extensibility/hooks/types";
17
17
  import reviewRequestTemplate from "../../../../prompts/review-request.md" with { type: "text" };
@@ -209,7 +209,7 @@ function buildReviewPrompt(mode: string, stats: DiffStats, rawDiff: string): str
209
209
  hunksPreview: skipDiff ? getDiffPreview(f.hunks, linesPerFile) : "",
210
210
  }));
211
211
 
212
- return renderPromptTemplate(reviewRequestTemplate, {
212
+ return prompt.render(reviewRequestTemplate, {
213
213
  mode,
214
214
  files: filesWithExt,
215
215
  excluded: stats.excluded,
@@ -6,7 +6,6 @@
6
6
  */
7
7
  import * as fs from "node:fs";
8
8
  import * as path from "node:path";
9
- import * as piCodingAgent from "@oh-my-pi/pi-coding-agent";
10
9
  import { getAgentDir, getProjectDir, isEnoent, logger } from "@oh-my-pi/pi-utils";
11
10
  import * as typebox from "@sinclair/typebox";
12
11
  import { getConfigDirs } from "../../config";
@@ -184,7 +183,7 @@ export async function loadCustomCommands(options: LoadCustomCommandsOptions = {}
184
183
  exec: (command: string, args: string[], execOptions) =>
185
184
  execCommand(command, args, execOptions?.cwd ?? cwd, execOptions),
186
185
  typebox,
187
- pi: piCodingAgent,
186
+ pi: await import("@oh-my-pi/pi-coding-agent"),
188
187
  };
189
188
 
190
189
  // 1. Load bundled commands first (lowest priority - can be overridden)
@@ -5,7 +5,7 @@
5
5
  * to avoid import resolution issues with custom tools loaded from user directories.
6
6
  */
7
7
  import * as path from "node:path";
8
- import * as piCodingAgent from "@oh-my-pi/pi-coding-agent";
8
+ import type { AgentToolResult } from "@oh-my-pi/pi-agent-core";
9
9
  import { logger } from "@oh-my-pi/pi-utils";
10
10
  import * as typebox from "@sinclair/typebox";
11
11
  import { toolCapability } from "../../capability/tool";
@@ -14,7 +14,6 @@ import type { ExecOptions } from "../../exec/exec";
14
14
  import { execCommand } from "../../exec/exec";
15
15
  import type { HookUIContext } from "../../extensibility/hooks/types";
16
16
  import { getAllPluginToolPaths } from "../../extensibility/plugins/loader";
17
- import type { PendingActionStore } from "../../tools/pending-action";
18
17
  import { createNoOpUIContext, resolvePath } from "../utils";
19
18
  import type { CustomToolAPI, CustomToolFactory, LoadedCustomTool, ToolLoadError } from "./types";
20
19
 
@@ -85,7 +84,17 @@ export class CustomToolLoader {
85
84
  #sharedApi: CustomToolAPI;
86
85
  #seenNames: Set<string>;
87
86
 
88
- constructor(cwd: string, builtInToolNames: string[], pendingActionStore?: PendingActionStore) {
87
+ constructor(
88
+ pi: typeof import("@oh-my-pi/pi-coding-agent"),
89
+ cwd: string,
90
+ builtInToolNames: string[],
91
+ pushPendingAction?: (action: {
92
+ label: string;
93
+ sourceToolName: string;
94
+ apply(reason: string): Promise<AgentToolResult<unknown>>;
95
+ reject?(reason: string): Promise<AgentToolResult<unknown> | undefined>;
96
+ }) => void,
97
+ ) {
89
98
  this.#sharedApi = {
90
99
  cwd,
91
100
  exec: (command: string, args: string[], options?: ExecOptions) =>
@@ -94,17 +103,16 @@ export class CustomToolLoader {
94
103
  hasUI: false,
95
104
  logger,
96
105
  typebox,
97
- pi: piCodingAgent,
106
+ pi,
98
107
  pushPendingAction: action => {
99
- if (!pendingActionStore) {
108
+ if (!pushPendingAction) {
100
109
  throw new Error("Pending action store unavailable for custom tools in this runtime.");
101
110
  }
102
- pendingActionStore.push({
111
+ pushPendingAction({
103
112
  label: action.label,
104
113
  sourceToolName: action.sourceToolName ?? "custom_tool",
105
114
  apply: action.apply,
106
115
  reject: action.reject,
107
- details: action.details,
108
116
  });
109
117
  },
110
118
  };
@@ -155,9 +163,19 @@ export async function loadCustomTools(
155
163
  pathsWithSources: ToolPathWithSource[],
156
164
  cwd: string,
157
165
  builtInToolNames: string[],
158
- pendingActionStore?: PendingActionStore,
166
+ pushPendingAction?: (action: {
167
+ label: string;
168
+ sourceToolName: string;
169
+ apply(reason: string): Promise<AgentToolResult<unknown>>;
170
+ reject?(reason: string): Promise<AgentToolResult<unknown> | undefined>;
171
+ }) => void,
159
172
  ) {
160
- const loader = new CustomToolLoader(cwd, builtInToolNames, pendingActionStore);
173
+ const loader = new CustomToolLoader(
174
+ await import("@oh-my-pi/pi-coding-agent"),
175
+ cwd,
176
+ builtInToolNames,
177
+ pushPendingAction,
178
+ );
161
179
  await loader.load(pathsWithSources);
162
180
  return {
163
181
  tools: loader.tools,
@@ -182,7 +200,12 @@ export async function discoverAndLoadCustomTools(
182
200
  configuredPaths: string[],
183
201
  cwd: string,
184
202
  builtInToolNames: string[],
185
- pendingActionStore?: PendingActionStore,
203
+ pushPendingAction?: (action: {
204
+ label: string;
205
+ sourceToolName: string;
206
+ apply(reason: string): Promise<AgentToolResult<unknown>>;
207
+ reject?(reason: string): Promise<AgentToolResult<unknown> | undefined>;
208
+ }) => void,
186
209
  ) {
187
210
  const allPathsWithSources: ToolPathWithSource[] = [];
188
211
  const seen = new Set<string>();
@@ -216,5 +239,5 @@ export async function discoverAndLoadCustomTools(
216
239
  addPath(resolvePath(configPath, cwd), { provider: "config", providerName: "Config", level: "project" });
217
240
  }
218
241
 
219
- return loadCustomTools(allPathsWithSources, cwd, builtInToolNames, pendingActionStore);
242
+ return loadCustomTools(allPathsWithSources, cwd, builtInToolNames, pushPendingAction);
220
243
  }
@@ -6,7 +6,6 @@ import * as fs from "node:fs/promises";
6
6
  import * as path from "node:path";
7
7
  import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
8
8
  import type { ImageContent, Model, TextContent } from "@oh-my-pi/pi-ai";
9
- import * as piCodingAgent from "@oh-my-pi/pi-coding-agent";
10
9
  import type { KeyId } from "@oh-my-pi/pi-tui";
11
10
  import { hasFsCode, isEacces, isEnoent, logger } from "@oh-my-pi/pi-utils";
12
11
  import type { TSchema } from "@sinclair/typebox";
@@ -102,7 +101,6 @@ export class ExtensionRuntime implements IExtensionRuntime {
102
101
  class ConcreteExtensionAPI implements ExtensionAPI, IExtensionRuntime {
103
102
  readonly logger = logger;
104
103
  readonly typebox = TypeBox;
105
- readonly pi = piCodingAgent;
106
104
  readonly flagValues = new Map<string, boolean | string>();
107
105
  readonly pendingProviderRegistrations: Array<{
108
106
  name: string;
@@ -111,6 +109,7 @@ class ConcreteExtensionAPI implements ExtensionAPI, IExtensionRuntime {
111
109
  }> = [];
112
110
 
113
111
  constructor(
112
+ public readonly pi: typeof import("@oh-my-pi/pi-coding-agent"),
114
113
  private readonly extension: Extension,
115
114
  private readonly runtime: IExtensionRuntime,
116
115
  private readonly cwd: string,
@@ -265,7 +264,13 @@ async function loadExtension(
265
264
  }
266
265
 
267
266
  const extension = createExtension(extensionPath, resolvedPath);
268
- const api = new ConcreteExtensionAPI(extension, runtime, cwd, eventBus);
267
+ const api = new ConcreteExtensionAPI(
268
+ await import("@oh-my-pi/pi-coding-agent"),
269
+ extension,
270
+ runtime,
271
+ cwd,
272
+ eventBus,
273
+ );
269
274
  await factory(api);
270
275
 
271
276
  return { extension, error: null };
@@ -286,7 +291,7 @@ export async function loadExtensionFromFactory(
286
291
  name = "<inline>",
287
292
  ): Promise<Extension> {
288
293
  const extension = createExtension(name, name);
289
- const api = new ConcreteExtensionAPI(extension, runtime, cwd, eventBus);
294
+ const api = new ConcreteExtensionAPI(await import("@oh-my-pi/pi-coding-agent"), extension, runtime, cwd, eventBus);
290
295
  await factory(api);
291
296
  return extension;
292
297
  }
@@ -262,6 +262,10 @@ export class ExtensionRunner {
262
262
  return allFlags;
263
263
  }
264
264
 
265
+ getFlagValues(): Map<string, boolean | string> {
266
+ return new Map(this.runtime.flagValues);
267
+ }
268
+
265
269
  setFlagValue(name: string, value: boolean | string): void {
266
270
  this.runtime.flagValues.set(name, value);
267
271
  }
@@ -695,7 +699,26 @@ export class ExtensionRunner {
695
699
 
696
700
  async emitContext(messages: AgentMessage[]): Promise<AgentMessage[]> {
697
701
  const ctx = this.createContext();
698
- let currentMessages = structuredClone(messages);
702
+
703
+ // Check if any extensions actually have context handlers before cloning
704
+ let hasContextHandlers = false;
705
+ for (const ext of this.extensions) {
706
+ if (ext.handlers.get("context")?.length) {
707
+ hasContextHandlers = true;
708
+ break;
709
+ }
710
+ }
711
+ if (!hasContextHandlers) return messages;
712
+
713
+ let currentMessages: AgentMessage[];
714
+ try {
715
+ currentMessages = structuredClone(messages);
716
+ } catch {
717
+ // Messages may contain non-cloneable objects (e.g. in ToolResultMessage.details
718
+ // or ProviderPayload). Fall back to a shallow array clone — extensions should
719
+ // return new message arrays rather than mutating in place.
720
+ currentMessages = [...messages];
721
+ }
699
722
 
700
723
  for (const ext of this.extensions) {
701
724
  const handlers = ext.handlers.get("context");
@@ -28,11 +28,11 @@ import type { Static, TSchema } from "@sinclair/typebox";
28
28
  import type { Rule } from "../../capability/rule";
29
29
  import type { KeybindingsManager } from "../../config/keybindings";
30
30
  import type { ModelRegistry } from "../../config/model-registry";
31
+ import type { EditToolDetails } from "../../edit";
31
32
  import type { BashResult } from "../../exec/bash-executor";
32
33
  import type { ExecOptions, ExecResult } from "../../exec/exec";
33
34
  import type { PythonResult } from "../../ipy/executor";
34
35
  import type { Theme } from "../../modes/theme/theme";
35
- import type { EditToolDetails } from "../../patch";
36
36
  import type { CompactionPreparation, CompactionResult } from "../../session/compaction";
37
37
  import type { CustomMessage } from "../../session/messages";
38
38
  import type {
@@ -2,7 +2,6 @@
2
2
  * Hook loader - loads TypeScript hook modules using native Bun import.
3
3
  */
4
4
  import * as path from "node:path";
5
- import * as piCodingAgent from "@oh-my-pi/pi-coding-agent";
6
5
  import { logger } from "@oh-my-pi/pi-utils";
7
6
  import * as typebox from "@sinclair/typebox";
8
7
  import { hookCapability } from "../../capability/hook";
@@ -87,16 +86,16 @@ export interface LoadHooksResult {
87
86
  * Create a HookAPI instance that collects handlers, renderers, and commands.
88
87
  * Returns the API, maps, and functions to set handlers later.
89
88
  */
90
- function createHookAPI(
89
+ async function createHookAPI(
91
90
  handlers: Map<string, HandlerFn[]>,
92
91
  cwd: string,
93
- ): {
92
+ ): Promise<{
94
93
  api: HookAPI;
95
94
  messageRenderers: Map<string, HookMessageRenderer>;
96
95
  commands: Map<string, RegisteredCommand>;
97
96
  setSendMessageHandler: (handler: SendMessageHandler) => void;
98
97
  setAppendEntryHandler: (handler: AppendEntryHandler) => void;
99
- } {
98
+ }> {
100
99
  let sendMessageHandler: SendMessageHandler | null = null;
101
100
  let appendEntryHandler: AppendEntryHandler | null = null;
102
101
  const messageRenderers = new Map<string, HookMessageRenderer>();
@@ -137,7 +136,7 @@ function createHookAPI(
137
136
  },
138
137
  logger,
139
138
  typebox,
140
- pi: piCodingAgent,
139
+ pi: await import("@oh-my-pi/pi-coding-agent"),
141
140
  } as HookAPI;
142
141
 
143
142
  return {
@@ -170,7 +169,7 @@ async function loadHook(hookPath: string, cwd: string): Promise<{ hook: LoadedHo
170
169
 
171
170
  // Create handlers map and API
172
171
  const handlers = new Map<string, HandlerFn[]>();
173
- const { api, messageRenderers, commands, setSendMessageHandler, setAppendEntryHandler } = createHookAPI(
172
+ const { api, messageRenderers, commands, setSendMessageHandler, setAppendEntryHandler } = await createHookAPI(
174
173
  handlers,
175
174
  cwd,
176
175
  );
@@ -9,9 +9,9 @@ import type { ImageContent, Message, Model, TextContent, ToolResultMessage } fro
9
9
  import type { Component, TUI } from "@oh-my-pi/pi-tui";
10
10
  import type { Rule } from "../../capability/rule";
11
11
  import type { ModelRegistry } from "../../config/model-registry";
12
+ import type { EditToolDetails } from "../../edit";
12
13
  import type { ExecOptions, ExecResult } from "../../exec/exec";
13
14
  import type { Theme } from "../../modes/theme/theme";
14
- import type { EditToolDetails } from "../../patch";
15
15
  import type { CompactionPreparation, CompactionResult } from "../../session/compaction";
16
16
  import type { HookMessage } from "../../session/messages";
17
17
  import type {
@@ -1,3 +1,4 @@
1
+ import { $which } from "@oh-my-pi/pi-utils";
1
2
  import { theme } from "../../modes/theme/theme";
2
3
  import type { DoctorCheck } from "./types";
3
4
 
@@ -12,7 +13,7 @@ export async function runDoctorChecks(): Promise<DoctorCheck[]> {
12
13
  ];
13
14
 
14
15
  for (const tool of tools) {
15
- const path = Bun.which(tool.name);
16
+ const path = $which(tool.name);
16
17
  checks.push({
17
18
  name: tool.name,
18
19
  status: path ? "ok" : "warning",
@@ -1,10 +1,7 @@
1
1
  import type { AutocompleteItem } from "@oh-my-pi/pi-tui";
2
+ import { parseFrontmatter, prompt } from "@oh-my-pi/pi-utils";
2
3
  import { slashCommandCapability } from "../capability/slash-command";
3
- import {
4
- appendInlineArgsFallback,
5
- renderPromptTemplate,
6
- templateUsesInlineArgPlaceholders,
7
- } from "../config/prompt-templates";
4
+ import { appendInlineArgsFallback, templateUsesInlineArgPlaceholders } from "../config/prompt-templates";
8
5
  import type { SlashCommand } from "../discovery";
9
6
  import { loadCapability } from "../discovery";
10
7
  import {
@@ -14,7 +11,6 @@ import {
14
11
  } from "../slash-commands/builtin-registry";
15
12
  import { EMBEDDED_COMMAND_TEMPLATES } from "../task/commands";
16
13
  import { parseCommandArgs, substituteArgs } from "../utils/command-args";
17
- import { parseFrontmatter } from "../utils/frontmatter";
18
14
 
19
15
  export type SlashCommandSource = "extension" | "prompt" | "skill";
20
16
 
@@ -223,7 +219,7 @@ export function expandSlashCommand(text: string, fileCommands: FileSlashCommand[
223
219
  const argsText = args.join(" ");
224
220
  const usesInlineArgPlaceholders = templateUsesInlineArgPlaceholders(fileCommand.content);
225
221
  const substituted = substituteArgs(fileCommand.content, args);
226
- const rendered = renderPromptTemplate(substituted, { args, ARGUMENTS: argsText, arguments: argsText });
222
+ const rendered = prompt.render(substituted, { args, ARGUMENTS: argsText, arguments: argsText });
227
223
  return appendInlineArgsFallback(rendered, argsText, usesInlineArgPlaceholders);
228
224
  }
229
225
 
package/src/index.ts CHANGED
@@ -16,6 +16,7 @@ export type * from "./config/prompt-templates";
16
16
  export * from "./config/prompt-templates";
17
17
  export type { RetrySettings, SkillsSettings } from "./config/settings";
18
18
  export { Settings, settings } from "./config/settings";
19
+ export * from "./edit/modes/hashline";
19
20
  // Custom commands
20
21
  export type * from "./extensibility/custom-commands/types";
21
22
  export type * from "./extensibility/custom-tools";
@@ -37,7 +38,6 @@ export * from "./modes";
37
38
  export * from "./modes/components";
38
39
  // Theme utilities for custom tools
39
40
  export * from "./modes/theme/theme";
40
- export * from "./patch/hashline";
41
41
  // SDK for programmatic usage
42
42
  export * from "./sdk";
43
43
  export * from "./session/agent-session";
@@ -46,6 +46,7 @@ export * from "./session/auth-storage";
46
46
  // Compaction
47
47
  export * from "./session/compaction";
48
48
  export * from "./session/messages";
49
+ export * from "./session/session-dump-format";
49
50
  export * from "./session/session-manager";
50
51
  export * from "./task/executor";
51
52
  export type * from "./task/types";