@desplega.ai/agent-swarm 1.131.0 → 1.133.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (265) hide show
  1. package/README.md +2 -2
  2. package/dist/{actions-w53mbns9.js → actions-cskpayd7.js} +11 -9
  3. package/dist/{anthropic-messages-rjmd2e7n.js → anthropic-messages-tpxtbx7b.js} +28 -12
  4. package/dist/{app-m9gwqsqv.js → app-ang77nbb.js} +8 -6
  5. package/dist/{assistant-bp65m6f3.js → assistant-b4exebdw.js} +16 -13
  6. package/dist/{azure-openai-responses-c0yjmbfd.js → azure-openai-responses-m3921t4y.js} +8 -8
  7. package/dist/{boot-reembed-62p0at2k.js → boot-reembed-ps2aftpb.js} +9 -7
  8. package/dist/{boot-reembed-xmfp6wdh.js → boot-reembed-znyk3nxb.js} +10 -8
  9. package/dist/{boot-scrub-logs-rr8q2dms.js → boot-scrub-logs-2ax3xgz1.js} +7 -5
  10. package/dist/{claude-adapter-2y47mrkd.js → claude-adapter-3ct8kgxw.js} +13 -9
  11. package/dist/{claude-managed-adapter-gv19tdew.js → claude-managed-adapter-akx4ntf2.js} +2 -2
  12. package/dist/{cli-t1xze5qh.js → cli-13w2hwkh.js} +1 -1
  13. package/dist/{cli-efxfnq6q.js → cli-1532szb1.js} +8 -6
  14. package/dist/{cli-d2tfkcga.js → cli-23d3rseb.js} +33 -17
  15. package/dist/{cli-nm5gtp1x.js → cli-2a75z9c5.js} +196 -7
  16. package/dist/{cli-fywms69x.js → cli-2t30rj0d.js} +1 -1
  17. package/dist/{cli-cvgjwnh1.js → cli-3c3tdgab.js} +10 -10
  18. package/dist/{cli-pm6xbh84.js → cli-3zzxa9jz.js} +46 -16
  19. package/dist/{cli-cdeg08wx.js → cli-4k6233gc.js} +1 -1
  20. package/dist/{cli-t55yw3a1.js → cli-5p2tz05r.js} +2 -2
  21. package/dist/{cli-0rcyq6d9.js → cli-5z2yhzvd.js} +125 -47
  22. package/dist/{cli-3kf51j15.js → cli-6zfq31nj.js} +9 -3
  23. package/dist/{cli-er3a4bb0.js → cli-8m9de10a.js} +6 -1
  24. package/dist/{cli-gm9cbhbq.js → cli-8t14vg9w.js} +27 -8
  25. package/dist/{cli-72q8m8gk.js → cli-9nfc2es8.js} +6 -6
  26. package/dist/{cli-5ze9rv97.js → cli-9ta3pwms.js} +11 -4
  27. package/dist/{cli-78xwvkhj.js → cli-avr0ns7z.js} +2 -2
  28. package/dist/{cli-n6h8qt8n.js → cli-c897wyhw.js} +12 -7
  29. package/dist/{cli-2q72qz2s.js → cli-dgk20ft7.js} +15 -6
  30. package/dist/cli-et1bbwqf.js +13 -0
  31. package/dist/{cli-bqw0mt1n.js → cli-f5w27pj3.js} +3 -3
  32. package/dist/{cli-2wadx7p2.js → cli-f8dre4bz.js} +2 -2
  33. package/dist/{cli-e7ckcg4v.js → cli-ftwpv451.js} +170 -387
  34. package/dist/{cli-g6b8x1h8.js → cli-g854s2dw.js} +1 -1
  35. package/dist/{cli-5kx36evn.js → cli-ggmdxeqa.js} +13 -8
  36. package/dist/cli-hkwfe3qj.js +530 -0
  37. package/dist/{cli-5dsbcjjw.js → cli-jqwnqx9m.js} +1 -1
  38. package/dist/{cli-9dr5qzc0.js → cli-ny122vh4.js} +21 -10
  39. package/dist/{cli-5v4rw6yd.js → cli-pvzqhbs9.js} +2 -2
  40. package/dist/cli-pzq5rjym.js +15 -0
  41. package/dist/{cli-xpxde7rm.js → cli-qes8ef67.js} +48 -30
  42. package/dist/{cli-sajgpkqg.js → cli-qs9hhsvb.js} +1723 -325
  43. package/dist/cli-qys3rhhz.js +27 -0
  44. package/dist/{cli-rzbpmxdr.js → cli-r6tpm37h.js} +2 -2
  45. package/dist/{cli-qv61hp37.js → cli-rch80c76.js} +2 -2
  46. package/dist/{cli-0258203w.js → cli-rpp0x4zy.js} +137 -17
  47. package/dist/{cli-5n6e5yfn.js → cli-s1k0wxhe.js} +12 -7
  48. package/dist/{cli-psh3c4va.js → cli-sfd9v7sj.js} +1 -1
  49. package/dist/{cli-70xqj0z3.js → cli-sh3ytsyj.js} +49 -39
  50. package/dist/{cli-zsspvvm2.js → cli-w1mp9jy7.js} +1 -1
  51. package/dist/{cli-d3x3x1c9.js → cli-xdb4mpnp.js} +2 -2
  52. package/dist/{cli-57khth04.js → cli-xmyqv00z.js} +120 -4
  53. package/dist/{cli-yw8vghx2.js → cli-xv8t8p3c.js} +65 -32
  54. package/dist/{cli-f1am9szv.js → cli-xxv5pn4f.js} +1 -1
  55. package/dist/{cli-t76vw5db.js → cli-y08jvxzx.js} +601 -140
  56. package/dist/{cli-a4jzdpa3.js → cli-zfbf3srj.js} +650 -245
  57. package/dist/{cli-a40rnf71.js → cli-zmq89k34.js} +6 -6
  58. package/dist/{cli-qmdnhpej.js → cli-zr9q011v.js} +23 -13
  59. package/dist/cli.js +22 -22
  60. package/dist/{codex-adapter-d24q75wk.js → codex-adapter-bef93pq4.js} +9 -7
  61. package/dist/{codex-hook-hq54t9ft.js → codex-hook-e8dba65p.js} +3 -2
  62. package/dist/{codex-session-runner-g50vcsex.js → codex-session-runner-5pkxys7x.js} +9 -7
  63. package/dist/{commands-c3nrc5hg.js → commands-e23s1bff.js} +9 -7
  64. package/dist/{db-nt7fg3tw.js → db-m3nabatn.js} +17 -5
  65. package/dist/{google-generative-ai-hysrd8qx.js → google-generative-ai-8maw55xh.js} +11 -8
  66. package/dist/{google-vertex-p5arrga0.js → google-vertex-8vjqmx8g.js} +11 -8
  67. package/dist/{handlers-fkr6swat.js → handlers-akazedvr.js} +16 -13
  68. package/dist/{hook-77wpvnjc.js → hook-ze4cpf0v.js} +55 -41
  69. package/dist/{hook-g019yehy.js → hook-zfqyyenw.js} +10 -7
  70. package/dist/{http-jewj0syk.js → http-sja0n6kw.js} +730 -150
  71. package/dist/index-0gr8vd6k.js +39 -0
  72. package/dist/{index-7n2xpbd4.js → index-354vkhdv.js} +154 -74
  73. package/dist/{index-070v4eyb.js → index-5qthek5t.js} +2 -3
  74. package/dist/{index-hfvggd0z.js → index-95t3cqq5.js} +16 -14
  75. package/dist/{index-9znhar3x.js → index-sea66619.js} +3 -4
  76. package/dist/{index-6gm5he8h.js → index-vpn36mma.js} +15 -13
  77. package/dist/{keepalive-j0fjvqfd.js → keepalive-3sp3meqj.js} +16 -10
  78. package/dist/{lead-1n8x30xe.js → lead-mefeewkf.js} +39 -36
  79. package/dist/{maintenance-gvev11sa.js → maintenance-8tbrbjfg.js} +10 -8
  80. package/dist/mistral-conversations-00zrp7g4.js +717 -0
  81. package/dist/{oauth-refresh-sweep-s9ecsvh0.js → oauth-refresh-sweep-srjwjz6m.js} +8 -6
  82. package/dist/{onboard-pvm5b38w.js → onboard-mt2tgyjg.js} +7 -7
  83. package/dist/{openai-codex-responses-5g27pd6f.js → openai-codex-responses-map32h2s.js} +18 -18
  84. package/dist/{openai-completions-3vkmfz2a.js → openai-completions-w871b1cq.js} +12 -11
  85. package/dist/{openai-responses-dg4t7j7a.js → openai-responses-b0j7xnw8.js} +15 -12
  86. package/dist/{opencode-adapter-1gm38aba.js → opencode-adapter-b39jefcj.js} +5 -3
  87. package/dist/{openrouter-images-g3gm8931.js → openrouter-images-srfkywy0.js} +4 -4
  88. package/dist/{otel-impl-p6jvgd9c.js → otel-impl-z6pxt3j9.js} +224 -9
  89. package/dist/{pi-mono-adapter-t091s27g.js → pi-mono-adapter-btvqmj58.js} +1138 -408
  90. package/dist/{pricing-refresh-k49rcyed.js → pricing-refresh-kyq8cezh.js} +9 -7
  91. package/dist/{rbac-roles-cr80g96t.js → rbac-roles-3ryfrfna.js} +8 -6
  92. package/dist/{rbac-roles-30pspvy7.js → rbac-roles-6qfvbee1.js} +9 -7
  93. package/dist/{render-v2-xm457ng1.js → render-v2-p61ppjey.js} +8 -6
  94. package/dist/{seed-pricing-yhpv35fr.js → seed-pricing-j56bbj4y.js} +8 -6
  95. package/dist/{setup-nytnmwwr.js → setup-tpr6c1kn.js} +2 -2
  96. package/dist/{worker-msytr2cw.js → worker-9w1v0mbb.js} +39 -36
  97. package/openapi.json +542 -16
  98. package/package.json +6 -6
  99. package/src/be/budget-refusal-notify.ts +14 -7
  100. package/src/be/db-queries/task-artifacts.ts +182 -0
  101. package/src/be/db.ts +783 -169
  102. package/src/be/events.ts +5 -0
  103. package/src/be/migrations/130_scratch_script_retention_grace.sql +6 -0
  104. package/src/be/migrations/131_session_logs_created_at_index.sql +2 -0
  105. package/src/be/migrations/132_multi_runtime_instances.sql +44 -0
  106. package/src/be/migrations/133_workflow_run_trigger_type.sql +12 -0
  107. package/src/be/migrations/135_backfill_task_pull_request_attachments.sql +228 -0
  108. package/src/be/migrations/136_task_requester_provenance.sql +37 -0
  109. package/src/be/migrations/137_memory_retrieval_composite_index.sql +1 -0
  110. package/src/be/multi-runtime.ts +430 -0
  111. package/src/be/scripts/db.ts +43 -1
  112. package/src/be/scripts/extract-schema.ts +3 -1
  113. package/src/be/scripts/retention.ts +164 -0
  114. package/src/be/scripts/run-saved.ts +16 -1
  115. package/src/be/scripts/typecheck.ts +3 -2
  116. package/src/be/seed-skills/index.ts +7 -0
  117. package/src/be/steering.ts +1 -1
  118. package/src/be/swarm-config-guard.ts +18 -0
  119. package/src/commands/credential-wait.ts +54 -0
  120. package/src/commands/onboard/steps/core-credentials.tsx +1 -1
  121. package/src/commands/onboard/steps/custom-templates.tsx +1 -1
  122. package/src/commands/onboard/steps/harness-credentials.tsx +1 -1
  123. package/src/commands/onboard/steps/health-check.tsx +1 -1
  124. package/src/commands/onboard/steps/start.tsx +1 -1
  125. package/src/commands/profile-sync-audit.ts +56 -0
  126. package/src/commands/profile-sync.ts +287 -15
  127. package/src/commands/provider-credentials.ts +46 -19
  128. package/src/commands/runner.ts +196 -33
  129. package/src/commands/templates.ts +22 -0
  130. package/src/github/handlers.ts +55 -9
  131. package/src/heartbeat/heartbeat.ts +71 -12
  132. package/src/hooks/hook.ts +49 -33
  133. package/src/http/active-sessions.ts +11 -1
  134. package/src/http/agents.ts +261 -30
  135. package/src/http/all-routes.ts +1 -0
  136. package/src/http/config.ts +13 -2
  137. package/src/http/core.ts +102 -4
  138. package/src/http/db-query-bounded.ts +313 -0
  139. package/src/http/db-query-shared.ts +249 -0
  140. package/src/http/db-query.ts +54 -42
  141. package/src/http/events.ts +2 -0
  142. package/src/http/index.ts +27 -2
  143. package/src/http/mcp-bridge.ts +37 -3
  144. package/src/http/mcp.ts +12 -0
  145. package/src/http/memory.ts +13 -2
  146. package/src/http/openapi.ts +1 -0
  147. package/src/http/poll.ts +70 -27
  148. package/src/http/route-def.ts +43 -9
  149. package/src/http/schedules.ts +2 -2
  150. package/src/http/scripts.ts +31 -4
  151. package/src/http/session-data.ts +44 -0
  152. package/src/http/tasks.ts +2 -2
  153. package/src/http/users.ts +74 -52
  154. package/src/http/webhooks.ts +5 -2
  155. package/src/http/workflows.ts +1 -4
  156. package/src/http/x.ts +14 -0
  157. package/src/integrations/kapso/inbound.ts +1 -1
  158. package/src/jira/outbound.ts +39 -8
  159. package/src/jira/webhook-lifecycle.ts +13 -2
  160. package/src/linear/outbound.ts +43 -10
  161. package/src/oauth/keepalive.ts +13 -2
  162. package/src/prompts/base-prompt.ts +1 -1
  163. package/src/prompts/session-templates.ts +45 -7
  164. package/src/providers/claude-adapter.ts +6 -0
  165. package/src/providers/claude-managed-models.ts +34 -10
  166. package/src/providers/codex-adapter.ts +6 -0
  167. package/src/providers/opencode-adapter.ts +6 -0
  168. package/src/providers/pi-mono-adapter.ts +8 -0
  169. package/src/providers/pricing-sources.md +24 -0
  170. package/src/queue-stall-alarm.ts +216 -0
  171. package/src/scheduler/scheduler.ts +7 -0
  172. package/src/script-workflows/executor.ts +2 -1
  173. package/src/scripts-runtime/executors/native.ts +2 -1
  174. package/src/scripts-runtime/executors/types.ts +5 -0
  175. package/src/scripts-runtime/loader.ts +5 -0
  176. package/src/scripts-runtime/swarm-config.ts +8 -0
  177. package/src/scripts-runtime/swarm-sdk.ts +7 -0
  178. package/src/scripts-runtime/types/stdlib.d.ts +1 -0
  179. package/src/scripts-runtime/types/swarm-sdk.d.ts +1 -0
  180. package/src/tests/budget-claim-gate.test.ts +20 -0
  181. package/src/tests/claude-adapter.test.ts +32 -0
  182. package/src/tests/claude-managed-adapter.test.ts +38 -7
  183. package/src/tests/codex-adapter.test.ts +30 -0
  184. package/src/tests/create-task-validation.test.ts +128 -0
  185. package/src/tests/credential-wait.test.ts +67 -1
  186. package/src/tests/db-query.test.ts +711 -2
  187. package/src/tests/http-api-integration.test.ts +81 -0
  188. package/src/tests/http-users.test.ts +166 -1
  189. package/src/tests/identity-field-budget.test.ts +176 -0
  190. package/src/tests/mcp-tools-user.test.ts +34 -0
  191. package/src/tests/mcp-user-route.test.ts +17 -0
  192. package/src/tests/metrics-http.test.ts +44 -0
  193. package/src/tests/multi-runtime-registration.test.ts +2703 -0
  194. package/src/tests/openapi-response-contract.test.ts +24 -0
  195. package/src/tests/openapi-route-headers.test.ts +83 -0
  196. package/src/tests/opencode-adapter.test.ts +40 -0
  197. package/src/tests/outbound-listener-rejection.test.ts +138 -0
  198. package/src/tests/pi-mono-adapter.test.ts +98 -0
  199. package/src/tests/profile-sync-warning.test.ts +277 -0
  200. package/src/tests/profile-sync.test.ts +232 -6
  201. package/src/tests/prompt-template-session.test.ts +32 -3
  202. package/src/tests/queue-stall-alarm.test.ts +226 -0
  203. package/src/tests/requester-comms.test.ts +32 -0
  204. package/src/tests/runner-requester-profile.test.ts +38 -0
  205. package/src/tests/sandboxed-process.test.ts +45 -0
  206. package/src/tests/schedule-target-type.test.ts +38 -0
  207. package/src/tests/script-connections.test.ts +16 -3
  208. package/src/tests/scripts-external-api.test.ts +8 -3
  209. package/src/tests/scripts-http.test.ts +12 -0
  210. package/src/tests/scripts-retention.test.ts +384 -0
  211. package/src/tests/scripts-runtime-identity.test.ts +375 -0
  212. package/src/tests/scripts-runtime.test.ts +4 -3
  213. package/src/tests/sdk-allowlist.test.ts +47 -0
  214. package/src/tests/send-task-requested-by.test.ts +9 -0
  215. package/src/tests/session-costs.test.ts +506 -1
  216. package/src/tests/shutdown-error-scrubbing.test.ts +92 -0
  217. package/src/tests/steering-core.test.ts +28 -0
  218. package/src/tests/task-artifacts.test.ts +386 -0
  219. package/src/tests/task-attachments-schema.test.ts +17 -1
  220. package/src/tests/task-completion-idempotency.test.ts +57 -0
  221. package/src/tests/task-pull-request-backfill-migration.test.ts +321 -0
  222. package/src/tests/update-profile-api.test.ts +67 -39
  223. package/src/tests/update-profile-auth.test.ts +48 -4
  224. package/src/tests/user-attribution-migration.test.ts +54 -0
  225. package/src/tests/user-identity.test.ts +72 -0
  226. package/src/tests/workflow-async-v2.test.ts +39 -1
  227. package/src/tests/workflow-http-v2.test.ts +3 -2
  228. package/src/tests/workflow-mcp-trigger-schema.test.ts +52 -3
  229. package/src/tests/workflow-retry-v2.test.ts +45 -1
  230. package/src/tests/workflow-swarm-script.test.ts +14 -5
  231. package/src/tools/db-query.ts +18 -8
  232. package/src/tools/get-task-details.ts +24 -3
  233. package/src/tools/manage-user.ts +36 -3
  234. package/src/tools/poll-task.ts +93 -14
  235. package/src/tools/script-common.ts +5 -0
  236. package/src/tools/send-task.ts +1 -4
  237. package/src/tools/store-progress.ts +13 -2
  238. package/src/tools/swarm-config/delete-config.ts +9 -0
  239. package/src/tools/swarm-config/set-config.ts +3 -2
  240. package/src/tools/task-action.ts +30 -0
  241. package/src/tools/task-tool-ctx.ts +8 -1
  242. package/src/tools/update-profile.ts +16 -21
  243. package/src/tools/utils.ts +5 -0
  244. package/src/tools/workflows/trigger-workflow.ts +7 -1
  245. package/src/types.ts +165 -1
  246. package/src/utils/github-pull-request.ts +76 -0
  247. package/src/utils/identity-field-budget.ts +73 -0
  248. package/src/utils/multi-runtime.ts +20 -0
  249. package/src/utils/requester-comms.ts +24 -0
  250. package/src/utils/sandboxed-process.ts +40 -14
  251. package/src/workflows/engine.ts +22 -2
  252. package/src/workflows/executors/swarm-script.ts +24 -1
  253. package/src/workflows/resume.ts +19 -11
  254. package/src/workflows/retry-poller.ts +1 -0
  255. package/src/workflows/triggers.ts +4 -0
  256. package/templates/schedules/daily-compounding-reflection/content.md +12 -3
  257. package/templates/skills/db-query-guidance/config.json +14 -0
  258. package/templates/skills/db-query-guidance/content.md +57 -0
  259. package/dist/cli-3te4jdkm.js +0 -225
  260. package/dist/cli-bmze986d.js +0 -7
  261. package/dist/cli-kh3ba2zf.js +0 -607
  262. package/dist/index-hp1kdbjj.js +0 -36
  263. package/dist/mistral-conversations-81fwav0j.js +0 -30578
  264. package/dist/otel-nqa12wf3.js +0 -3149
  265. package/dist/{pi-messages-tqj8q6b9.js → pi-messages-sssy32x4.js} +3 -3
@@ -29,7 +29,7 @@ import {
29
29
  streamSimple,
30
30
  summarizeSession,
31
31
  validateToolArguments
32
- } from "./cli-0rcyq6d9.js";
32
+ } from "./cli-5z2yhzvd.js";
33
33
  import {
34
34
  checkToolLoop,
35
35
  clearToolHistory
@@ -42,11 +42,14 @@ import {
42
42
  buildRatingsFromLlm,
43
43
  fetchRetrievalsForTask,
44
44
  postRatings
45
- } from "./cli-2wadx7p2.js";
45
+ } from "./cli-f8dre4bz.js";
46
46
  import"./cli-e7d20cty.js";
47
47
  import {
48
48
  applyReasoningEffort
49
49
  } from "./cli-8whqavk2.js";
50
+ import {
51
+ swarmRuntimeInstanceId
52
+ } from "./cli-pzq5rjym.js";
50
53
  import {
51
54
  DEFAULT_OPENROUTER_BASE_URL,
52
55
  getOpenRouterBaseUrl
@@ -54,12 +57,13 @@ import {
54
57
  import {
55
58
  McpHttpClient
56
59
  } from "./cli-epan13p1.js";
57
- import"./cli-zsspvvm2.js";
60
+ import"./cli-qys3rhhz.js";
61
+ import"./cli-w1mp9jy7.js";
58
62
  import {
59
63
  init_secret_scrubber,
60
64
  scrubSecrets
61
65
  } from "./cli-4j2mrzag.js";
62
- import"./cli-5dsbcjjw.js";
66
+ import"./cli-jqwnqx9m.js";
63
67
  import {
64
68
  require_signal_exit
65
69
  } from "./cli-ap1czx8f.js";
@@ -47994,11 +47998,11 @@ One of the following config files must be in the directory tree: "${p.ROOT_CONFI
47994
47998
  const r3 = _path().resolve(t3, e3).split(_path().sep);
47995
47999
  return new RegExp(["^", ...r3.map((e4, t4) => {
47996
48000
  const c = t4 === r3.length - 1;
47997
- return e4 === "**" ? c ? p : l3 : e4 === "*" ? c ? a : o : e4.startsWith("*.") ? i + escapeRegExp(e4.slice(1)) + (c ? s : n) : escapeRegExp(e4) + (c ? s : n);
48001
+ return e4 === "**" ? c ? p : l3 : e4 === "*" ? c ? a : o : e4.startsWith("*.") ? i + escapeRegExp2(e4.slice(1)) + (c ? s : n) : escapeRegExp2(e4) + (c ? s : n);
47998
48002
  })].join(""));
47999
48003
  };
48000
48004
  const n = `\\${_path().sep}`, s = `(?:${n}|$)`, i = `[^${n}]+`, o = `(?:${i}${n})`, a = `(?:${i}${s})`, l3 = `${o}*?`, p = `${o}*?${a}?`;
48001
- function escapeRegExp(e3) {
48005
+ function escapeRegExp2(e3) {
48002
48006
  return e3.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&");
48003
48007
  }
48004
48008
  }, "./node_modules/.pnpm/@babel+core@7.29.0/node_modules/@babel/core/lib/config/plugin.js"(e2, t2, r2) {
@@ -93190,11 +93194,11 @@ var require_snapshot_recorder = __commonJS((exports, module) => {
93190
93194
  var { setTimeout: setTimeout2, clearTimeout: clearTimeout2 } = __require("node:timers");
93191
93195
  var { InvalidArgumentError, UndiciError } = require_errors2();
93192
93196
  var { hashId, isUrlExcludedFactory, normalizeHeaders, createHeaderFilters } = require_snapshot_utils();
93193
- function normalizeUrlForMatching(url, matchQuery, normalizeQuery) {
93197
+ function normalizeUrlForMatching(url, matchQuery, normalizeQuery2) {
93194
93198
  if (matchQuery === false)
93195
93199
  return `${url.origin}${url.pathname}`;
93196
- if (normalizeQuery) {
93197
- const normalized = String(normalizeQuery(url.searchParams) ?? "");
93200
+ if (normalizeQuery2) {
93201
+ const normalized = String(normalizeQuery2(url.searchParams) ?? "");
93198
93202
  return normalized ? `${url.origin}${url.pathname}?${normalized}` : `${url.origin}${url.pathname}`;
93199
93203
  }
93200
93204
  return url.toString();
@@ -104574,6 +104578,14 @@ function parseArgs(args) {
104574
104578
  } else if (arg === "--theme" && i + 1 < args.length) {
104575
104579
  result.themes = result.themes ?? [];
104576
104580
  result.themes.push(args[++i]);
104581
+ } else if (arg === "--use-theme") {
104582
+ const themeName = args[i + 1];
104583
+ if (themeName === undefined || themeName.startsWith("-")) {
104584
+ result.diagnostics.push({ type: "error", message: "--use-theme requires a theme name" });
104585
+ } else {
104586
+ result.useTheme = themeName;
104587
+ i++;
104588
+ }
104577
104589
  } else if (arg === "--no-skills" || arg === "-ns") {
104578
104590
  result.noSkills = true;
104579
104591
  } else if (arg === "--no-prompt-templates" || arg === "-np") {
@@ -104691,6 +104703,7 @@ ${source_default.bold("Options:")}
104691
104703
  --prompt-template <path> Load a prompt template file or directory (can be used multiple times)
104692
104704
  --no-prompt-templates, -np Disable prompt template discovery and loading
104693
104705
  --theme <path> Load a theme file or directory (can be used multiple times)
104706
+ --use-theme <name[/name]> Set the initial interactive theme for this run
104694
104707
  --no-themes Disable theme discovery and loading
104695
104708
  --no-context-files, -nc Disable AGENTS.md and CLAUDE.md discovery and loading
104696
104709
  --export <file> Export session file to HTML and exit
@@ -109061,6 +109074,14 @@ var TUI_KEYBINDINGS = {
109061
109074
  defaultKeys: [],
109062
109075
  description: "Scroll viewport down half a page"
109063
109076
  },
109077
+ "tui.altScreen.lineUp": {
109078
+ defaultKeys: [],
109079
+ description: "Scroll viewport up one line"
109080
+ },
109081
+ "tui.altScreen.lineDown": {
109082
+ defaultKeys: [],
109083
+ description: "Scroll viewport down one line"
109084
+ },
109064
109085
  "tui.altScreen.previousPrompt": {
109065
109086
  defaultKeys: "ctrl+shift+up",
109066
109087
  description: "Jump to previous semantic prompt"
@@ -109069,6 +109090,22 @@ var TUI_KEYBINDINGS = {
109069
109090
  defaultKeys: "ctrl+shift+down",
109070
109091
  description: "Jump to next semantic prompt"
109071
109092
  },
109093
+ "tui.altScreen.search": {
109094
+ defaultKeys: "ctrl+shift+f",
109095
+ description: "Search the primary scroll view"
109096
+ },
109097
+ "tui.altScreen.searchNext": {
109098
+ defaultKeys: ["enter", "ctrl+g"],
109099
+ description: "Select the next search match"
109100
+ },
109101
+ "tui.altScreen.searchPrevious": {
109102
+ defaultKeys: ["shift+enter", "ctrl+shift+g"],
109103
+ description: "Select the previous search match"
109104
+ },
109105
+ "tui.altScreen.searchClose": {
109106
+ defaultKeys: "escape",
109107
+ description: "Close transcript search"
109108
+ },
109072
109109
  "tui.altScreen.top": { defaultKeys: "home", description: "Scroll viewport to top" },
109073
109110
  "tui.altScreen.bottom": { defaultKeys: "end", description: "Scroll viewport to bottom" }
109074
109111
  };
@@ -110207,6 +110244,9 @@ class TuiBase extends Container {
110207
110244
  hasOverlay() {
110208
110245
  return this.overlayStack.some((o) => this.isOverlayVisible(o));
110209
110246
  }
110247
+ isOverlayFocused() {
110248
+ return this.overlayStack.some((entry) => entry.component === this.focusedComponent && this.isOverlayVisible(entry));
110249
+ }
110210
110250
  isOverlayVisible(entry) {
110211
110251
  if (entry.hidden)
110212
110252
  return false;
@@ -113806,6 +113846,15 @@ class LatexParser {
113806
113846
  }
113807
113847
  let command = "";
113808
113848
  const first = this.source[this.position] ?? "";
113849
+ if (first === `
113850
+ ` || first === "\r") {
113851
+ this.position++;
113852
+ if (first === "\r" && this.source[this.position] === `
113853
+ `) {
113854
+ this.position++;
113855
+ }
113856
+ return " ";
113857
+ }
113809
113858
  if (/[A-Za-z]/.test(first)) {
113810
113859
  const start = this.position;
113811
113860
  while (this.position < this.source.length && /[A-Za-z]/.test(this.source[this.position] ?? "")) {
@@ -114009,7 +114058,7 @@ class LatexParser {
114009
114058
  return value2;
114010
114059
  }
114011
114060
  parseRequiredArgumentValue() {
114012
- while (this.position < this.source.length && /[ \t]/.test(this.source[this.position] ?? "")) {
114061
+ while (this.position < this.source.length && /\s/.test(this.source[this.position] ?? "")) {
114013
114062
  this.position++;
114014
114063
  }
114015
114064
  if (this.position >= this.source.length) {
@@ -114899,6 +114948,7 @@ class ScrollView extends Container {
114899
114948
  contentHeight = 0;
114900
114949
  currentViewportHeight = 0;
114901
114950
  followingEnd;
114951
+ followSuppressedAtEnd = false;
114902
114952
  requestRenderCallback;
114903
114953
  transientScrollbarVisible = false;
114904
114954
  scrollbarActive = false;
@@ -114978,15 +115028,21 @@ class ScrollView extends Container {
114978
115028
  this.scrollbarActive = active;
114979
115029
  this.markScrollbarActivity();
114980
115030
  }
114981
- scrollTo(scrollTop) {
115031
+ scrollTo(scrollTop, options = {}) {
114982
115032
  const requested = Number.isFinite(scrollTop) ? Math.trunc(scrollTop) : this.currentScrollTop;
114983
115033
  const maxScrollTop = Math.max(0, this.contentHeight - this.currentViewportHeight);
114984
115034
  const next = Math.max(0, Math.min(maxScrollTop, requested));
114985
- if (next === this.currentScrollTop)
115035
+ const nextFollowSuppressedAtEnd = options.disableFollow === true && next === maxScrollTop;
115036
+ const nextFollowingEnd = !nextFollowSuppressedAtEnd && this.followEnd && next === maxScrollTop;
115037
+ if (next === this.currentScrollTop && nextFollowingEnd === this.followingEnd && nextFollowSuppressedAtEnd === this.followSuppressedAtEnd) {
114986
115038
  return;
115039
+ }
115040
+ const moved = next !== this.currentScrollTop;
114987
115041
  this.currentScrollTop = next;
114988
- this.followingEnd = this.followEnd && next === maxScrollTop;
114989
- this.markScrollbarActivity();
115042
+ this.followingEnd = nextFollowingEnd;
115043
+ this.followSuppressedAtEnd = nextFollowSuppressedAtEnd;
115044
+ if (moved)
115045
+ this.markScrollbarActivity();
114990
115046
  this.requestRenderCallback?.();
114991
115047
  }
114992
115048
  scrollBy(lines) {
@@ -114997,18 +115053,21 @@ class ScrollView extends Container {
114997
115053
  const start = this.followingEnd ? maxScrollTop : this.currentScrollTop;
114998
115054
  const next = Math.max(0, Math.min(maxScrollTop, start + requested));
114999
115055
  const moved = next - start;
115056
+ const wasFollowingEnd = this.followingEnd;
115000
115057
  this.currentScrollTop = next;
115001
115058
  this.followingEnd = this.followEnd && next === maxScrollTop;
115002
- if (moved !== 0) {
115059
+ this.followSuppressedAtEnd = false;
115060
+ if (moved !== 0)
115003
115061
  this.markScrollbarActivity();
115062
+ if (moved !== 0 || this.followingEnd !== wasFollowingEnd)
115004
115063
  this.requestRenderCallback?.();
115005
- }
115006
115064
  return requested - moved;
115007
115065
  }
115008
115066
  scrollToStart() {
115009
115067
  const changed = this.currentScrollTop !== 0 || this.followingEnd !== (this.followEnd && this.contentHeight <= this.currentViewportHeight);
115010
115068
  this.currentScrollTop = 0;
115011
115069
  this.followingEnd = this.followEnd && this.contentHeight <= this.currentViewportHeight;
115070
+ this.followSuppressedAtEnd = false;
115012
115071
  if (changed) {
115013
115072
  this.markScrollbarActivity();
115014
115073
  this.requestRenderCallback?.();
@@ -115019,6 +115078,7 @@ class ScrollView extends Container {
115019
115078
  const changed = this.currentScrollTop !== next || this.followingEnd !== this.followEnd;
115020
115079
  this.currentScrollTop = next;
115021
115080
  this.followingEnd = this.followEnd;
115081
+ this.followSuppressedAtEnd = false;
115022
115082
  if (changed) {
115023
115083
  this.markScrollbarActivity();
115024
115084
  this.requestRenderCallback?.();
@@ -115033,8 +115093,11 @@ class ScrollView extends Container {
115033
115093
  this.currentScrollTop = maxScrollTop;
115034
115094
  else
115035
115095
  this.currentScrollTop = Math.max(0, Math.min(this.currentScrollTop, maxScrollTop));
115036
- if (this.followEnd && this.currentScrollTop === maxScrollTop)
115096
+ if (this.currentScrollTop < maxScrollTop)
115097
+ this.followSuppressedAtEnd = false;
115098
+ if (this.followEnd && this.currentScrollTop === maxScrollTop && !this.followSuppressedAtEnd) {
115037
115099
  this.followingEnd = true;
115100
+ }
115038
115101
  if (this.contentHeight <= this.currentViewportHeight)
115039
115102
  this.hideTransientScrollbar();
115040
115103
  }
@@ -115293,6 +115356,8 @@ class VStack extends Stack {
115293
115356
  // node_modules/@earendil-works/pi-tui/dist/stdin-buffer.js
115294
115357
  import { EventEmitter } from "events";
115295
115358
  var ESC = "\x1B";
115359
+ var DEFAULT_SEQUENCE_TIMEOUT_MS = 50;
115360
+ var DEFAULT_ESCAPE_TIMEOUT_MS = 10;
115296
115361
  var BRACKETED_PASTE_START = "\x1B[200~";
115297
115362
  var BRACKETED_PASTE_END = "\x1B[201~";
115298
115363
  function isCompleteSequence(data) {
@@ -115433,12 +115498,14 @@ class StdinBuffer extends EventEmitter {
115433
115498
  buffer = "";
115434
115499
  timeout = null;
115435
115500
  timeoutMs;
115501
+ escapeTimeoutMs;
115436
115502
  pasteMode = false;
115437
115503
  pasteBuffer = "";
115438
115504
  pendingKittyPrintableCodepoint;
115439
115505
  constructor(options = {}) {
115440
115506
  super();
115441
- this.timeoutMs = options.timeout ?? 10;
115507
+ this.timeoutMs = options.timeout ?? DEFAULT_SEQUENCE_TIMEOUT_MS;
115508
+ this.escapeTimeoutMs = options.escapeTimeout ?? DEFAULT_ESCAPE_TIMEOUT_MS;
115442
115509
  }
115443
115510
  process(data) {
115444
115511
  if (this.timeout) {
@@ -115512,12 +115579,13 @@ class StdinBuffer extends EventEmitter {
115512
115579
  this.emitDataSequence(sequence);
115513
115580
  }
115514
115581
  if (this.buffer.length > 0) {
115582
+ const timeoutMs = this.buffer === ESC ? this.escapeTimeoutMs : this.timeoutMs;
115515
115583
  this.timeout = setTimeout(() => {
115516
115584
  const flushed = this.flush();
115517
115585
  for (const sequence of flushed) {
115518
115586
  this.emitDataSequence(sequence);
115519
115587
  }
115520
- }, this.timeoutMs);
115588
+ }, timeoutMs);
115521
115589
  }
115522
115590
  }
115523
115591
  emitDataSequence(sequence) {
@@ -115650,6 +115718,19 @@ function normalizeNativeShiftEnterInput(data, shouldDetectNativeShiftEnter, isSh
115650
115718
  return NATIVE_SHIFT_ENTER_SEQUENCE;
115651
115719
  return data;
115652
115720
  }
115721
+ var DEFAULT_ESCAPE_TIMEOUT_MS2 = 10;
115722
+ var DEFAULT_SSH_ESCAPE_TIMEOUT_MS = 100;
115723
+ function resolveEscapeTimeoutMs(env = process.env) {
115724
+ const configured = Number(env.PI_TUI_ESC_TIMEOUT);
115725
+ if (Number.isFinite(configured) && configured > 0) {
115726
+ return configured;
115727
+ }
115728
+ if (env.SSH_CONNECTION || env.SSH_TTY) {
115729
+ return DEFAULT_SSH_ESCAPE_TIMEOUT_MS;
115730
+ }
115731
+ return DEFAULT_ESCAPE_TIMEOUT_MS2;
115732
+ }
115733
+
115653
115734
  class ProcessTerminal {
115654
115735
  wasRaw = false;
115655
115736
  inputHandler;
@@ -115699,7 +115780,7 @@ class ProcessTerminal {
115699
115780
  this.queryAndEnableKittyProtocol();
115700
115781
  }
115701
115782
  setupStdinBuffer() {
115702
- this.stdinBuffer = new StdinBuffer({ timeout: 10 });
115783
+ this.stdinBuffer = new StdinBuffer({ escapeTimeout: resolveEscapeTimeoutMs() });
115703
115784
  this.stdinBuffer.on("data", (sequence) => {
115704
115785
  const negotiationSequence = this.readKeyboardProtocolNegotiationSequence(sequence);
115705
115786
  if (negotiationSequence === "pending") {
@@ -115967,6 +116048,123 @@ class ProcessTerminal {
115967
116048
  return true;
115968
116049
  }
115969
116050
  }
116051
+ // node_modules/@earendil-works/pi-tui/dist/alt-screen-search.js
116052
+ var segmenter2 = getGraphemeSegmenter();
116053
+ function appendMappedText(text, span, corpus) {
116054
+ corpus.text += text;
116055
+ for (let index = 0;index < text.length; index++)
116056
+ corpus.source.push(span);
116057
+ }
116058
+ function buildSearchCorpus(lines) {
116059
+ const corpus = { text: "", source: [] };
116060
+ let pendingSeparator = false;
116061
+ for (let row = 0;row < lines.length; row++) {
116062
+ const line = stripTerminalSequences(lines[row] ?? "");
116063
+ let column = 0;
116064
+ for (const grapheme of segmenter2.segment(line)) {
116065
+ const text = grapheme.segment;
116066
+ const width = visibleWidth(text);
116067
+ if (/^\s+$/u.test(text)) {
116068
+ if (corpus.text.length > 0)
116069
+ pendingSeparator = true;
116070
+ column += width;
116071
+ continue;
116072
+ }
116073
+ if (pendingSeparator) {
116074
+ appendMappedText(" ", undefined, corpus);
116075
+ pendingSeparator = false;
116076
+ }
116077
+ appendMappedText(text, { row, startCol: column, endCol: column + width }, corpus);
116078
+ column += width;
116079
+ }
116080
+ if (corpus.text.length > 0)
116081
+ pendingSeparator = true;
116082
+ }
116083
+ return corpus;
116084
+ }
116085
+ function normalizeQuery(query) {
116086
+ return query.replace(/\s+/gu, " ").trim();
116087
+ }
116088
+ function escapeRegExp(text) {
116089
+ return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
116090
+ }
116091
+ function findAltScreenSearchMatches(lines, query) {
116092
+ const normalizedQuery = normalizeQuery(query);
116093
+ if (!normalizedQuery)
116094
+ return [];
116095
+ const corpus = buildSearchCorpus(lines);
116096
+ const expression = new RegExp(escapeRegExp(normalizedQuery), "giu");
116097
+ const matches = [];
116098
+ for (const match of corpus.text.matchAll(expression)) {
116099
+ const start = match.index;
116100
+ const end = start + match[0].length;
116101
+ const segments = [];
116102
+ for (let index = start;index < end; index++) {
116103
+ const span = corpus.source[index];
116104
+ if (!span)
116105
+ continue;
116106
+ const previous = segments[segments.length - 1];
116107
+ if (previous && previous.row === span.row && span.startCol <= previous.endCol) {
116108
+ previous.endCol = Math.max(previous.endCol, span.endCol);
116109
+ } else {
116110
+ segments.push({ ...span });
116111
+ }
116112
+ }
116113
+ if (segments.length > 0)
116114
+ matches.push({ segments });
116115
+ }
116116
+ return matches;
116117
+ }
116118
+ function getAltScreenSearchMatchKey(match) {
116119
+ const first = match.segments[0];
116120
+ const last = match.segments[match.segments.length - 1];
116121
+ return first && last ? `${first.row}:${first.startCol}:${last.row}:${last.endCol}` : "";
116122
+ }
116123
+
116124
+ class AltScreenSearchComponent {
116125
+ input = new Input;
116126
+ onQueryChange;
116127
+ resultCount = 0;
116128
+ resultIndex = -1;
116129
+ _focused = false;
116130
+ constructor(onQueryChange) {
116131
+ this.onQueryChange = onQueryChange;
116132
+ }
116133
+ get focused() {
116134
+ return this._focused;
116135
+ }
116136
+ set focused(value2) {
116137
+ this._focused = value2;
116138
+ this.input.focused = value2;
116139
+ }
116140
+ setResult(index, count) {
116141
+ this.resultIndex = index;
116142
+ this.resultCount = count;
116143
+ }
116144
+ handleInput(data) {
116145
+ const previous = this.input.getValue();
116146
+ this.input.handleInput(data);
116147
+ const query = this.input.getValue();
116148
+ if (query !== previous)
116149
+ this.onQueryChange(query);
116150
+ }
116151
+ invalidate() {
116152
+ this.input.invalidate();
116153
+ }
116154
+ render(width) {
116155
+ const safeWidth = Math.max(1, width);
116156
+ const label = " Find transcript";
116157
+ const query = this.input.getValue();
116158
+ const status = !query ? "" : this.resultCount === 0 ? "No matches " : `${this.resultIndex + 1}/${this.resultCount} `;
116159
+ const labelWidth = visibleWidth(label);
116160
+ const statusWidth = visibleWidth(status);
116161
+ const gap = " ".repeat(Math.max(1, safeWidth - labelWidth - statusWidth));
116162
+ const title = truncateToWidth(`${label}${gap}${status}`, safeWidth, "");
116163
+ const padding = " ".repeat(Math.max(0, safeWidth - visibleWidth(title)));
116164
+ return [`\x1B[7m${title}${padding}\x1B[27m`, ...this.input.render(safeWidth)];
116165
+ }
116166
+ }
116167
+
115970
116168
  // node_modules/@earendil-works/pi-tui/dist/components/alt-screen-flash.js
115971
116169
  var DEFAULT_DURATION_MS = 1000;
115972
116170
 
@@ -116227,10 +116425,11 @@ function paintBox(box, screen, totalWidth) {
116227
116425
  if (visibleRows < imageMetadata.rows)
116228
116426
  line = cropKittyImageLine(line, 0, visibleRows);
116229
116427
  }
116230
- if (isImageLine(line) && box.rect.x === 0 && box.rect.width >= totalWidth)
116428
+ if (box.rect.x === 0 && box.rect.width >= totalWidth && (isImageLine(line) || !screen[row])) {
116231
116429
  screen[row] = line;
116232
- else
116430
+ } else {
116233
116431
  screen[row] = compositeTuiLine(screen[row] ?? "", line, box.rect.x, box.rect.width, totalWidth);
116432
+ }
116234
116433
  }
116235
116434
  }
116236
116435
  for (const child of box.children)
@@ -116359,12 +116558,16 @@ class TuiAltScreen extends TuiBase {
116359
116558
  selectionPressActive = false;
116360
116559
  scrollbarDrag;
116361
116560
  scrollbarHover;
116561
+ activeSearch;
116362
116562
  pressedUrl;
116363
116563
  selectionDragged = false;
116364
116564
  wheelScrollLines;
116365
116565
  mouseEnabled;
116566
+ searchMatchStyle;
116567
+ searchCurrentMatchStyle;
116366
116568
  openUrl;
116367
116569
  onRightClickPaste;
116570
+ copySelection;
116368
116571
  constructor(terminal, showHardwareCursor, logDirectory, options = {}) {
116369
116572
  super(terminal, showHardwareCursor, logDirectory);
116370
116573
  this.implicitDocument = {
@@ -116378,8 +116581,11 @@ class TuiAltScreen extends TuiBase {
116378
116581
  this.flashes = new AltScreenFlashContainer(() => this.requestRender());
116379
116582
  this.wheelScrollLines = Math.max(1, Math.floor(options.wheelScrollLines ?? 1));
116380
116583
  this.mouseEnabled = options.mouse ?? true;
116584
+ this.searchMatchStyle = options.searchMatchStyle ?? ((text) => `\x1B[4m${text}\x1B[24m`);
116585
+ this.searchCurrentMatchStyle = options.searchCurrentMatchStyle ?? ((text) => `\x1B[1;7m${text}\x1B[22;27m`);
116381
116586
  this.openUrl = options.openUrl;
116382
116587
  this.onRightClickPaste = options.onRightClickPaste;
116588
+ this.copySelection = options.copySelection;
116383
116589
  this.addInputListener((data) => this.handleViewportInput(data));
116384
116590
  }
116385
116591
  get viewportTop() {
@@ -116433,6 +116639,7 @@ class TuiAltScreen extends TuiBase {
116433
116639
  this.terminal.write(`${ENTER_ALT_SCREEN}${DISABLE_AUTOWRAP}${this.mouseEnabled ? mouseSequence : ""}\x1B[2J\x1B[H\x1B[?25l`);
116434
116640
  }
116435
116641
  beforeTerminalStop(_options) {
116642
+ this.closeSearch();
116436
116643
  this.stopSelectionAutoScroll();
116437
116644
  this.selectionPressActive = false;
116438
116645
  this.stopScrollbarHover();
@@ -116548,12 +116755,119 @@ class TuiAltScreen extends TuiBase {
116548
116755
  return;
116549
116756
  }
116550
116757
  }
116758
+ openSearch() {
116759
+ if (this.activeSearch) {
116760
+ this.activeSearch.overlay?.focus();
116761
+ return;
116762
+ }
116763
+ const component = new AltScreenSearchComponent((query) => this.updateSearchQuery(query));
116764
+ const search = {
116765
+ component,
116766
+ query: "",
116767
+ matches: [],
116768
+ selectedIndex: -1,
116769
+ anchorRow: this.getPrimaryScrollView().scrollTop,
116770
+ selectionMode: "query"
116771
+ };
116772
+ this.activeSearch = search;
116773
+ search.overlay = this.showOverlay(component, {
116774
+ anchor: "top-right",
116775
+ width: "40%",
116776
+ minWidth: 24,
116777
+ margin: 1
116778
+ });
116779
+ }
116780
+ closeSearch() {
116781
+ const search = this.activeSearch;
116782
+ if (!search)
116783
+ return;
116784
+ this.activeSearch = undefined;
116785
+ search.overlay?.hide();
116786
+ this.requestRender();
116787
+ }
116788
+ updateSearchQuery(query) {
116789
+ const search = this.activeSearch;
116790
+ if (!search || query === search.query)
116791
+ return;
116792
+ const selected = search.matches[search.selectedIndex];
116793
+ search.anchorRow = selected?.segments[0]?.row ?? this.getPrimaryScrollView().scrollTop;
116794
+ search.query = query;
116795
+ search.selectionMode = "query";
116796
+ search.component.setResult(-1, 0);
116797
+ this.requestRender();
116798
+ }
116799
+ navigateSearch(direction) {
116800
+ const search = this.activeSearch;
116801
+ if (!search?.query)
116802
+ return;
116803
+ search.selectionMode = direction < 0 ? "previous" : "next";
116804
+ this.requestRender();
116805
+ }
116806
+ refreshSearch(layout) {
116807
+ const search = this.activeSearch;
116808
+ if (!search)
116809
+ return false;
116810
+ const scrollView = layout.primaryScrollView ?? this.implicitScrollView;
116811
+ const box = getScrollViewBox(layout, scrollView);
116812
+ const lines = box?.scrollContentLines;
116813
+ if (!lines || !search.query.trim()) {
116814
+ search.matches = [];
116815
+ search.selectedIndex = -1;
116816
+ search.selectedKey = undefined;
116817
+ search.selectionMode = "retain";
116818
+ search.component.setResult(-1, 0);
116819
+ return false;
116820
+ }
116821
+ const shouldRevealSelection = search.selectionMode !== "retain";
116822
+ const matches = findAltScreenSearchMatches(lines, search.query);
116823
+ const exactIndex = search.selectedKey ? matches.findIndex((match) => getAltScreenSearchMatchKey(match) === search.selectedKey) : -1;
116824
+ let selectedIndex = -1;
116825
+ if (matches.length > 0) {
116826
+ if (search.selectionMode === "query") {
116827
+ selectedIndex = matches.findIndex((match) => (match.segments[0]?.row ?? 0) >= search.anchorRow);
116828
+ if (selectedIndex < 0)
116829
+ selectedIndex = 0;
116830
+ } else if (search.selectionMode === "next") {
116831
+ const baseIndex = exactIndex >= 0 ? exactIndex : Math.min(search.selectedIndex, matches.length - 1);
116832
+ selectedIndex = baseIndex < 0 ? 0 : (baseIndex + 1) % matches.length;
116833
+ } else if (search.selectionMode === "previous") {
116834
+ const baseIndex = exactIndex >= 0 ? exactIndex : Math.min(search.selectedIndex, matches.length - 1);
116835
+ selectedIndex = baseIndex < 0 ? matches.length - 1 : (baseIndex - 1 + matches.length) % matches.length;
116836
+ } else {
116837
+ selectedIndex = exactIndex >= 0 ? exactIndex : Math.min(Math.max(0, search.selectedIndex), matches.length - 1);
116838
+ }
116839
+ }
116840
+ search.matches = matches;
116841
+ search.selectedIndex = selectedIndex;
116842
+ search.selectedKey = selectedIndex >= 0 ? getAltScreenSearchMatchKey(matches[selectedIndex]) : undefined;
116843
+ search.selectionMode = "retain";
116844
+ search.component.setResult(selectedIndex, matches.length);
116845
+ if (!shouldRevealSelection)
116846
+ return false;
116847
+ const selected = matches[selectedIndex];
116848
+ const firstSegment = selected?.segments[0];
116849
+ const lastSegment = selected?.segments[selected.segments.length - 1];
116850
+ if (!box || !firstSegment || !lastSegment || scrollView.viewportHeight <= 0)
116851
+ return false;
116852
+ const before = scrollView.scrollTop;
116853
+ const visibleBottom = before + scrollView.viewportHeight - 1;
116854
+ let target = before;
116855
+ if (firstSegment.row < before || lastSegment.row > visibleBottom) {
116856
+ target = firstSegment.row - Math.floor(scrollView.viewportHeight / 3);
116857
+ }
116858
+ scrollView.scrollTo(target, { disableFollow: true });
116859
+ return scrollView.scrollTop !== before;
116860
+ }
116551
116861
  flash(message, durationMs) {
116552
116862
  this.flashes.flash(message, durationMs);
116553
116863
  }
116864
+ shouldDeferViewportInputToOverlay() {
116865
+ return this.isOverlayFocused() && this.activeSearch?.overlay?.isFocused() !== true;
116866
+ }
116554
116867
  handleViewportInput(data) {
116555
116868
  if (data === FOCUS_OUT) {
116556
116869
  const hadActiveSelection = this.selectionPressActive;
116870
+ const hadNonEmptyActiveSelection = hadActiveSelection && this.getSelectionBounds() !== undefined;
116557
116871
  this.selectionPressActive = false;
116558
116872
  this.stopSelectionAutoScroll();
116559
116873
  this.stopScrollbarHover();
@@ -116565,15 +116879,18 @@ class TuiAltScreen extends TuiBase {
116565
116879
  this.selectionFocus = undefined;
116566
116880
  this.selectionGranularity = "character";
116567
116881
  this.selectionInitialRange = undefined;
116882
+ if (hadNonEmptyActiveSelection)
116883
+ this.requestRender();
116568
116884
  }
116569
116885
  this.lastClick = undefined;
116570
- this.requestRender();
116571
116886
  return { consume: true };
116572
116887
  }
116573
116888
  if (data === FOCUS_IN)
116574
116889
  return { consume: true };
116575
116890
  const wheelEvent = this.parseWheelEvent(data);
116576
116891
  if (wheelEvent) {
116892
+ if (this.shouldDeferViewportInputToOverlay())
116893
+ return;
116577
116894
  this.routeWheel(wheelEvent);
116578
116895
  return { consume: true };
116579
116896
  }
@@ -116592,6 +116909,30 @@ class TuiAltScreen extends TuiBase {
116592
116909
  return { consume: true };
116593
116910
  const keybindings = getKeybindings();
116594
116911
  const isRelease = isKeyRelease(data);
116912
+ if (keybindings.matches(data, "tui.altScreen.search")) {
116913
+ if (!isRelease)
116914
+ this.openSearch();
116915
+ return { consume: true };
116916
+ }
116917
+ if (this.activeSearch?.overlay?.isFocused()) {
116918
+ if (keybindings.matches(data, "tui.altScreen.searchNext")) {
116919
+ if (!isRelease)
116920
+ this.navigateSearch(1);
116921
+ return { consume: true };
116922
+ }
116923
+ if (keybindings.matches(data, "tui.altScreen.searchPrevious")) {
116924
+ if (!isRelease)
116925
+ this.navigateSearch(-1);
116926
+ return { consume: true };
116927
+ }
116928
+ if (keybindings.matches(data, "tui.altScreen.searchClose")) {
116929
+ if (!isRelease)
116930
+ this.closeSearch();
116931
+ return { consume: true };
116932
+ }
116933
+ }
116934
+ if (this.shouldDeferViewportInputToOverlay())
116935
+ return;
116595
116936
  if (keybindings.matches(data, "tui.altScreen.pageUp")) {
116596
116937
  if (!isRelease) {
116597
116938
  this.scrollBy(-Math.max(1, this.getPrimaryScrollView().viewportHeight - PAGE_SCROLL_OVERLAP));
@@ -116614,6 +116955,16 @@ class TuiAltScreen extends TuiBase {
116614
116955
  this.scrollBy(Math.max(1, Math.floor(this.getPrimaryScrollView().viewportHeight / 2)));
116615
116956
  return { consume: true };
116616
116957
  }
116958
+ if (keybindings.matches(data, "tui.altScreen.lineUp")) {
116959
+ if (!isRelease)
116960
+ this.scrollBy(-1);
116961
+ return { consume: true };
116962
+ }
116963
+ if (keybindings.matches(data, "tui.altScreen.lineDown")) {
116964
+ if (!isRelease)
116965
+ this.scrollBy(1);
116966
+ return { consume: true };
116967
+ }
116617
116968
  if (keybindings.matches(data, "tui.altScreen.previousPrompt")) {
116618
116969
  if (!isRelease)
116619
116970
  this.scrollToPrompt(-1);
@@ -116907,7 +117258,8 @@ class TuiAltScreen extends TuiBase {
116907
117258
  this.selectionDragPointer = undefined;
116908
117259
  }
116909
117260
  handleSelectionMouseEvent(event) {
116910
- if ((event.button & 3) !== 0)
117261
+ const button = event.button & 3;
117262
+ if (button !== 0 && !(event.release && button === 3))
116911
117263
  return;
116912
117264
  const anchorScrollView = this.selectionAnchor?.scrollView;
116913
117265
  const point = this.getSelectionPoint(event, anchorScrollView);
@@ -116983,7 +117335,7 @@ class TuiAltScreen extends TuiBase {
116983
117335
  }
116984
117336
  return { start: Math.max(minColumn, start), end: Math.min(maxColumn, end) };
116985
117337
  }
116986
- copySelectionToClipboard() {
117338
+ async copySelectionToClipboard() {
116987
117339
  const selection = this.getSelectionBounds();
116988
117340
  if (!selection)
116989
117341
  return;
@@ -117006,9 +117358,83 @@ class TuiAltScreen extends TuiBase {
117006
117358
  `);
117007
117359
  if (text.length === 0)
117008
117360
  return;
117361
+ if (this.copySelection) {
117362
+ const ok = await this.copySelection(text);
117363
+ this.flash(ok ? "Copied!" : "Copy failed");
117364
+ return;
117365
+ }
117009
117366
  this.terminal.write(`\x1B]52;c;${Buffer.from(text).toString("base64")}\x07`);
117010
117367
  this.flash("Copied!");
117011
117368
  }
117369
+ applySearchTextHighlight(text, current) {
117370
+ const style = current ? this.searchCurrentMatchStyle : this.searchMatchStyle;
117371
+ let result = "";
117372
+ let plainStart = 0;
117373
+ let index = 0;
117374
+ while (index < text.length) {
117375
+ const ansi = extractAnsiCode(text, index);
117376
+ if (!ansi) {
117377
+ index += 1;
117378
+ continue;
117379
+ }
117380
+ if (index > plainStart)
117381
+ result += style(text.slice(plainStart, index));
117382
+ result += ansi.code;
117383
+ index += ansi.length;
117384
+ plainStart = index;
117385
+ }
117386
+ if (plainStart < text.length)
117387
+ result += style(text.slice(plainStart));
117388
+ return result;
117389
+ }
117390
+ applySearchHighlights(screen, layout) {
117391
+ const search = this.activeSearch;
117392
+ if (!search || search.selectedIndex < 0 || search.matches.length === 0)
117393
+ return screen;
117394
+ const scrollView = layout.primaryScrollView ?? this.implicitScrollView;
117395
+ const box = getScrollViewBox(layout, scrollView);
117396
+ if (!box)
117397
+ return screen;
117398
+ const rangesByRow = new Map;
117399
+ const scrollbarColumn = getScrollbarGeometry(box)?.column;
117400
+ const minRow = Math.max(0, box.rect.y, box.clip.y);
117401
+ const maxRow = Math.min(screen.length, box.rect.y + box.rect.height, box.clip.y + box.clip.height);
117402
+ const minColumn = Math.max(0, box.rect.x, box.clip.x);
117403
+ const maxColumn = Math.min(this.terminal.columns, box.rect.x + box.rect.width, box.clip.x + box.clip.width, scrollbarColumn ?? Number.POSITIVE_INFINITY);
117404
+ for (let matchIndex = 0;matchIndex < search.matches.length; matchIndex++) {
117405
+ for (const segment of search.matches[matchIndex].segments) {
117406
+ const row = box.rect.y + segment.row - scrollView.scrollTop;
117407
+ if (row < minRow || row >= maxRow)
117408
+ continue;
117409
+ const startCol = Math.max(minColumn, box.rect.x + segment.startCol);
117410
+ const endCol = Math.min(maxColumn, box.rect.x + segment.endCol);
117411
+ if (endCol <= startCol)
117412
+ continue;
117413
+ const ranges = rangesByRow.get(row) ?? [];
117414
+ ranges.push({ startCol, endCol, current: matchIndex === search.selectedIndex });
117415
+ rangesByRow.set(row, ranges);
117416
+ }
117417
+ }
117418
+ const result = [...screen];
117419
+ for (const [row, ranges] of rangesByRow) {
117420
+ let line = result[row] ?? "";
117421
+ if (isImageLine(line))
117422
+ continue;
117423
+ const lineWidth = visibleWidth(line);
117424
+ for (const range of ranges.sort((a, b2) => b2.startCol - a.startCol)) {
117425
+ const startCol = Math.min(range.startCol, lineWidth);
117426
+ const endCol = Math.min(range.endCol, lineWidth);
117427
+ if (endCol <= startCol)
117428
+ continue;
117429
+ const before = sliceByColumn(line, 0, startCol, true);
117430
+ const highlighted = sliceByColumn(line, startCol, endCol - startCol, true);
117431
+ const after = sliceByColumn(line, endCol, Math.max(0, lineWidth - endCol), true);
117432
+ line = `${before}${this.applySearchTextHighlight(highlighted, range.current)}${after}`;
117433
+ }
117434
+ result[row] = line;
117435
+ }
117436
+ return result;
117437
+ }
117012
117438
  applySelectionHighlight(text) {
117013
117439
  let result = "\x1B[7m";
117014
117440
  let index = 0;
@@ -117097,8 +117523,12 @@ class TuiAltScreen extends TuiBase {
117097
117523
  const width = Math.max(1, this.terminal.columns);
117098
117524
  const height = Math.max(1, this.terminal.rows);
117099
117525
  const root = this.layoutRoot ?? this.implicitScrollView;
117100
- const nextLayout = renderLayoutFrame(root, width, height, () => this.requestRender());
117526
+ let nextLayout = renderLayoutFrame(root, width, height, () => this.requestRender());
117527
+ if (this.refreshSearch(nextLayout)) {
117528
+ nextLayout = renderLayoutFrame(root, width, height, () => this.requestRender());
117529
+ }
117101
117530
  let screen = nextLayout.lines.map((line) => line.replace(OSC133_ZONE_PREFIX2, ""));
117531
+ screen = this.applySearchHighlights(screen, nextLayout);
117102
117532
  screen = this.compositeOverlays(screen, width, height);
117103
117533
  if (screen.length > height)
117104
117534
  screen = screen.slice(screen.length - height);
@@ -117847,6 +118277,8 @@ var ThemeJsonSchema = exports_typebox.Object({
117847
118277
  thinkingText: ColorValueSchema,
117848
118278
  selectedBg: ColorValueSchema,
117849
118279
  scrollbarThumb: exports_typebox.Optional(ColorValueSchema),
118280
+ searchMatchBg: exports_typebox.Optional(ColorValueSchema),
118281
+ searchMatchText: exports_typebox.Optional(ColorValueSchema),
117850
118282
  userMessageBg: ColorValueSchema,
117851
118283
  userMessageText: ColorValueSchema,
117852
118284
  customMessageBg: ColorValueSchema,
@@ -118022,7 +118454,9 @@ function withThemeColorFallbacks(colors) {
118022
118454
  return {
118023
118455
  ...colors,
118024
118456
  thinkingMax: colors.thinkingMax ?? colors.thinkingXhigh,
118025
- scrollbarThumb: colors.scrollbarThumb ?? colors.selectedBg
118457
+ scrollbarThumb: colors.scrollbarThumb ?? colors.selectedBg,
118458
+ searchMatchBg: colors.searchMatchBg ?? colors.selectedBg,
118459
+ searchMatchText: colors.searchMatchText ?? colors.text
118026
118460
  };
118027
118461
  }
118028
118462
 
@@ -118039,14 +118473,19 @@ class Theme {
118039
118473
  this.sourceInfo = options.sourceInfo;
118040
118474
  this.mode = mode;
118041
118475
  this.fgColors = new Map;
118042
- const colors = { ...fgColors, thinkingMax: fgColors.thinkingMax ?? fgColors.thinkingXhigh };
118476
+ const colors = {
118477
+ ...fgColors,
118478
+ thinkingMax: fgColors.thinkingMax ?? fgColors.thinkingXhigh,
118479
+ searchMatchText: fgColors.searchMatchText ?? fgColors.text
118480
+ };
118043
118481
  for (const [key, value2] of Object.entries(colors)) {
118044
118482
  this.fgColors.set(key, fgAnsi(value2, mode));
118045
118483
  }
118046
118484
  this.bgColors = new Map;
118047
118485
  const backgrounds = {
118048
118486
  ...bgColors,
118049
- scrollbarThumb: bgColors.scrollbarThumb ?? bgColors.selectedBg
118487
+ scrollbarThumb: bgColors.scrollbarThumb ?? bgColors.selectedBg,
118488
+ searchMatchBg: bgColors.searchMatchBg ?? bgColors.selectedBg
118050
118489
  };
118051
118490
  for (const [key, value2] of Object.entries(backgrounds)) {
118052
118491
  this.bgColors.set(key, bgAnsi(value2, mode));
@@ -118262,6 +118701,7 @@ function createTheme(themeJson, mode, sourcePath) {
118262
118701
  const bgColorKeys = new Set([
118263
118702
  "selectedBg",
118264
118703
  "scrollbarThumb",
118704
+ "searchMatchBg",
118265
118705
  "userMessageBg",
118266
118706
  "customMessageBg",
118267
118707
  "toolPendingBg",
@@ -120546,7 +120986,7 @@ class SessionManager {
120546
120986
  if (this.fileEntries.length === 0) {
120547
120987
  const explicitPath = this.sessionFile;
120548
120988
  if (statSync4(explicitPath).size > 0) {
120549
- throw new Error(`Session file is not a valid pi session: ${explicitPath}`);
120989
+ throw new Error(`Session file is not a valid ${APP_NAME} session: ${explicitPath}`);
120550
120990
  }
120551
120991
  this.newSession();
120552
120992
  this.sessionFile = explicitPath;
@@ -122400,6 +122840,7 @@ __export(exports_dist2, {
122400
122840
  setDefaultStreamFn: () => setDefaultStreamFn,
122401
122841
  sessionEntryToContextMessages: () => sessionEntryToContextMessages2,
122402
122842
  serializeConversation: () => serializeConversation2,
122843
+ scanningEntries: () => scanningEntries,
122403
122844
  sanitizeBinaryOutput: () => sanitizeBinaryOutput2,
122404
122845
  runAgentLoopContinue: () => runAgentLoopContinue,
122405
122846
  runAgentLoop: () => runAgentLoop,
@@ -122415,7 +122856,6 @@ __export(exports_dist2, {
122415
122856
  getOrUndefined: () => getOrUndefined,
122416
122857
  getOrThrow: () => getOrThrow,
122417
122858
  getLastAssistantUsage: () => getLastAssistantUsage2,
122418
- getFileSystemResultOrThrow: () => getFileSystemResultOrThrow,
122419
122859
  generateSummaryWithUsage: () => generateSummaryWithUsage2,
122420
122860
  generateSummary: () => generateSummary2,
122421
122861
  generateBranchSummary: () => generateBranchSummary2,
@@ -124192,10 +124632,10 @@ class Session {
124192
124632
  assertValidCursor(query.cursor?.afterSeq);
124193
124633
  return this.storage.findEntries(resultLimit === query.limit ? query : { ...query, limit: resultLimit });
124194
124634
  }
124195
- async queryBranchEntries(lane, query = {}, resultLimit = query.limit) {
124635
+ async queryBranchEntries(defaultLane, query = {}, resultLimit = query.limit) {
124196
124636
  assertValidLimit(query.limit);
124197
124637
  assertValidCursor(query.cursor?.afterSeq);
124198
- const start = query.start ?? await this.getLeafIdForLane(lane);
124638
+ const start = query.start ?? await this.getLeafIdForLane(defaultLane);
124199
124639
  if (start === null)
124200
124640
  return [];
124201
124641
  const storageQuery = resultLimit === query.limit ? query : { ...query, limit: resultLimit };
@@ -124232,15 +124672,88 @@ class Session {
124232
124672
  }
124233
124673
  }
124234
124674
 
124675
+ // node_modules/@earendil-works/pi-agent-core/dist/harness/types.js
124676
+ function ok(value2) {
124677
+ return { ok: true, value: value2 };
124678
+ }
124679
+ function err(error) {
124680
+ return { ok: false, error };
124681
+ }
124682
+ function getOrThrow(result) {
124683
+ if (!result.ok)
124684
+ throw result.error;
124685
+ return result.value;
124686
+ }
124687
+ function getOrUndefined(result) {
124688
+ return result.ok ? result.value : undefined;
124689
+ }
124690
+ function toError(error) {
124691
+ if (error instanceof Error)
124692
+ return error;
124693
+ if (typeof error === "string")
124694
+ return new Error(error);
124695
+ try {
124696
+ return new Error(JSON.stringify(error));
124697
+ } catch {
124698
+ return new Error(String(error));
124699
+ }
124700
+ }
124701
+
124702
+ class FileError extends Error {
124703
+ code;
124704
+ path;
124705
+ constructor(code, message, path7, cause) {
124706
+ super(message, cause === undefined ? undefined : { cause });
124707
+ this.name = "FileError";
124708
+ this.code = code;
124709
+ this.path = path7;
124710
+ }
124711
+ }
124712
+
124713
+ class ExecutionError extends Error {
124714
+ code;
124715
+ constructor(code, message, cause) {
124716
+ super(message, cause === undefined ? undefined : { cause });
124717
+ this.name = "ExecutionError";
124718
+ this.code = code;
124719
+ }
124720
+ }
124721
+
124722
+ class CompactionError extends Error {
124723
+ code;
124724
+ constructor(code, message, cause) {
124725
+ super(message, cause === undefined ? undefined : { cause });
124726
+ this.name = "CompactionError";
124727
+ this.code = code;
124728
+ }
124729
+ }
124730
+
124731
+ class BranchSummaryError extends Error {
124732
+ code;
124733
+ constructor(code, message, cause) {
124734
+ super(message, cause === undefined ? undefined : { cause });
124735
+ this.name = "BranchSummaryError";
124736
+ this.code = code;
124737
+ }
124738
+ }
124739
+
124235
124740
  // node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl/errors.js
124741
+ class JsonlDecodeError extends Error {
124742
+ kind;
124743
+ constructor(kind, message, cause) {
124744
+ super(message, cause === undefined ? undefined : { cause });
124745
+ this.name = "JsonlDecodeError";
124746
+ this.kind = kind;
124747
+ }
124748
+ }
124236
124749
  function fileResult(result, message) {
124237
124750
  if (!result.ok) {
124238
124751
  throw new SessionError(result.error.code === "not_found" ? "not_found" : "storage", `${message}: ${result.error.message}`, result.error);
124239
124752
  }
124240
124753
  return result.value;
124241
124754
  }
124242
- function invalidFile(path7, line, message, cause) {
124243
- return new SessionError("invalid_entry", `Invalid JSONL v4 session ${path7}: line ${line} ${message}`, cause);
124755
+ function invalidFile(path7, line, cause) {
124756
+ return new SessionError("invalid_entry", `Invalid JSONL v4 session ${path7}: line ${line} ${cause.message}`, cause);
124244
124757
  }
124245
124758
 
124246
124759
  // node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl/codec.js
@@ -124268,70 +124781,82 @@ var OPERATION_KINDS = new Set(["run", "compaction", "navigation"]);
124268
124781
  function isObject(value2) {
124269
124782
  return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
124270
124783
  }
124271
- function parseObject(line, path7, lineNumber) {
124784
+ function parseObject(line) {
124272
124785
  let value2;
124273
124786
  try {
124274
124787
  value2 = JSON.parse(line);
124275
124788
  } catch (error) {
124276
- throw invalidFile(path7, lineNumber, "is not valid JSON", error instanceof Error ? error : undefined);
124789
+ throw new JsonlDecodeError("syntax", "is not valid JSON", error instanceof Error ? error : undefined);
124277
124790
  }
124278
124791
  if (!isObject(value2))
124279
- throw invalidFile(path7, lineNumber, "is not a JSON object");
124792
+ throw new JsonlDecodeError("schema", "is not a JSON object");
124280
124793
  return value2;
124281
124794
  }
124282
- function requireString(value2, path7, line, field) {
124795
+ function requireString(value2, field) {
124283
124796
  if (typeof value2 !== "string")
124284
- throw invalidFile(path7, line, `has invalid ${field}`);
124797
+ throw new JsonlDecodeError("schema", `has invalid ${field}`);
124285
124798
  return value2;
124286
124799
  }
124287
- function requireSequence(value2, path7, line) {
124288
- if (!Number.isSafeInteger(value2) || value2 <= 0)
124289
- throw invalidFile(path7, line, "has invalid seq");
124800
+ function requireSequence(value2) {
124801
+ if (!Number.isSafeInteger(value2) || value2 <= 0) {
124802
+ throw new JsonlDecodeError("schema", "has invalid seq");
124803
+ }
124290
124804
  return value2;
124291
124805
  }
124292
- function requireTimestamp(value2, path7, line) {
124293
- if (!Number.isSafeInteger(value2) || value2 < 0)
124294
- throw invalidFile(path7, line, "has invalid timestamp");
124806
+ function requireTimestamp(value2) {
124807
+ if (!Number.isSafeInteger(value2) || value2 < 0) {
124808
+ throw new JsonlDecodeError("schema", "has invalid timestamp");
124809
+ }
124295
124810
  return value2;
124296
124811
  }
124297
- function requireNullableId(value2, path7, line, field) {
124812
+ function requireNullableId(value2, field) {
124298
124813
  if (value2 !== null && typeof value2 !== "string") {
124299
- throw invalidFile(path7, line, `has invalid ${field}`);
124814
+ throw new JsonlDecodeError("schema", `has invalid ${field}`);
124300
124815
  }
124301
124816
  return value2;
124302
124817
  }
124303
- function parseHeader(line, path7) {
124304
- const value2 = parseObject(line, path7, 1);
124818
+ function decodeHeader(line) {
124819
+ const value2 = parseObject(line);
124305
124820
  if (value2.kind !== "header")
124306
- throw invalidFile(path7, 1, "is not a header");
124821
+ throw new JsonlDecodeError("schema", "is not a header");
124307
124822
  if (value2.version !== 4)
124308
- throw invalidFile(path7, 1, "has unsupported session version");
124823
+ throw new JsonlDecodeError("schema", "has unsupported session version");
124309
124824
  const parentSessionId = value2.parentSessionId;
124310
124825
  if (parentSessionId !== undefined && typeof parentSessionId !== "string") {
124311
- throw invalidFile(path7, 1, "has invalid parentSessionId");
124826
+ throw new JsonlDecodeError("schema", "has invalid parentSessionId");
124312
124827
  }
124313
124828
  const legacyParentSessionPath = value2.legacyParentSessionPath;
124314
124829
  if (legacyParentSessionPath !== undefined && typeof legacyParentSessionPath !== "string") {
124315
- throw invalidFile(path7, 1, "has invalid legacyParentSessionPath");
124830
+ throw new JsonlDecodeError("schema", "has invalid legacyParentSessionPath");
124316
124831
  }
124317
124832
  if (parentSessionId !== undefined && legacyParentSessionPath !== undefined) {
124318
- throw invalidFile(path7, 1, "has both parentSessionId and legacyParentSessionPath");
124833
+ throw new JsonlDecodeError("schema", "has both parentSessionId and legacyParentSessionPath");
124319
124834
  }
124320
124835
  const metadataValue = value2.metadata;
124321
- if (metadataValue !== undefined && !isObject(metadataValue))
124322
- throw invalidFile(path7, 1, "has invalid metadata");
124836
+ if (metadataValue !== undefined && !isObject(metadataValue)) {
124837
+ throw new JsonlDecodeError("schema", "has invalid metadata");
124838
+ }
124323
124839
  const metadata = metadataValue;
124324
124840
  return {
124325
124841
  kind: "header",
124326
124842
  version: 4,
124327
- id: requireString(value2.id, path7, 1, "id"),
124328
- createdAt: requireTimestamp(value2.createdAt, path7, 1),
124329
- cwd: requireString(value2.cwd, path7, 1, "cwd"),
124843
+ id: requireString(value2.id, "id"),
124844
+ createdAt: requireTimestamp(value2.createdAt),
124845
+ cwd: requireString(value2.cwd, "cwd"),
124330
124846
  parentSessionId,
124331
124847
  legacyParentSessionPath,
124332
124848
  metadata
124333
124849
  };
124334
124850
  }
124851
+ function parseHeader(line) {
124852
+ try {
124853
+ return ok(decodeHeader(line));
124854
+ } catch (error) {
124855
+ if (error instanceof JsonlDecodeError)
124856
+ return err(error);
124857
+ throw error;
124858
+ }
124859
+ }
124335
124860
  function encodeHeader(header) {
124336
124861
  return `${JSON.stringify(header)}
124337
124862
  `;
@@ -124349,73 +124874,97 @@ function metadataFromHeader(header, path7, modifiedAt) {
124349
124874
  ...header.metadata === undefined ? {} : { metadata: header.metadata }
124350
124875
  };
124351
124876
  }
124352
- function parseMutation(line, path7, lineNumber) {
124353
- const value2 = parseObject(line, path7, lineNumber);
124354
- const seq = requireSequence(value2.seq, path7, lineNumber);
124355
- switch (value2.kind) {
124356
- case "entry": {
124357
- const lane = value2.lane === undefined ? undefined : requireString(value2.lane, path7, lineNumber, "lane");
124358
- const id = requireString(value2.id, path7, lineNumber, "id");
124359
- const type = requireString(value2.type, path7, lineNumber, "entry type");
124360
- if (!ENTRY_TYPES.has(type))
124361
- throw invalidFile(path7, lineNumber, `has unknown entry type ${type}`);
124362
- const parentId = requireNullableId(value2.parentId, path7, lineNumber, "parentId");
124363
- const timestamp = requireTimestamp(value2.timestamp, path7, lineNumber);
124364
- if (type === "custom")
124365
- requireString(value2.customType, path7, lineNumber, "customType");
124366
- const { kind: _kind, lane: _lane, ...entryFields } = value2;
124367
- const entry = { ...entryFields, id, type, parentId, seq, timestamp };
124368
- return lane === undefined ? { kind: "entry", entry } : { kind: "entry", lane, entry };
124369
- }
124370
- case "record": {
124371
- const id = requireString(value2.id, path7, lineNumber, "id");
124372
- const lane = requireString(value2.lane, path7, lineNumber, "lane");
124373
- const type = requireString(value2.type, path7, lineNumber, "record type");
124374
- if (!RECORD_TYPES.has(type))
124375
- throw invalidFile(path7, lineNumber, `has unknown record type ${type}`);
124376
- const timestamp = requireTimestamp(value2.timestamp, path7, lineNumber);
124377
- if (type === "operation_started") {
124378
- if (!isObject(value2.intent))
124379
- throw invalidFile(path7, lineNumber, "has invalid intent");
124380
- const operationKind = requireString(value2.intent.kind, path7, lineNumber, "operation kind");
124381
- if (!OPERATION_KINDS.has(operationKind)) {
124382
- throw invalidFile(path7, lineNumber, `has unknown operation kind ${operationKind}`);
124383
- }
124384
- }
124385
- if (type === "operation_finished")
124386
- requireString(value2.runId, path7, lineNumber, "runId");
124387
- const { kind: _kind, ...recordFields } = value2;
124388
- return {
124389
- kind: "record",
124390
- record: { ...recordFields, id, lane, type, seq, timestamp }
124391
- };
124877
+ function parseEntryMutation(value2, seq) {
124878
+ const lane = value2.lane === undefined ? undefined : requireString(value2.lane, "lane");
124879
+ const id = requireString(value2.id, "id");
124880
+ const type = requireString(value2.type, "entry type");
124881
+ if (!ENTRY_TYPES.has(type)) {
124882
+ throw new JsonlDecodeError("schema", `has unknown entry type ${type}`);
124883
+ }
124884
+ const parentId = requireNullableId(value2.parentId, "parentId");
124885
+ const timestamp = requireTimestamp(value2.timestamp);
124886
+ if (type === "custom")
124887
+ requireString(value2.customType, "customType");
124888
+ const { kind: _kind, lane: _lane, ...entryFields } = value2;
124889
+ const entry = { ...entryFields, id, type, parentId, seq, timestamp };
124890
+ return lane === undefined ? { kind: "entry", entry } : { kind: "entry", lane, entry };
124891
+ }
124892
+ function parseRecordMutation(value2, seq) {
124893
+ const id = requireString(value2.id, "id");
124894
+ const lane = requireString(value2.lane, "lane");
124895
+ const type = requireString(value2.type, "record type");
124896
+ if (!RECORD_TYPES.has(type)) {
124897
+ throw new JsonlDecodeError("schema", `has unknown record type ${type}`);
124898
+ }
124899
+ const timestamp = requireTimestamp(value2.timestamp);
124900
+ if (type === "operation_started") {
124901
+ if (!isObject(value2.intent))
124902
+ throw new JsonlDecodeError("schema", "has invalid intent");
124903
+ const operationKind = requireString(value2.intent.kind, "operation kind");
124904
+ if (!OPERATION_KINDS.has(operationKind)) {
124905
+ throw new JsonlDecodeError("schema", `has unknown operation kind ${operationKind}`);
124906
+ }
124907
+ }
124908
+ if (type === "operation_finished")
124909
+ requireString(value2.runId, "runId");
124910
+ const { kind: _kind, ...recordFields } = value2;
124911
+ return {
124912
+ kind: "record",
124913
+ record: { ...recordFields, id, lane, type, seq, timestamp }
124914
+ };
124915
+ }
124916
+ function parseLaneMutation(value2, seq) {
124917
+ return {
124918
+ kind: "lane",
124919
+ seq,
124920
+ lane: requireString(value2.lane, "lane"),
124921
+ leafId: requireNullableId(value2.leafId, "leafId")
124922
+ };
124923
+ }
124924
+ function parseFactMutation(value2, seq) {
124925
+ if (value2.fact === "name") {
124926
+ if (value2.name !== undefined && typeof value2.name !== "string") {
124927
+ throw new JsonlDecodeError("schema", "has invalid name");
124928
+ }
124929
+ return { kind: "fact", seq, fact: "name", name: value2.name };
124930
+ }
124931
+ if (value2.fact === "label") {
124932
+ if (value2.label !== undefined && typeof value2.label !== "string") {
124933
+ throw new JsonlDecodeError("schema", "has invalid label");
124392
124934
  }
124935
+ return {
124936
+ kind: "fact",
124937
+ seq,
124938
+ fact: "label",
124939
+ targetId: requireString(value2.targetId, "targetId"),
124940
+ label: value2.label
124941
+ };
124942
+ }
124943
+ throw new JsonlDecodeError("schema", "has unknown fact type");
124944
+ }
124945
+ function decodeMutation(line) {
124946
+ const value2 = parseObject(line);
124947
+ const seq = requireSequence(value2.seq);
124948
+ switch (value2.kind) {
124949
+ case "entry":
124950
+ return parseEntryMutation(value2, seq);
124951
+ case "record":
124952
+ return parseRecordMutation(value2, seq);
124393
124953
  case "lane":
124394
- return {
124395
- kind: "lane",
124396
- seq,
124397
- lane: requireString(value2.lane, path7, lineNumber, "lane"),
124398
- leafId: requireNullableId(value2.leafId, path7, lineNumber, "leafId")
124399
- };
124954
+ return parseLaneMutation(value2, seq);
124400
124955
  case "fact":
124401
- if (value2.fact === "name") {
124402
- return { kind: "fact", seq, fact: "name", name: requireString(value2.name, path7, lineNumber, "name") };
124403
- }
124404
- if (value2.fact === "label") {
124405
- if (value2.label !== undefined && typeof value2.label !== "string") {
124406
- throw invalidFile(path7, lineNumber, "has invalid label");
124407
- }
124408
- return {
124409
- kind: "fact",
124410
- seq,
124411
- fact: "label",
124412
- targetId: requireString(value2.targetId, path7, lineNumber, "targetId"),
124413
- label: value2.label
124414
- };
124415
- }
124416
- throw invalidFile(path7, lineNumber, "has unknown fact type");
124956
+ return parseFactMutation(value2, seq);
124417
124957
  default:
124418
- throw invalidFile(path7, lineNumber, "has unknown mutation kind");
124958
+ throw new JsonlDecodeError("schema", "has unknown mutation kind");
124959
+ }
124960
+ }
124961
+ function parseMutation(line) {
124962
+ try {
124963
+ return ok(decodeMutation(line));
124964
+ } catch (error) {
124965
+ if (error instanceof JsonlDecodeError)
124966
+ return err(error);
124967
+ throw error;
124419
124968
  }
124420
124969
  }
124421
124970
  function encodeMutation(mutation) {
@@ -124768,28 +125317,38 @@ class JsonlSessionStorage {
124768
125317
  `);
124769
125318
  if (physicalLines.at(-1) === "")
124770
125319
  physicalLines.pop();
124771
- if (physicalLines.length === 0 || !physicalLines[0])
124772
- throw invalidFile(path7, 1, "is missing a header");
124773
- const header = parseHeader(physicalLines[0], path7);
125320
+ if (physicalLines.length === 0 || !physicalLines[0]) {
125321
+ throw invalidFile(path7, 1, new JsonlDecodeError("schema", "is missing a header"));
125322
+ }
125323
+ const headerResult = parseHeader(physicalLines[0]);
125324
+ if (!headerResult.ok)
125325
+ throw invalidFile(path7, 1, headerResult.error);
124774
125326
  const fileInfo = fileResult(await fs5.fileInfo(path7), `Failed to read session metadata ${path7}`);
124775
- const storage = new JsonlSessionStorage(fs5, metadataFromHeader(header, path7, fileInfo.mtimeMs));
125327
+ const storage = new JsonlSessionStorage(fs5, metadataFromHeader(headerResult.value, path7, fileInfo.mtimeMs));
124776
125328
  for (let index = 1;index < physicalLines.length; index++) {
124777
125329
  const line = physicalLines[index];
124778
- let mutation;
124779
- try {
124780
- mutation = parseMutation(line, path7, index + 1);
124781
- } catch (error) {
124782
- if (index !== physicalLines.length - 1 || !(error instanceof SessionError) || error.cause === undefined)
124783
- throw error;
124784
- const validPrefix = `${physicalLines.slice(0, index).join(`
125330
+ const mutationResult = parseMutation(line);
125331
+ if (!mutationResult.ok) {
125332
+ const isTornTail = index === physicalLines.length - 1 && mutationResult.error.kind === "syntax";
125333
+ if (isTornTail) {
125334
+ const validPrefix = `${physicalLines.slice(0, index).join(`
124785
125335
  `)}
124786
125336
  `;
124787
- await publishFileAtomically(fs5, path7, async (tempPath) => {
124788
- fileResult(await fs5.writeFile(tempPath, validPrefix), `Failed to stage torn-tail repair ${path7}`);
124789
- });
124790
- return storage;
125337
+ await publishFileAtomically(fs5, path7, async (tempPath) => {
125338
+ fileResult(await fs5.writeFile(tempPath, validPrefix), `Failed to stage torn-tail repair ${path7}`);
125339
+ });
125340
+ return storage;
125341
+ }
125342
+ throw invalidFile(path7, index + 1, mutationResult.error);
125343
+ }
125344
+ try {
125345
+ storage.applyMutation(mutationResult.value);
125346
+ } catch (error) {
125347
+ if (error instanceof SessionError && error.code === "invalid_entry") {
125348
+ throw invalidFile(path7, index + 1, error);
125349
+ }
125350
+ throw error;
124791
125351
  }
124792
- storage.applyMutation(mutation, path7, index + 1);
124793
125352
  }
124794
125353
  if (!content.endsWith(`
124795
125354
  `)) {
@@ -124932,10 +125491,8 @@ class JsonlSessionStorage {
124932
125491
  async appendMutation(mutation) {
124933
125492
  fileResult(await this.fs.appendFile(this.metadata.path, encodeMutation(mutation)), `Failed to append session ${this.metadata.path}`);
124934
125493
  }
124935
- applyMutation(mutation, path7 = this.metadata.path, line = this.state.nextSequence + 1) {
124936
- this.state.applyMutation(mutation, (message) => {
124937
- throw invalidFile(path7, line, message);
124938
- });
125494
+ applyMutation(mutation) {
125495
+ this.state.applyMutation(mutation);
124939
125496
  }
124940
125497
  }
124941
125498
 
@@ -124946,9 +125503,53 @@ function validateSessionId(id) {
124946
125503
  throw new SessionError("invalid_payload", "Session id must be non-empty, contain only alphanumeric characters, '-', '_', and '.', and start and end with an alphanumeric character");
124947
125504
  }
124948
125505
  }
124949
- function sessionDirectoryName(cwd) {
125506
+ function jsonlSessionDirectoryName(cwd) {
124950
125507
  return `--${cwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
124951
125508
  }
125509
+ async function jsonlSessionsRoot(options) {
125510
+ return fileResult(await options.fs.absolutePath(options.sessionsRoot), `Failed to resolve sessions root ${options.sessionsRoot}`);
125511
+ }
125512
+ async function jsonlSessionDirectory(fs5, sessionsRoot, cwd) {
125513
+ return fileResult(await fs5.joinPath([sessionsRoot, jsonlSessionDirectoryName(cwd)]), `Failed to resolve sessions directory for ${cwd}`);
125514
+ }
125515
+ async function jsonlSessionDirectories(options, cwd) {
125516
+ const sessionsRoot = await jsonlSessionsRoot(options);
125517
+ if (cwd !== undefined) {
125518
+ const resolvedCwd = fileResult(await options.fs.absolutePath(cwd), `Failed to resolve session cwd ${cwd}`);
125519
+ const directory = await jsonlSessionDirectory(options.fs, sessionsRoot, resolvedCwd);
125520
+ return fileResult(await options.fs.exists(directory), `Failed to check sessions directory ${directory}`) ? [directory] : [];
125521
+ }
125522
+ if (!fileResult(await options.fs.exists(sessionsRoot), `Failed to check sessions directory ${sessionsRoot}`))
125523
+ return [];
125524
+ return fileResult(await options.fs.listDir(sessionsRoot), `Failed to list sessions directory ${sessionsRoot}`).filter((entry) => entry.kind === "directory" || entry.kind === "symlink").map((entry) => entry.path);
125525
+ }
125526
+ async function listJsonlSessionMetadata(options, query = {}) {
125527
+ const metadata = [];
125528
+ for (const directory of await jsonlSessionDirectories(options, query.cwd)) {
125529
+ const files = fileResult(await options.fs.listDir(directory), `Failed to list sessions directory ${directory}`).filter((entry) => entry.kind !== "directory" && entry.name.endsWith(".jsonl"));
125530
+ for (const file of files) {
125531
+ const [firstLine] = fileResult(await options.fs.readTextLines(file.path, { maxLines: 1 }), `Failed to read session header ${file.path}`);
125532
+ if (!firstLine)
125533
+ continue;
125534
+ const headerResult = parseHeader(firstLine);
125535
+ if (!headerResult.ok)
125536
+ continue;
125537
+ metadata.push(metadataFromHeader(headerResult.value, file.path, file.mtimeMs));
125538
+ }
125539
+ }
125540
+ return metadata.sort((left, right) => right.modifiedAt - left.modifiedAt);
125541
+ }
125542
+ async function loadJsonlSessionStorage(options, metadata) {
125543
+ if (!fileResult(await options.fs.exists(metadata.path), `Failed to check session ${metadata.path}`)) {
125544
+ throw new SessionError("not_found", `Session not found: ${metadata.id}`);
125545
+ }
125546
+ const storage = await JsonlSessionStorage.load(options.fs, metadata.path);
125547
+ const loadedMetadata = await storage.getMetadata();
125548
+ if (loadedMetadata.id !== metadata.id) {
125549
+ throw new SessionError("invalid_entry", `Session id does not match header: ${metadata.id}`);
125550
+ }
125551
+ return storage;
125552
+ }
124952
125553
  function sessionFileName(createdAt, id) {
124953
125554
  const timestamp = new Date(createdAt).toISOString().replace(/[:.]/g, "-");
124954
125555
  return `${timestamp}_${id}.jsonl`;
@@ -124957,14 +125558,18 @@ function sessionFileName(createdAt, id) {
124957
125558
  class JsonlSessionRepo {
124958
125559
  fs;
124959
125560
  sessionsRootInput;
125561
+ activeCreateDestinations = new Set;
124960
125562
  rootPromise;
124961
125563
  constructor(options) {
124962
125564
  this.fs = options.fs;
124963
125565
  this.sessionsRootInput = options.sessionsRoot;
124964
125566
  }
124965
125567
  async create(options) {
124966
- const { header, path: path7 } = await this.prepareCreate(options);
124967
- return new Session(await JsonlSessionStorage.create(this.fs, path7, header));
125568
+ const destination = await this.resolveCreateDestination(options);
125569
+ return this.claimCreateDestination(destination, async () => {
125570
+ const { header, path: path7 } = await this.prepareCreate(destination, options);
125571
+ return new Session(await JsonlSessionStorage.create(this.fs, path7, header));
125572
+ });
124968
125573
  }
124969
125574
  async open(metadata) {
124970
125575
  return new Session(await this.loadStorage(metadata));
@@ -124977,27 +125582,39 @@ class JsonlSessionRepo {
124977
125582
  }
124978
125583
  async fork(source, options) {
124979
125584
  const sourceStorage = await this.loadStorage(source);
124980
- const { header, path: path7 } = await this.prepareCreate({
125585
+ const createOptions = {
124981
125586
  ...options,
124982
125587
  parentSessionId: options.parentSessionId ?? source.id
125588
+ };
125589
+ const destination = await this.resolveCreateDestination(createOptions);
125590
+ return this.claimCreateDestination(destination, async () => {
125591
+ const { header, path: path7 } = await this.prepareCreate(destination, createOptions);
125592
+ return new Session(await sourceStorage.fork(path7, header, options));
124983
125593
  });
124984
- return new Session(await sourceStorage.fork(path7, header, options));
124985
125594
  }
124986
125595
  async loadStorage(metadata) {
124987
- if (!fileResult(await this.fs.exists(metadata.path), `Failed to check session ${metadata.path}`)) {
124988
- throw new SessionError("not_found", `Session not found: ${metadata.id}`);
124989
- }
124990
- const storage = await JsonlSessionStorage.load(this.fs, metadata.path);
124991
- const loadedMetadata = await storage.getMetadata();
124992
- if (loadedMetadata.id !== metadata.id) {
124993
- throw new SessionError("invalid_entry", `Session id does not match header: ${metadata.id}`);
124994
- }
124995
- return storage;
125596
+ return loadJsonlSessionStorage({ fs: this.fs, sessionsRoot: this.sessionsRootInput }, metadata);
124996
125597
  }
124997
- async prepareCreate(options) {
125598
+ async resolveCreateDestination(options) {
124998
125599
  const id = options.id ?? uuidv7();
124999
125600
  validateSessionId(id);
125000
125601
  const cwd = fileResult(await this.fs.absolutePath(options.cwd), `Failed to resolve session cwd ${options.cwd}`);
125602
+ return { id, cwd };
125603
+ }
125604
+ async claimCreateDestination(destination, operation) {
125605
+ const key = `${destination.cwd}\x00${destination.id}`;
125606
+ if (this.activeCreateDestinations.has(key)) {
125607
+ throw new SessionError("already_exists", `Session already exists: ${destination.id}`);
125608
+ }
125609
+ this.activeCreateDestinations.add(key);
125610
+ try {
125611
+ return await operation();
125612
+ } finally {
125613
+ this.activeCreateDestinations.delete(key);
125614
+ }
125615
+ }
125616
+ async prepareCreate(destination, options) {
125617
+ const { id, cwd } = destination;
125001
125618
  if (await this.sessionIdExists(id, cwd)) {
125002
125619
  throw new SessionError("already_exists", `Session already exists: ${id}`);
125003
125620
  }
@@ -125019,20 +125636,7 @@ class JsonlSessionRepo {
125019
125636
  return { header, path: path7 };
125020
125637
  }
125021
125638
  async listDirect(options) {
125022
- const directories = await this.sessionDirectories(options.cwd);
125023
- const metadata = [];
125024
- for (const directory of directories) {
125025
- const files = fileResult(await this.fs.listDir(directory), `Failed to list sessions directory ${directory}`).filter((entry) => entry.kind !== "directory" && entry.name.endsWith(".jsonl"));
125026
- for (const file of files) {
125027
- const content = fileResult(await this.fs.readTextFile(file.path), `Failed to read session header ${file.path}`);
125028
- const firstLine = content.split(`
125029
- `, 1)[0];
125030
- if (!firstLine)
125031
- throw invalidFile(file.path, 1, "is missing a header");
125032
- metadata.push(metadataFromHeader(parseHeader(firstLine, file.path), file.path, file.mtimeMs));
125033
- }
125034
- }
125035
- return metadata.sort((left, right) => right.modifiedAt - left.modifiedAt);
125639
+ return listJsonlSessionMetadata({ fs: this.fs, sessionsRoot: this.sessionsRootInput }, options);
125036
125640
  }
125037
125641
  async sessionIdExists(id, cwd) {
125038
125642
  const suffix = `_${id}.jsonl`;
@@ -125042,19 +125646,8 @@ class JsonlSessionRepo {
125042
125646
  const files = fileResult(await this.fs.listDir(directory), `Failed to list sessions directory ${directory}`);
125043
125647
  return files.some((entry) => entry.kind !== "directory" && entry.name.endsWith(suffix));
125044
125648
  }
125045
- async sessionDirectories(cwd) {
125046
- const root = await this.root();
125047
- if (cwd !== undefined) {
125048
- const resolvedCwd = fileResult(await this.fs.absolutePath(cwd), `Failed to resolve session cwd ${cwd}`);
125049
- const directory = await this.sessionDirectory(resolvedCwd);
125050
- return fileResult(await this.fs.exists(directory), `Failed to check sessions directory ${directory}`) ? [directory] : [];
125051
- }
125052
- if (!fileResult(await this.fs.exists(root), `Failed to check sessions directory ${root}`))
125053
- return [];
125054
- return fileResult(await this.fs.listDir(root), `Failed to list sessions directory ${root}`).filter((entry) => entry.kind === "directory" || entry.kind === "symlink").map((entry) => entry.path);
125055
- }
125056
125649
  async sessionDirectory(cwd) {
125057
- return fileResult(await this.fs.joinPath([await this.root(), sessionDirectoryName(cwd)]), `Failed to resolve sessions directory for ${cwd}`);
125650
+ return fileResult(await this.fs.joinPath([await this.root(), jsonlSessionDirectoryName(cwd)]), `Failed to resolve sessions directory for ${cwd}`);
125058
125651
  }
125059
125652
  root() {
125060
125653
  this.rootPromise ??= this.fs.absolutePath(this.sessionsRootInput).then((result) => fileResult(result, `Failed to resolve sessions root ${this.sessionsRootInput}`));
@@ -125199,71 +125792,6 @@ class InMemorySessionRepo {
125199
125792
  return storage;
125200
125793
  }
125201
125794
  }
125202
- // node_modules/@earendil-works/pi-agent-core/dist/harness/types.js
125203
- function ok(value2) {
125204
- return { ok: true, value: value2 };
125205
- }
125206
- function err(error) {
125207
- return { ok: false, error };
125208
- }
125209
- function getOrThrow(result) {
125210
- if (!result.ok)
125211
- throw result.error;
125212
- return result.value;
125213
- }
125214
- function getOrUndefined(result) {
125215
- return result.ok ? result.value : undefined;
125216
- }
125217
- function toError(error) {
125218
- if (error instanceof Error)
125219
- return error;
125220
- if (typeof error === "string")
125221
- return new Error(error);
125222
- try {
125223
- return new Error(JSON.stringify(error));
125224
- } catch {
125225
- return new Error(String(error));
125226
- }
125227
- }
125228
-
125229
- class FileError extends Error {
125230
- code;
125231
- path;
125232
- constructor(code, message, path7, cause) {
125233
- super(message, cause === undefined ? undefined : { cause });
125234
- this.name = "FileError";
125235
- this.code = code;
125236
- this.path = path7;
125237
- }
125238
- }
125239
-
125240
- class ExecutionError extends Error {
125241
- code;
125242
- constructor(code, message, cause) {
125243
- super(message, cause === undefined ? undefined : { cause });
125244
- this.name = "ExecutionError";
125245
- this.code = code;
125246
- }
125247
- }
125248
-
125249
- class CompactionError extends Error {
125250
- code;
125251
- constructor(code, message, cause) {
125252
- super(message, cause === undefined ? undefined : { cause });
125253
- this.name = "CompactionError";
125254
- this.code = code;
125255
- }
125256
- }
125257
-
125258
- class BranchSummaryError extends Error {
125259
- code;
125260
- constructor(code, message, cause) {
125261
- super(message, cause === undefined ? undefined : { cause });
125262
- this.name = "BranchSummaryError";
125263
- this.code = code;
125264
- }
125265
- }
125266
-
125267
125795
  // node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/utils.js
125268
125796
  function createFileOps2() {
125269
125797
  return {
@@ -126311,48 +126839,6 @@ function substituteArgs(content, args) {
126311
126839
  function formatPromptTemplateInvocation(template, args = []) {
126312
126840
  return substituteArgs(template.content, args);
126313
126841
  }
126314
- // node_modules/@earendil-works/pi-agent-core/dist/harness/session/search.js
126315
- function getFileSystemResultOrThrow(result, message) {
126316
- if (!result.ok) {
126317
- const code = result.error.code === "not_found" ? "not_found" : "storage";
126318
- throw new SessionError(code, `${message}: ${result.error.message}`, result.error);
126319
- }
126320
- return result.value;
126321
- }
126322
-
126323
- class ScanningSessionSearch {
126324
- source;
126325
- constructor(source) {
126326
- this.source = source;
126327
- }
126328
- async search(options) {
126329
- const normalizedText = options.text.trim().toLowerCase();
126330
- if (!normalizedText)
126331
- return [];
126332
- const hits = [];
126333
- for (const metadata of await this.source.list()) {
126334
- const cwd = metadata.cwd;
126335
- if (options.cwd !== undefined && cwd !== options.cwd)
126336
- continue;
126337
- const session2 = await this.source.open(metadata);
126338
- for (const entry of await session2.findEntries({ order: "oldestFirst" })) {
126339
- const payload = JSON.stringify(entry);
126340
- if (!payload.toLowerCase().includes(normalizedText))
126341
- continue;
126342
- hits.push({
126343
- metadata,
126344
- entryId: entry.id,
126345
- timestamp: new Date(entry.timestamp).toISOString(),
126346
- snippet: payload
126347
- });
126348
- }
126349
- }
126350
- return hits;
126351
- }
126352
- }
126353
- function createScanningSessionSearch(source) {
126354
- return new ScanningSessionSearch(source);
126355
- }
126356
126842
  // node_modules/@earendil-works/pi-agent-core/dist/harness/skills.js
126357
126843
  var import_ignore = __toESM(require_ignore(), 1);
126358
126844
  var MAX_NAME_LENGTH = 64;
@@ -128028,9 +128514,9 @@ function overlapCount(a, b2) {
128028
128514
  }
128029
128515
  return k;
128030
128516
  }
128031
- function segment(string, segmenter2) {
128517
+ function segment(string, segmenter3) {
128032
128518
  const parts = [];
128033
- for (const segmentObj of Array.from(segmenter2.segment(string))) {
128519
+ for (const segmentObj of Array.from(segmenter3.segment(string))) {
128034
128520
  const segment2 = segmentObj.segment;
128035
128521
  if (parts.length && /\s/.test(parts[parts.length - 1]) && /\s/.test(segment2)) {
128036
128522
  parts[parts.length - 1] += segment2;
@@ -128040,9 +128526,9 @@ function segment(string, segmenter2) {
128040
128526
  }
128041
128527
  return parts;
128042
128528
  }
128043
- function trailingWs(string, segmenter2) {
128044
- if (segmenter2) {
128045
- return leadingAndTrailingWs(string, segmenter2)[1];
128529
+ function trailingWs(string, segmenter3) {
128530
+ if (segmenter3) {
128531
+ return leadingAndTrailingWs(string, segmenter3)[1];
128046
128532
  }
128047
128533
  let i;
128048
128534
  for (i = string.length - 1;i >= 0; i--) {
@@ -128052,21 +128538,21 @@ function trailingWs(string, segmenter2) {
128052
128538
  }
128053
128539
  return string.substring(i + 1);
128054
128540
  }
128055
- function leadingWs(string, segmenter2) {
128056
- if (segmenter2) {
128057
- return leadingAndTrailingWs(string, segmenter2)[0];
128541
+ function leadingWs(string, segmenter3) {
128542
+ if (segmenter3) {
128543
+ return leadingAndTrailingWs(string, segmenter3)[0];
128058
128544
  }
128059
128545
  const match = string.match(/^\s*/);
128060
128546
  return match ? match[0] : "";
128061
128547
  }
128062
- function leadingAndTrailingWs(string, segmenter2) {
128063
- if (!segmenter2) {
128548
+ function leadingAndTrailingWs(string, segmenter3) {
128549
+ if (!segmenter3) {
128064
128550
  return [leadingWs(string), trailingWs(string)];
128065
128551
  }
128066
- if (segmenter2.resolvedOptions().granularity != "word") {
128552
+ if (segmenter3.resolvedOptions().granularity != "word") {
128067
128553
  throw new Error('The segmenter passed must have a granularity of "word"');
128068
128554
  }
128069
- const segments = segment(string, segmenter2);
128555
+ const segments = segment(string, segmenter3);
128070
128556
  const firstSeg = segments[0];
128071
128557
  const lastSeg = segments[segments.length - 1];
128072
128558
  const head = /\s/.test(firstSeg) ? firstSeg : "";
@@ -128089,11 +128575,11 @@ class WordDiff extends Diff {
128089
128575
  tokenize(value2, options = {}) {
128090
128576
  let parts;
128091
128577
  if (options.intlSegmenter) {
128092
- const segmenter2 = options.intlSegmenter;
128093
- if (segmenter2.resolvedOptions().granularity != "word") {
128578
+ const segmenter3 = options.intlSegmenter;
128579
+ if (segmenter3.resolvedOptions().granularity != "word") {
128094
128580
  throw new Error('The segmenter passed must have a granularity of "word"');
128095
128581
  }
128096
- parts = segment(value2, segmenter2);
128582
+ parts = segment(value2, segmenter3);
128097
128583
  } else {
128098
128584
  parts = value2.match(tokenizeIncludingWhitespace) || [];
128099
128585
  }
@@ -128162,10 +128648,10 @@ function diffWords(oldStr, newStr, options) {
128162
128648
  }
128163
128649
  return wordDiff.diff(oldStr, newStr, options);
128164
128650
  }
128165
- function dedupeWhitespaceInChangeObjects(startKeep, deletion, insertion, endKeep, segmenter2) {
128651
+ function dedupeWhitespaceInChangeObjects(startKeep, deletion, insertion, endKeep, segmenter3) {
128166
128652
  if (deletion && insertion) {
128167
- const [oldWsPrefix, oldWsSuffix] = leadingAndTrailingWs(deletion.value, segmenter2);
128168
- const [newWsPrefix, newWsSuffix] = leadingAndTrailingWs(insertion.value, segmenter2);
128653
+ const [oldWsPrefix, oldWsSuffix] = leadingAndTrailingWs(deletion.value, segmenter3);
128654
+ const [newWsPrefix, newWsSuffix] = leadingAndTrailingWs(insertion.value, segmenter3);
128169
128655
  if (startKeep) {
128170
128656
  const commonWsPrefix = longestCommonPrefix(oldWsPrefix, newWsPrefix);
128171
128657
  startKeep.value = replaceSuffix(startKeep.value, newWsPrefix, commonWsPrefix);
@@ -128180,15 +128666,15 @@ function dedupeWhitespaceInChangeObjects(startKeep, deletion, insertion, endKeep
128180
128666
  }
128181
128667
  } else if (insertion) {
128182
128668
  if (startKeep) {
128183
- const ws = leadingWs(insertion.value, segmenter2);
128669
+ const ws = leadingWs(insertion.value, segmenter3);
128184
128670
  insertion.value = insertion.value.substring(ws.length);
128185
128671
  }
128186
128672
  if (endKeep) {
128187
- const ws = leadingWs(endKeep.value, segmenter2);
128673
+ const ws = leadingWs(endKeep.value, segmenter3);
128188
128674
  endKeep.value = endKeep.value.substring(ws.length);
128189
128675
  }
128190
128676
  } else if (startKeep && endKeep) {
128191
- const newWsFull = leadingWs(endKeep.value, segmenter2), [delWsStart, delWsEnd] = leadingAndTrailingWs(deletion.value, segmenter2);
128677
+ const newWsFull = leadingWs(endKeep.value, segmenter3), [delWsStart, delWsEnd] = leadingAndTrailingWs(deletion.value, segmenter3);
128192
128678
  const newWsStart = longestCommonPrefix(newWsFull, delWsStart);
128193
128679
  deletion.value = removePrefix(deletion.value, newWsStart);
128194
128680
  const newWsEnd = longestCommonSuffix(removePrefix(newWsFull, newWsStart), delWsEnd);
@@ -128196,13 +128682,13 @@ function dedupeWhitespaceInChangeObjects(startKeep, deletion, insertion, endKeep
128196
128682
  endKeep.value = replacePrefix(endKeep.value, newWsFull, newWsEnd);
128197
128683
  startKeep.value = replaceSuffix(startKeep.value, newWsFull, newWsFull.slice(0, newWsFull.length - newWsEnd.length));
128198
128684
  } else if (endKeep) {
128199
- const endKeepWsPrefix = leadingWs(endKeep.value, segmenter2);
128200
- const deletionWsSuffix = trailingWs(deletion.value, segmenter2);
128685
+ const endKeepWsPrefix = leadingWs(endKeep.value, segmenter3);
128686
+ const deletionWsSuffix = trailingWs(deletion.value, segmenter3);
128201
128687
  const overlap = maximumOverlap(deletionWsSuffix, endKeepWsPrefix);
128202
128688
  deletion.value = removeSuffix(deletion.value, overlap);
128203
128689
  } else if (startKeep) {
128204
- const startKeepWsSuffix = trailingWs(startKeep.value, segmenter2);
128205
- const deletionWsPrefix = leadingWs(deletion.value, segmenter2);
128690
+ const startKeepWsSuffix = trailingWs(startKeep.value, segmenter3);
128691
+ const deletionWsPrefix = leadingWs(deletion.value, segmenter3);
128206
128692
  const overlap = maximumOverlap(startKeepWsSuffix, deletionWsPrefix);
128207
128693
  deletion.value = removePrefix(deletion.value, overlap);
128208
128694
  }
@@ -129532,6 +130018,7 @@ function processProxyEvent(proxyEvent, partial) {
129532
130018
  case "toolcall_end": {
129533
130019
  const content = partial.content[proxyEvent.contentIndex];
129534
130020
  if (content?.type === "toolCall") {
130021
+ Object.assign(content, proxyEvent.toolCall);
129535
130022
  delete content.partialJson;
129536
130023
  return {
129537
130024
  type: "toolcall_end",
@@ -129558,6 +130045,108 @@ function processProxyEvent(proxyEvent, partial) {
129558
130045
  }
129559
130046
  }
129560
130047
  }
130048
+ // node_modules/@earendil-works/pi-agent-core/dist/search/scanning.js
130049
+ function defaultSearchText(_metadata, entry, label) {
130050
+ return label === undefined ? JSON.stringify(entry) : `${JSON.stringify(entry)} ${label}`;
130051
+ }
130052
+ async function* scanReadableEntries(readable, metadata, options, query = {}) {
130053
+ const projectText = options.projectText ?? defaultSearchText;
130054
+ const pageSize = query.limit ?? options.pageSize ?? 100;
130055
+ let afterSeq = query.afterSeq ?? 0;
130056
+ const entryTypes = query.entryTypes === undefined ? undefined : new Set(query.entryTypes);
130057
+ while (true) {
130058
+ const entries = await readable.findEntries({
130059
+ order: "oldestFirst",
130060
+ limit: pageSize,
130061
+ cursor: { afterSeq },
130062
+ type: query.entryTypes?.length === 1 ? query.entryTypes[0] : undefined
130063
+ });
130064
+ if (entries.length === 0)
130065
+ break;
130066
+ for (const entry of entries) {
130067
+ if (entryTypes !== undefined && !entryTypes.has(entry.type))
130068
+ continue;
130069
+ const label = await readable.getLabel(entry.id);
130070
+ yield {
130071
+ entryId: entry.id,
130072
+ seq: entry.seq,
130073
+ type: entry.type,
130074
+ timestamp: entry.timestamp,
130075
+ text: projectText(metadata, entry, label),
130076
+ fields: label === undefined ? undefined : { label }
130077
+ };
130078
+ }
130079
+ afterSeq = entries[entries.length - 1]?.seq ?? afterSeq;
130080
+ if (entries.length < pageSize)
130081
+ break;
130082
+ }
130083
+ }
130084
+ async function* scanningEntries(readable, options = {}) {
130085
+ yield* scanReadableEntries(readable, await readable.getMetadata(), options);
130086
+ }
130087
+ async function* arraySource(readables) {
130088
+ yield* readables;
130089
+ }
130090
+ function readablesFor(source, options) {
130091
+ return typeof source === "function" ? source(options) : arraySource(source);
130092
+ }
130093
+ function defaultMatch(queryText, candidate) {
130094
+ return candidate.text.toLowerCase().includes(queryText);
130095
+ }
130096
+ function throwIfAborted(signal) {
130097
+ if (!signal?.aborted)
130098
+ return;
130099
+ if (signal.reason instanceof Error)
130100
+ throw signal.reason;
130101
+ const error = new Error("The operation was aborted");
130102
+ error.name = "AbortError";
130103
+ throw error;
130104
+ }
130105
+ function createDefaultScanningHit(metadata, candidate) {
130106
+ return {
130107
+ sessionId: metadata.id,
130108
+ entryId: candidate.entryId,
130109
+ timestamp: candidate.timestamp,
130110
+ snippet: candidate.text
130111
+ };
130112
+ }
130113
+ function createScanningSessionSearch(source, options = {}) {
130114
+ const createHit = options.createHit ?? ((metadata, candidate) => createDefaultScanningHit(metadata, candidate));
130115
+ return {
130116
+ async* search(text, searchOptions = {}) {
130117
+ const normalizedText = text.trim().toLowerCase();
130118
+ if (!normalizedText || searchOptions.limit !== undefined && searchOptions.limit <= 0)
130119
+ return;
130120
+ if (searchOptions.entryTypes?.length === 0)
130121
+ return;
130122
+ let hitCount = 0;
130123
+ const seenSessionIds = new Set;
130124
+ const entryTypes = searchOptions.entryTypes === undefined ? undefined : new Set(searchOptions.entryTypes);
130125
+ const sourceOptions = options.sourceOptions?.(normalizedText, searchOptions);
130126
+ for await (const readable of readablesFor(source, sourceOptions)) {
130127
+ throwIfAborted(searchOptions.signal);
130128
+ const metadata = await readable.getMetadata();
130129
+ if (seenSessionIds.has(metadata.id))
130130
+ throw new Error(`Duplicate sessionId: ${metadata.id}`);
130131
+ seenSessionIds.add(metadata.id);
130132
+ for await (const candidate of scanReadableEntries(readable, metadata, options, {
130133
+ entryTypes: searchOptions.entryTypes
130134
+ })) {
130135
+ throwIfAborted(searchOptions.signal);
130136
+ if (entryTypes !== undefined && !entryTypes.has(candidate.type))
130137
+ continue;
130138
+ const matches = options.match?.(normalizedText, candidate, metadata) ?? defaultMatch(normalizedText, candidate);
130139
+ if (!matches)
130140
+ continue;
130141
+ yield createHit(metadata, candidate);
130142
+ hitCount += 1;
130143
+ if (searchOptions.limit !== undefined && hitCount >= searchOptions.limit)
130144
+ return;
130145
+ }
130146
+ }
130147
+ }
130148
+ };
130149
+ }
129561
130150
  // node_modules/@earendil-works/pi-ai/dist/oauth.js
129562
130151
  var exports_oauth = {};
129563
130152
 
@@ -132440,7 +133029,8 @@ ${content}
132440
133029
  prompt2 += formatSkillsForPrompt(skills2);
132441
133030
  }
132442
133031
  prompt2 += `
132443
- Current working directory: ${promptCwd}`;
133032
+ Current working directory: ${promptCwd}
133033
+ `;
132444
133034
  return prompt2;
132445
133035
  }
132446
133036
  const readmePath = getReadmePath();
@@ -132571,6 +133161,15 @@ function truncateToVisualLines(text, maxVisualLines, width, paddingX = 0) {
132571
133161
  return { visualLines: truncatedLines, skippedCount };
132572
133162
  }
132573
133163
 
133164
+ // node_modules/@earendil-works/pi-coding-agent/dist/core/experimental.js
133165
+ var PREFER_STRICT_TOOL_SAMPLING = { type: "json_schema", strict: "prefer" };
133166
+ function areExperimentalFeaturesEnabled() {
133167
+ return process.env.PI_EXPERIMENTAL === "1";
133168
+ }
133169
+ function getExperimentalToolSampling() {
133170
+ return areExperimentalFeaturesEnabled() ? PREFER_STRICT_TOOL_SAMPLING : undefined;
133171
+ }
133172
+
132574
133173
  // node_modules/@earendil-works/pi-coding-agent/dist/core/tools/output-accumulator.js
132575
133174
  import { randomBytes as randomBytes2 } from "node:crypto";
132576
133175
  import { createWriteStream as createWriteStream2 } from "node:fs";
@@ -133024,6 +133623,7 @@ function createBashToolDefinition(cwd, options) {
133024
133623
  promptSnippet: bashToolSystemPromptContribution.snippet,
133025
133624
  promptGuidelines: exposeSessionEnvironment ? [...bashToolSystemPromptContribution.guidelines] : undefined,
133026
133625
  parameters: bashSchema2,
133626
+ constrainedSampling: getExperimentalToolSampling(),
133027
133627
  async execute(_toolCallId, { command, timeout }, signal, onUpdate, ctx) {
133028
133628
  const resolvedCommand = commandPrefix ? `${commandPrefix}
133029
133629
  ${command}` : command;
@@ -133918,36 +134518,37 @@ function createEditToolDefinition(cwd, options) {
133918
134518
  promptSnippet: editToolSystemPromptContribution.snippet,
133919
134519
  promptGuidelines: [...editToolSystemPromptContribution.guidelines],
133920
134520
  parameters: editSchema2,
134521
+ constrainedSampling: getExperimentalToolSampling(),
133921
134522
  renderShell: "self",
133922
134523
  prepareArguments: prepareEditArguments2,
133923
134524
  async execute(_toolCallId, input, signal, _onUpdate, _ctx) {
133924
134525
  const { path: path8, edits } = validateEditInput2(input);
133925
134526
  const absolutePath = resolveToCwd(path8, cwd);
133926
134527
  return withFileMutationQueue2(absolutePath, async () => {
133927
- const throwIfAborted = () => {
134528
+ const throwIfAborted2 = () => {
133928
134529
  if (signal?.aborted)
133929
134530
  throw new Error("Operation aborted");
133930
134531
  };
133931
- throwIfAborted();
134532
+ throwIfAborted2();
133932
134533
  try {
133933
134534
  await ops.access(absolutePath);
133934
134535
  } catch (error) {
133935
- throwIfAborted();
134536
+ throwIfAborted2();
133936
134537
  const errorMessage = error instanceof Error && "code" in error ? `Error code: ${error.code}` : String(error);
133937
134538
  throw new Error(`Could not edit file: ${path8}. ${errorMessage}.`);
133938
134539
  }
133939
- throwIfAborted();
134540
+ throwIfAborted2();
133940
134541
  const buffer = await ops.readFile(absolutePath);
133941
134542
  const rawContent = buffer.toString("utf-8");
133942
- throwIfAborted();
134543
+ throwIfAborted2();
133943
134544
  const { bom, text: content } = stripBom2(rawContent);
133944
134545
  const originalEnding = detectLineEnding2(content);
133945
134546
  const normalizedContent = normalizeToLF2(content);
133946
134547
  const { baseContent, newContent } = applyEditsToNormalizedContent2(normalizedContent, edits, path8);
133947
- throwIfAborted();
134548
+ throwIfAborted2();
133948
134549
  const finalContent = bom + restoreLineEndings2(newContent, originalEnding);
133949
134550
  await ops.writeFile(absolutePath, finalContent);
133950
- throwIfAborted();
134551
+ throwIfAborted2();
133951
134552
  const diffResult = generateDiffString2(baseContent, newContent);
133952
134553
  const patch = generateUnifiedPatch2(path8, baseContent, newContent);
133953
134554
  return {
@@ -134023,7 +134624,6 @@ import { spawn as spawn5 } from "child_process";
134023
134624
  import path8 from "path";
134024
134625
 
134025
134626
  // node_modules/@earendil-works/pi-coding-agent/dist/utils/tools-manager.js
134026
- init_source();
134027
134627
  import { spawnSync as spawnSync3 } from "child_process";
134028
134628
  import { chmodSync, createWriteStream as createWriteStream3, existsSync as existsSync9, mkdirSync as mkdirSync3, readdirSync as readdirSync7, renameSync, rmSync } from "fs";
134029
134629
  import { arch, platform } from "os";
@@ -134300,7 +134900,7 @@ var TERMUX_PACKAGES = {
134300
134900
  fd: "fd",
134301
134901
  rg: "ripgrep"
134302
134902
  };
134303
- async function ensureTool(tool, silent = false) {
134903
+ async function ensureTool(tool, onStatus) {
134304
134904
  const existingPath = getToolPath(tool);
134305
134905
  if (existingPath) {
134306
134906
  return existingPath;
@@ -134309,31 +134909,24 @@ async function ensureTool(tool, silent = false) {
134309
134909
  if (!config)
134310
134910
  return;
134311
134911
  if (isOfflineModeEnabled()) {
134312
- if (!silent) {
134313
- console.log(source_default.yellow(`${config.name} not found. Offline mode enabled, skipping download.`));
134314
- }
134912
+ onStatus?.({ type: "warning", message: `${config.name} not found. Offline mode enabled, skipping download.` });
134315
134913
  return;
134316
134914
  }
134317
134915
  if (platform() === "android") {
134318
134916
  const pkgName = TERMUX_PACKAGES[tool] ?? tool;
134319
- if (!silent) {
134320
- console.log(source_default.yellow(`${config.name} not found. Install with: pkg install ${pkgName}`));
134321
- }
134917
+ onStatus?.({ type: "warning", message: `${config.name} not found. Install with: pkg install ${pkgName}` });
134322
134918
  return;
134323
134919
  }
134324
- if (!silent) {
134325
- console.log(source_default.dim(`${config.name} not found. Downloading...`));
134326
- }
134920
+ onStatus?.({ type: "info", message: `${config.name} not found. Downloading...` });
134327
134921
  try {
134328
134922
  const path8 = await downloadTool(tool);
134329
- if (!silent) {
134330
- console.log(source_default.dim(`${config.name} installed to ${path8}`));
134331
- }
134923
+ onStatus?.({ type: "info", message: `${config.name} installed to ${path8}` });
134332
134924
  return path8;
134333
134925
  } catch (e) {
134334
- if (!silent) {
134335
- console.log(source_default.yellow(`Failed to download ${config.name}: ${e instanceof Error ? e.message : e}`));
134336
- }
134926
+ onStatus?.({
134927
+ type: "warning",
134928
+ message: `Failed to download ${config.name}: ${e instanceof Error ? e.message : e}`
134929
+ });
134337
134930
  return;
134338
134931
  }
134339
134932
  }
@@ -134488,7 +135081,7 @@ function createFindToolDefinition(cwd, options) {
134488
135081
  }));
134489
135082
  return;
134490
135083
  }
134491
- const fdPath = await ensureTool("fd", true);
135084
+ const fdPath = await ensureTool("fd");
134492
135085
  if (signal?.aborted) {
134493
135086
  settle(() => reject(new Error("Operation aborted")));
134494
135087
  return;
@@ -134717,7 +135310,7 @@ function createGrepToolDefinition(cwd, options) {
134717
135310
  };
134718
135311
  (async () => {
134719
135312
  try {
134720
- const rgPath = await ensureTool("rg", true);
135313
+ const rgPath = await ensureTool("rg");
134721
135314
  if (!rgPath) {
134722
135315
  settle(() => reject(new Error("ripgrep (rg) is not available and could not be downloaded")));
134723
135316
  return;
@@ -135312,6 +135905,7 @@ function createReadToolDefinition(cwd, options) {
135312
135905
  promptSnippet: readToolSystemPromptContribution.snippet,
135313
135906
  promptGuidelines: [...readToolSystemPromptContribution.guidelines],
135314
135907
  parameters: readSchema2,
135908
+ constrainedSampling: getExperimentalToolSampling(),
135315
135909
  async execute(_toolCallId, { path: path10, offset, limit }, signal, _onUpdate, ctx) {
135316
135910
  return new Promise((resolve7, reject) => {
135317
135911
  if (signal?.aborted) {
@@ -135583,19 +136177,20 @@ function createWriteToolDefinition(cwd, options) {
135583
136177
  promptSnippet: writeToolSystemPromptContribution.snippet,
135584
136178
  promptGuidelines: [...writeToolSystemPromptContribution.guidelines],
135585
136179
  parameters: writeSchema2,
136180
+ constrainedSampling: getExperimentalToolSampling(),
135586
136181
  async execute(_toolCallId, { path: path10, content }, signal, _onUpdate, _ctx) {
135587
136182
  const absolutePath = resolveToCwd(path10, cwd);
135588
136183
  const dir = dirname11(absolutePath);
135589
136184
  return withFileMutationQueue2(absolutePath, async () => {
135590
- const throwIfAborted = () => {
136185
+ const throwIfAborted2 = () => {
135591
136186
  if (signal?.aborted)
135592
136187
  throw new Error("Operation aborted");
135593
136188
  };
135594
- throwIfAborted();
136189
+ throwIfAborted2();
135595
136190
  await ops.mkdir(dir);
135596
- throwIfAborted();
136191
+ throwIfAborted2();
135597
136192
  await ops.writeFile(absolutePath, content);
135598
- throwIfAborted();
136193
+ throwIfAborted2();
135599
136194
  return {
135600
136195
  content: [{ type: "text", text: `Successfully wrote ${content.length} bytes to ${path10}` }],
135601
136196
  details: undefined
@@ -136515,7 +137110,7 @@ ${args}` : skillBlock;
136515
137110
  };
136516
137111
  if (options?.deliverAs === "nextTurn") {
136517
137112
  this._pendingNextTurnMessages.push(appMessage);
136518
- } else if (this.isStreaming) {
137113
+ } else if (this.isStreaming && options?.triggerTurn !== false) {
136519
137114
  if (options?.deliverAs === "followUp") {
136520
137115
  this.agent.followUp(appMessage);
136521
137116
  } else {
@@ -136551,7 +137146,7 @@ ${args}` : skillBlock;
136551
137146
  images = undefined;
136552
137147
  }
136553
137148
  await this.prompt(text, {
136554
- expandPromptTemplates: false,
137149
+ expandPromptTemplates: options?.expandPromptTemplates ?? false,
136555
137150
  streamingBehavior: options?.deliverAs,
136556
137151
  images,
136557
137152
  source: "extension"
@@ -137714,9 +138309,8 @@ ${command}` : command;
137714
138309
  percent
137715
138310
  };
137716
138311
  }
137717
- async exportToHtml(outputPath) {
137718
- const configuredThemeName = this.settingsManager.getTheme();
137719
- const themeName = configuredThemeName && getThemeByName(configuredThemeName) ? configuredThemeName : undefined;
138312
+ async exportToHtml(outputPath, options = {}) {
138313
+ const themeName = [options.themeName, this.settingsManager.getTheme()].find((candidate) => candidate !== undefined && getThemeByName(candidate) !== undefined);
137720
138314
  const toolRenderer = createToolHtmlRenderer({
137721
138315
  getToolDefinition: (name) => this.getToolDefinition(name),
137722
138316
  theme,
@@ -140412,6 +141006,7 @@ var OpenAIResponsesCompatSchema = exports_typebox.Object({
140412
141006
  supportsLongCacheRetention: exports_typebox.Optional(exports_typebox.Boolean()),
140413
141007
  supportsStrictMode: exports_typebox.Optional(exports_typebox.Boolean()),
140414
141008
  supportsOpenAIGrammarTools: exports_typebox.Optional(exports_typebox.Boolean()),
141009
+ supportsAdditionalTools: exports_typebox.Optional(exports_typebox.Boolean()),
140415
141010
  supportsToolSearch: exports_typebox.Optional(exports_typebox.Boolean())
140416
141011
  });
140417
141012
  var AnthropicMessagesCompatSchema = exports_typebox.Object({
@@ -148268,6 +148863,14 @@ class SettingsManager {
148268
148863
  this.markModified("tuiMode");
148269
148864
  this.save();
148270
148865
  }
148866
+ getFullscreenExitOutput() {
148867
+ return this.settings.fullscreenExitOutput === "resume-hint" ? "resume-hint" : "transcript";
148868
+ }
148869
+ setFullscreenExitOutput(output) {
148870
+ this.globalSettings.fullscreenExitOutput = output;
148871
+ this.markModified("fullscreenExitOutput");
148872
+ this.save();
148873
+ }
148271
148874
  getFullscreenScrollbar() {
148272
148875
  const mode = this.settings.fullscreenScrollbar;
148273
148876
  return mode === "always" || mode === "hidden" ? mode : "auto";
@@ -148302,6 +148905,10 @@ class SettingsManager {
148302
148905
  getEnabledModels() {
148303
148906
  return this.settings.enabledModels;
148304
148907
  }
148908
+ getDefaultTools() {
148909
+ const tools2 = this.settings.defaultTools;
148910
+ return tools2 ? [...tools2] : undefined;
148911
+ }
148305
148912
  setEnabledModels(patterns) {
148306
148913
  this.globalSettings.enabledModels = patterns;
148307
148914
  this.markModified("enabledModels");
@@ -149729,10 +150336,11 @@ async function createAgentSession(options = {}) {
149729
150336
  thinkingLevel = clampThinkingLevel(model, thinkingLevel);
149730
150337
  }
149731
150338
  const defaultActiveToolNames = ["read", "bash", "edit", "write"];
150339
+ const configuredDefaultToolNames = settingsManager.getDefaultTools();
149732
150340
  const allowedToolNames = options.tools ?? (options.noTools === "all" ? [] : undefined);
149733
150341
  const excludedToolNames = options.excludeTools;
149734
150342
  const excludedToolNameSet = excludedToolNames ? new Set(excludedToolNames) : undefined;
149735
- const initialActiveToolNames = (options.tools ? [...options.tools] : options.noTools ? [] : defaultActiveToolNames).filter((name) => !excludedToolNameSet?.has(name));
150343
+ const initialActiveToolNames = (options.tools ?? (options.noTools ? [] : configuredDefaultToolNames ?? defaultActiveToolNames)).filter((name) => !excludedToolNameSet?.has(name));
149736
150344
  let agent2;
149737
150345
  const convertToLlmWithBlockImages = (messages2) => {
149738
150346
  const converted = convertToLlm(messages2);
@@ -150040,9 +150648,9 @@ init_source();
150040
150648
  class AuthCommandError extends Error {
150041
150649
  }
150042
150650
  var AUTH_COMMAND_USAGE = {
150043
- check: "pi auth check --provider <provider> [--json] [--credentials] [--no-refresh]",
150044
- api_key: "pi auth print-api-key --provider <provider> [--model <model>]",
150045
- bearer_token: "pi auth print-bearer-token --provider <provider> [--model <model>] [--min-expiry <duration>]"
150651
+ check: `${APP_NAME} auth check --provider <provider> [--json] [--credentials] [--no-refresh]`,
150652
+ api_key: `${APP_NAME} auth print-api-key --provider <provider> [--model <model>]`,
150653
+ bearer_token: `${APP_NAME} auth print-bearer-token --provider <provider> [--model <model>] [--min-expiry <duration>]`
150046
150654
  };
150047
150655
  function getAuthCommandName(kind) {
150048
150656
  return kind === "check" ? "auth check" : kind === "api_key" ? "auth print-api-key" : "auth print-bearer-token";
@@ -150066,7 +150674,7 @@ function parseAuthCommand(args) {
150066
150674
  return;
150067
150675
  const kind = args[1] === "check" ? "check" : args[1] === "print-api-key" ? "api_key" : args[1] === "print-bearer-token" ? "bearer_token" : undefined;
150068
150676
  if (!kind) {
150069
- throw new AuthCommandError(`Unknown auth command "${args[1] ?? ""}". Use "pi auth print-api-key", "pi auth print-bearer-token", or "pi auth check".`);
150677
+ throw new AuthCommandError(`Unknown auth command "${args[1] ?? ""}". Use "${APP_NAME} auth print-api-key", "${APP_NAME} auth print-bearer-token", or "${APP_NAME} auth check".`);
150070
150678
  }
150071
150679
  const commandArgs = [];
150072
150680
  let json = false;
@@ -150410,11 +151018,6 @@ init_source();
150410
151018
  // node_modules/@earendil-works/pi-coding-agent/dist/cli/startup-ui.js
150411
151019
  import { existsSync as existsSync20 } from "fs";
150412
151020
 
150413
- // node_modules/@earendil-works/pi-coding-agent/dist/core/experimental.js
150414
- function areExperimentalFeaturesEnabled() {
150415
- return process.env.PI_EXPERIMENTAL === "1";
150416
- }
150417
-
150418
151021
  // node_modules/@earendil-works/pi-coding-agent/dist/core/keybindings.js
150419
151022
  import { existsSync as existsSync19, readFileSync as readFileSync14 } from "fs";
150420
151023
  import { join as join30 } from "path";
@@ -152124,7 +152727,7 @@ function formatProjectTrustPrompt(cwd) {
152124
152727
  return `Trust project folder?
152125
152728
  ${cwd}
152126
152729
 
152127
- This allows pi to load ${CONFIG_DIR_NAME} settings and resources, install missing project packages, and execute project extensions.`;
152730
+ This allows ${APP_NAME} to load ${CONFIG_DIR_NAME} settings and resources, install missing project packages, and execute project extensions.`;
152128
152731
  }
152129
152732
  async function selectProjectTrustOption(cwd, ctx) {
152130
152733
  const options = getProjectTrustOptions(cwd, { includeSessionOnly: true });
@@ -156824,7 +157427,7 @@ var WIDTHS = [
156824
157427
  ];
156825
157428
 
156826
157429
  // node_modules/grok-mermaid/dist/width.js
156827
- var segmenter2 = new Intl.Segmenter("en", { granularity: "grapheme" });
157430
+ var segmenter3 = new Intl.Segmenter("en", { granularity: "grapheme" });
156828
157431
  var VS16 = 65039;
156829
157432
  var isRegionalIndicator = (cp) => cp >= 127462 && cp <= 127487;
156830
157433
  function codePointWidth(cp) {
@@ -156859,12 +157462,12 @@ function clusterWidth(cluster) {
156859
157462
  return vs16 || regional >= 2 ? 2 : w2;
156860
157463
  }
156861
157464
  function* measured(s) {
156862
- for (const { segment: segment2 } of segmenter2.segment(s))
157465
+ for (const { segment: segment2 } of segmenter3.segment(s))
156863
157466
  yield [segment2, clusterWidth(segment2)];
156864
157467
  }
156865
157468
  function stringWidth(s) {
156866
157469
  let w2 = 0;
156867
- for (const { segment: segment2 } of segmenter2.segment(s))
157470
+ for (const { segment: segment2 } of segmenter3.segment(s))
156868
157471
  w2 += clusterWidth(segment2);
156869
157472
  return w2;
156870
157473
  }
@@ -159537,6 +160140,39 @@ ${codeSpan(styledWarning)}
159537
160140
  };
159538
160141
  }
159539
160142
 
160143
+ // node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/model-catalog-refresh.js
160144
+ class ModelCatalogRefreshCoordinator {
160145
+ activeByRuntime = new WeakMap;
160146
+ refresh(modelRuntime, signal) {
160147
+ signal.throwIfAborted();
160148
+ let active = this.activeByRuntime.get(modelRuntime);
160149
+ if (!active) {
160150
+ const controller = new AbortController;
160151
+ let created;
160152
+ const operation = modelRuntime.refresh({ signal: controller.signal });
160153
+ const promise = raceWithAbortSignal(operation, controller.signal).finally(() => {
160154
+ if (this.activeByRuntime.get(modelRuntime) === created) {
160155
+ this.activeByRuntime.delete(modelRuntime);
160156
+ }
160157
+ });
160158
+ created = { controller, promise, waiters: 0 };
160159
+ active = created;
160160
+ this.activeByRuntime.set(modelRuntime, active);
160161
+ }
160162
+ active.waiters++;
160163
+ return raceWithAbortSignal(active.promise, signal).finally(() => {
160164
+ active.waiters--;
160165
+ if (active.waiters === 0 && this.activeByRuntime.get(modelRuntime) === active) {
160166
+ active.controller.abort();
160167
+ }
160168
+ });
160169
+ }
160170
+ }
160171
+ var modelCatalogRefreshCoordinator = new ModelCatalogRefreshCoordinator;
160172
+ function refreshModelCatalogs(modelRuntime, signal) {
160173
+ return modelCatalogRefreshCoordinator.refresh(modelRuntime, signal);
160174
+ }
160175
+
159540
160176
  // node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/model-search.js
159541
160177
  function getModelSearchText(item) {
159542
160178
  const { id, provider } = item;
@@ -159656,7 +160292,7 @@ class ModelSelectorComponent extends Container {
159656
160292
  this.refreshAbortController.abort();
159657
160293
  }, timeoutMs);
159658
160294
  try {
159659
- const result2 = await this.modelRuntime.refresh({ signal: this.refreshAbortController.signal });
160295
+ const result2 = await refreshModelCatalogs(this.modelRuntime, this.refreshAbortController.signal);
159660
160296
  if (this.closed)
159661
160297
  return;
159662
160298
  this.refreshStatusMessage = "";
@@ -160635,6 +161271,13 @@ class SettingsSelectorComponent extends Container {
160635
161271
  currentValue: config.tuiMode,
160636
161272
  values: ["regular", "fullscreen"]
160637
161273
  },
161274
+ {
161275
+ id: "fullscreen-exit-output",
161276
+ label: "Fullscreen exit output",
161277
+ description: "Print the transcript or only a session resume hint when exiting fullscreen mode",
161278
+ currentValue: config.fullscreenExitOutput,
161279
+ values: ["transcript", "resume-hint"]
161280
+ },
160638
161281
  {
160639
161282
  id: "fullscreen-scrollbar",
160640
161283
  label: "Fullscreen scrollbar",
@@ -160826,6 +161469,9 @@ class SettingsSelectorComponent extends Container {
160826
161469
  case "tui-mode":
160827
161470
  callbacks.onTuiModeChange(newValue);
160828
161471
  break;
161472
+ case "fullscreen-exit-output":
161473
+ callbacks.onFullscreenExitOutputChange(newValue);
161474
+ break;
160829
161475
  case "fullscreen-scrollbar":
160830
161476
  callbacks.onFullscreenScrollbarChange(newValue);
160831
161477
  break;
@@ -160938,6 +161584,8 @@ class IdleStatus {
160938
161584
  }
160939
161585
 
160940
161586
  // node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/tool-execution.js
161587
+ var FALLBACK_PREVIEW_LINES = 10;
161588
+
160941
161589
  class ToolExecutionComponent extends Container {
160942
161590
  contentBox;
160943
161591
  contentText;
@@ -161042,7 +161690,17 @@ class ToolExecutionComponent extends Container {
161042
161690
  if (!output) {
161043
161691
  return;
161044
161692
  }
161045
- return new Text(theme.fg("toolOutput", output), 0, 0);
161693
+ const lines = output.split(`
161694
+ `);
161695
+ const displayLines = this.expanded ? lines : lines.slice(0, FALLBACK_PREVIEW_LINES);
161696
+ const remaining = lines.length - displayLines.length;
161697
+ let text = displayLines.map((line) => theme.fg("toolOutput", line)).join(`
161698
+ `);
161699
+ if (remaining > 0) {
161700
+ text += `${theme.fg("muted", `
161701
+ ... (${remaining} more lines,`)} ${keyHint("app.tools.expand", "to expand")}${theme.fg("muted", ")")}`;
161702
+ }
161703
+ return new Text(text, 0, 0);
161046
161704
  }
161047
161705
  updateArgs(args) {
161048
161706
  this.args = args;
@@ -162496,19 +163154,21 @@ class UserMessageSelectorComponent extends Container {
162496
163154
  // node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/theme/theme-controller.js
162497
163155
  class InteractiveThemeController {
162498
163156
  ui;
162499
- settingsManager;
163157
+ getSettingsManager;
162500
163158
  showError;
162501
163159
  onChanged;
163160
+ currentThemeSetting;
162502
163161
  terminalTheme = detectTerminalBackgroundFromEnv().theme;
162503
163162
  activeThemeName;
162504
163163
  autoSyncEnabled = false;
162505
163164
  terminalColorSchemeUnsubscribe;
162506
- constructor(ui2, settingsManager, showError, onChanged) {
163165
+ constructor(ui2, options) {
162507
163166
  this.ui = ui2;
162508
- this.settingsManager = settingsManager;
162509
- this.showError = showError;
162510
- this.onChanged = onChanged;
162511
- this.activeThemeName = resolveThemeSetting(this.settingsManager.getThemeSetting(), this.terminalTheme);
163167
+ this.getSettingsManager = options.getSettingsManager;
163168
+ this.showError = options.showError;
163169
+ this.onChanged = options.onChanged;
163170
+ this.currentThemeSetting = options.initialThemeSetting;
163171
+ this.activeThemeName = resolveThemeSetting(this.currentThemeSetting ?? this.getSettingsManager().getThemeSetting(), this.terminalTheme);
162512
163172
  initTheme(this.activeThemeName, true);
162513
163173
  this.bindTerminalColorSchemeListener();
162514
163174
  }
@@ -162518,7 +163178,8 @@ class InteractiveThemeController {
162518
163178
  this.ui.setTerminalColorSchemeNotifications(this.autoSyncEnabled);
162519
163179
  }
162520
163180
  async applyFromSettings() {
162521
- const themeSetting = this.settingsManager.getThemeSetting();
163181
+ const settingsManager = this.getSettingsManager();
163182
+ const themeSetting = this.currentThemeSetting ?? settingsManager.getThemeSetting();
162522
163183
  const autoTheme = parseAutoThemeSetting(themeSetting);
162523
163184
  if (autoTheme) {
162524
163185
  this.terminalTheme = await detectTerminalThemeForAuto({ ui: this.ui, timeoutMs: 100 });
@@ -162536,13 +163197,24 @@ class InteractiveThemeController {
162536
163197
  if (!this.applyThemeName(detection.theme).success)
162537
163198
  return;
162538
163199
  if (detection.confidence === "high") {
162539
- this.settingsManager.setTheme(detection.theme);
162540
- await this.settingsManager.flush();
163200
+ settingsManager.setTheme(detection.theme);
163201
+ await settingsManager.flush();
162541
163202
  }
162542
163203
  }
163204
+ getThemeSelection() {
163205
+ return this.currentThemeSetting ?? this.getSettingsManager().getThemeSetting() ?? this.activeThemeName;
163206
+ }
162543
163207
  setThemeName(themeName, showError = false) {
162544
163208
  this.setAutoSync(false);
162545
- return this.applyThemeName(themeName, showError);
163209
+ const result2 = this.applyThemeName(themeName, showError);
163210
+ if (result2.success) {
163211
+ this.currentThemeSetting = themeName;
163212
+ }
163213
+ return result2;
163214
+ }
163215
+ async setThemeSetting(themeSetting) {
163216
+ this.currentThemeSetting = themeSetting;
163217
+ await this.applyFromSettings();
162546
163218
  }
162547
163219
  setThemeInstance(themeInstance) {
162548
163220
  this.setAutoSync(false);
@@ -162593,7 +163265,7 @@ Fell back to dark theme.`);
162593
163265
  if (!this.autoSyncEnabled)
162594
163266
  return;
162595
163267
  this.terminalTheme = terminalTheme;
162596
- const autoTheme = parseAutoThemeSetting(this.settingsManager.getThemeSetting());
163268
+ const autoTheme = parseAutoThemeSetting(this.currentThemeSetting ?? this.getSettingsManager().getThemeSetting());
162597
163269
  if (!autoTheme) {
162598
163270
  this.setAutoSync(false);
162599
163271
  return;
@@ -162701,9 +163373,20 @@ function formatLoginProviderCompletionDescription(provider) {
162701
163373
  function createInteractiveTui(options) {
162702
163374
  const terminal = options.terminal ?? new ProcessTerminal;
162703
163375
  if (options.tuiMode === "fullscreen") {
163376
+ const styleSearchMatch = (text) => theme.bg("searchMatchBg", theme.fg("searchMatchText", text));
162704
163377
  return new TuiAltScreen(terminal, options.showHardwareCursor, options.logDirectory, {
163378
+ searchMatchStyle: (text) => theme.underline(styleSearchMatch(text)),
163379
+ searchCurrentMatchStyle: (text) => theme.bold(theme.inverse(styleSearchMatch(text))),
162705
163380
  openUrl: openBrowser,
162706
- onRightClickPaste: options.onRightClickPaste
163381
+ onRightClickPaste: options.onRightClickPaste,
163382
+ copySelection: async (text) => {
163383
+ try {
163384
+ await copyToClipboard(text);
163385
+ return true;
163386
+ } catch {
163387
+ return false;
163388
+ }
163389
+ }
162707
163390
  });
162708
163391
  }
162709
163392
  return new TuiMainScreen(terminal, options.showHardwareCursor, options.logDirectory);
@@ -162783,6 +163466,7 @@ class InteractiveMode {
162783
163466
  anthropicSubscriptionWarningShown = false;
162784
163467
  lastStatusSpacer = undefined;
162785
163468
  lastStatusText = undefined;
163469
+ managedToolStatusStarted = false;
162786
163470
  streamingComponent = undefined;
162787
163471
  streamingMessage = undefined;
162788
163472
  pendingTools = new Map;
@@ -162843,6 +163527,7 @@ class InteractiveMode {
162843
163527
  });
162844
163528
  this.runtimeHost.setRebindSession(async () => {
162845
163529
  await this.rebindCurrentSession({ renderBeforeBind: true });
163530
+ await this.themeController.applyFromSettings();
162846
163531
  });
162847
163532
  this.version = VERSION;
162848
163533
  this.renderer = createInteractiveTui({
@@ -162883,7 +163568,12 @@ class InteractiveMode {
162883
163568
  this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
162884
163569
  this.outputPad = this.settingsManager.getOutputPad();
162885
163570
  setRegisteredThemes(this.session.resourceLoader.getThemes().themes);
162886
- this.themeController = new InteractiveThemeController(this.ui, this.settingsManager, (message) => this.showError(message), () => this.updateEditorBorderColor());
163571
+ this.themeController = new InteractiveThemeController(this.ui, {
163572
+ getSettingsManager: () => this.settingsManager,
163573
+ showError: (message) => this.showError(message),
163574
+ onChanged: () => this.updateEditorBorderColor(),
163575
+ initialThemeSetting: options.initialThemeSetting
163576
+ });
162887
163577
  }
162888
163578
  getAutocompleteSourceTag(sourceInfo) {
162889
163579
  if (!sourceInfo) {
@@ -163030,14 +163720,14 @@ class InteractiveMode {
163030
163720
  tui.setLayoutRoot(this.fullscreenLayoutRoot);
163031
163721
  }
163032
163722
  }
163033
- stopInteractiveTui() {
163034
- if (this.renderer.mode === "fullscreen") {
163723
+ stopInteractiveTui(fullscreenExitOutput) {
163724
+ if (this.renderer.mode === "fullscreen" && fullscreenExitOutput === "transcript") {
163035
163725
  while (this.renderer.hasOverlayEntries)
163036
163726
  this.renderer.hideOverlay();
163037
163727
  this.switchTuiMode("regular", false, false);
163038
163728
  this.renderer.renderNow();
163039
163729
  }
163040
- this.ui.stop();
163730
+ this.ui.stop({ preserveScreen: this.renderer.mode === "fullscreen" });
163041
163731
  }
163042
163732
  switchTuiMode(mode, restoreProgress = true, startRenderer = true) {
163043
163733
  const previousUi = this.renderer;
@@ -163091,8 +163781,6 @@ class InteractiveMode {
163091
163781
  return;
163092
163782
  this.registerSignalHandlers();
163093
163783
  this.changelogMarkdown = this.getChangelogForDisplay();
163094
- const [fdPath] = await Promise.all([ensureTool("fd"), ensureTool("rg")]);
163095
- this.fdPath = fdPath;
163096
163784
  if (this.session.scopedModels.length > 0 && (this.options.verbose || !this.settingsManager.getQuietStartup())) {
163097
163785
  const modelList = this.session.scopedModels.map((sm) => {
163098
163786
  const thinkingStr = sm.thinkingLevel ? `:${sm.thinkingLevel}` : "";
@@ -163131,9 +163819,10 @@ class InteractiveMode {
163131
163819
  this.widgetContainerBelow,
163132
163820
  this.footerContainer
163133
163821
  ]);
163822
+ this.defaultEditor.onAction("app.clear", () => this.handleCtrlC());
163823
+ this.defaultEditor.onCtrlD = () => this.handleCtrlD();
163824
+ this.defaultEditor.onSubmit = (text) => this.handleStartupSubmit(text);
163134
163825
  this.ui.setFocus(this.editor);
163135
- this.setupKeyHandlers();
163136
- this.setupEditorSubmitHandler();
163137
163826
  this.ui.start();
163138
163827
  this.isInitialized = true;
163139
163828
  await this.themeController.applyFromSettings();
@@ -163187,6 +163876,14 @@ ${onboarding}`, this.getStartupExpansionState(), 1, 0);
163187
163876
  this.headerContainer.addChild(this.builtInHeader);
163188
163877
  }
163189
163878
  this.ui.requestRender();
163879
+ const [fdPath] = await Promise.all([
163880
+ ensureTool("fd", (status) => this.showManagedToolStatus(status)),
163881
+ ensureTool("rg", (status) => this.showManagedToolStatus(status))
163882
+ ]);
163883
+ this.fdPath = fdPath;
163884
+ this.setupKeyHandlers();
163885
+ this.setupEditorSubmitHandler();
163886
+ this.ui.requestRender();
163190
163887
  await this.rebindCurrentSession();
163191
163888
  this.renderInitialMessages();
163192
163889
  onThemeChange(() => {
@@ -163213,7 +163910,7 @@ ${onboarding}`, this.getStartupExpansionState(), 1, 0);
163213
163910
  if (!process.env.PI_OFFLINE) {
163214
163911
  const controller = new AbortController;
163215
163912
  const timeout = setTimeout(() => controller.abort(), 15000);
163216
- this.session.modelRuntime.refresh({ signal: controller.signal }).then(() => this.updateAvailableProviderCount()).catch(() => {}).finally(() => clearTimeout(timeout));
163913
+ refreshModelCatalogs(this.session.modelRuntime, controller.signal).then(() => this.updateAvailableProviderCount()).catch(() => {}).finally(() => clearTimeout(timeout));
163217
163914
  }
163218
163915
  checkForNewPiVersion(this.version).then((newRelease) => {
163219
163916
  if (newRelease) {
@@ -163923,7 +164620,7 @@ ${warningLines}`, 0, 0));
163923
164620
  const message = error instanceof Error ? error.message : String(error);
163924
164621
  this.showError(`${prefix}: ${message}`);
163925
164622
  stopThemeWatcher();
163926
- this.stop();
164623
+ this.stop("transcript");
163927
164624
  process.exit(1);
163928
164625
  }
163929
164626
  renderCurrentSessionState() {
@@ -164590,6 +165287,10 @@ ${message}`, ["Yes", "No"], opts);
164590
165287
  }
164591
165288
  } catch {}
164592
165289
  }
165290
+ handleStartupSubmit(text) {
165291
+ this.editor.setText(text);
165292
+ this.showStatus("Startup is still in progress");
165293
+ }
164593
165294
  setupEditorSubmitHandler() {
164594
165295
  this.defaultEditor.onSubmit = async (text) => {
164595
165296
  text = text.trim();
@@ -165032,6 +165733,18 @@ ${message}`, ["Yes", "No"], opts);
165032
165733
  const textBlocks = typeof message.content === "string" ? [{ type: "text", text: message.content }] : message.content.filter((c) => c.type === "text");
165033
165734
  return textBlocks.map((c) => c.text).join("");
165034
165735
  }
165736
+ showManagedToolStatus(status) {
165737
+ if (!this.managedToolStatusStarted) {
165738
+ this.chatContainer.addChild(new Spacer(1));
165739
+ this.managedToolStatusStarted = true;
165740
+ }
165741
+ const message = status.type === "warning" ? `Warning: ${status.message}` : status.message;
165742
+ const color = status.type === "warning" ? "warning" : "dim";
165743
+ this.chatContainer.addChild(new Text(theme.fg(color, message), 1, 0));
165744
+ this.lastStatusSpacer = undefined;
165745
+ this.lastStatusText = undefined;
165746
+ this.ui.requestRender();
165747
+ }
165035
165748
  showStatus(message) {
165036
165749
  const children = this.chatContainer.children;
165037
165750
  const last = children.length > 0 ? children[children.length - 1] : undefined;
@@ -165324,7 +166037,7 @@ ${message}`, ["Yes", "No"], opts);
165324
166037
  try {
165325
166038
  this.ui.stop();
165326
166039
  } catch {}
165327
- console.error("pi exiting due to uncaughtException:");
166040
+ console.error(`${APP_NAME} exiting due to uncaughtException:`);
165328
166041
  console.error(error);
165329
166042
  process.exit(1);
165330
166043
  }
@@ -165746,7 +166459,7 @@ ${packageLines}`, 1, 0));
165746
166459
  httpIdleTimeoutMs: this.settingsManager.getHttpIdleTimeoutMs(),
165747
166460
  thinkingLevel: this.session.thinkingLevel,
165748
166461
  availableThinkingLevels: this.session.getAvailableThinkingLevels(),
165749
- currentTheme: this.settingsManager.getThemeSetting() || "dark",
166462
+ currentTheme: this.themeController.getThemeSelection() || "dark",
165750
166463
  terminalTheme: this.themeController.getTerminalTheme(),
165751
166464
  availableThemes: getAvailableThemes(),
165752
166465
  hideThinkingBlock: this.hideThinkingBlock,
@@ -165765,6 +166478,7 @@ ${packageLines}`, 1, 0));
165765
166478
  clearOnShrink: this.settingsManager.getClearOnShrink(),
165766
166479
  showTerminalProgress: this.settingsManager.getShowTerminalProgress(),
165767
166480
  tuiMode: this.ui.mode,
166481
+ fullscreenExitOutput: this.settingsManager.getFullscreenExitOutput(),
165768
166482
  fullscreenScrollbar: this.settingsManager.getFullscreenScrollbar(),
165769
166483
  warnings: this.settingsManager.getWarnings()
165770
166484
  }, {
@@ -165820,7 +166534,7 @@ ${packageLines}`, 1, 0));
165820
166534
  },
165821
166535
  onThemeChange: (themeSetting) => {
165822
166536
  this.settingsManager.setTheme(themeSetting);
165823
- this.themeController.applyFromSettings();
166537
+ this.themeController.setThemeSetting(themeSetting);
165824
166538
  },
165825
166539
  onThemePreview: (themeName) => this.themeController.preview(themeName),
165826
166540
  onHideThinkingBlockChange: (hidden) => {
@@ -165917,6 +166631,9 @@ ${packageLines}`, 1, 0));
165917
166631
  this.statusContainer.clear();
165918
166632
  this.showStatus(`TUI mode: ${mode}`);
165919
166633
  },
166634
+ onFullscreenExitOutputChange: (output) => {
166635
+ this.settingsManager.setFullscreenExitOutput(output);
166636
+ },
165920
166637
  onFullscreenScrollbarChange: (mode) => {
165921
166638
  this.settingsManager.setFullscreenScrollbar(mode);
165922
166639
  this.applyFullscreenScrollbarSetting();
@@ -165966,7 +166683,7 @@ ${packageLines}`, 1, 0));
165966
166683
  controller.abort();
165967
166684
  }, 15000);
165968
166685
  try {
165969
- const result2 = await this.session.modelRuntime.refresh({ signal: controller.signal });
166686
+ const result2 = await refreshModelCatalogs(this.session.modelRuntime, controller.signal);
165970
166687
  if (result2.aborted && timedOut) {
165971
166688
  this.showWarning("Model refresh timed out; searching cached models.");
165972
166689
  } else if (result2.errors.size > 0) {
@@ -166042,7 +166759,7 @@ ${packageLines}`, 1, 0));
166042
166759
  onSelect: (selection) => {
166043
166760
  trustStore.setMany(selection.updates);
166044
166761
  done();
166045
- this.showStatus(`Saved trust decision: ${selection.trusted ? "trusted" : "untrusted"}. Restart pi for this to take effect.`);
166762
+ this.showStatus(`Saved trust decision: ${selection.trusted ? "trusted" : "untrusted"}. Restart ${APP_NAME} for this to take effect.`);
166046
166763
  },
166047
166764
  onCancel: () => {
166048
166765
  done();
@@ -166136,7 +166853,7 @@ ${packageLines}`, 1, 0));
166136
166853
  this.ui.requestRender();
166137
166854
  }
166138
166855
  });
166139
- this.session.modelRuntime.refresh({ signal: controller.signal }).then((result2) => {
166856
+ refreshModelCatalogs(this.session.modelRuntime, controller.signal).then((result2) => {
166140
166857
  if (disposed)
166141
166858
  return;
166142
166859
  availableModels = [...this.session.modelRuntime.getAvailableSnapshot()];
@@ -166630,7 +167347,7 @@ ${packageLines}`, 1, 0));
166630
167347
  this.ui.requestRender();
166631
167348
  };
166632
167349
  const dialog = new LoginDialogComponent(this.ui, providerOption.id, () => restoreEditor(), providerOption.name, `${providerOption.name} setup`);
166633
- dialog.showInfo(`${providerOption.method?.name ?? "Authentication"} is configured outside pi.`, [], true);
167350
+ dialog.showInfo(`${providerOption.method?.name ?? "Authentication"} is configured outside ${APP_NAME}.`, [], true);
166634
167351
  this.editorContainer.clear();
166635
167352
  this.editorContainer.addChild(dialog);
166636
167353
  this.ui.setFocus(dialog);
@@ -166848,7 +167565,9 @@ ${packageLines}`, 1, 0));
166848
167565
  const filePath = this.session.exportToJsonl(outputPath);
166849
167566
  this.showStatus(`Session exported to: ${filePath}`);
166850
167567
  } else {
166851
- const filePath = await this.session.exportToHtml(outputPath);
167568
+ const filePath = await this.session.exportToHtml(outputPath, {
167569
+ themeName: theme.name
167570
+ });
166852
167571
  this.showStatus(`Session exported to: ${filePath}`);
166853
167572
  }
166854
167573
  } catch (error) {
@@ -166934,7 +167653,7 @@ ${packageLines}`, 1, 0));
166934
167653
  }
166935
167654
  const tmpFile = path12.join(os5.tmpdir(), "session.html");
166936
167655
  try {
166937
- await this.session.exportToHtml(tmpFile);
167656
+ await this.session.exportToHtml(tmpFile, { themeName: theme.name });
166938
167657
  } catch (error) {
166939
167658
  this.showError(`Failed to export session: ${error instanceof Error ? error.message : "Unknown error"}`);
166940
167659
  return;
@@ -167356,7 +168075,7 @@ ${theme.fg("muted", debugLogPath)}`, 1, 1));
167356
168075
  await this.session.compact(customInstructions);
167357
168076
  } catch {}
167358
168077
  }
167359
- stop() {
168078
+ stop(fullscreenExitOutput = this.settingsManager.getFullscreenExitOutput()) {
167360
168079
  this.disposeActiveSelector();
167361
168080
  if (this.settingsManager.getShowTerminalProgress()) {
167362
168081
  this.ui.terminal.setProgress(false);
@@ -167370,7 +168089,7 @@ ${theme.fg("muted", debugLogPath)}`, 1, 1));
167370
168089
  this.unsubscribe();
167371
168090
  }
167372
168091
  if (this.isInitialized) {
167373
- this.stopInteractiveTui();
168092
+ this.stopInteractiveTui(fullscreenExitOutput);
167374
168093
  this.isInitialized = false;
167375
168094
  }
167376
168095
  this.unregisterSignalHandlers();
@@ -167381,12 +168100,15 @@ function toJsonEvent(event) {
167381
168100
  if (event.type !== "message_update") {
167382
168101
  return event;
167383
168102
  }
168103
+ if (event.message.role !== "assistant") {
168104
+ throw new Error("message_update message is not an assistant message");
168105
+ }
167384
168106
  const assistantMessageEvent = event.assistantMessageEvent;
167385
168107
  if (!("partial" in assistantMessageEvent)) {
167386
- return { type: "message_update", assistantMessageEvent };
168108
+ return { type: "message_update", usage: event.message.usage, assistantMessageEvent };
167387
168109
  }
167388
168110
  const { partial: _partial, ...deltaEvent } = assistantMessageEvent;
167389
- return { type: "message_update", assistantMessageEvent: deltaEvent };
168111
+ return { type: "message_update", usage: event.message.usage, assistantMessageEvent: deltaEvent };
167390
168112
  }
167391
168113
 
167392
168114
  // node_modules/@earendil-works/pi-coding-agent/dist/modes/print-mode.js
@@ -169543,7 +170265,7 @@ function updateTargetIncludesSelf(target) {
169543
170265
  function updateTargetIncludesExtensions(target) {
169544
170266
  return target.type === "all" || target.type === "extensions";
169545
170267
  }
169546
- async function refreshModelCatalogs(agentDir) {
170268
+ async function refreshModelCatalogs2(agentDir) {
169547
170269
  const controller = new AbortController;
169548
170270
  const timeout = setTimeout(() => controller.abort(), 15000);
169549
170271
  try {
@@ -169576,7 +170298,7 @@ function printSelfUpdateUnavailable(npmCommand, updatePackageTarget = PACKAGE_NA
169576
170298
  const entrypoint = process.argv[1];
169577
170299
  if (entrypoint) {
169578
170300
  console.error("");
169579
- console.error(`Location of pi executable: ${entrypoint}`);
170301
+ console.error(`Location of ${APP_NAME} executable: ${entrypoint}`);
169580
170302
  }
169581
170303
  }
169582
170304
  function printSelfUpdateFallback(command) {
@@ -169807,7 +170529,7 @@ async function handlePackageCommand(args, runtimeOptions = {}) {
169807
170529
  }
169808
170530
  if (options.command === "update" && options.updateTarget?.type === "models") {
169809
170531
  try {
169810
- await refreshModelCatalogs(getAgentDir());
170532
+ await refreshModelCatalogs2(getAgentDir());
169811
170533
  } catch (error) {
169812
170534
  const message = error instanceof Error ? error.message : "Unknown model catalog refresh error";
169813
170535
  console.error(source_default.red(`Error: ${message}`));
@@ -169955,7 +170677,7 @@ async function handlePackageCommand(args, runtimeOptions = {}) {
169955
170677
  }
169956
170678
 
169957
170679
  // node_modules/@earendil-works/pi-coding-agent/dist/main.js
169958
- var EXTENSION_LOAD_FAILURE_HINT = 'Hint: Start without extensions using "pi -ne".';
170680
+ var EXTENSION_LOAD_FAILURE_HINT = `Hint: Start without extensions using "${APP_NAME} -ne".`;
169959
170681
  async function readPipedStdin() {
169960
170682
  if (process.stdin.isTTY) {
169961
170683
  return;
@@ -170392,6 +171114,9 @@ async function main(args, options) {
170392
171114
  await showFirstTimeSetup(startupSettingsManager);
170393
171115
  time("firstTimeSetup");
170394
171116
  }
171117
+ if (appMode === "interactive" && parsed.useTheme !== undefined) {
171118
+ startupSettingsManager.applyOverrides({ theme: parsed.useTheme });
171119
+ }
170395
171120
  const envSessionDir = process.env[ENV_SESSION_DIR];
170396
171121
  const sessionDir = (parsed.sessionDir ? normalizePath(parsed.sessionDir) : undefined) ?? (envSessionDir ? expandTildePath(envSessionDir) : undefined) ?? startupSettingsManager.getSessionDir();
170397
171122
  let sessionManager = await createSessionManager(parsed, cwd, sessionDir, startupSettingsManager);
@@ -170592,7 +171317,8 @@ async function main(args, options) {
170592
171317
  initialImages,
170593
171318
  initialMessages: parsed.messages,
170594
171319
  verbose: parsed.verbose,
170595
- tuiMode: parsed.tuiMode
171320
+ tuiMode: parsed.tuiMode,
171321
+ initialThemeSetting: parsed.useTheme
170596
171322
  });
170597
171323
  if (startupBenchmark) {
170598
171324
  await interactiveMode.init();
@@ -171873,6 +172599,10 @@ class PiMonoAdapter {
171873
172599
  if (config.apiUrl && config.apiKey) {
171874
172600
  try {
171875
172601
  const mcpClient = new McpHttpClient(config.apiUrl, config.apiKey, config.agentId, config.taskId);
172602
+ const runtimeInstanceId = swarmRuntimeInstanceId();
172603
+ if (runtimeInstanceId) {
172604
+ mcpClient.customHeaders["X-Runtime-Instance-ID"] = runtimeInstanceId;
172605
+ }
171876
172606
  await mcpClient.initialize();
171877
172607
  const tools2 = await mcpClient.listTools();
171878
172608
  customTools = mcpToolsToDefinitions(mcpClient, tools2);