@oh-my-pi/pi-coding-agent 17.2.8 → 17.2.10

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 (246) hide show
  1. package/CHANGELOG.md +94 -1
  2. package/dist/{CHANGELOG-0b0w17se.md → CHANGELOG-rhwzpexa.md} +94 -1
  3. package/dist/cli.js +13216 -15614
  4. package/dist/types/async/job-manager.d.ts +12 -0
  5. package/dist/types/cli/args.d.ts +1 -0
  6. package/dist/types/config/model-resolver.d.ts +13 -0
  7. package/dist/types/discovery/agents.d.ts +11 -0
  8. package/dist/types/extensibility/custom-commands/types.d.ts +5 -5
  9. package/dist/types/extensibility/custom-tools/types.d.ts +1 -1
  10. package/dist/types/extensibility/extensions/loader.d.ts +2 -2
  11. package/dist/types/extensibility/extensions/types.d.ts +34 -8
  12. package/dist/types/extensibility/hooks/types.d.ts +5 -5
  13. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +10 -0
  14. package/dist/types/hindsight/state.d.ts +0 -1
  15. package/dist/types/index.d.ts +2 -2
  16. package/dist/types/lsp/utils.d.ts +8 -0
  17. package/dist/types/main.d.ts +1 -1
  18. package/dist/types/modes/acp/acp-agent.d.ts +1 -1
  19. package/dist/types/modes/acp/acp-client-bridge.d.ts +1 -1
  20. package/dist/types/modes/acp/acp-event-mapper.d.ts +1 -1
  21. package/dist/types/modes/acp/acp-mode.d.ts +1 -1
  22. package/dist/types/modes/components/agent-hub-projection.d.ts +38 -0
  23. package/dist/types/modes/components/agent-hub-renderer.d.ts +41 -0
  24. package/dist/types/modes/components/agent-hub.d.ts +12 -5
  25. package/dist/types/modes/components/model-browser.d.ts +9 -1
  26. package/dist/types/modes/interactive-mode.d.ts +8 -1
  27. package/dist/types/modes/session-observer-registry.d.ts +2 -0
  28. package/dist/types/registry/agent-lifecycle.d.ts +1 -1
  29. package/dist/types/registry/agent-registry.d.ts +47 -0
  30. package/dist/types/registry/persisted-agents.d.ts +3 -1
  31. package/dist/types/session/agent-session-types.d.ts +7 -0
  32. package/dist/types/session/agent-session.d.ts +8 -4
  33. package/dist/types/session/session-entries.d.ts +10 -0
  34. package/dist/types/session/session-loader.d.ts +11 -1
  35. package/dist/types/session/session-manager.d.ts +9 -1
  36. package/dist/types/session/session-tools.d.ts +10 -1
  37. package/dist/types/session/turn-recovery.d.ts +6 -2
  38. package/dist/types/slash-commands/acp-builtins.d.ts +1 -1
  39. package/dist/types/slash-commands/available-commands.d.ts +1 -1
  40. package/dist/types/task/executor.d.ts +10 -0
  41. package/dist/types/task/index.d.ts +2 -3
  42. package/dist/types/task/read-only-policy.d.ts +3 -0
  43. package/dist/types/task/structured-subagent.d.ts +2 -0
  44. package/dist/types/task/types.d.ts +4 -0
  45. package/dist/types/tools/bash.d.ts +3 -3
  46. package/dist/types/tools/browser/launch.d.ts +3 -0
  47. package/dist/types/tools/browser/tab-worker.d.ts +2 -2
  48. package/dist/types/tools/path-utils.d.ts +8 -0
  49. package/dist/types/tools/read.d.ts +3 -5
  50. package/dist/types/tools/run-scope.d.ts +27 -1
  51. package/dist/types/tools/shell-tokenize.d.ts +21 -3
  52. package/dist/types/tools/terminal-output.d.ts +1 -1
  53. package/dist/types/utils/changelog.d.ts +3 -2
  54. package/dist/types/utils/late-cleanup.d.ts +2 -0
  55. package/dist/types/utils/turndown.d.ts +1 -1
  56. package/dist/types/vibe/runtime.d.ts +4 -3
  57. package/dist/types/web/scrapers/readthedocs.d.ts +0 -3
  58. package/dist/types/web/search/providers/browser-headers.d.ts +2 -3
  59. package/examples/custom-tools/README.md +1 -1
  60. package/examples/extensions/README.md +1 -1
  61. package/examples/extensions/api-demo.ts +5 -6
  62. package/examples/extensions/chalk-logger.ts +1 -1
  63. package/examples/extensions/hello.ts +2 -2
  64. package/examples/extensions/reload-runtime.ts +1 -1
  65. package/examples/extensions/tools.ts +2 -2
  66. package/examples/extensions/with-deps/index.ts +1 -1
  67. package/examples/sdk/06-extensions.ts +1 -1
  68. package/package.json +15 -31
  69. package/scripts/bundle-dist.ts +1 -11
  70. package/scripts/omp +11 -2
  71. package/src/advisor/runtime.ts +54 -0
  72. package/src/async/job-manager.ts +26 -0
  73. package/src/cli/agents-cli.ts +1 -1
  74. package/src/cli/args.ts +24 -2
  75. package/src/cli/auth-broker-cli.ts +1 -1
  76. package/src/cli/auth-gateway-cli.ts +1 -1
  77. package/src/cli/bench-cli.ts +1 -1
  78. package/src/cli/claude-trace-cli.ts +1 -1
  79. package/src/cli/config-cli.ts +1 -1
  80. package/src/cli/dry-balance-cli.ts +1 -1
  81. package/src/cli/file-processor.ts +1 -1
  82. package/src/cli/flag-tables.ts +4 -0
  83. package/src/cli/grep-cli.ts +1 -1
  84. package/src/cli/grievances-cli.ts +1 -1
  85. package/src/cli/help-extra.ts +1 -1
  86. package/src/cli/models-cli.ts +1 -1
  87. package/src/cli/plugin-cli.ts +1 -1
  88. package/src/cli/read-cli.ts +1 -1
  89. package/src/cli/setup-cli.ts +1 -1
  90. package/src/cli/shell-cli.ts +1 -1
  91. package/src/cli/ssh-cli.ts +1 -1
  92. package/src/cli/stats-cli.ts +4 -4
  93. package/src/cli/tiny-models-cli.ts +1 -1
  94. package/src/cli/ttsr-cli.ts +1 -1
  95. package/src/cli/update-cli.ts +3 -57
  96. package/src/cli/usage-cli.ts +1 -1
  97. package/src/cli/web-search-cli.ts +1 -1
  98. package/src/cli/worktree-cli.ts +1 -1
  99. package/src/commands/say.ts +1 -1
  100. package/src/commands/token.ts +1 -1
  101. package/src/commit/agentic/agent.ts +1 -1
  102. package/src/commit/cli.ts +1 -1
  103. package/src/config/config-file.ts +2 -2
  104. package/src/config/keybindings.ts +3 -3
  105. package/src/config/model-discovery.ts +36 -2
  106. package/src/config/model-registry.ts +4 -1
  107. package/src/config/model-resolver.ts +54 -7
  108. package/src/discovery/agents.ts +73 -3
  109. package/src/discovery/builtin-rules/ts-no-inline-cast-access.md +5 -4
  110. package/src/discovery/builtin-rules/ts-no-tiny-functions.md +1 -1
  111. package/src/discovery/claude.ts +8 -5
  112. package/src/discovery/cursor.ts +8 -5
  113. package/src/discovery/gemini.ts +11 -8
  114. package/src/discovery/vscode.ts +1 -0
  115. package/src/discovery/windsurf.ts +4 -2
  116. package/src/edit/hashline/filesystem.ts +7 -7
  117. package/src/eval/agent-bridge.ts +1 -3
  118. package/src/eval/jl/prelude.jl +4 -4
  119. package/src/eval/js/shared/prelude.txt +2 -2
  120. package/src/eval/py/prelude.py +0 -3
  121. package/src/eval/py/runner.py +38 -1
  122. package/src/eval/rb/prelude.rb +1 -2
  123. package/src/extensibility/custom-commands/loader.ts +4 -4
  124. package/src/extensibility/custom-commands/types.ts +5 -5
  125. package/src/extensibility/custom-tools/loader.ts +4 -4
  126. package/src/extensibility/custom-tools/types.ts +1 -1
  127. package/src/extensibility/extensions/loader.ts +7 -6
  128. package/src/extensibility/extensions/types.ts +39 -8
  129. package/src/extensibility/hooks/loader.ts +4 -5
  130. package/src/extensibility/hooks/types.ts +5 -5
  131. package/src/extensibility/legacy-pi-coding-agent-shim.ts +23 -0
  132. package/src/extensibility/plugins/legacy-pi-compat.ts +53 -0
  133. package/src/hindsight/state.ts +2 -22
  134. package/src/index.ts +2 -2
  135. package/src/internal-urls/memory-protocol.ts +33 -0
  136. package/src/launch/terminal-output.ts +1 -1
  137. package/src/lsp/client.ts +2 -2
  138. package/src/lsp/utils.ts +29 -0
  139. package/src/main.ts +85 -21
  140. package/src/markit/converters/docx.ts +1 -1
  141. package/src/markit/converters/epub.ts +1 -1
  142. package/src/markit/converters/pptx.ts +1 -1
  143. package/src/markit/converters/xlsx.ts +1 -1
  144. package/src/modes/acp/acp-agent.ts +7 -6
  145. package/src/modes/acp/acp-client-bridge.ts +1 -1
  146. package/src/modes/acp/acp-event-mapper.ts +1 -1
  147. package/src/modes/acp/acp-mode.ts +1 -1
  148. package/src/modes/components/agent-hub-projection.ts +248 -0
  149. package/src/modes/components/agent-hub-renderer.ts +194 -0
  150. package/src/modes/components/agent-hub.ts +670 -192
  151. package/src/modes/components/agent-transcript-viewer.ts +1 -3
  152. package/src/modes/components/assistant-message.ts +1 -1
  153. package/src/modes/components/bordered-loader.ts +1 -1
  154. package/src/modes/components/custom-editor.ts +2 -2
  155. package/src/modes/components/extensions/inspector-panel.ts +11 -6
  156. package/src/modes/components/footer.ts +1 -3
  157. package/src/modes/components/hook-input.ts +1 -1
  158. package/src/modes/components/hook-selector.ts +2 -2
  159. package/src/modes/components/model-browser.ts +11 -3
  160. package/src/modes/components/model-hub.ts +4 -1
  161. package/src/modes/components/session-selector.ts +2 -2
  162. package/src/modes/components/status-line/segments.ts +4 -3
  163. package/src/modes/controllers/event-controller.ts +188 -7
  164. package/src/modes/controllers/extension-ui-controller.ts +4 -3
  165. package/src/modes/controllers/mcp-command-controller.ts +81 -20
  166. package/src/modes/controllers/selector-controller.ts +40 -49
  167. package/src/modes/controllers/streaming-reveal.ts +1 -1
  168. package/src/modes/interactive-mode.ts +13 -2
  169. package/src/modes/print-mode.ts +29 -12
  170. package/src/modes/rpc/rpc-mode.ts +2 -2
  171. package/src/modes/runtime-init.ts +1 -1
  172. package/src/modes/session-observer-registry.ts +5 -0
  173. package/src/modes/theme/theme.ts +2 -2
  174. package/src/prompts/tools/computer.md +1 -1
  175. package/src/registry/agent-lifecycle.ts +35 -10
  176. package/src/registry/agent-registry.ts +65 -2
  177. package/src/registry/persisted-agents.ts +341 -16
  178. package/src/sdk.ts +11 -5
  179. package/src/session/agent-session-types.ts +8 -0
  180. package/src/session/agent-session.ts +177 -188
  181. package/src/session/session-entries.ts +10 -0
  182. package/src/session/session-listing.ts +1 -1
  183. package/src/session/session-loader.ts +74 -8
  184. package/src/session/session-manager.ts +22 -2
  185. package/src/session/session-paths.ts +105 -110
  186. package/src/session/session-tools.ts +28 -1
  187. package/src/session/settings-stream-fn.ts +7 -3
  188. package/src/session/turn-recovery.ts +236 -18
  189. package/src/session/unexpected-stop-classifier.ts +12 -3
  190. package/src/slash-commands/acp-builtins.ts +1 -1
  191. package/src/slash-commands/available-commands.ts +1 -1
  192. package/src/slash-commands/helpers/stats-dashboard.ts +2 -1
  193. package/src/task/agents.ts +1 -1
  194. package/src/task/executor.ts +173 -47
  195. package/src/task/index.ts +9 -28
  196. package/src/task/isolation-runner.ts +43 -12
  197. package/src/task/persisted-revive.ts +12 -5
  198. package/src/task/read-only-policy.ts +27 -0
  199. package/src/task/structured-subagent.ts +31 -6
  200. package/src/task/types.ts +4 -0
  201. package/src/tiny/worker.ts +1 -1
  202. package/src/tools/auto-generated-guard.ts +1 -1
  203. package/src/tools/bash-interactive.ts +4 -4
  204. package/src/tools/bash-interceptor.ts +8 -4
  205. package/src/tools/bash.ts +6 -6
  206. package/src/tools/browser/cmux/cmux-tab.ts +66 -18
  207. package/src/tools/browser/launch.ts +76 -19
  208. package/src/tools/browser/readable.ts +9 -9
  209. package/src/tools/browser/tab-supervisor.ts +12 -3
  210. package/src/tools/browser/tab-worker-entry.ts +1 -1
  211. package/src/tools/browser/tab-worker.ts +101 -12
  212. package/src/tools/fetch.ts +1 -1
  213. package/src/tools/glob.ts +4 -2
  214. package/src/tools/path-utils.ts +22 -5
  215. package/src/tools/read.ts +22 -5
  216. package/src/tools/run-scope.ts +290 -4
  217. package/src/tools/shell-tokenize.ts +38 -9
  218. package/src/tools/terminal-output.ts +1 -1
  219. package/src/tools/todo.ts +1 -1
  220. package/src/utils/changelog.ts +5 -4
  221. package/src/utils/clipboard.ts +10 -2
  222. package/src/utils/external-editor.ts +4 -2
  223. package/src/utils/jj.ts +1 -1
  224. package/src/utils/late-cleanup.ts +17 -0
  225. package/src/utils/turndown.ts +1 -2
  226. package/src/vibe/runtime.ts +202 -58
  227. package/src/web/scrapers/arxiv.ts +1 -1
  228. package/src/web/scrapers/go-pkg.ts +1 -1
  229. package/src/web/scrapers/hackage.ts +1 -12
  230. package/src/web/scrapers/iacr.ts +1 -1
  231. package/src/web/scrapers/readthedocs.ts +2 -1
  232. package/src/web/scrapers/twitter.ts +2 -2
  233. package/src/web/scrapers/types.ts +1 -1
  234. package/src/web/scrapers/wikipedia.ts +1 -1
  235. package/src/web/search/providers/browser-headers.ts +12 -39
  236. package/src/web/search/providers/codex.ts +3 -26
  237. package/src/web/search/providers/duckduckgo.ts +1 -1
  238. package/src/web/search/providers/ecosia.ts +2 -2
  239. package/src/web/search/providers/exa.ts +1 -1
  240. package/src/web/search/providers/firecrawl.ts +1 -1
  241. package/src/web/search/providers/google.ts +1 -1
  242. package/src/web/search/providers/mojeek.ts +2 -2
  243. package/src/web/search/providers/perplexity.ts +1 -1
  244. package/src/web/search/providers/public.ts +1 -1
  245. package/src/web/search/providers/startpage.ts +2 -2
  246. package/src/markit/converters/mammoth.d.ts +0 -24
@@ -87,11 +87,11 @@ export class HashlineFilesystem extends Filesystem {
87
87
  return resolvePlanPath(this.session, relativePath);
88
88
  }
89
89
 
90
- canonicalPath(relativePath: string): string {
90
+ override canonicalPath(relativePath: string): string {
91
91
  return canonicalSnapshotKey(this.resolveAbsolute(relativePath));
92
92
  }
93
93
 
94
- allowTagPathRecovery(authoredPath: string, resolvedPath: string): boolean {
94
+ override allowTagPathRecovery(authoredPath: string, resolvedPath: string): boolean {
95
95
  // Internal-URL authored targets (`local://`, `vault://`, …) are approved
96
96
  // at the lower "read" privilege; never let one redirect onto a "write".
97
97
  if (isInternalUrlPath(authoredPath)) return false;
@@ -125,7 +125,7 @@ export class HashlineFilesystem extends Filesystem {
125
125
  return content;
126
126
  }
127
127
 
128
- async readBinary(relativePath: string): Promise<Uint8Array | undefined> {
128
+ override async readBinary(relativePath: string): Promise<Uint8Array | undefined> {
129
129
  const absolutePath = this.resolveAbsolute(relativePath);
130
130
  if (isNotebookPath(absolutePath)) return undefined;
131
131
  try {
@@ -136,7 +136,7 @@ export class HashlineFilesystem extends Filesystem {
136
136
  }
137
137
  }
138
138
 
139
- async preflightWrite(relativePath: string, options?: PreflightWriteOptions): Promise<void> {
139
+ override async preflightWrite(relativePath: string, options?: PreflightWriteOptions): Promise<void> {
140
140
  const fileOp = options?.fileOp;
141
141
  if (fileOp?.kind === "rem") {
142
142
  enforcePlanModeWrite(this.session, relativePath, { op: "delete" });
@@ -149,7 +149,7 @@ export class HashlineFilesystem extends Filesystem {
149
149
  enforcePlanModeWrite(this.session, relativePath, { op: "update" });
150
150
  }
151
151
 
152
- async delete(relativePath: string): Promise<void> {
152
+ override async delete(relativePath: string): Promise<void> {
153
153
  enforcePlanModeWrite(this.session, relativePath, { op: "delete" });
154
154
  const absolutePath = this.resolveAbsolute(relativePath);
155
155
  try {
@@ -168,7 +168,7 @@ export class HashlineFilesystem extends Filesystem {
168
168
  invalidateFsScanAfterWrite(absolutePath);
169
169
  }
170
170
 
171
- async move(fromRelative: string, toRelative: string, content?: string): Promise<void> {
171
+ override async move(fromRelative: string, toRelative: string, content?: string): Promise<void> {
172
172
  enforcePlanModeWrite(this.session, fromRelative, { op: "update", move: toRelative });
173
173
  const fromAbsolute = this.resolveAbsolute(fromRelative);
174
174
  const toAbsolute = this.resolveAbsolute(toRelative);
@@ -240,7 +240,7 @@ export class HashlineFilesystem extends Filesystem {
240
240
  return { text: content };
241
241
  }
242
242
 
243
- async exists(relativePath: string): Promise<boolean> {
243
+ override async exists(relativePath: string): Promise<boolean> {
244
244
  const absolutePath = this.resolveAbsolute(relativePath);
245
245
  return Bun.file(absolutePath).exists();
246
246
  }
@@ -23,7 +23,6 @@ export const EVAL_AGENT_BRIDGE_NAME = "__agent__";
23
23
  const agentArgsSchema = type({
24
24
  prompt: "string>0",
25
25
  "agent?": "string>0",
26
- "model?": "string>0|string>0[]",
27
26
  "label?": "string",
28
27
  "schema?": "unknown",
29
28
  "schemaMode?": "'permissive' | 'strict'",
@@ -31,12 +30,12 @@ const agentArgsSchema = type({
31
30
  "apply?": "boolean",
32
31
  "merge?": "boolean",
33
32
  "handle?": "boolean",
33
+ "+": "delete",
34
34
  });
35
35
 
36
36
  interface EvalAgentArgs {
37
37
  prompt: string;
38
38
  agent?: string;
39
- model?: string | string[];
40
39
  label?: string;
41
40
  schema?: unknown;
42
41
  schemaMode?: StructuredSubagentSchemaMode;
@@ -148,7 +147,6 @@ export async function runEvalAgent(args: unknown, options: EvalAgentBridgeOption
148
147
  invocationKind: "eval",
149
148
  assignment: parsed.prompt,
150
149
  ...(parsed.agent !== undefined ? { agent: parsed.agent } : {}),
151
- ...(parsed.model !== undefined ? { model: parsed.model } : {}),
152
150
  ...(Object.hasOwn(parsed, "schema") ? { outputSchema: parsed.schema } : {}),
153
151
  ...(parsed.schemaMode !== undefined ? { schemaMode: parsed.schemaMode } : {}),
154
152
  ...(parsed.label !== undefined ? { identity: { label: parsed.label } } : {}),
@@ -519,14 +519,11 @@ function completion(prompt::String; model="default", system=nothing, schema=noth
519
519
  return schema === nothing ? text : Main.json_parse(string(text))
520
520
  end
521
521
 
522
- function agent(prompt::String; agent="task", model=nothing, label=nothing, schema=nothing, schema_mode=nothing, isolated=nothing, apply=nothing, merge=nothing, handle=false, kwargs...)
522
+ function agent(prompt::String; agent="task", label=nothing, schema=nothing, schema_mode=nothing, isolated=nothing, apply=nothing, merge=nothing, handle=false, kwargs...)
523
523
  args_dict = Dict{String, Any}("prompt" => prompt)
524
524
  if agent !== nothing
525
525
  args_dict["agent"] = agent
526
526
  end
527
- if model !== nothing
528
- args_dict["model"] = model
529
- end
530
527
  if label !== nothing
531
528
  args_dict["label"] = label
532
529
  end
@@ -545,6 +542,9 @@ function agent(prompt::String; agent="task", model=nothing, label=nothing, schem
545
542
  if merge !== nothing
546
543
  args_dict["merge"] = Bool(merge)
547
544
  end
545
+ if haskey(kwargs, :model)
546
+ error("agent() no longer accepts a per-call model override; the selected agent's frontmatter model is used")
547
+ end
548
548
  handle_result = handle
549
549
  for (k, v) in kwargs
550
550
  args_dict[string(k)] = v
@@ -104,8 +104,8 @@ if (!globalThis.__omp_js_prelude_loaded__) {
104
104
  "agent",
105
105
  opts,
106
106
  rest,
107
- ["agent", "model", "label", "schema", "isolated", "apply", "merge", "schemaMode"],
108
- "{ agent, model, label, schema, isolated, apply, merge, schemaMode, handle }",
107
+ ["agent", "label", "schema", "isolated", "apply", "merge", "schemaMode"],
108
+ "{ agent, label, schema, isolated, apply, merge, schemaMode, handle }",
109
109
  );
110
110
  const { handle, ...callArgs } = o;
111
111
  const res = await globalThis.__omp_call_tool__("__agent__", { prompt, ...callArgs, handle: Boolean(handle) });
@@ -488,7 +488,6 @@ if "__omp_prelude_loaded__" not in globals():
488
488
  prompt,
489
489
  *,
490
490
  agent="task",
491
- model=None,
492
491
  label=None,
493
492
  schema=None,
494
493
  schema_mode=None,
@@ -506,8 +505,6 @@ if "__omp_prelude_loaded__" not in globals():
506
505
  args = {"prompt": prompt}
507
506
  if agent is not None:
508
507
  args["agent"] = agent
509
- if model is not None:
510
- args["model"] = model
511
508
  if label is not None:
512
509
  args["label"] = label
513
510
  if schema is not None:
@@ -40,6 +40,7 @@ import os
40
40
  import re
41
41
  import runpy
42
42
  import shlex
43
+ import shutil
43
44
  import signal
44
45
  import subprocess
45
46
  import sys
@@ -576,8 +577,10 @@ class _BoundedLineScanner:
576
577
  def _magic_pip(args: str) -> None:
577
578
  argv = shlex.split(args) if args else ["--help"]
578
579
  cmd = [sys.executable, "-m", "pip", *argv]
580
+ # stdin=DEVNULL: see _run_shell_body.
579
581
  proc = subprocess.Popen(
580
582
  cmd,
583
+ stdin=subprocess.DEVNULL,
581
584
  stdout=subprocess.PIPE,
582
585
  stderr=subprocess.STDOUT,
583
586
  )
@@ -737,9 +740,37 @@ def _magic_run(args: str) -> None:
737
740
  _STATE.user_ns[name] = value
738
741
 
739
742
 
743
+ def _resolve_bash() -> str:
744
+ if os.name != "nt":
745
+ return "/bin/bash"
746
+ # Prefer Git Bash over WSL's System32 bash.exe, which runs inside a
747
+ # separate Linux environment and does not share the Windows filesystem
748
+ # layout or PATH.
749
+ for env_var, suffix in (
750
+ ("ProgramFiles", r"Git\bin\bash.exe"),
751
+ ("ProgramFiles(x86)", r"Git\bin\bash.exe"),
752
+ ("LOCALAPPDATA", r"Programs\Git\bin\bash.exe"),
753
+ ):
754
+ root = os.environ.get(env_var)
755
+ if root:
756
+ candidate = os.path.join(root, suffix)
757
+ if os.path.isfile(candidate):
758
+ return candidate
759
+ found = shutil.which("bash")
760
+ if found and "system32" not in found.lower():
761
+ return found
762
+ # WSL's System32 bash.exe runs in a separate Linux environment, so
763
+ # silently falling back to it would execute the cell somewhere the user
764
+ # did not intend; fail loudly instead.
765
+ raise RuntimeError(
766
+ "%%bash requires a POSIX bash, but none was found. "
767
+ "Install Git for Windows or add a non-WSL bash to PATH."
768
+ )
769
+
770
+
740
771
  @cell_magic("bash")
741
772
  def _magic_cell_bash(args: str, body: str) -> int:
742
- return _run_shell_body(body, shell_arg="/bin/bash")
773
+ return _run_shell_body(body, shell_arg=_resolve_bash())
743
774
 
744
775
 
745
776
  @cell_magic("capture")
@@ -780,8 +811,12 @@ def _magic_cell_writefile(args: str, body: str) -> str:
780
811
 
781
812
 
782
813
  def _run_shell_body(body: str, *, shell_arg: str) -> int:
814
+ # stdin=DEVNULL: children must not inherit the runner's stdin, which is
815
+ # the host's NDJSON control channel (a reading child would steal frames,
816
+ # and inheriting the pipe deadlocks nested interpreters on Windows).
783
817
  proc = subprocess.Popen(
784
818
  [shell_arg, "-c", body],
819
+ stdin=subprocess.DEVNULL,
785
820
  stdout=subprocess.PIPE,
786
821
  stderr=subprocess.STDOUT,
787
822
  )
@@ -821,9 +856,11 @@ class _ShellResult(list):
821
856
 
822
857
 
823
858
  def __omp_shell(cmd: str) -> _ShellResult:
859
+ # stdin=DEVNULL: see _run_shell_body.
824
860
  proc = subprocess.Popen(
825
861
  cmd,
826
862
  shell=True,
863
+ stdin=subprocess.DEVNULL,
827
864
  stdout=subprocess.PIPE,
828
865
  stderr=subprocess.STDOUT,
829
866
  )
@@ -392,10 +392,9 @@ unless defined?($__omp_prelude_loaded) && $__omp_prelude_loaded
392
392
  schema.nil? ? text : JSON.parse(text)
393
393
  end
394
394
 
395
- def agent(prompt, agent: "task", model: nil, label: nil, schema: nil, schema_mode: nil, isolated: nil, apply: nil, merge: nil, handle: false)
395
+ def agent(prompt, agent: "task", label: nil, schema: nil, schema_mode: nil, isolated: nil, apply: nil, merge: nil, handle: false)
396
396
  args = { "prompt" => prompt }
397
397
  args["agent"] = agent unless agent.nil?
398
- args["model"] = model unless model.nil?
399
398
  args["label"] = label unless label.nil?
400
399
  args["schema"] = schema unless schema.nil?
401
400
  args["schemaMode"] = schema_mode unless schema_mode.nil?
@@ -6,8 +6,8 @@
6
6
  */
7
7
  import * as fs from "node:fs";
8
8
  import * as path from "node:path";
9
- import * as arktype from "@oh-my-pi/omptype";
10
- import * as zodModule from "@oh-my-pi/omptype/zod";
9
+ import { type } from "@oh-my-pi/omptype";
10
+ import * as zod from "@oh-my-pi/omptype/zod";
11
11
  import { getAgentDir, getProjectDir, isEnoent, logger } from "@oh-my-pi/pi-utils";
12
12
  import { getConfigDirs } from "../../config";
13
13
  import { execCommand } from "../../exec/exec";
@@ -187,8 +187,8 @@ export async function loadCustomCommands(options: LoadCustomCommandsOptions = {}
187
187
  exec: (command: string, args: string[], execOptions) =>
188
188
  execCommand(command, args, execOptions?.cwd ?? cwd, execOptions),
189
189
  typebox,
190
- arktype,
191
- zod: zodModule,
190
+ arktype: type,
191
+ zod,
192
192
  pi: PiCodingAgent,
193
193
  };
194
194
 
@@ -5,7 +5,7 @@
5
5
  * Unlike markdown commands which expand to prompts, custom commands can execute
6
6
  * arbitrary logic with full access to the hook context.
7
7
  */
8
- import type * as arktype from "@oh-my-pi/omptype";
8
+ import type { type as ArkType } from "@oh-my-pi/omptype";
9
9
  import type * as TypeBox from "@oh-my-pi/omptype/typebox";
10
10
  import type * as zod from "@oh-my-pi/omptype/zod";
11
11
  import type { ExecOptions, ExecResult, HookCommandContext } from "../../extensibility/hooks/types";
@@ -23,11 +23,11 @@ export interface CustomCommandAPI {
23
23
  cwd: string;
24
24
  /** Execute a shell command */
25
25
  exec(command: string, args: string[], options?: ExecOptions): Promise<ExecResult>;
26
- /** Injected zod-backed typebox shim (legacy/compat). */
26
+ /** Injected TypeBox shim (legacy/compat). */
27
27
  typebox: typeof TypeBox;
28
- /** Injected arktype module for validation in custom commands. */
29
- arktype: typeof arktype;
30
- /** Injected omptype-backed zod facade for command validation. */
28
+ /** Injected omptype schema builder for custom commands. */
29
+ arktype: typeof ArkType;
30
+ /** Injected Zod-compatible omptype builder for custom commands. */
31
31
  zod: typeof zod;
32
32
  /** Injected pi-coding-agent exports */
33
33
  pi: typeof PiCodingAgent;
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * Custom tool loader - loads TypeScript tool modules using native Bun import.
3
3
  *
4
- * Dependencies (the zod-backed typebox shim and pi-coding-agent) are injected via the
5
- * CustomToolAPI to avoid import resolution issues with custom tools loaded from user directories.
4
+ * Dependencies are injected through CustomToolAPI so tools loaded from user
5
+ * directories do not depend on workspace module resolution.
6
6
  */
7
7
  import * as path from "node:path";
8
8
  import { type } from "@oh-my-pi/omptype";
9
- import * as zodModule from "@oh-my-pi/omptype/zod";
9
+ import * as zod from "@oh-my-pi/omptype/zod";
10
10
  import type { AgentToolResult } from "@oh-my-pi/pi-agent-core";
11
11
  import { logger } from "@oh-my-pi/pi-utils";
12
12
  import { toolCapability } from "../../capability/tool";
@@ -149,7 +149,7 @@ export class CustomToolLoader {
149
149
  logger,
150
150
  typebox,
151
151
  arktype: type,
152
- zod: zodModule,
152
+ zod,
153
153
  pi,
154
154
  pushPendingAction: action => {
155
155
  if (!pushPendingAction) {
@@ -70,7 +70,7 @@ export interface CustomToolAPI {
70
70
  typebox: typeof TypeBox;
71
71
  /** Injected arktype module for arktype-authored custom tools. */
72
72
  arktype: typeof ArkType;
73
- /** Injected omptype-backed zod facade for parameter schemas. */
73
+ /** Injected Zod-compatible omptype builder for custom tools. */
74
74
  zod: typeof zod;
75
75
  /** Injected pi-coding-agent exports */
76
76
  pi: typeof PiCodingAgent;
@@ -4,8 +4,8 @@
4
4
  import type * as fs1 from "node:fs";
5
5
  import * as fs from "node:fs/promises";
6
6
  import * as path from "node:path";
7
- import { Type } from "@oh-my-pi/omptype";
8
- import * as zodModule from "@oh-my-pi/omptype/zod";
7
+ import { type } from "@oh-my-pi/omptype";
8
+ import * as zod from "@oh-my-pi/omptype/zod";
9
9
  import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
10
10
  import type {
11
11
  ImageContent,
@@ -46,6 +46,7 @@ import type {
46
46
  ProviderConfig,
47
47
  RegisteredCommand,
48
48
  ToolDefinition,
49
+ ToolInfo,
49
50
  } from "./types";
50
51
 
51
52
  installLegacyPiSpecifierShim();
@@ -92,7 +93,7 @@ export class ExtensionRuntime implements IExtensionRuntime {
92
93
  throw new ExtensionRuntimeNotInitializedError();
93
94
  }
94
95
 
95
- getAllTools(): string[] {
96
+ getAllTools(): ToolInfo[] {
96
97
  throw new ExtensionRuntimeNotInitializedError();
97
98
  }
98
99
 
@@ -141,8 +142,8 @@ export class ExtensionRuntime implements IExtensionRuntime {
141
142
  class ConcreteExtensionAPI implements ExtensionAPI, IExtensionRuntime {
142
143
  readonly logger = logger;
143
144
  readonly typebox = TypeBox;
144
- readonly arktype = Type;
145
- readonly zod = zodModule;
145
+ readonly arktype = type;
146
+ readonly zod = zod;
146
147
  readonly flagValues = new Map<string, boolean | string>();
147
148
  readonly pendingProviderRegistrations: Array<{
148
149
  name: string;
@@ -245,7 +246,7 @@ class ConcreteExtensionAPI implements ExtensionAPI, IExtensionRuntime {
245
246
  return this.runtime.getActiveTools();
246
247
  }
247
248
 
248
- getAllTools(): string[] {
249
+ getAllTools(): ToolInfo[] {
249
250
  return this.runtime.getAllTools();
250
251
  }
251
252
 
@@ -8,7 +8,7 @@
8
8
  * - Interact with the user via UI primitives
9
9
  */
10
10
 
11
- import type { Type as arktype } from "@oh-my-pi/omptype";
11
+ import type { type as ArkType } from "@oh-my-pi/omptype";
12
12
  import type * as TypeBox from "@oh-my-pi/omptype/typebox";
13
13
  import type * as zod from "@oh-my-pi/omptype/zod";
14
14
  import type {
@@ -596,6 +596,36 @@ export interface ToolDefinition<TParams extends TSchema = TSchema, TDetails = un
596
596
  ) => Component;
597
597
  }
598
598
 
599
+ /** Whether a tool's source is scoped to the user, the project, or a transient runtime session. */
600
+ export type SourceScope = "user" | "project" | "temporary";
601
+
602
+ /** Whether a tool's source came from an installed package or a top-level (loose) file. */
603
+ export type SourceOrigin = "package" | "top-level";
604
+
605
+ /**
606
+ * Provenance metadata describing where a registered tool came from. Mirrors the
607
+ * `@earendil-works/pi-coding-agent` `SourceInfo` contract so extensions authored
608
+ * against upstream pi (e.g. gentle-pi) can read `sourceInfo.source` unchanged.
609
+ */
610
+ export interface SourceInfo {
611
+ /** Synthetic or on-disk identifier for the tool's origin (e.g. `<builtin:read>`). */
612
+ path: string;
613
+ /** Origin class: `"builtin"`, `"sdk"`, `"mcp"`, or `"extension"`. */
614
+ source: string;
615
+ scope: SourceScope;
616
+ origin: SourceOrigin;
617
+ baseDir?: string;
618
+ }
619
+
620
+ /** Tool metadata returned by {@link ExtensionAPI.getAllTools}: identity, schema, and source provenance. */
621
+ export interface ToolInfo {
622
+ name: string;
623
+ description: string;
624
+ parameters: TSchema;
625
+ promptGuidelines?: string[];
626
+ sourceInfo: SourceInfo;
627
+ }
628
+
599
629
  // ============================================================================
600
630
  // Resource Events
601
631
  // ============================================================================
@@ -1109,12 +1139,13 @@ export interface ExtensionAPI {
1109
1139
  /** File logger for error/warning/debug messages */
1110
1140
  logger: typeof PiLogger;
1111
1141
 
1112
- /** Injected zod-backed typebox shim for legacy `Type.Object(...)` parameter authoring. */
1142
+ /** Injected TypeBox shim for legacy `Type.Object(...)` parameter authoring. */
1113
1143
  typebox: typeof TypeBox;
1114
1144
 
1115
- /** Injected arktype module for arktype-authored extension tools (canonical going forward). */
1116
- arktype: typeof arktype;
1117
- /** Injected omptype-backed zod facade for extension tool parameter schemas. */
1145
+ /** Injected omptype schema builder for extension tools. */
1146
+ arktype: typeof ArkType;
1147
+
1148
+ /** Injected Zod-compatible omptype builder for extension tools. */
1118
1149
  zod: typeof zod;
1119
1150
 
1120
1151
  /** Injected pi-coding-agent exports for accessing SDK utilities */
@@ -1267,8 +1298,8 @@ export interface ExtensionAPI {
1267
1298
  /** Get the list of currently active tool names. */
1268
1299
  getActiveTools(): string[];
1269
1300
 
1270
- /** Get all configured tools (built-in + extension tools). */
1271
- getAllTools(): string[];
1301
+ /** Get all configured tools (built-in + extension tools) with schema and source metadata. */
1302
+ getAllTools(): ToolInfo[];
1272
1303
 
1273
1304
  /** Set the active tools by name. */
1274
1305
  setActiveTools(toolNames: string[]): Promise<void>;
@@ -1464,7 +1495,7 @@ export type AppendEntryHandler = <T = unknown>(customType: string, data?: T) =>
1464
1495
 
1465
1496
  export type GetActiveToolsHandler = () => string[];
1466
1497
 
1467
- export type GetAllToolsHandler = () => string[];
1498
+ export type GetAllToolsHandler = () => ToolInfo[];
1468
1499
 
1469
1500
  export type GetCommandsHandler = () => SlashCommandInfo[];
1470
1501
 
@@ -2,8 +2,8 @@
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 arktype from "@oh-my-pi/omptype";
6
- import * as zodModule from "@oh-my-pi/omptype/zod";
5
+ import { type } from "@oh-my-pi/omptype";
6
+ import * as zod from "@oh-my-pi/omptype/zod";
7
7
  import { logger } from "@oh-my-pi/pi-utils";
8
8
  import { hookCapability } from "../../capability/hook";
9
9
  import type { Hook } from "../../discovery";
@@ -122,9 +122,8 @@ async function createHookAPI(
122
122
  },
123
123
  logger,
124
124
  typebox,
125
- // HookAPI.arktype is typed as the arktype `Type` constructor; expose it from the module namespace.
126
- arktype: arktype.Type,
127
- zod: zodModule,
125
+ arktype: type,
126
+ zod,
128
127
  pi: PiCodingAgent,
129
128
  } as HookAPI;
130
129
 
@@ -1,4 +1,4 @@
1
- import type { Type } from "@oh-my-pi/omptype";
1
+ import type { type as ArkType } from "@oh-my-pi/omptype";
2
2
  import type * as TypeBox from "@oh-my-pi/omptype/typebox";
3
3
  import type * as zod from "@oh-my-pi/omptype/zod";
4
4
  import type { ImageContent, Message, Model, TextContent } from "@oh-my-pi/pi-ai";
@@ -582,11 +582,11 @@ export interface HookAPI {
582
582
 
583
583
  /** File logger for error/warning/debug messages */
584
584
  logger: typeof PiLogger;
585
- /** Injected zod-backed typebox shim (legacy/compat — prefer `arktype`). */
585
+ /** Injected TypeBox shim (legacy/compat — prefer `arktype`). */
586
586
  typebox: typeof TypeBox;
587
- /** Injected arktype module for arktype-authored hooks. */
588
- arktype: typeof Type;
589
- /** Injected omptype-backed zod facade for hook validation. */
587
+ /** Injected omptype schema builder for hooks. */
588
+ arktype: typeof ArkType;
589
+ /** Injected Zod-compatible omptype builder for hooks. */
590
590
  zod: typeof zod;
591
591
  /** Injected pi-coding-agent exports */
592
592
  pi: typeof PiCodingAgent;
@@ -54,6 +54,7 @@ import { ReadTool } from "../tools/read";
54
54
  import { formatBytes } from "../tools/render-utils";
55
55
  import { WriteTool } from "../tools/write";
56
56
  import { EventBus } from "../utils/event-bus";
57
+ import { convertImageToPng } from "../utils/image-loading";
57
58
  import { discoverExtensionPaths, loadExtensionFromFactory, loadExtensions } from "./extensions";
58
59
  import { ExtensionRuntime } from "./extensions/loader";
59
60
  import type { ExtensionFactory, ToolDefinition } from "./extensions/types";
@@ -384,6 +385,28 @@ async function executeLegacyBashOperations(
384
385
  }
385
386
  }
386
387
 
388
+ /**
389
+ * Convert an image attachment to PNG using the legacy package-root contract.
390
+ *
391
+ * Invalid or unsupported image data returns `null`, matching Pi's historical
392
+ * helper instead of surfacing Bun's decoder error to extensions.
393
+ */
394
+ export async function convertToPng(
395
+ base64Data: string,
396
+ mimeType: string,
397
+ ): Promise<{ data: string; mimeType: string } | null> {
398
+ if (mimeType === "image/png") {
399
+ return { data: base64Data, mimeType };
400
+ }
401
+
402
+ try {
403
+ const converted = await convertImageToPng({ type: "image", data: base64Data, mimeType });
404
+ return { data: converted.data, mimeType: converted.mimeType };
405
+ } catch {
406
+ return null;
407
+ }
408
+ }
409
+
387
410
  /** Format the active shortcut for legacy extensions that render keybinding hints. */
388
411
  export function keyText(action: Keybinding): string {
389
412
  return formatKeyHints(getKeybindings().getKeys(action));
@@ -423,6 +423,25 @@ function isGlobalRequireCall(node: StructuralAstNode | null, scope: BindingScope
423
423
  );
424
424
  }
425
425
 
426
+ /**
427
+ * Whether `node` is a `createRequire(...)` factory call imported from
428
+ * `node:module` (or its `module` alias).
429
+ */
430
+ function isCreateRequireInvocation(
431
+ node: StructuralAstNode | null,
432
+ createRequireBindings: ReadonlySet<string>,
433
+ moduleNamespaceBindings: ReadonlySet<string>,
434
+ ): boolean {
435
+ if (node?.type !== "CallExpression") return false;
436
+ const callee = asAstNode(node.callee);
437
+ if (callee?.type === "Identifier" && typeof callee.name === "string") {
438
+ return createRequireBindings.has(callee.name);
439
+ }
440
+ if (callee?.type !== "MemberExpression" || staticMemberPropertyName(callee) !== "createRequire") return false;
441
+ const object = asAstNode(callee.object);
442
+ return object?.type === "Identifier" && typeof object.name === "string" && moduleNamespaceBindings.has(object.name);
443
+ }
444
+
426
445
  function staticMemberPropertyName(node: StructuralAstNode): string | null {
427
446
  const property = asAstNode(node.property);
428
447
  if (node.computed !== true && property?.type === "Identifier" && typeof property.name === "string") {
@@ -458,6 +477,28 @@ function collectExtensionSpecifierReferences(
458
477
  references.push({ kind, specifier: node.value, start: node.start, end: node.end });
459
478
  }
460
479
  };
480
+ const createRequireBindings = new Set<string>();
481
+ const moduleNamespaceBindings = new Set<string>();
482
+ for (const { node } of collectScopedAstNodes(ast, candidate => candidate.type === "ImportDeclaration")) {
483
+ const source = asAstNode(node.source);
484
+ if (source?.type !== "StringLiteral" || (source.value !== "node:module" && source.value !== "module")) continue;
485
+ for (const value of Array.isArray(node.specifiers) ? node.specifiers : []) {
486
+ const specifier = asAstNode(value);
487
+ const local = asAstNode(specifier?.local);
488
+ if (local?.type !== "Identifier" || typeof local.name !== "string") continue;
489
+ if (specifier?.type === "ImportNamespaceSpecifier") {
490
+ moduleNamespaceBindings.add(local.name);
491
+ } else if (specifier?.type === "ImportSpecifier") {
492
+ const imported = asAstNode(specifier.imported);
493
+ if (
494
+ (imported?.type === "Identifier" && imported.name === "createRequire") ||
495
+ (imported?.type === "StringLiteral" && imported.value === "createRequire")
496
+ ) {
497
+ createRequireBindings.add(local.name);
498
+ }
499
+ }
500
+ }
501
+ }
461
502
  for (const { node, scope } of collectScopedAstNodes(ast, isSpecifierReferenceNode)) {
462
503
  if (
463
504
  node.type === "ImportDeclaration" ||
@@ -478,6 +519,18 @@ function collectExtensionSpecifierReferences(
478
519
  record("import", nodeArgument(node, 0));
479
520
  } else if (isIdentifier(callee, "require") && !scopeHasBinding(scope, REQUIRE_BINDING)) {
480
521
  record("require", nodeArgument(node, 0));
522
+ } else if (isCreateRequireInvocation(callee, createRequireBindings, moduleNamespaceBindings)) {
523
+ // `createRequire(base)(spec)` — pin the invoked bare dependency so it
524
+ // loads without a runtime `node_modules` lookup. Relative specifiers
525
+ // resolve against `base`, which is not rewritten, so restrict to bare.
526
+ const argument = nodeArgument(node, 0);
527
+ if (
528
+ argument?.type === "StringLiteral" &&
529
+ typeof argument.value === "string" &&
530
+ isBareExtensionDependencySpecifier(argument.value)
531
+ ) {
532
+ record("require", argument);
533
+ }
481
534
  }
482
535
  }
483
536
  }
@@ -411,24 +411,6 @@ export class HindsightSessionState {
411
411
  }
412
412
  }
413
413
 
414
- async maybeRecallOnAgentStart(): Promise<void> {
415
- if (!this.config.autoRecall || this.hasRecalledForFirstTurn) return;
416
- const messages = extractMessages(this.session.sessionManager);
417
- const lastUser = messages.findLast(m => m.role === "user");
418
- if (!lastUser) return;
419
-
420
- const query = composeRecallQuery(lastUser.content, messages, this.config.recallContextTurns);
421
- const truncated = truncateRecallQuery(query, lastUser.content, this.config.recallMaxQueryChars);
422
- const { context, ok } = await this.recallForContext(truncated);
423
- if (!ok) return;
424
-
425
- this.hasRecalledForFirstTurn = true;
426
- if (!context) return;
427
-
428
- this.lastRecallSnippet = context;
429
- await this.#refreshBaseSystemPromptAfter("recall");
430
- }
431
-
432
414
  async beforeAgentStartPrompt(promptText: string): Promise<string | undefined> {
433
415
  if (this.config.mentalModelsEnabled && this.mentalModelsLoadPromise && this.mentalModelsLoadedAt === undefined) {
434
416
  await Promise.race([this.mentalModelsLoadPromise, Bun.sleep(MENTAL_MODEL_FIRST_TURN_DEADLINE_MS)]);
@@ -509,9 +491,7 @@ export class HindsightSessionState {
509
491
  attachSessionListeners(): void {
510
492
  this.unsubscribe?.();
511
493
  this.unsubscribe = this.session.subscribe(event => {
512
- if (event.type === "agent_start") {
513
- void this.maybeRecallOnAgentStart();
514
- } else if (event.type === "agent_end") {
494
+ if (event.type === "agent_end") {
515
495
  void this.maybeRetainOnAgentEnd();
516
496
  // Drain any queued tool-initiated retain calls now that the turn
517
497
  // is settled. The queue is also debounced/size-bounded, but
@@ -540,7 +520,7 @@ export class HindsightSessionState {
540
520
  this.retainQueue.dispose();
541
521
  }
542
522
 
543
- async #refreshBaseSystemPromptAfter(reason: "recall" | "MM load" | "MM reload" | "MM TTL reload"): Promise<void> {
523
+ async #refreshBaseSystemPromptAfter(reason: "MM load" | "MM reload" | "MM TTL reload"): Promise<void> {
544
524
  try {
545
525
  await this.session.refreshBaseSystemPrompt();
546
526
  } catch (err) {