@oh-my-pi/pi-coding-agent 17.0.9 → 17.1.0

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 (285) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/README.md +1 -1
  3. package/dist/cli.js +4659 -4261
  4. package/dist/types/async/job-manager.d.ts +37 -1
  5. package/dist/types/cli/args.d.ts +2 -0
  6. package/dist/types/cli/bench-cli.d.ts +41 -0
  7. package/dist/types/commands/bench.d.ts +15 -3
  8. package/dist/types/commands/launch.d.ts +4 -0
  9. package/dist/types/config/model-registry.d.ts +3 -1
  10. package/dist/types/config/models-config-schema.d.ts +18 -6
  11. package/dist/types/config/models-config.d.ts +14 -5
  12. package/dist/types/config/provider-globals.d.ts +2 -2
  13. package/dist/types/config/service-tier.d.ts +5 -1
  14. package/dist/types/config/settings-schema.d.ts +316 -47
  15. package/dist/types/config/settings.d.ts +7 -0
  16. package/dist/types/dap/client.d.ts +20 -0
  17. package/dist/types/exec/bash-executor.d.ts +38 -0
  18. package/dist/types/exec/direnv.d.ts +33 -0
  19. package/dist/types/export/html/args.d.ts +15 -0
  20. package/dist/types/export/html/index.d.ts +11 -26
  21. package/dist/types/export/html/web-palette.d.ts +55 -116
  22. package/dist/types/extensibility/extensions/loader.d.ts +3 -0
  23. package/dist/types/extensibility/extensions/types.d.ts +17 -2
  24. package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +13 -0
  25. package/dist/types/live/controller.d.ts +40 -0
  26. package/dist/types/live/protocol.d.ts +85 -0
  27. package/dist/types/live/protocol.test.d.ts +1 -0
  28. package/dist/types/live/transport.d.ts +48 -0
  29. package/dist/types/live/visualizer.d.ts +33 -0
  30. package/dist/types/lsp/client.d.ts +16 -1
  31. package/dist/types/mcp/config-writer.test.d.ts +1 -0
  32. package/dist/types/mcp/oauth-discovery.d.ts +2 -0
  33. package/dist/types/mcp/smithery-auth.d.ts +1 -1
  34. package/dist/types/mcp/smithery-auth.test.d.ts +1 -0
  35. package/dist/types/memory-backend/tool-names.d.ts +2 -0
  36. package/dist/types/modes/components/login-dialog.d.ts +2 -2
  37. package/dist/types/modes/components/plan-review-overlay.d.ts +26 -0
  38. package/dist/types/modes/components/settings-defs.d.ts +7 -1
  39. package/dist/types/modes/components/status-line/component.jj-cache.test.d.ts +1 -0
  40. package/dist/types/modes/components/tool-execution.d.ts +9 -1
  41. package/dist/types/modes/controllers/command-controller.d.ts +1 -1
  42. package/dist/types/modes/controllers/event-controller.d.ts +6 -1
  43. package/dist/types/modes/controllers/live-command-controller.d.ts +14 -0
  44. package/dist/types/modes/interactive-mode.d.ts +5 -0
  45. package/dist/types/modes/theme/theme.d.ts +1 -1
  46. package/dist/types/modes/types.d.ts +3 -1
  47. package/dist/types/modes/utils/context-usage.d.ts +12 -2
  48. package/dist/types/plan-mode/plan-files.d.ts +10 -0
  49. package/dist/types/registry/agent-lifecycle.d.ts +21 -9
  50. package/dist/types/registry/agent-registry.d.ts +11 -4
  51. package/dist/types/sdk.d.ts +15 -1
  52. package/dist/types/secrets/index.d.ts +15 -2
  53. package/dist/types/secrets/obfuscator.d.ts +113 -15
  54. package/dist/types/session/acp-permission-gate.d.ts +19 -0
  55. package/dist/types/session/agent-session-events.d.ts +84 -0
  56. package/dist/types/session/agent-session-types.d.ts +314 -0
  57. package/dist/types/session/agent-session.d.ts +118 -755
  58. package/dist/types/session/async-job-delivery.d.ts +28 -0
  59. package/dist/types/session/bash-runner.d.ts +75 -0
  60. package/dist/types/session/blob-store.d.ts +11 -1
  61. package/dist/types/session/blob-store.test.d.ts +1 -0
  62. package/dist/types/session/checkpoint-entries.d.ts +28 -0
  63. package/dist/types/session/eval-runner.d.ts +49 -0
  64. package/dist/types/session/irc-bridge.d.ts +46 -0
  65. package/dist/types/session/messages.d.ts +22 -0
  66. package/dist/types/session/model-controls.d.ts +180 -0
  67. package/dist/types/session/prewalk.d.ts +46 -0
  68. package/dist/types/session/queued-messages.d.ts +22 -0
  69. package/dist/types/session/redis-session-storage.d.ts +1 -0
  70. package/dist/types/session/retry-fallback-chains.d.ts +74 -0
  71. package/dist/types/session/role-models.d.ts +15 -0
  72. package/dist/types/session/session-advisors.d.ts +228 -0
  73. package/dist/types/session/session-entries.d.ts +8 -0
  74. package/dist/types/session/session-handoff.d.ts +70 -0
  75. package/dist/types/session/session-history-format.d.ts +6 -1
  76. package/dist/types/session/session-maintenance.d.ts +246 -0
  77. package/dist/types/session/session-manager.d.ts +41 -0
  78. package/dist/types/session/session-memory.d.ts +56 -0
  79. package/dist/types/session/session-provider-boundary.d.ts +62 -0
  80. package/dist/types/session/session-stats.d.ts +48 -0
  81. package/dist/types/session/session-tools.d.ts +143 -0
  82. package/dist/types/session/session-workspace.d.ts +32 -0
  83. package/dist/types/session/stream-guards.d.ts +44 -0
  84. package/dist/types/session/todo-tracker.d.ts +56 -0
  85. package/dist/types/session/ttsr-coordinator.d.ts +51 -0
  86. package/dist/types/session/turn-recovery.d.ts +225 -0
  87. package/dist/types/system-prompt.d.ts +2 -0
  88. package/dist/types/task/executor.d.ts +4 -1
  89. package/dist/types/task/output-manager.d.ts +3 -1
  90. package/dist/types/tools/approval.d.ts +1 -0
  91. package/dist/types/tools/browser/launch.d.ts +5 -0
  92. package/dist/types/tools/image-gen.d.ts +4 -4
  93. package/dist/types/tools/image-providers.d.ts +38 -0
  94. package/dist/types/tools/index.d.ts +5 -0
  95. package/dist/types/tools/path-utils.d.ts +8 -0
  96. package/dist/types/tools/todo.d.ts +10 -4
  97. package/dist/types/tools/xdev.d.ts +5 -1
  98. package/dist/types/tts/streaming-player.d.ts +14 -0
  99. package/dist/types/tts/vocalizer.d.ts +5 -0
  100. package/dist/types/utils/jj.d.ts +29 -0
  101. package/dist/types/utils/lang-from-path.d.ts +1 -0
  102. package/dist/types/utils/title-generator.d.ts +30 -0
  103. package/dist/types/vibe/runtime.d.ts +36 -8
  104. package/dist/types/web/search/index.d.ts +1 -1
  105. package/dist/types/web/search/provider.d.ts +15 -5
  106. package/dist/types/web/search/types.d.ts +94 -0
  107. package/package.json +12 -12
  108. package/scripts/generate-share-viewer.ts +4 -6
  109. package/src/advisor/__tests__/advisor.test.ts +643 -0
  110. package/src/advisor/runtime.ts +144 -43
  111. package/src/async/job-manager.ts +90 -2
  112. package/src/cli/args.ts +2 -0
  113. package/src/cli/auth-gateway-cli.ts +18 -3
  114. package/src/cli/bench-cli.ts +395 -29
  115. package/src/cli/flag-tables.ts +3 -0
  116. package/src/cli/gc-cli.ts +2 -2
  117. package/src/cli/usage-cli.ts +10 -1
  118. package/src/collab/host.ts +3 -2
  119. package/src/commands/bench.ts +18 -4
  120. package/src/commands/launch.ts +4 -0
  121. package/src/config/model-registry.ts +10 -2
  122. package/src/config/models-config-schema.ts +15 -4
  123. package/src/config/provider-globals.ts +9 -9
  124. package/src/config/service-tier.ts +26 -1
  125. package/src/config/settings-schema.ts +209 -51
  126. package/src/config/settings.ts +81 -2
  127. package/src/dap/client.ts +37 -4
  128. package/src/edit/index.ts +49 -12
  129. package/src/eval/py/executor.ts +103 -33
  130. package/src/exec/bash-executor.ts +89 -6
  131. package/src/exec/direnv.ts +145 -0
  132. package/src/export/html/args.ts +20 -0
  133. package/src/export/html/index.ts +45 -47
  134. package/src/export/html/template.css +19 -1
  135. package/src/export/html/template.html +6 -0
  136. package/src/export/html/template.js +21 -0
  137. package/src/export/html/tool-views.generated.js +31 -31
  138. package/src/export/html/web-palette.ts +116 -132
  139. package/src/export/share.ts +249 -49
  140. package/src/extensibility/extensions/loader.ts +29 -1
  141. package/src/extensibility/extensions/runner.ts +6 -0
  142. package/src/extensibility/extensions/types.ts +33 -2
  143. package/src/extensibility/plugins/legacy-pi-compat.ts +19 -1
  144. package/src/hindsight/state.ts +64 -6
  145. package/src/internal-urls/local-protocol.ts +2 -1
  146. package/src/internal-urls/memory-protocol.ts +2 -2
  147. package/src/internal-urls/skill-protocol.ts +2 -2
  148. package/src/internal-urls/ssh-protocol.ts +2 -1
  149. package/src/internal-urls/vault-protocol.ts +2 -1
  150. package/src/live/audio-worklet.txt +59 -0
  151. package/src/live/browser-runtime.txt +221 -0
  152. package/src/live/controller.ts +464 -0
  153. package/src/live/prompts/agent-final-message.md +3 -0
  154. package/src/live/prompts/live-instructions.md +23 -0
  155. package/src/live/protocol.test.ts +140 -0
  156. package/src/live/protocol.ts +233 -0
  157. package/src/live/transport.ts +490 -0
  158. package/src/live/visualizer.ts +234 -0
  159. package/src/lsp/client.ts +29 -9
  160. package/src/lsp/index.ts +19 -4
  161. package/src/main.ts +8 -1
  162. package/src/mcp/config-writer.test.ts +43 -0
  163. package/src/mcp/config-writer.ts +109 -82
  164. package/src/mcp/oauth-discovery.ts +10 -2
  165. package/src/mcp/smithery-auth.test.ts +29 -0
  166. package/src/mcp/smithery-auth.ts +3 -2
  167. package/src/memories/index.ts +40 -20
  168. package/src/memory-backend/tool-names.ts +2 -0
  169. package/src/mnemopi/backend.ts +24 -7
  170. package/src/modes/acp/acp-agent.ts +56 -10
  171. package/src/modes/acp/acp-event-mapper.ts +8 -1
  172. package/src/modes/components/agent-hub.ts +1 -1
  173. package/src/modes/components/login-dialog.ts +14 -4
  174. package/src/modes/components/plan-review-overlay.ts +350 -35
  175. package/src/modes/components/settings-defs.ts +23 -1
  176. package/src/modes/components/settings-selector.ts +169 -1
  177. package/src/modes/components/status-line/component.jj-cache.test.ts +229 -0
  178. package/src/modes/components/status-line/component.ts +123 -2
  179. package/src/modes/components/tool-execution.ts +11 -2
  180. package/src/modes/controllers/command-controller.ts +56 -15
  181. package/src/modes/controllers/event-controller.ts +111 -16
  182. package/src/modes/controllers/extension-ui-controller.test.ts +1 -0
  183. package/src/modes/controllers/extension-ui-controller.ts +20 -4
  184. package/src/modes/controllers/live-command-controller.ts +178 -0
  185. package/src/modes/controllers/mcp-command-controller.ts +10 -1
  186. package/src/modes/controllers/omfg-controller.ts +44 -40
  187. package/src/modes/controllers/selector-controller.ts +18 -10
  188. package/src/modes/controllers/session-focus-controller.ts +6 -1
  189. package/src/modes/interactive-mode.ts +157 -38
  190. package/src/modes/runtime-init.ts +2 -0
  191. package/src/modes/setup-wizard/scenes/web-search.ts +17 -9
  192. package/src/modes/theme/theme.ts +1 -1
  193. package/src/modes/types.ts +3 -1
  194. package/src/modes/utils/context-usage.ts +23 -7
  195. package/src/modes/utils/ui-helpers.ts +10 -5
  196. package/src/plan-mode/plan-files.ts +40 -0
  197. package/src/prompts/bench/cache-prefix-chunk.md +1 -0
  198. package/src/prompts/bench/cache-prefix.md +3 -0
  199. package/src/prompts/bench/cache-suffix.md +1 -0
  200. package/src/prompts/system/custom-system-prompt.md +1 -1
  201. package/src/prompts/system/project-prompt.md +8 -1
  202. package/src/prompts/system/subagent-async-pending.md +6 -0
  203. package/src/prompts/system/system-prompt.md +1 -1
  204. package/src/prompts/system/workflow-notice.md +48 -40
  205. package/src/prompts/system/xdev-mount-notice.md +4 -0
  206. package/src/prompts/tools/todo.md +3 -1
  207. package/src/registry/agent-lifecycle.ts +116 -46
  208. package/src/registry/agent-registry.ts +35 -8
  209. package/src/registry/persisted-agents.ts +26 -2
  210. package/src/sdk.ts +210 -136
  211. package/src/secrets/index.ts +127 -4
  212. package/src/secrets/obfuscator.ts +2328 -105
  213. package/src/session/acp-permission-gate.ts +165 -0
  214. package/src/session/agent-session-error-log.test.ts +1 -1
  215. package/src/session/agent-session-events.ts +66 -0
  216. package/src/session/agent-session-types.ts +334 -0
  217. package/src/session/agent-session.ts +1892 -12226
  218. package/src/session/artifacts.ts +6 -5
  219. package/src/session/async-job-delivery.ts +74 -0
  220. package/src/session/bash-runner.ts +326 -0
  221. package/src/session/blob-store.test.ts +56 -0
  222. package/src/session/blob-store.ts +18 -2
  223. package/src/session/checkpoint-entries.ts +81 -0
  224. package/src/session/eval-runner.ts +212 -0
  225. package/src/session/indexed-session-storage.ts +9 -2
  226. package/src/session/irc-bridge.ts +203 -0
  227. package/src/session/messages.ts +263 -1
  228. package/src/session/model-controls.ts +714 -0
  229. package/src/session/prewalk.ts +252 -0
  230. package/src/session/queued-messages.ts +93 -0
  231. package/src/session/redis-session-storage.ts +51 -11
  232. package/src/session/retry-fallback-chains.ts +455 -0
  233. package/src/session/role-models.ts +85 -0
  234. package/src/session/session-advisors.ts +1679 -0
  235. package/src/session/session-context.ts +6 -3
  236. package/src/session/session-entries.ts +8 -0
  237. package/src/session/session-handoff.ts +308 -0
  238. package/src/session/session-history-format.ts +20 -9
  239. package/src/session/session-loader.ts +0 -46
  240. package/src/session/session-maintenance.ts +2983 -0
  241. package/src/session/session-manager.ts +390 -30
  242. package/src/session/session-memory.ts +222 -0
  243. package/src/session/session-provider-boundary.ts +307 -0
  244. package/src/session/session-stats.ts +293 -0
  245. package/src/session/session-tools.ts +943 -0
  246. package/src/session/session-workspace.ts +53 -0
  247. package/src/session/stream-guards.ts +417 -0
  248. package/src/session/todo-tracker.ts +380 -0
  249. package/src/session/ttsr-coordinator.ts +496 -0
  250. package/src/session/turn-recovery.ts +1629 -0
  251. package/src/slash-commands/builtin-registry.ts +92 -11
  252. package/src/slash-commands/helpers/usage-report.ts +22 -3
  253. package/src/system-prompt.ts +41 -17
  254. package/src/task/executor.ts +285 -70
  255. package/src/task/index.ts +98 -105
  256. package/src/task/output-manager.ts +25 -3
  257. package/src/task/persisted-revive.ts +4 -3
  258. package/src/task/structured-subagent.ts +1 -0
  259. package/src/tools/approval.ts +55 -10
  260. package/src/tools/bash-interactive.ts +26 -0
  261. package/src/tools/bash-skill-urls.ts +28 -2
  262. package/src/tools/bash.ts +339 -123
  263. package/src/tools/browser/launch.ts +9 -1
  264. package/src/tools/browser/tab-supervisor.ts +8 -5
  265. package/src/tools/browser.ts +94 -47
  266. package/src/tools/image-gen.ts +20 -28
  267. package/src/tools/image-providers.ts +50 -0
  268. package/src/tools/index.ts +9 -0
  269. package/src/tools/path-utils.ts +49 -2
  270. package/src/tools/read.ts +156 -88
  271. package/src/tools/renderers.ts +7 -1
  272. package/src/tools/todo.ts +101 -11
  273. package/src/tools/vibe.ts +1 -2
  274. package/src/tools/write.ts +51 -1
  275. package/src/tools/xdev.ts +67 -3
  276. package/src/tts/streaming-player.ts +62 -4
  277. package/src/tts/vocalizer.ts +18 -1
  278. package/src/utils/jj.ts +125 -4
  279. package/src/utils/lang-from-path.ts +7 -0
  280. package/src/utils/title-generator.ts +137 -1
  281. package/src/vibe/runtime.ts +852 -72
  282. package/src/web/search/index.ts +3 -6
  283. package/src/web/search/provider.ts +31 -16
  284. package/src/web/search/providers/xai.ts +7 -1
  285. package/src/web/search/types.ts +3 -0
@@ -7,9 +7,24 @@ import type { ToolSession } from "../sdk";
7
7
  import { enforceInlineByteCap } from "../session/streaming-output";
8
8
  import { truncateForPrompt } from "./approval";
9
9
  import { resolveCmuxKind } from "./browser/cmux/rpc";
10
- import { acquireBrowser, type BrowserHandle, type BrowserKind, type BrowserKindTag } from "./browser/registry";
10
+ import {
11
+ acquireBrowser,
12
+ type BrowserHandle,
13
+ type BrowserKind,
14
+ type BrowserKindTag,
15
+ holdBrowser,
16
+ releaseBrowser,
17
+ } from "./browser/registry";
11
18
  import type { Observation, ScreenshotResult } from "./browser/tab-protocol";
12
- import { acquireTab, dropHeadlessTabs, getTab, releaseAllTabs, releaseTab, runInTab } from "./browser/tab-supervisor";
19
+ import {
20
+ type AcquireTabResult,
21
+ acquireTab,
22
+ dropHeadlessTabs,
23
+ getTab,
24
+ releaseAllTabs,
25
+ releaseTab,
26
+ runInTab,
27
+ } from "./browser/tab-supervisor";
13
28
  import type { OutputMeta } from "./output-meta";
14
29
  import { resolveToCwd } from "./path-utils";
15
30
  import { ToolAbortError, ToolError, throwIfAborted } from "./tool-errors";
@@ -232,52 +247,84 @@ export class BrowserTool implements AgentTool<typeof browserSchema, BrowserToolD
232
247
  );
233
248
  }
234
249
 
235
- const browser = await untilAborted(signal, () =>
236
- acquireBrowser(kind, {
237
- cwd: this.session.cwd,
238
- viewport: params.viewport
239
- ? {
240
- width: params.viewport.width,
241
- height: params.viewport.height,
242
- deviceScaleFactor: params.viewport.scale,
243
- }
244
- : undefined,
245
- appArgs: params.app?.args,
246
- signal,
247
- }),
248
- );
250
+ // The requested timeout must cover the *entire* open — browser
251
+ // acquisition (CDP discovery/connect), queued tab acquisition, worker
252
+ // creation, and navigation — not only `acquireTab`. Compose one deadline
253
+ // from the caller signal and `params.timeout` and thread it through both
254
+ // stages so a stalled acquisition rejects at the requested boundary.
255
+ const timeoutSignal = AbortSignal.timeout(timeoutMs);
256
+ const openSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
257
+ try {
258
+ const browser = await untilAborted(openSignal, () =>
259
+ acquireBrowser(kind, {
260
+ cwd: this.session.cwd,
261
+ viewport: params.viewport
262
+ ? {
263
+ width: params.viewport.width,
264
+ height: params.viewport.height,
265
+ deviceScaleFactor: params.viewport.scale,
266
+ }
267
+ : undefined,
268
+ appArgs: params.app?.args,
269
+ signal: openSignal,
270
+ }),
271
+ );
272
+
273
+ // Hold one open-acquisition lease across the whole tab acquisition.
274
+ // A freshly-created browser sits in the registry at refCount 0 until a
275
+ // tab takes a hold; without this lease an abort/timeout mid-acquisition
276
+ // (or a sibling open of a different tab name on the same browser that
277
+ // fails) could dispose it out from under this operation. The lease is
278
+ // released exactly once — the success and failure paths are mutually
279
+ // exclusive — transferring ownership to the published tab on success or
280
+ // rolling the fresh browser back on failure.
281
+ holdBrowser(browser);
282
+ let result: AcquireTabResult;
283
+ try {
284
+ result = await untilAborted(openSignal, () =>
285
+ acquireTab(name, browser, {
286
+ url: params.url,
287
+ waitUntil: params.wait_until,
288
+ viewport: params.viewport
289
+ ? {
290
+ width: params.viewport.width,
291
+ height: params.viewport.height,
292
+ deviceScaleFactor: params.viewport.scale,
293
+ }
294
+ : undefined,
295
+ target: params.app?.target,
296
+ timeoutMs,
297
+ dialogs: params.dialogs,
298
+ signal: openSignal,
299
+ ownerSessionId: this.session.getSessionId?.() ?? undefined,
300
+ }),
301
+ );
302
+ } catch (error) {
303
+ await releaseBrowser(browser, { kill: false });
304
+ throw error;
305
+ }
306
+ await releaseBrowser(browser, { kill: false });
249
307
 
250
- const result = await untilAborted(signal, () =>
251
- acquireTab(name, browser, {
252
- url: params.url,
253
- waitUntil: params.wait_until,
254
- viewport: params.viewport
255
- ? {
256
- width: params.viewport.width,
257
- height: params.viewport.height,
258
- deviceScaleFactor: params.viewport.scale,
259
- }
260
- : undefined,
261
- target: params.app?.target,
262
- timeoutMs,
263
- dialogs: params.dialogs,
264
- signal,
265
- ownerSessionId: this.session.getSessionId?.() ?? undefined,
266
- }),
267
- );
268
- const tab = result.tab;
269
- const url = tab.info.url;
270
- const title = tab.info.title ?? "";
271
- details.url = url;
272
- details.viewport = tab.info.viewport;
273
- const verb = result.created ? "Opened" : "Reused";
274
- const lines = [
275
- `${verb} tab ${JSON.stringify(name)} on ${describeBrowser(browser)}`,
276
- `URL: ${url}`,
277
- title ? `Title: ${title}` : null,
278
- ].filter((l): l is string => typeof l === "string");
279
- details.result = lines.join("\n");
280
- return toolResult(details).text(lines.join("\n")).done();
308
+ const tab = result.tab;
309
+ const url = tab.info.url;
310
+ const title = tab.info.title ?? "";
311
+ details.url = url;
312
+ details.viewport = tab.info.viewport;
313
+ const verb = result.created ? "Opened" : "Reused";
314
+ const lines = [
315
+ `${verb} tab ${JSON.stringify(name)} on ${describeBrowser(browser)}`,
316
+ `URL: ${url}`,
317
+ title ? `Title: ${title}` : null,
318
+ ].filter((l): l is string => typeof l === "string");
319
+ details.result = lines.join("\n");
320
+ return toolResult(details).text(lines.join("\n")).done();
321
+ } catch (error) {
322
+ // Caller cancellation stays a ToolAbortError; the requested timeout
323
+ // becomes a timeout ToolError; anything else passes through unchanged.
324
+ if (signal?.aborted) throw error instanceof ToolAbortError ? error : new ToolAbortError();
325
+ if (timeoutSignal.aborted) throw new ToolError(`Browser open timed out after ${timeoutMs}ms`);
326
+ throw error;
327
+ }
281
328
  }
282
329
 
283
330
  async #close(
@@ -27,6 +27,7 @@ import { settings } from "../config/settings";
27
27
  import type { CustomTool } from "../extensibility/custom-tools/types";
28
28
  import { ohMyPiXAIUserAgent, resolveXAIHttpCredentials } from "../lib/xai-http";
29
29
  import imageGenDescription from "../prompts/tools/image-gen.md" with { type: "text" };
30
+ import { AUTO_IMAGE_PROVIDER_ORDER, type ImageProvider, isImageProviderId } from "./image-providers";
30
31
  import { resolveReadPath } from "./path-utils";
31
32
 
32
33
  const DEFAULT_MODEL = "gemini-3-pro-image-preview";
@@ -44,7 +45,7 @@ const DEFAULT_ANTIGRAVITY_ENDPOINT_SANDBOX = "https://daily-cloudcode-pa.sandbox
44
45
  const IMAGE_SYSTEM_INSTRUCTION =
45
46
  "You are an AI image generator. Generate images based on user descriptions. Focus on creating high-quality, visually appealing images that match the user's request.";
46
47
 
47
- export type ImageProvider = "antigravity" | "gemini" | "openai" | "openai-codex" | "openrouter" | "xai";
48
+ export type { ImageProvider } from "./image-providers";
48
49
  export type ImageProviderPreference = ImageProvider | "auto";
49
50
 
50
51
  interface ImageApiKey {
@@ -57,17 +58,8 @@ interface ImageApiKey {
57
58
  const COMMON_IMAGE_ASPECT_RATIOS = ["1:1", "3:4", "4:3", "9:16", "16:9"] as const;
58
59
  const XAI_IMAGE_ASPECT_RATIOS = [...COMMON_IMAGE_ASPECT_RATIOS, "3:2", "2:3"] as const;
59
60
  const COMMON_IMAGE_ASPECT_RATIO_SET = new Set<string>(COMMON_IMAGE_ASPECT_RATIOS);
60
- const IMAGE_PROVIDER_CHOICES = [
61
- "auto",
62
- "antigravity",
63
- "gemini",
64
- "openai",
65
- "openai-codex",
66
- "openrouter",
67
- "xai",
68
- ] as const;
69
- const IMAGE_PROVIDER_PREFERENCES = new Set<string>(IMAGE_PROVIDER_CHOICES);
70
- const AUTO_IMAGE_PROVIDER_ORDER = ["openai", "openai-codex", "antigravity", "xai", "openrouter", "gemini"] as const;
61
+ const IMAGE_PROVIDER_REQUEST_CHOICES = ["auto", ...AUTO_IMAGE_PROVIDER_ORDER] as const;
62
+ const IMAGE_PROVIDER_PREFERENCES = new Set<string>(IMAGE_PROVIDER_REQUEST_CHOICES);
71
63
 
72
64
  const responseModalitySchema = type('"IMAGE" | "TEXT"');
73
65
 
@@ -81,8 +73,8 @@ const inputImageSchema = type({
81
73
  });
82
74
 
83
75
  const imageProviderSchema = type
84
- .enumerated(...IMAGE_PROVIDER_CHOICES)
85
- .describe("image provider for this request; overrides the providers.image setting (default: use the setting)");
76
+ .enumerated(...IMAGE_PROVIDER_REQUEST_CHOICES)
77
+ .describe("image provider for this request; overrides the providers.imageOrder setting (default: use the setting)");
86
78
 
87
79
  export const imageGenSchema = type({
88
80
  subject: type("string").describe("main subject"),
@@ -447,16 +439,16 @@ function extractOpenRouterImageUrls(message: OpenRouterMessage | undefined): str
447
439
  return urls;
448
440
  }
449
441
 
450
- /** Preferred provider set via settings (default: auto) */
451
- let preferredImageProvider: ImageProviderPreference = "auto";
442
+ /** Configured provider priority set via `providers.imageOrder` (default: none). */
443
+ let configuredImageProviderOrder: readonly ImageProvider[] = [];
452
444
 
453
445
  export function isImageProviderPreference(value: unknown): value is ImageProviderPreference {
454
446
  return typeof value === "string" && IMAGE_PROVIDER_PREFERENCES.has(value);
455
447
  }
456
448
 
457
- /** Set the preferred image provider from settings */
458
- export function setPreferredImageProvider(provider: ImageProviderPreference): void {
459
- preferredImageProvider = provider;
449
+ /** Set the configured image-provider priority from settings; invalid IDs are dropped. */
450
+ export function setImageProviderOrder(providers: readonly string[]): void {
451
+ configuredImageProviderOrder = providers.filter(isImageProviderId);
460
452
  }
461
453
  function assertImageAspectRatioSupported(provider: ImageProvider, aspectRatio: ImageGenParams["aspect_ratio"]): void {
462
454
  if (!aspectRatio || provider === "xai" || COMMON_IMAGE_ASPECT_RATIO_SET.has(aspectRatio)) {
@@ -624,19 +616,19 @@ function activeImageProvider(model: Model | undefined): Exclude<ImageProviderPre
624
616
  }
625
617
  }
626
618
 
627
- function imageProviderOrder(
628
- activeModel: Model | undefined,
629
- preference: ImageProviderPreference = preferredImageProvider,
630
- ): Array<Exclude<ImageProviderPreference, "auto">> {
631
- const providers: Array<Exclude<ImageProviderPreference, "auto">> = [];
632
- const added = new Set<Exclude<ImageProviderPreference, "auto">>();
633
- const add = (provider: Exclude<ImageProviderPreference, "auto"> | null): void => {
619
+ function imageProviderOrder(activeModel: Model | undefined, requested?: ImageProviderPreference): ImageProvider[] {
620
+ const providers: ImageProvider[] = [];
621
+ const added = new Set<ImageProvider>();
622
+ const add = (provider: ImageProvider | null): void => {
634
623
  if (!provider || added.has(provider)) return;
635
624
  added.add(provider);
636
625
  providers.push(provider);
637
626
  };
638
627
 
639
- if (preference !== "auto") add(preference);
628
+ // Per-request provider wins, then the configured priority list, then the
629
+ // active session's provider, then the built-in auto order.
630
+ if (requested !== undefined && requested !== "auto") add(requested);
631
+ for (const provider of configuredImageProviderOrder) add(provider);
640
632
  add(activeImageProvider(activeModel));
641
633
  for (const provider of AUTO_IMAGE_PROVIDER_ORDER) add(provider);
642
634
  return providers;
@@ -1109,7 +1101,7 @@ export const imageGenTool: CustomTool<typeof imageGenSchema, ImageGenToolDetails
1109
1101
  async execute(_toolCallId, params, _onUpdate, ctx, signal) {
1110
1102
  return untilAborted(signal, async () => {
1111
1103
  const sessionId = ctx.sessionManager.getSessionId();
1112
- const providerOrder = imageProviderOrder(ctx.model, params.provider ?? preferredImageProvider);
1104
+ const providerOrder = imageProviderOrder(ctx.model, params.provider);
1113
1105
  const cwd = ctx.sessionManager.getCwd();
1114
1106
  const requestSignal = ptree.combineSignals(signal, IMAGE_TIMEOUT);
1115
1107
  const fetchImpl = ctx.fetch ?? fetch;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Image Generation Providers
3
+ *
4
+ * Leaf module (no runtime deps) shared by the image_gen tool, the settings
5
+ * schema, and settings migrations — mirrors `web/search/types.ts` so the
6
+ * provider list, auto order, and settings choices never drift apart.
7
+ */
8
+
9
+ /** Image generation backends, in settings/tool vocabulary. */
10
+ export type ImageProvider = "antigravity" | "gemini" | "openai" | "openai-codex" | "openrouter" | "xai";
11
+
12
+ /** Auto-resolution fallback order when no configured entry or session provider matches. */
13
+ export const AUTO_IMAGE_PROVIDER_ORDER: readonly ImageProvider[] = [
14
+ "openai",
15
+ "openai-codex",
16
+ "antigravity",
17
+ "xai",
18
+ "openrouter",
19
+ "gemini",
20
+ ];
21
+
22
+ /** Settings choices for `providers.imageOrder` (labels shared with the retired single-preference enum). */
23
+ export const IMAGE_PROVIDER_CHOICES = [
24
+ {
25
+ value: "openai",
26
+ label: "OpenAI",
27
+ description: "OPENAI_API_KEY (gpt-image-2) or active GPT model; falls back to a connected Codex subscription",
28
+ },
29
+ {
30
+ value: "openai-codex",
31
+ label: "OpenAI Codex (ChatGPT)",
32
+ description: "Uses a connected Codex / ChatGPT subscription — no OPENAI_API_KEY needed",
33
+ },
34
+ {
35
+ value: "antigravity",
36
+ label: "Antigravity",
37
+ description: "Requires google-antigravity OAuth",
38
+ },
39
+ {
40
+ value: "xai",
41
+ label: "xAI Grok Imagine",
42
+ description: "Requires xAI Grok OAuth or XAI_API_KEY",
43
+ },
44
+ { value: "gemini", label: "Gemini", description: "Requires GEMINI_API_KEY" },
45
+ { value: "openrouter", label: "OpenRouter", description: "Requires OPENROUTER_API_KEY" },
46
+ ] as const satisfies ReadonlyArray<{ value: ImageProvider; label: string; description: string }>;
47
+
48
+ export function isImageProviderId(value: unknown): value is ImageProvider {
49
+ return typeof value === "string" && AUTO_IMAGE_PROVIDER_ORDER.includes(value as ImageProvider);
50
+ }
@@ -26,6 +26,7 @@ import type { ArtifactManager } from "../session/artifacts";
26
26
  import type { ClientBridge } from "../session/client-bridge";
27
27
  import type { CustomMessage } from "../session/messages";
28
28
  import type { UsageStatistics } from "../session/session-entries";
29
+ import type { SessionManager } from "../session/session-manager";
29
30
  import type { ToolChoiceQueue } from "../session/tool-choice-queue";
30
31
  import { TaskTool } from "../task";
31
32
  import type { AgentOutputManager } from "../task/output-manager";
@@ -144,6 +145,8 @@ export interface DeferredDiagnosticsEntry {
144
145
  export interface ToolSession {
145
146
  /** Current working directory */
146
147
  cwd: string;
148
+ /** Additional workspace directories beyond cwd (multi-root), forwarded to subagents. */
149
+ additionalDirectories?: string[];
147
150
  /** Whether UI is available */
148
151
  hasUI: boolean;
149
152
  /**
@@ -215,6 +218,8 @@ export interface ToolSession {
215
218
  getEvalSessionId?: () => string | null;
216
219
  /** Get session file */
217
220
  getSessionFile: () => string | null;
221
+ /** Parent session journal used by tools that persist runtime lifecycle state. */
222
+ sessionManager?: Pick<SessionManager, "appendCustomEntry" | "ensureOnDisk" | "flush" | "getBranch" | "getEntries">;
218
223
  /** Get eval kernel owner ID for session-scoped retained-kernel cleanup. */
219
224
  getEvalKernelOwnerId?: () => string | null;
220
225
  /** Reject new eval work once session disposal has started. */
@@ -513,6 +518,9 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
513
518
  if (!requestedTools.includes(name)) requestedTools.push(name);
514
519
  }
515
520
  }
521
+ if (session.settings.get("memory.backend") === "mnemopi" && !requestedTools.includes("memory_edit")) {
522
+ requestedTools.push("memory_edit");
523
+ }
516
524
  // Auto-learn tools are gated by `autolearn.enabled` but, like the memory
517
525
  // tools above, must also be force-included into an explicit requestedTools
518
526
  // list so a restricted top-level session whose controller/guidance is
@@ -556,6 +564,7 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
556
564
  if (name === "retain" || name === "recall" || name === "reflect") {
557
565
  return ["hindsight", "mnemopi"].includes(session.settings.get("memory.backend") ?? "");
558
566
  }
567
+ if (name === "memory_edit") return session.settings.get("memory.backend") === "mnemopi";
559
568
  if (name === "manage_skill") return session.settings.get("autolearn.enabled") && (session.taskDepth ?? 0) === 0;
560
569
  if (name === "learn") {
561
570
  return (
@@ -2,10 +2,11 @@ import * as fs from "node:fs";
2
2
  import * as os from "node:os";
3
3
  import * as path from "node:path";
4
4
  import * as url from "node:url";
5
- import { isEnoent, isEnotdir, stripWindowsExtendedLengthPathPrefix } from "@oh-my-pi/pi-utils";
5
+ import { glob } from "@oh-my-pi/pi-natives";
6
+ import { isEnoent, isEnotdir, stripWindowsExtendedLengthPathPrefix, untilAborted } from "@oh-my-pi/pi-utils";
6
7
  import type { Skill } from "../extensibility/skills";
7
8
  import { InternalUrlRouter, type LocalProtocolOptions } from "../internal-urls";
8
- import { ToolError } from "./tool-errors";
9
+ import { ToolAbortError, ToolError } from "./tool-errors";
9
10
 
10
11
  const UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
11
12
  // A single line-range chunk: `N`, `N-M`, `N+K`, or open-ended `N-`. `..` is
@@ -1132,6 +1133,52 @@ export function resolveReadPath(filePath: string, cwd: string): string {
1132
1133
  return resolved;
1133
1134
  }
1134
1135
 
1136
+ const WORKSPACE_SUFFIX_TIMEOUT_MS = 5000;
1137
+
1138
+ function escapeGlobMetachars(value: string): string {
1139
+ return value.replace(/[*?[{]/g, "[$&]");
1140
+ }
1141
+
1142
+ /**
1143
+ * Find a unique workspace entry whose trailing path matches a missing authored path.
1144
+ * Returns `null` for no match, ambiguity, timeout, or scan failure.
1145
+ */
1146
+ export async function findUniqueWorkspaceSuffix(
1147
+ rawPath: string,
1148
+ cwd: string,
1149
+ signal?: AbortSignal,
1150
+ ): Promise<{ absolutePath: string; displayPath: string } | null> {
1151
+ const normalized = rawPath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+$/, "");
1152
+ if (!normalized) return null;
1153
+
1154
+ const timeoutSignal = AbortSignal.timeout(WORKSPACE_SUFFIX_TIMEOUT_MS);
1155
+ const combinedSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
1156
+
1157
+ let matches: string[];
1158
+ try {
1159
+ const result = await untilAborted(combinedSignal, () =>
1160
+ glob({
1161
+ pattern: `**/${escapeGlobMetachars(normalized)}`,
1162
+ path: cwd,
1163
+ hidden: true,
1164
+ }),
1165
+ );
1166
+ matches = result.matches.map(match => match.path);
1167
+ } catch (error) {
1168
+ if (error instanceof Error && error.name === "AbortError") {
1169
+ if (!signal?.aborted) return null;
1170
+ throw new ToolAbortError();
1171
+ }
1172
+ return null;
1173
+ }
1174
+
1175
+ if (matches.length !== 1) return null;
1176
+ return {
1177
+ absolutePath: path.resolve(cwd, matches[0]),
1178
+ displayPath: matches[0],
1179
+ };
1180
+ }
1181
+
1135
1182
  // =============================================================================
1136
1183
  // Tool-scope resolution (search/ast tools)
1137
1184
  // =============================================================================