@averyyy/pi-coding-agent 0.85.1-piclient.9 → 0.86.1-piclient.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (324) hide show
  1. package/CHANGELOG.md +109 -0
  2. package/README.md +3 -1
  3. package/dist/bundle/chunks/anthropic-messages-MYU5ZMRF.js +6 -0
  4. package/dist/bundle/chunks/azure-openai-responses-QJ7SPP5M.js +2 -0
  5. package/dist/bundle/chunks/bedrock-converse-stream.js +47 -38
  6. package/dist/bundle/chunks/chunk-7L6OAVKC.js +1663 -0
  7. package/dist/bundle/chunks/chunk-A3JYRWB6.js +2 -0
  8. package/dist/bundle/chunks/chunk-DTD7JQ7Y.js +42 -0
  9. package/dist/bundle/chunks/chunk-FUXEF6JQ.js +44 -0
  10. package/dist/bundle/chunks/{chunk-IDDQWTHI.js → chunk-RCZIEVGO.js} +10 -1
  11. package/dist/bundle/chunks/chunk-S7SZN6Z3.js +2 -0
  12. package/dist/bundle/chunks/chunk-UZNGGY6N.js +11 -0
  13. package/dist/bundle/chunks/chunk-XREANBOX.js +2 -0
  14. package/dist/bundle/chunks/devin-runtime.js +7 -2
  15. package/dist/bundle/chunks/github-copilot.js +1 -1
  16. package/dist/bundle/chunks/google-generative-ai-IMS5EK2Y.js +2 -0
  17. package/dist/bundle/chunks/google-vertex-O4YJEQUW.js +2 -0
  18. package/dist/bundle/chunks/jiti-loader-C3NU2SDH.js +21 -0
  19. package/dist/bundle/chunks/jiti-static-loader-E6ANQD5T.js +2 -0
  20. package/dist/bundle/chunks/meta.js +2 -0
  21. package/dist/bundle/chunks/mistral-conversations-NOSQHBEY.js +5 -0
  22. package/dist/bundle/chunks/{node-KG362ECQ.js → node-O3RIBJRH.js} +1 -1
  23. package/dist/bundle/chunks/openai-codex-responses-SG7PGUUZ.js +10 -0
  24. package/dist/bundle/chunks/openai-completions-CYGM3XXP.js +7 -0
  25. package/dist/bundle/chunks/openai-responses-AH7QTG23.js +2 -0
  26. package/dist/bundle/chunks/{pi-messages-PML4SPVJ.js → pi-messages-GFKBWJHZ.js} +1 -1
  27. package/dist/bundle/chunks/virtual-modules-SC5AFIK5.js +2 -0
  28. package/dist/bundle/cli-runtime.js +3 -0
  29. package/dist/bundle/cli.js +4 -2
  30. package/dist/bundle/index.js +1 -1
  31. package/dist/bundle/rpc-entry.js +1 -1
  32. package/dist/cli/args.d.ts.map +1 -1
  33. package/dist/cli/args.js +1 -0
  34. package/dist/cli/args.js.map +1 -1
  35. package/dist/cli/session-picker.d.ts +1 -1
  36. package/dist/cli/session-picker.d.ts.map +1 -1
  37. package/dist/cli/session-picker.js.map +1 -1
  38. package/dist/config.d.ts.map +1 -1
  39. package/dist/config.js +1 -1
  40. package/dist/config.js.map +1 -1
  41. package/dist/core/agent-session.d.ts +56 -6
  42. package/dist/core/agent-session.d.ts.map +1 -1
  43. package/dist/core/agent-session.js +362 -183
  44. package/dist/core/agent-session.js.map +1 -1
  45. package/dist/core/bug-report-upload.d.ts +12 -0
  46. package/dist/core/bug-report-upload.d.ts.map +1 -0
  47. package/dist/core/bug-report-upload.js +22 -0
  48. package/dist/core/bug-report-upload.js.map +1 -0
  49. package/dist/core/bug-report.d.ts +176 -0
  50. package/dist/core/bug-report.d.ts.map +1 -0
  51. package/dist/core/bug-report.js +292 -0
  52. package/dist/core/bug-report.js.map +1 -0
  53. package/dist/core/cache-stats.d.ts.map +1 -1
  54. package/dist/core/cache-stats.js +12 -1
  55. package/dist/core/cache-stats.js.map +1 -1
  56. package/dist/core/cache-warmer.d.ts +102 -0
  57. package/dist/core/cache-warmer.d.ts.map +1 -0
  58. package/dist/core/cache-warmer.js +342 -0
  59. package/dist/core/cache-warmer.js.map +1 -0
  60. package/dist/core/compaction/compaction.d.ts +2 -2
  61. package/dist/core/compaction/compaction.d.ts.map +1 -1
  62. package/dist/core/compaction/compaction.js +23 -18
  63. package/dist/core/compaction/compaction.js.map +1 -1
  64. package/dist/core/crash-log.d.ts +22 -0
  65. package/dist/core/crash-log.d.ts.map +1 -0
  66. package/dist/core/crash-log.js +71 -0
  67. package/dist/core/crash-log.js.map +1 -0
  68. package/dist/core/experimental.d.ts +0 -4
  69. package/dist/core/experimental.d.ts.map +1 -1
  70. package/dist/core/experimental.js +0 -4
  71. package/dist/core/experimental.js.map +1 -1
  72. package/dist/core/extensions/index.d.ts +1 -1
  73. package/dist/core/extensions/index.d.ts.map +1 -1
  74. package/dist/core/extensions/index.js.map +1 -1
  75. package/dist/core/extensions/jiti-loader.d.ts +2 -0
  76. package/dist/core/extensions/jiti-loader.d.ts.map +1 -0
  77. package/dist/core/extensions/jiti-loader.js +4 -0
  78. package/dist/core/extensions/jiti-loader.js.map +1 -0
  79. package/dist/core/extensions/jiti-static-loader.d.ts +2 -0
  80. package/dist/core/extensions/jiti-static-loader.d.ts.map +1 -0
  81. package/dist/core/extensions/jiti-static-loader.js +4 -0
  82. package/dist/core/extensions/jiti-static-loader.js.map +1 -0
  83. package/dist/core/extensions/loader.d.ts.map +1 -1
  84. package/dist/core/extensions/loader.js +38 -51
  85. package/dist/core/extensions/loader.js.map +1 -1
  86. package/dist/core/extensions/runner.d.ts +10 -7
  87. package/dist/core/extensions/runner.d.ts.map +1 -1
  88. package/dist/core/extensions/runner.js +85 -67
  89. package/dist/core/extensions/runner.js.map +1 -1
  90. package/dist/core/extensions/types.d.ts +60 -50
  91. package/dist/core/extensions/types.d.ts.map +1 -1
  92. package/dist/core/extensions/types.js.map +1 -1
  93. package/dist/core/extensions/virtual-modules.d.ts +3 -0
  94. package/dist/core/extensions/virtual-modules.d.ts.map +1 -0
  95. package/dist/core/extensions/virtual-modules.js +38 -0
  96. package/dist/core/extensions/virtual-modules.js.map +1 -0
  97. package/dist/core/extensions/wrapper.d.ts.map +1 -1
  98. package/dist/core/extensions/wrapper.js +1 -20
  99. package/dist/core/extensions/wrapper.js.map +1 -1
  100. package/dist/core/index.d.ts +2 -1
  101. package/dist/core/index.d.ts.map +1 -1
  102. package/dist/core/index.js.map +1 -1
  103. package/dist/core/keybindings.d.ts +1 -1
  104. package/dist/core/keybindings.d.ts.map +1 -1
  105. package/dist/core/keybindings.js +1 -1
  106. package/dist/core/keybindings.js.map +1 -1
  107. package/dist/core/messages.d.ts.map +1 -1
  108. package/dist/core/messages.js +1 -0
  109. package/dist/core/messages.js.map +1 -1
  110. package/dist/core/model-config.d.ts +101 -20
  111. package/dist/core/model-config.d.ts.map +1 -1
  112. package/dist/core/model-config.js +25 -18
  113. package/dist/core/model-config.js.map +1 -1
  114. package/dist/core/model-registry.d.ts +5 -1
  115. package/dist/core/model-registry.d.ts.map +1 -1
  116. package/dist/core/model-registry.js +8 -0
  117. package/dist/core/model-registry.js.map +1 -1
  118. package/dist/core/model-resolver.d.ts.map +1 -1
  119. package/dist/core/model-resolver.js +2 -1
  120. package/dist/core/model-resolver.js.map +1 -1
  121. package/dist/core/model-runtime.d.ts.map +1 -1
  122. package/dist/core/model-runtime.js +5 -3
  123. package/dist/core/model-runtime.js.map +1 -1
  124. package/dist/core/provider-composer.d.ts +3 -2
  125. package/dist/core/provider-composer.d.ts.map +1 -1
  126. package/dist/core/provider-composer.js +2 -0
  127. package/dist/core/provider-composer.js.map +1 -1
  128. package/dist/core/radius.d.ts +3 -0
  129. package/dist/core/radius.d.ts.map +1 -1
  130. package/dist/core/radius.js +6 -0
  131. package/dist/core/radius.js.map +1 -1
  132. package/dist/core/sdk.d.ts.map +1 -1
  133. package/dist/core/sdk.js +61 -41
  134. package/dist/core/sdk.js.map +1 -1
  135. package/dist/core/session-export.d.ts +6 -2
  136. package/dist/core/session-export.d.ts.map +1 -1
  137. package/dist/core/session-export.js +14 -13
  138. package/dist/core/session-export.js.map +1 -1
  139. package/dist/core/session-manager.d.ts +29 -6
  140. package/dist/core/session-manager.d.ts.map +1 -1
  141. package/dist/core/session-manager.js +159 -96
  142. package/dist/core/session-manager.js.map +1 -1
  143. package/dist/core/settings-manager.d.ts +20 -4
  144. package/dist/core/settings-manager.d.ts.map +1 -1
  145. package/dist/core/settings-manager.js +43 -7
  146. package/dist/core/settings-manager.js.map +1 -1
  147. package/dist/core/slash-commands.d.ts.map +1 -1
  148. package/dist/core/slash-commands.js +1 -0
  149. package/dist/core/slash-commands.js.map +1 -1
  150. package/dist/core/system-prompt.d.ts +49 -6
  151. package/dist/core/system-prompt.d.ts.map +1 -1
  152. package/dist/core/system-prompt.js +121 -92
  153. package/dist/core/system-prompt.js.map +1 -1
  154. package/dist/core/tools/bash.d.ts +2 -1
  155. package/dist/core/tools/bash.d.ts.map +1 -1
  156. package/dist/core/tools/bash.js +19 -5
  157. package/dist/core/tools/bash.js.map +1 -1
  158. package/dist/core/tools/edit.d.ts.map +1 -1
  159. package/dist/core/tools/edit.js +1 -2
  160. package/dist/core/tools/edit.js.map +1 -1
  161. package/dist/core/tools/read.d.ts.map +1 -1
  162. package/dist/core/tools/read.js +1 -2
  163. package/dist/core/tools/read.js.map +1 -1
  164. package/dist/core/tools/renderers/bash.d.ts.map +1 -1
  165. package/dist/core/tools/renderers/bash.js +9 -1
  166. package/dist/core/tools/renderers/bash.js.map +1 -1
  167. package/dist/core/tools/write.d.ts.map +1 -1
  168. package/dist/core/tools/write.js +1 -2
  169. package/dist/core/tools/write.js.map +1 -1
  170. package/dist/core/usage-totals.d.ts +1 -1
  171. package/dist/core/usage-totals.d.ts.map +1 -1
  172. package/dist/core/usage-totals.js +5 -1
  173. package/dist/core/usage-totals.js.map +1 -1
  174. package/dist/extensions/llama/client.d.ts +2 -0
  175. package/dist/extensions/llama/client.d.ts.map +1 -1
  176. package/dist/extensions/llama/client.js +7 -3
  177. package/dist/extensions/llama/client.js.map +1 -1
  178. package/dist/extensions/llama/provider.d.ts.map +1 -1
  179. package/dist/extensions/llama/provider.js +20 -5
  180. package/dist/extensions/llama/provider.js.map +1 -1
  181. package/dist/index.d.ts +3 -2
  182. package/dist/index.d.ts.map +1 -1
  183. package/dist/index.js.map +1 -1
  184. package/dist/main.d.ts.map +1 -1
  185. package/dist/main.js +13 -9
  186. package/dist/main.js.map +1 -1
  187. package/dist/migrations.d.ts.map +1 -1
  188. package/dist/migrations.js +2 -2
  189. package/dist/migrations.js.map +1 -1
  190. package/dist/modes/interactive/bug-report.d.ts +16 -0
  191. package/dist/modes/interactive/bug-report.d.ts.map +1 -0
  192. package/dist/modes/interactive/bug-report.js +206 -0
  193. package/dist/modes/interactive/bug-report.js.map +1 -0
  194. package/dist/modes/interactive/chat-viewport.d.ts.map +1 -1
  195. package/dist/modes/interactive/chat-viewport.js +1 -1
  196. package/dist/modes/interactive/chat-viewport.js.map +1 -1
  197. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  198. package/dist/modes/interactive/components/branch-summary-message.js +12 -5
  199. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  200. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  201. package/dist/modes/interactive/components/compaction-summary-message.js +12 -5
  202. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  203. package/dist/modes/interactive/components/custom-editor.d.ts +3 -3
  204. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  205. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  206. package/dist/modes/interactive/components/extension-editor.d.ts +4 -1
  207. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  208. package/dist/modes/interactive/components/extension-editor.js +7 -2
  209. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  210. package/dist/modes/interactive/components/extension-input.d.ts +2 -0
  211. package/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
  212. package/dist/modes/interactive/components/extension-input.js +6 -0
  213. package/dist/modes/interactive/components/extension-input.js.map +1 -1
  214. package/dist/modes/interactive/components/extension-selector.d.ts +1 -0
  215. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  216. package/dist/modes/interactive/components/extension-selector.js +4 -0
  217. package/dist/modes/interactive/components/extension-selector.js.map +1 -1
  218. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  219. package/dist/modes/interactive/components/footer.js +4 -1
  220. package/dist/modes/interactive/components/footer.js.map +1 -1
  221. package/dist/modes/interactive/components/session-selector.d.ts +5 -5
  222. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  223. package/dist/modes/interactive/components/session-selector.js +74 -48
  224. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  225. package/dist/modes/interactive/components/settings-selector.d.ts +3 -1
  226. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  227. package/dist/modes/interactive/components/settings-selector.js +11 -0
  228. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  229. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  230. package/dist/modes/interactive/components/skill-invocation-message.js +11 -4
  231. package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  232. package/dist/modes/interactive/components/status-indicator.d.ts +2 -2
  233. package/dist/modes/interactive/components/status-indicator.d.ts.map +1 -1
  234. package/dist/modes/interactive/components/status-indicator.js +7 -7
  235. package/dist/modes/interactive/components/status-indicator.js.map +1 -1
  236. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  237. package/dist/modes/interactive/components/tool-execution.js +18 -9
  238. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  239. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  240. package/dist/modes/interactive/components/tree-selector.js +2 -0
  241. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  242. package/dist/modes/interactive/interactive-mode.d.ts +15 -2
  243. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  244. package/dist/modes/interactive/interactive-mode.js +235 -86
  245. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  246. package/dist/modes/interactive/session-share.d.ts +2 -0
  247. package/dist/modes/interactive/session-share.d.ts.map +1 -1
  248. package/dist/modes/interactive/session-share.js +8 -4
  249. package/dist/modes/interactive/session-share.js.map +1 -1
  250. package/dist/modes/interactive/tui-renderer.d.ts.map +1 -1
  251. package/dist/modes/interactive/tui-renderer.js +2 -2
  252. package/dist/modes/interactive/tui-renderer.js.map +1 -1
  253. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  254. package/dist/modes/rpc/rpc-mode.js +2 -2
  255. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  256. package/dist/utils/clipboard-command.d.ts +7 -0
  257. package/dist/utils/clipboard-command.d.ts.map +1 -0
  258. package/dist/utils/clipboard-command.js +45 -0
  259. package/dist/utils/clipboard-command.js.map +1 -0
  260. package/dist/utils/clipboard-image.d.ts.map +1 -1
  261. package/dist/utils/clipboard-image.js +53 -81
  262. package/dist/utils/clipboard-image.js.map +1 -1
  263. package/dist/utils/clipboard.d.ts.map +1 -1
  264. package/dist/utils/clipboard.js +111 -121
  265. package/dist/utils/clipboard.js.map +1 -1
  266. package/dist/utils/wsl.d.ts +3 -0
  267. package/dist/utils/wsl.d.ts.map +1 -0
  268. package/dist/utils/wsl.js +15 -0
  269. package/dist/utils/wsl.js.map +1 -0
  270. package/dist/utils/zip.d.ts +7 -0
  271. package/dist/utils/zip.d.ts.map +1 -0
  272. package/dist/utils/zip.js +60 -0
  273. package/dist/utils/zip.js.map +1 -0
  274. package/docs/compaction.md +39 -13
  275. package/docs/custom-provider.md +22 -13
  276. package/docs/development.md +4 -4
  277. package/docs/docs.json +4 -0
  278. package/docs/environment-variables.md +1 -0
  279. package/docs/extensions.md +57 -43
  280. package/docs/json.md +4 -4
  281. package/docs/models.md +33 -2
  282. package/docs/providers.md +10 -2
  283. package/docs/sdk.md +4 -2
  284. package/docs/security.md +1 -1
  285. package/docs/session-format.md +82 -40
  286. package/docs/sessions.md +27 -0
  287. package/docs/settings.md +60 -1
  288. package/docs/termux.md +0 -1
  289. package/docs/tui.md +2 -2
  290. package/docs/usage.md +1 -0
  291. package/examples/extensions/README.md +0 -1
  292. package/examples/extensions/custom-provider-anthropic/index.ts +18 -12
  293. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  294. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  295. package/examples/extensions/custom-provider-gitlab-duo/index.ts +2 -2
  296. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  297. package/examples/extensions/gondolin/package-lock.json +2 -2
  298. package/examples/extensions/gondolin/package.json +1 -1
  299. package/examples/extensions/prompt-customizer.ts +19 -67
  300. package/examples/extensions/sandbox/package-lock.json +2 -2
  301. package/examples/extensions/sandbox/package.json +1 -1
  302. package/examples/extensions/with-deps/package-lock.json +2 -2
  303. package/examples/extensions/with-deps/package.json +1 -1
  304. package/package.json +17 -23
  305. package/dist/bundle/chunks/anthropic-messages-VWZZOSJQ.js +0 -6
  306. package/dist/bundle/chunks/azure-openai-responses-B467Y57F.js +0 -2
  307. package/dist/bundle/chunks/chunk-6WPJTKUF.js +0 -11
  308. package/dist/bundle/chunks/chunk-AXIIZGTV.js +0 -2
  309. package/dist/bundle/chunks/chunk-CLNPYIDP.js +0 -42
  310. package/dist/bundle/chunks/chunk-GMWCTUPB.js +0 -2
  311. package/dist/bundle/chunks/chunk-ORJJLIYM.js +0 -1648
  312. package/dist/bundle/chunks/chunk-U6ZMQKGD.js +0 -50
  313. package/dist/bundle/chunks/chunk-UAQELI3K.js +0 -2
  314. package/dist/bundle/chunks/google-generative-ai-Q2K7FBDI.js +0 -2
  315. package/dist/bundle/chunks/google-vertex-7UJBF4S7.js +0 -2
  316. package/dist/bundle/chunks/mistral-conversations-4M6BZBX6.js +0 -5
  317. package/dist/bundle/chunks/openai-codex-responses-MERYAUSE.js +0 -10
  318. package/dist/bundle/chunks/openai-completions-EKZT2IH2.js +0 -7
  319. package/dist/bundle/chunks/openai-responses-YZ6M7S4X.js +0 -2
  320. package/dist/utils/clipboard-native.d.ts +0 -11
  321. package/dist/utils/clipboard-native.d.ts.map +0 -1
  322. package/dist/utils/clipboard-native.js +0 -20
  323. package/dist/utils/clipboard-native.js.map +0 -1
  324. package/examples/extensions/kimi-deferred-tools.ts +0 -61
@@ -1,8 +1,8 @@
1
- import { uuidv7 } from "@earendil-works/pi-ai";
1
+ import { getCurrentSystemMessage, uuidv7, } from "@earendil-works/pi-ai";
2
2
  import { randomUUID } from "crypto";
3
3
  import { appendFileSync, closeSync, createReadStream, existsSync, mkdirSync, openSync, readdirSync, readSync, statSync, writeFileSync, } from "fs";
4
4
  import { readdir, stat } from "fs/promises";
5
- import { join, resolve } from "path";
5
+ import { basename, join, resolve } from "path";
6
6
  import { createInterface } from "readline";
7
7
  import { StringDecoder } from "string_decoder";
8
8
  import { APP_NAME, getAgentDir as getDefaultAgentDir, getSessionsDir } from "../config.js";
@@ -168,6 +168,8 @@ export function sessionEntryToContextMessages(entry) {
168
168
  const message = entry.message;
169
169
  // Session files are parsed without validation; old versions, forks, or
170
170
  // hand-edited files can contain messages with null/missing content.
171
+ if (message.role === "system" && message.content == null)
172
+ return [{ ...message, content: "" }];
171
173
  if ((message.role === "user" || message.role === "assistant" || message.role === "toolResult") &&
172
174
  message.content == null) {
173
175
  return [{ ...message, content: [] }];
@@ -183,7 +185,8 @@ export function sessionEntryToContextMessages(entry) {
183
185
  return [createBranchSummaryMessage(entry.summary, entry.fromId, entry.timestamp)];
184
186
  }
185
187
  if (entry.type === "compaction") {
186
- return [createCompactionSummaryMessage(entry.summary, entry.tokensBefore, entry.timestamp)];
188
+ const summary = createCompactionSummaryMessage(entry.summary, entry.tokensBefore, entry.timestamp);
189
+ return entry.systemMessage ? [entry.systemMessage, summary] : [summary];
187
190
  }
188
191
  return [];
189
192
  }
@@ -217,7 +220,7 @@ export function buildContextEntries(entries, leafId, byId) {
217
220
  if (entry.id === compaction.firstKeptEntryId) {
218
221
  foundFirstKept = true;
219
222
  }
220
- if (foundFirstKept) {
223
+ if (foundFirstKept && !(entry.type === "message" && entry.message.role === "system")) {
221
224
  contextEntries.push(entry);
222
225
  }
223
226
  }
@@ -399,14 +402,16 @@ export function findMostRecentSession(sessionDir, cwd) {
399
402
  const resolvedCwd = cwd ? resolvePath(cwd) : undefined;
400
403
  try {
401
404
  const files = readdirSync(resolvedSessionDir)
402
- .filter((f) => f.endsWith(".jsonl"))
403
- .map((f) => join(resolvedSessionDir, f))
404
- .map((path) => ({ path, header: readSessionHeaderForDiscovery(path) }))
405
- .filter((file) => file.header !== null &&
406
- (!resolvedCwd || sessionCwdMatches(getSessionHeaderCwd(file.header), resolvedCwd)))
407
- .map(({ path }) => ({ path, mtime: statSync(path).mtime }))
408
- .sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
409
- return files[0]?.path || null;
405
+ .filter((file) => file.endsWith(".jsonl"))
406
+ .map((file) => join(resolvedSessionDir, file))
407
+ .map((path) => ({ path, mtime: statSync(path).mtimeMs }))
408
+ .sort((a, b) => b.mtime - a.mtime);
409
+ for (const { path } of files) {
410
+ const header = readSessionHeaderForDiscovery(path);
411
+ if (header && (!resolvedCwd || sessionCwdMatches(getSessionHeaderCwd(header), resolvedCwd)))
412
+ return path;
413
+ }
414
+ return null;
410
415
  }
411
416
  catch {
412
417
  // Directory access and stat races make recent-session discovery unavailable.
@@ -439,9 +444,9 @@ function getMessageActivityTime(entry) {
439
444
  const t = new Date(entry.timestamp).getTime();
440
445
  return Number.isNaN(t) ? undefined : t;
441
446
  }
442
- async function buildSessionInfo(filePath) {
447
+ async function buildSessionInfo(filePath, signal, fileStats) {
443
448
  try {
444
- const stats = await stat(filePath);
449
+ const stats = fileStats ?? (await stat(filePath));
445
450
  let header = null;
446
451
  let messageCount = 0;
447
452
  let firstMessage = "";
@@ -449,7 +454,7 @@ async function buildSessionInfo(filePath) {
449
454
  let name;
450
455
  let lastActivityTime;
451
456
  const rl = createInterface({
452
- input: createReadStream(filePath, { encoding: "utf8" }),
457
+ input: createReadStream(filePath, { encoding: "utf8", signal }),
453
458
  crlfDelay: Infinity,
454
459
  });
455
460
  for await (const line of rl) {
@@ -510,67 +515,63 @@ async function buildSessionInfo(filePath) {
510
515
  };
511
516
  }
512
517
  catch {
518
+ signal?.throwIfAborted();
513
519
  return null;
514
520
  }
515
521
  }
516
522
  const MAX_CONCURRENT_SESSION_INFO_LOADS = 10;
517
- async function buildSessionInfosWithConcurrency(files, onLoaded) {
518
- const results = new Array(files.length).fill(null);
519
- const inFlight = new Set();
523
+ const MAX_CONCURRENT_SESSION_DISCOVERY_LOADS = 64;
524
+ const CURRENT_SESSION_LIST_PUBLISH_INTERVAL = 10;
525
+ const ALL_SESSION_LIST_PUBLISH_INTERVAL = 100;
526
+ async function mapWithConcurrency(items, limit, map, signal) {
527
+ const results = new Array(items.length);
520
528
  let nextIndex = 0;
521
- const startNext = () => {
522
- const index = nextIndex++;
523
- const file = files[index];
524
- if (!file)
525
- return;
526
- let task;
527
- task = buildSessionInfo(file)
528
- .then((info) => {
529
- results[index] = info;
530
- })
531
- .catch(() => {
532
- results[index] = null;
533
- })
534
- .finally(() => {
535
- inFlight.delete(task);
536
- onLoaded();
537
- });
538
- inFlight.add(task);
539
- };
540
- while (nextIndex < files.length || inFlight.size > 0) {
541
- while (nextIndex < files.length && inFlight.size < MAX_CONCURRENT_SESSION_INFO_LOADS) {
542
- startNext();
543
- }
544
- if (inFlight.size > 0) {
545
- await Promise.race(inFlight);
529
+ const worker = async () => {
530
+ while (nextIndex < items.length) {
531
+ signal?.throwIfAborted();
532
+ const index = nextIndex++;
533
+ results[index] = await map(items[index], index);
546
534
  }
547
- }
535
+ };
536
+ await Promise.all(Array.from({ length: Math.min(limit, items.length) }, () => worker()));
548
537
  return results;
549
538
  }
550
- async function listSessionsFromDir(dir, onProgress, progressOffset = 0, progressTotal) {
551
- const sessions = [];
552
- if (!existsSync(dir)) {
553
- return sessions;
554
- }
539
+ function sortSessionInfos(sessions) {
540
+ return sessions.sort((a, b) => b.modified.getTime() - a.modified.getTime());
541
+ }
542
+ function buildSessionInfosWithConcurrency(files, onLoaded, signal) {
543
+ return mapWithConcurrency(files, MAX_CONCURRENT_SESSION_INFO_LOADS, async (file, index) => {
544
+ const info = await buildSessionInfo(file.path, signal, file.stats);
545
+ onLoaded(info, index);
546
+ return info;
547
+ }, signal);
548
+ }
549
+ async function listSessionsFromDir(dir, onProgress, signal) {
550
+ signal?.throwIfAborted();
551
+ if (!existsSync(dir))
552
+ return [];
555
553
  try {
556
554
  const dirEntries = await readdir(dir);
557
- const files = dirEntries.filter((f) => f.endsWith(".jsonl")).map((f) => join(dir, f));
558
- const total = progressTotal ?? files.length;
555
+ const files = dirEntries
556
+ .filter((file) => file.endsWith(".jsonl"))
557
+ .sort((a, b) => b.localeCompare(a))
558
+ .map((file) => ({ path: join(dir, file) }));
559
+ const total = files.length;
560
+ const partialSessions = [];
559
561
  let loaded = 0;
560
- const results = await buildSessionInfosWithConcurrency(files, () => {
562
+ const results = await buildSessionInfosWithConcurrency(files, (info) => {
561
563
  loaded++;
562
- onProgress?.(progressOffset + loaded, total);
563
- });
564
- for (const info of results) {
565
- if (info) {
566
- sessions.push(info);
567
- }
568
- }
564
+ if (info)
565
+ partialSessions.push(info);
566
+ const publishPartial = loaded === 1 || loaded % CURRENT_SESSION_LIST_PUBLISH_INTERVAL === 0 || loaded === files.length;
567
+ onProgress?.(loaded, total, publishPartial ? sortSessionInfos([...partialSessions]) : undefined);
568
+ }, signal);
569
+ return results.filter((info) => info !== null);
569
570
  }
570
571
  catch {
571
- // Return empty list on error
572
+ signal?.throwIfAborted();
573
+ return [];
572
574
  }
573
- return sessions;
574
575
  }
575
576
  /**
576
577
  * Manages conversation sessions as append-only trees stored in JSONL files.
@@ -865,19 +866,38 @@ export class SessionManager {
865
866
  this._appendEntry(entry);
866
867
  return entry.id;
867
868
  }
869
+ /** Append model-attributed usage that does not participate in LLM context. Returns the appended entry. */
870
+ appendUsage(kind, provider, model, usage, note) {
871
+ const entry = {
872
+ type: "usage",
873
+ id: generateId(this.byId),
874
+ parentId: this.leafId,
875
+ timestamp: new Date().toISOString(),
876
+ kind,
877
+ provider,
878
+ model,
879
+ usage,
880
+ ...(note ? { note } : {}),
881
+ };
882
+ this._appendEntry(entry);
883
+ return entry;
884
+ }
868
885
  /** Append a compaction summary as child of current leaf, then advance leaf. Returns entry id. */
869
886
  appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromHook, usage) {
887
+ const timestamp = new Date().toISOString();
888
+ const systemMessage = getCurrentSystemMessage(this.buildSessionContext().messages);
870
889
  const entry = {
871
890
  type: "compaction",
872
891
  id: generateId(this.byId),
873
892
  parentId: this.leafId,
874
- timestamp: new Date().toISOString(),
893
+ timestamp,
875
894
  summary,
876
895
  firstKeptEntryId,
877
896
  tokensBefore,
878
897
  details,
879
898
  usage,
880
899
  fromHook,
900
+ ...(systemMessage ? { systemMessage: { ...systemMessage, timestamp: new Date(timestamp).getTime() } } : {}),
881
901
  };
882
902
  this._appendEntry(entry);
883
903
  return entry.id;
@@ -1353,67 +1373,110 @@ export class SessionManager {
1353
1373
  }
1354
1374
  return new SessionManager(resolvedTargetCwd, dir, newSessionFile, true);
1355
1375
  }
1376
+ /**
1377
+ * Find an exact session ID without loading transcript bodies.
1378
+ * @param cwd Working directory (used to compute default session directory)
1379
+ * @param id Exact session ID
1380
+ * @param sessionDir Optional session directory. If omitted, uses default (~/.pi/agent/sessions/<encoded-cwd>/).
1381
+ */
1382
+ static findById(cwd, id, sessionDir) {
1383
+ const dir = sessionDir ? normalizePath(sessionDir) : getDefaultSessionDir(cwd);
1384
+ const filterCwd = sessionDir !== undefined && dir !== getDefaultSessionDirPath(cwd);
1385
+ const resolvedCwd = resolvePath(cwd);
1386
+ try {
1387
+ for (const file of readdirSync(dir)) {
1388
+ if (!file.endsWith(".jsonl"))
1389
+ continue;
1390
+ const path = join(dir, file);
1391
+ const header = readSessionHeaderForDiscovery(path);
1392
+ if (header?.id !== id)
1393
+ continue;
1394
+ if (filterCwd && !sessionCwdMatches(getSessionHeaderCwd(header), resolvedCwd))
1395
+ continue;
1396
+ return path;
1397
+ }
1398
+ }
1399
+ catch {
1400
+ // Exact session discovery is best-effort, matching list().
1401
+ }
1402
+ return undefined;
1403
+ }
1356
1404
  /**
1357
1405
  * List all sessions for a directory.
1358
1406
  * @param cwd Working directory (used to compute default session directory)
1359
1407
  * @param sessionDir Optional session directory. If omitted, uses default (~/.pi/agent/sessions/<encoded-cwd>/).
1360
1408
  * @param onProgress Optional callback for progress updates (loaded, total)
1361
1409
  */
1362
- static async list(cwd, sessionDir, onProgress) {
1410
+ static async list(cwd, sessionDir, onProgress, signal) {
1363
1411
  const dir = sessionDir ? normalizePath(sessionDir) : getDefaultSessionDir(cwd);
1364
1412
  const filterCwd = sessionDir !== undefined && dir !== getDefaultSessionDirPath(cwd);
1365
1413
  const resolvedCwd = resolvePath(cwd);
1366
- const sessions = (await listSessionsFromDir(dir, onProgress)).filter((session) => !filterCwd || sessionCwdMatches(session.cwd, resolvedCwd));
1367
- sessions.sort((a, b) => b.modified.getTime() - a.modified.getTime());
1368
- return sessions;
1369
- }
1370
- static async listAll(sessionDirOrOnProgress, onProgress) {
1414
+ const includeSession = (session) => !filterCwd || sessionCwdMatches(session.cwd, resolvedCwd);
1415
+ const progress = onProgress
1416
+ ? (loaded, total, partialSessions) => onProgress(loaded, total, partialSessions?.filter(includeSession))
1417
+ : undefined;
1418
+ const sessions = (await listSessionsFromDir(dir, progress, signal)).filter(includeSession);
1419
+ return sortSessionInfos(sessions);
1420
+ }
1421
+ static async listAll(sessionDirOrOnProgress, onProgressOrSignal, signal) {
1371
1422
  const customSessionDir = typeof sessionDirOrOnProgress === "string" ? normalizePath(sessionDirOrOnProgress) : undefined;
1372
- const progress = typeof sessionDirOrOnProgress === "function" ? sessionDirOrOnProgress : onProgress;
1423
+ const progress = typeof sessionDirOrOnProgress === "function"
1424
+ ? sessionDirOrOnProgress
1425
+ : typeof onProgressOrSignal === "function"
1426
+ ? onProgressOrSignal
1427
+ : undefined;
1428
+ const abortSignal = typeof sessionDirOrOnProgress === "string" || typeof onProgressOrSignal === "function"
1429
+ ? signal
1430
+ : (onProgressOrSignal ?? signal);
1431
+ abortSignal?.throwIfAborted();
1373
1432
  if (customSessionDir) {
1374
- const sessions = await listSessionsFromDir(customSessionDir, progress);
1375
- sessions.sort((a, b) => b.modified.getTime() - a.modified.getTime());
1376
- return sessions;
1433
+ return sortSessionInfos(await listSessionsFromDir(customSessionDir, progress, abortSignal));
1377
1434
  }
1378
1435
  const sessionsDir = getSessionsDir();
1379
1436
  try {
1380
- if (!existsSync(sessionsDir)) {
1437
+ if (!existsSync(sessionsDir))
1381
1438
  return [];
1382
- }
1383
1439
  const entries = await readdir(sessionsDir, { withFileTypes: true });
1384
1440
  const dirs = entries
1385
1441
  .filter((entry) => entry.isDirectory() || entry.isSymbolicLink())
1386
1442
  .map((entry) => join(sessionsDir, entry.name));
1387
- // Count total files first for accurate progress
1388
- let totalFiles = 0;
1389
- const dirFiles = [];
1390
- for (const dir of dirs) {
1443
+ const dirFiles = await mapWithConcurrency(dirs, MAX_CONCURRENT_SESSION_DISCOVERY_LOADS, async (dir) => {
1391
1444
  try {
1392
- const files = (await readdir(dir)).filter((f) => f.endsWith(".jsonl"));
1393
- dirFiles.push(files.map((f) => join(dir, f)));
1394
- totalFiles += files.length;
1445
+ return (await readdir(dir)).filter((file) => file.endsWith(".jsonl")).map((file) => join(dir, file));
1395
1446
  }
1396
1447
  catch {
1397
- dirFiles.push([]);
1448
+ return [];
1398
1449
  }
1399
- }
1400
- // Process all files with progress tracking
1401
- let loaded = 0;
1402
- const sessions = [];
1450
+ }, abortSignal);
1403
1451
  const allFiles = dirFiles.flat();
1404
- const results = await buildSessionInfosWithConcurrency(allFiles, () => {
1405
- loaded++;
1406
- progress?.(loaded, totalFiles);
1407
- });
1408
- for (const info of results) {
1409
- if (info) {
1410
- sessions.push(info);
1452
+ const candidates = await mapWithConcurrency(allFiles, MAX_CONCURRENT_SESSION_DISCOVERY_LOADS, async (path) => {
1453
+ try {
1454
+ return { path, stats: await stat(path) };
1411
1455
  }
1412
- }
1413
- sessions.sort((a, b) => b.modified.getTime() - a.modified.getTime());
1414
- return sessions;
1456
+ catch {
1457
+ return { path };
1458
+ }
1459
+ }, abortSignal);
1460
+ candidates.sort((a, b) => (b.stats?.mtimeMs ?? Number.NEGATIVE_INFINITY) - (a.stats?.mtimeMs ?? Number.NEGATIVE_INFINITY) ||
1461
+ basename(b.path).localeCompare(basename(a.path)));
1462
+ const totalFiles = candidates.length;
1463
+ let loaded = 0;
1464
+ let firstCandidateLoaded = false;
1465
+ const partialSessions = [];
1466
+ const results = await buildSessionInfosWithConcurrency(candidates, (info, index) => {
1467
+ loaded++;
1468
+ if (index === 0)
1469
+ firstCandidateLoaded = true;
1470
+ if (info)
1471
+ partialSessions.push(info);
1472
+ const publishPartial = firstCandidateLoaded &&
1473
+ (index === 0 || loaded % ALL_SESSION_LIST_PUBLISH_INTERVAL === 0 || loaded === totalFiles);
1474
+ progress?.(loaded, totalFiles, publishPartial ? sortSessionInfos([...partialSessions]) : undefined);
1475
+ }, abortSignal);
1476
+ return sortSessionInfos(results.filter((info) => info !== null));
1415
1477
  }
1416
1478
  catch {
1479
+ abortSignal?.throwIfAborted();
1417
1480
  return [];
1418
1481
  }
1419
1482
  }