p-spore 0.1.4 → 0.1.42

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 (393) hide show
  1. package/README.md +110 -15
  2. package/dist/agents/agent-manager.d.ts +3 -3
  3. package/dist/agents/agent-manager.d.ts.map +1 -1
  4. package/dist/agents/agent-manager.js +3 -2
  5. package/dist/agents/agent-manager.js.map +1 -1
  6. package/dist/application/agent-application.d.ts +29 -5
  7. package/dist/application/agent-application.d.ts.map +1 -1
  8. package/dist/application/agent-application.js +444 -14
  9. package/dist/application/agent-application.js.map +1 -1
  10. package/dist/articles/diff.d.ts +70 -0
  11. package/dist/articles/diff.d.ts.map +1 -0
  12. package/dist/articles/diff.js +330 -0
  13. package/dist/articles/diff.js.map +1 -0
  14. package/dist/articles/docx.d.ts +10 -0
  15. package/dist/articles/docx.d.ts.map +1 -0
  16. package/dist/articles/docx.js +219 -0
  17. package/dist/articles/docx.js.map +1 -0
  18. package/dist/articles/draft.d.ts +18 -0
  19. package/dist/articles/draft.d.ts.map +1 -0
  20. package/dist/articles/draft.js +88 -0
  21. package/dist/articles/draft.js.map +1 -0
  22. package/dist/articles/edits.d.ts +24 -0
  23. package/dist/articles/edits.d.ts.map +1 -0
  24. package/dist/articles/edits.js +54 -0
  25. package/dist/articles/edits.js.map +1 -0
  26. package/dist/articles/html.d.ts +10 -0
  27. package/dist/articles/html.d.ts.map +1 -0
  28. package/dist/articles/html.js +122 -0
  29. package/dist/articles/html.js.map +1 -0
  30. package/dist/articles/plain.d.ts +8 -0
  31. package/dist/articles/plain.d.ts.map +1 -0
  32. package/dist/articles/plain.js +69 -0
  33. package/dist/articles/plain.js.map +1 -0
  34. package/dist/articles/preview.d.ts +43 -0
  35. package/dist/articles/preview.d.ts.map +1 -0
  36. package/dist/articles/preview.js +90 -0
  37. package/dist/articles/preview.js.map +1 -0
  38. package/dist/articles/publish.d.ts +10 -0
  39. package/dist/articles/publish.d.ts.map +1 -0
  40. package/dist/articles/publish.js +54 -0
  41. package/dist/articles/publish.js.map +1 -0
  42. package/dist/articles/receipt.d.ts +16 -0
  43. package/dist/articles/receipt.d.ts.map +1 -0
  44. package/dist/articles/receipt.js +27 -0
  45. package/dist/articles/receipt.js.map +1 -0
  46. package/dist/articles/store.d.ts +78 -0
  47. package/dist/articles/store.d.ts.map +1 -0
  48. package/dist/articles/store.js +308 -0
  49. package/dist/articles/store.js.map +1 -0
  50. package/dist/articles/tool.d.ts +14 -0
  51. package/dist/articles/tool.d.ts.map +1 -0
  52. package/dist/articles/tool.js +175 -0
  53. package/dist/articles/tool.js.map +1 -0
  54. package/dist/articles/writer-context.d.ts +5 -0
  55. package/dist/articles/writer-context.d.ts.map +1 -0
  56. package/dist/articles/writer-context.js +56 -0
  57. package/dist/articles/writer-context.js.map +1 -0
  58. package/dist/articles/writer.d.ts +18 -0
  59. package/dist/articles/writer.d.ts.map +1 -0
  60. package/dist/articles/writer.js +79 -0
  61. package/dist/articles/writer.js.map +1 -0
  62. package/dist/articles/zip-write.d.ts +11 -0
  63. package/dist/articles/zip-write.d.ts.map +1 -0
  64. package/dist/articles/zip-write.js +63 -0
  65. package/dist/articles/zip-write.js.map +1 -0
  66. package/dist/bundled-skills/README.md +17 -0
  67. package/dist/bundled-skills/aegis-search/SKILL.md +59 -0
  68. package/dist/bundled-skills/aegis-search/bin/.gitkeep +0 -0
  69. package/dist/bundled-skills/article-canvas/SKILL.md +28 -0
  70. package/dist/bundled-skills/binaries.json +23 -0
  71. package/dist/bundled-skills/doc-extract/SKILL.md +56 -0
  72. package/dist/cli/main.d.ts.map +1 -1
  73. package/dist/cli/main.js +407 -54
  74. package/dist/cli/main.js.map +1 -1
  75. package/dist/config/config.d.ts +43 -0
  76. package/dist/config/config.d.ts.map +1 -1
  77. package/dist/config/config.js +150 -6
  78. package/dist/config/config.js.map +1 -1
  79. package/dist/daemon/daemon.d.ts.map +1 -1
  80. package/dist/daemon/daemon.js +80 -11
  81. package/dist/daemon/daemon.js.map +1 -1
  82. package/dist/desktop/application.d.ts +4 -0
  83. package/dist/desktop/application.d.ts.map +1 -0
  84. package/dist/desktop/application.js +1688 -0
  85. package/dist/desktop/application.js.map +1 -0
  86. package/dist/desktop/article-export.d.ts +12 -0
  87. package/dist/desktop/article-export.d.ts.map +1 -0
  88. package/dist/desktop/article-export.js +87 -0
  89. package/dist/desktop/article-export.js.map +1 -0
  90. package/dist/desktop/assets/pspore.png +0 -0
  91. package/dist/desktop/assets/pspore.svg +24 -0
  92. package/dist/desktop/conversation-smoke.d.ts +5 -0
  93. package/dist/desktop/conversation-smoke.d.ts.map +1 -0
  94. package/dist/desktop/conversation-smoke.js +93 -0
  95. package/dist/desktop/conversation-smoke.js.map +1 -0
  96. package/dist/desktop/daemon-bridge.d.ts.map +1 -1
  97. package/dist/desktop/daemon-bridge.js +62 -17
  98. package/dist/desktop/daemon-bridge.js.map +1 -1
  99. package/dist/desktop/ipc-contract.d.ts +124 -0
  100. package/dist/desktop/ipc-contract.d.ts.map +1 -1
  101. package/dist/desktop/ipc-contract.js +191 -12
  102. package/dist/desktop/ipc-contract.js.map +1 -1
  103. package/dist/desktop/local-path.d.ts +15 -0
  104. package/dist/desktop/local-path.d.ts.map +1 -0
  105. package/dist/desktop/local-path.js +59 -0
  106. package/dist/desktop/local-path.js.map +1 -0
  107. package/dist/desktop/main.d.ts +10 -1
  108. package/dist/desktop/main.d.ts.map +1 -1
  109. package/dist/desktop/main.js +122 -530
  110. package/dist/desktop/main.js.map +1 -1
  111. package/dist/desktop/native-startup.d.ts +4 -0
  112. package/dist/desktop/native-startup.d.ts.map +1 -0
  113. package/dist/desktop/native-startup.js +57 -0
  114. package/dist/desktop/native-startup.js.map +1 -0
  115. package/dist/desktop/preload.cjs +28 -1
  116. package/dist/desktop/renderer/article-panel.d.ts +45 -0
  117. package/dist/desktop/renderer/article-panel.d.ts.map +1 -0
  118. package/dist/desktop/renderer/article-panel.js +1126 -0
  119. package/dist/desktop/renderer/article-panel.js.map +1 -0
  120. package/dist/desktop/renderer/article-split.d.ts +32 -0
  121. package/dist/desktop/renderer/article-split.d.ts.map +1 -0
  122. package/dist/desktop/renderer/article-split.js +48 -0
  123. package/dist/desktop/renderer/article-split.js.map +1 -0
  124. package/dist/desktop/renderer/composer-file-references.d.ts +23 -0
  125. package/dist/desktop/renderer/composer-file-references.d.ts.map +1 -0
  126. package/dist/desktop/renderer/composer-file-references.js +51 -0
  127. package/dist/desktop/renderer/composer-file-references.js.map +1 -0
  128. package/dist/desktop/renderer/conversation.d.ts +23 -0
  129. package/dist/desktop/renderer/conversation.d.ts.map +1 -0
  130. package/dist/desktop/renderer/conversation.js +66 -0
  131. package/dist/desktop/renderer/conversation.js.map +1 -0
  132. package/dist/desktop/renderer/dom-reconcile.d.ts +15 -0
  133. package/dist/desktop/renderer/dom-reconcile.d.ts.map +1 -0
  134. package/dist/desktop/renderer/dom-reconcile.js +33 -0
  135. package/dist/desktop/renderer/dom-reconcile.js.map +1 -0
  136. package/dist/desktop/renderer/index.html +211 -34
  137. package/dist/desktop/renderer/indexing-view.d.ts +22 -0
  138. package/dist/desktop/renderer/indexing-view.d.ts.map +1 -0
  139. package/dist/desktop/renderer/indexing-view.js +115 -0
  140. package/dist/desktop/renderer/indexing-view.js.map +1 -0
  141. package/dist/desktop/renderer/markdown-view.d.ts +23 -0
  142. package/dist/desktop/renderer/markdown-view.d.ts.map +1 -0
  143. package/dist/desktop/renderer/markdown-view.js +436 -0
  144. package/dist/desktop/renderer/markdown-view.js.map +1 -0
  145. package/dist/desktop/renderer/motion.d.ts +67 -0
  146. package/dist/desktop/renderer/motion.d.ts.map +1 -0
  147. package/dist/desktop/renderer/motion.js +296 -0
  148. package/dist/desktop/renderer/motion.js.map +1 -0
  149. package/dist/desktop/renderer/renderer.js +1788 -151
  150. package/dist/desktop/renderer/renderer.js.map +1 -1
  151. package/dist/desktop/renderer/session-find.d.ts +18 -0
  152. package/dist/desktop/renderer/session-find.d.ts.map +1 -0
  153. package/dist/desktop/renderer/session-find.js +29 -0
  154. package/dist/desktop/renderer/session-find.js.map +1 -0
  155. package/dist/desktop/renderer/session-search.d.ts +30 -0
  156. package/dist/desktop/renderer/session-search.d.ts.map +1 -0
  157. package/dist/desktop/renderer/session-search.js +296 -0
  158. package/dist/desktop/renderer/session-search.js.map +1 -0
  159. package/dist/desktop/renderer/store.d.ts +33 -0
  160. package/dist/desktop/renderer/store.d.ts.map +1 -1
  161. package/dist/desktop/renderer/store.js +92 -19
  162. package/dist/desktop/renderer/store.js.map +1 -1
  163. package/dist/desktop/renderer/styles.css +910 -268
  164. package/dist/desktop/renderer/vendor/gsap.min.js +11 -0
  165. package/dist/desktop/renderer-diagnostics.d.ts +5 -0
  166. package/dist/desktop/renderer-diagnostics.d.ts.map +1 -0
  167. package/dist/desktop/renderer-diagnostics.js +47 -0
  168. package/dist/desktop/renderer-diagnostics.js.map +1 -0
  169. package/dist/desktop/security.d.ts +26 -1
  170. package/dist/desktop/security.d.ts.map +1 -1
  171. package/dist/desktop/security.js +26 -6
  172. package/dist/desktop/security.js.map +1 -1
  173. package/dist/desktop/smoke-fixture.d.ts.map +1 -1
  174. package/dist/desktop/smoke-fixture.js +77 -3
  175. package/dist/desktop/smoke-fixture.js.map +1 -1
  176. package/dist/desktop/splash/index.html +25 -0
  177. package/dist/desktop/splash/splash.d.ts +2 -0
  178. package/dist/desktop/splash/splash.d.ts.map +1 -0
  179. package/dist/desktop/splash/splash.js +8 -0
  180. package/dist/desktop/splash/splash.js.map +1 -0
  181. package/dist/desktop/splash/styles.css +24 -0
  182. package/dist/desktop/system-open.d.ts +16 -0
  183. package/dist/desktop/system-open.d.ts.map +1 -0
  184. package/dist/desktop/system-open.js +90 -0
  185. package/dist/desktop/system-open.js.map +1 -0
  186. package/dist/desktop/validation.d.ts.map +1 -1
  187. package/dist/desktop/validation.js +99 -21
  188. package/dist/desktop/validation.js.map +1 -1
  189. package/dist/desktop/window-security.d.ts +4 -0
  190. package/dist/desktop/window-security.d.ts.map +1 -0
  191. package/dist/desktop/window-security.js +26 -0
  192. package/dist/desktop/window-security.js.map +1 -0
  193. package/dist/documents/errors.d.ts +12 -0
  194. package/dist/documents/errors.d.ts.map +1 -0
  195. package/dist/documents/errors.js +12 -0
  196. package/dist/documents/errors.js.map +1 -0
  197. package/dist/documents/extract.d.ts +37 -0
  198. package/dist/documents/extract.d.ts.map +1 -0
  199. package/dist/documents/extract.js +90 -0
  200. package/dist/documents/extract.js.map +1 -0
  201. package/dist/documents/legacy-office.d.ts +23 -0
  202. package/dist/documents/legacy-office.d.ts.map +1 -0
  203. package/dist/documents/legacy-office.js +439 -0
  204. package/dist/documents/legacy-office.js.map +1 -0
  205. package/dist/documents/ole.d.ts +20 -0
  206. package/dist/documents/ole.d.ts.map +1 -0
  207. package/dist/documents/ole.js +189 -0
  208. package/dist/documents/ole.js.map +1 -0
  209. package/dist/documents/ooxml.d.ts +23 -0
  210. package/dist/documents/ooxml.d.ts.map +1 -0
  211. package/dist/documents/ooxml.js +197 -0
  212. package/dist/documents/ooxml.js.map +1 -0
  213. package/dist/documents/pdf.d.ts +20 -0
  214. package/dist/documents/pdf.d.ts.map +1 -0
  215. package/dist/documents/pdf.js +95 -0
  216. package/dist/documents/pdf.js.map +1 -0
  217. package/dist/documents/plain-text.d.ts +22 -0
  218. package/dist/documents/plain-text.d.ts.map +1 -0
  219. package/dist/documents/plain-text.js +57 -0
  220. package/dist/documents/plain-text.js.map +1 -0
  221. package/dist/documents/zip.d.ts +15 -0
  222. package/dist/documents/zip.d.ts.map +1 -0
  223. package/dist/documents/zip.js +137 -0
  224. package/dist/documents/zip.js.map +1 -0
  225. package/dist/domain/indexing.d.ts +60 -0
  226. package/dist/domain/indexing.d.ts.map +1 -0
  227. package/dist/domain/indexing.js +13 -0
  228. package/dist/domain/indexing.js.map +1 -0
  229. package/dist/domain/runtime.d.ts +133 -5
  230. package/dist/domain/runtime.d.ts.map +1 -1
  231. package/dist/gateway/local-gateway.d.ts +8 -0
  232. package/dist/gateway/local-gateway.d.ts.map +1 -1
  233. package/dist/gateway/local-gateway.js +105 -21
  234. package/dist/gateway/local-gateway.js.map +1 -1
  235. package/dist/indexing/index-service.d.ts +56 -0
  236. package/dist/indexing/index-service.d.ts.map +1 -0
  237. package/dist/indexing/index-service.js +262 -0
  238. package/dist/indexing/index-service.js.map +1 -0
  239. package/dist/kernel/loop.d.ts +10 -0
  240. package/dist/kernel/loop.d.ts.map +1 -1
  241. package/dist/kernel/loop.js +6 -2
  242. package/dist/kernel/loop.js.map +1 -1
  243. package/dist/kernel/reducer.d.ts.map +1 -1
  244. package/dist/kernel/reducer.js +17 -1
  245. package/dist/kernel/reducer.js.map +1 -1
  246. package/dist/kernel/state.d.ts +17 -4
  247. package/dist/kernel/state.d.ts.map +1 -1
  248. package/dist/kernel/state.js +2 -0
  249. package/dist/kernel/state.js.map +1 -1
  250. package/dist/pi/context-budget.d.ts +8 -0
  251. package/dist/pi/context-budget.d.ts.map +1 -0
  252. package/dist/pi/context-budget.js +62 -0
  253. package/dist/pi/context-budget.js.map +1 -0
  254. package/dist/pi/pi-runtime-adapter.d.ts +38 -3
  255. package/dist/pi/pi-runtime-adapter.d.ts.map +1 -1
  256. package/dist/pi/pi-runtime-adapter.js +551 -56
  257. package/dist/pi/pi-runtime-adapter.js.map +1 -1
  258. package/dist/plugins/bundled-skills.d.ts +56 -0
  259. package/dist/plugins/bundled-skills.d.ts.map +1 -0
  260. package/dist/plugins/bundled-skills.js +243 -0
  261. package/dist/plugins/bundled-skills.js.map +1 -0
  262. package/dist/plugins/extension-manager.d.ts +23 -1
  263. package/dist/plugins/extension-manager.d.ts.map +1 -1
  264. package/dist/plugins/extension-manager.js +117 -0
  265. package/dist/plugins/extension-manager.js.map +1 -1
  266. package/dist/plugins/types.d.ts +29 -0
  267. package/dist/plugins/types.d.ts.map +1 -1
  268. package/dist/plugins/workspace-skills.d.ts +36 -0
  269. package/dist/plugins/workspace-skills.d.ts.map +1 -0
  270. package/dist/plugins/workspace-skills.js +309 -0
  271. package/dist/plugins/workspace-skills.js.map +1 -0
  272. package/dist/policy/policy.d.ts.map +1 -1
  273. package/dist/policy/policy.js +22 -3
  274. package/dist/policy/policy.js.map +1 -1
  275. package/dist/protocol/client.d.ts +1 -0
  276. package/dist/protocol/client.d.ts.map +1 -1
  277. package/dist/protocol/client.js +49 -24
  278. package/dist/protocol/client.js.map +1 -1
  279. package/dist/protocol/limits.d.ts +69 -0
  280. package/dist/protocol/limits.d.ts.map +1 -0
  281. package/dist/protocol/limits.js +166 -0
  282. package/dist/protocol/limits.js.map +1 -0
  283. package/dist/protocol/schema.d.ts +6 -0
  284. package/dist/protocol/schema.d.ts.map +1 -1
  285. package/dist/protocol/schema.js.map +1 -1
  286. package/dist/protocol/server-frame-codec.d.ts +18 -0
  287. package/dist/protocol/server-frame-codec.d.ts.map +1 -0
  288. package/dist/protocol/server-frame-codec.js +211 -0
  289. package/dist/protocol/server-frame-codec.js.map +1 -0
  290. package/dist/service/legacy-daemon.d.ts +6 -0
  291. package/dist/service/legacy-daemon.d.ts.map +1 -0
  292. package/dist/service/legacy-daemon.js +42 -0
  293. package/dist/service/legacy-daemon.js.map +1 -0
  294. package/dist/service/service-launch.d.ts +10 -0
  295. package/dist/service/service-launch.d.ts.map +1 -0
  296. package/dist/service/service-launch.js +56 -0
  297. package/dist/service/service-launch.js.map +1 -0
  298. package/dist/service/system-service.d.ts +57 -0
  299. package/dist/service/system-service.d.ts.map +1 -0
  300. package/dist/service/system-service.js +263 -0
  301. package/dist/service/system-service.js.map +1 -0
  302. package/dist/sessions/session-catalog.d.ts +45 -0
  303. package/dist/sessions/session-catalog.d.ts.map +1 -0
  304. package/dist/sessions/session-catalog.js +270 -0
  305. package/dist/sessions/session-catalog.js.map +1 -0
  306. package/dist/sessions/session-file.d.ts +12 -0
  307. package/dist/sessions/session-file.d.ts.map +1 -0
  308. package/dist/sessions/session-file.js +47 -0
  309. package/dist/sessions/session-file.js.map +1 -0
  310. package/dist/settings/compaction-settings.d.ts +10 -0
  311. package/dist/settings/compaction-settings.d.ts.map +1 -0
  312. package/dist/settings/compaction-settings.js +8 -0
  313. package/dist/settings/compaction-settings.js.map +1 -0
  314. package/dist/settings/indexing-settings-service.d.ts +16 -0
  315. package/dist/settings/indexing-settings-service.d.ts.map +1 -0
  316. package/dist/settings/indexing-settings-service.js +57 -0
  317. package/dist/settings/indexing-settings-service.js.map +1 -0
  318. package/dist/settings/indexing-settings.d.ts +30 -0
  319. package/dist/settings/indexing-settings.d.ts.map +1 -0
  320. package/dist/settings/indexing-settings.js +58 -0
  321. package/dist/settings/indexing-settings.js.map +1 -0
  322. package/dist/settings/llm-settings-service.d.ts +6 -0
  323. package/dist/settings/llm-settings-service.d.ts.map +1 -1
  324. package/dist/settings/llm-settings-service.js +137 -12
  325. package/dist/settings/llm-settings-service.js.map +1 -1
  326. package/dist/settings/llm-settings.d.ts +68 -2
  327. package/dist/settings/llm-settings.d.ts.map +1 -1
  328. package/dist/settings/llm-settings.js +119 -17
  329. package/dist/settings/llm-settings.js.map +1 -1
  330. package/dist/settings/model-api.d.ts +10 -0
  331. package/dist/settings/model-api.d.ts.map +1 -0
  332. package/dist/settings/model-api.js +10 -0
  333. package/dist/settings/model-api.js.map +1 -0
  334. package/dist/storage/home.d.ts +1 -0
  335. package/dist/storage/home.d.ts.map +1 -1
  336. package/dist/storage/home.js +37 -2
  337. package/dist/storage/home.js.map +1 -1
  338. package/dist/tools/core-tools.d.ts +2 -0
  339. package/dist/tools/core-tools.d.ts.map +1 -1
  340. package/dist/tools/core-tools.js +91 -1
  341. package/dist/tools/core-tools.js.map +1 -1
  342. package/dist/ui/shared/activity.d.ts +2 -0
  343. package/dist/ui/shared/activity.d.ts.map +1 -1
  344. package/dist/ui/shared/activity.js +18 -4
  345. package/dist/ui/shared/activity.js.map +1 -1
  346. package/dist/ui/shared/full-message.d.ts +22 -0
  347. package/dist/ui/shared/full-message.d.ts.map +1 -0
  348. package/dist/ui/shared/full-message.js +40 -0
  349. package/dist/ui/shared/full-message.js.map +1 -0
  350. package/dist/ui/shared/local-links.d.ts +7 -0
  351. package/dist/ui/shared/local-links.d.ts.map +1 -0
  352. package/dist/ui/shared/local-links.js +24 -0
  353. package/dist/ui/shared/local-links.js.map +1 -0
  354. package/dist/ui/shared/markdown-stream.d.ts +25 -0
  355. package/dist/ui/shared/markdown-stream.d.ts.map +1 -0
  356. package/dist/ui/shared/markdown-stream.js +35 -0
  357. package/dist/ui/shared/markdown-stream.js.map +1 -0
  358. package/dist/ui/shared/markdown.d.ts +70 -0
  359. package/dist/ui/shared/markdown.d.ts.map +1 -0
  360. package/dist/ui/shared/markdown.js +434 -0
  361. package/dist/ui/shared/markdown.js.map +1 -0
  362. package/dist/ui/shared/presentation.d.ts +10 -2
  363. package/dist/ui/shared/presentation.d.ts.map +1 -1
  364. package/dist/ui/shared/presentation.js +83 -22
  365. package/dist/ui/shared/presentation.js.map +1 -1
  366. package/dist/ui/shared/protocol-state.d.ts +19 -0
  367. package/dist/ui/shared/protocol-state.d.ts.map +1 -1
  368. package/dist/ui/shared/protocol-state.js +188 -26
  369. package/dist/ui/shared/protocol-state.js.map +1 -1
  370. package/dist/ui/shared/virtual-list.d.ts +34 -0
  371. package/dist/ui/shared/virtual-list.d.ts.map +1 -0
  372. package/dist/ui/shared/virtual-list.js +143 -0
  373. package/dist/ui/shared/virtual-list.js.map +1 -0
  374. package/dist/ui/tui/commands.d.ts +1 -1
  375. package/dist/ui/tui/commands.d.ts.map +1 -1
  376. package/dist/ui/tui/commands.js +8 -3
  377. package/dist/ui/tui/commands.js.map +1 -1
  378. package/dist/ui/tui/store.d.ts +22 -1
  379. package/dist/ui/tui/store.d.ts.map +1 -1
  380. package/dist/ui/tui/store.js +50 -3
  381. package/dist/ui/tui/store.js.map +1 -1
  382. package/dist/ui/tui/tui-app.d.ts.map +1 -1
  383. package/dist/ui/tui/tui-app.js +231 -31
  384. package/dist/ui/tui/tui-app.js.map +1 -1
  385. package/dist/ui/tui/view.d.ts +7 -0
  386. package/dist/ui/tui/view.d.ts.map +1 -1
  387. package/dist/ui/tui/view.js +161 -57
  388. package/dist/ui/tui/view.js.map +1 -1
  389. package/dist/version.d.ts +1 -1
  390. package/dist/version.d.ts.map +1 -1
  391. package/dist/version.js +1 -1
  392. package/dist/version.js.map +1 -1
  393. package/package.json +13 -2
@@ -1,7 +1,14 @@
1
- import { mkdir, realpath } from "node:fs/promises";
1
+ import { mkdir, realpath, writeFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  import { randomUUID } from "node:crypto";
4
- import { createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, ModelRuntime, SessionManager, } from "@earendil-works/pi-coding-agent";
4
+ import { createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, createSyntheticSourceInfo, ModelRuntime, SessionManager, SettingsManager, estimateTokens, findCutPoint, getLatestCompactionEntry, sessionEntryToContextMessages, } from "@earendil-works/pi-coding-agent";
5
+ import { articleDraftFromToolUpdate, articleDraftFromUpdate } from "../articles/draft.js";
6
+ import { ArticlePreviewBus } from "../articles/preview.js";
7
+ import { publishedArticleValue } from "../articles/publish.js";
8
+ import { resolveSessionFile } from "../sessions/session-file.js";
9
+ import { toolInputText } from "../ui/shared/activity.js";
10
+ import { boundToolResult, estimateTextTokens } from "./context-budget.js";
11
+ const CONTEXT_RESUME_MESSAGE = "pspore-context-resume";
5
12
  export class PiRuntimeAdapter {
6
13
  #agentDir;
7
14
  #sessionRoot;
@@ -9,9 +16,12 @@ export class PiRuntimeAdapter {
9
16
  #resolveModel;
10
17
  #createTools;
11
18
  #createSkillPaths;
19
+ #createSkills;
12
20
  #toolGate;
13
21
  #clock;
14
22
  #idFactory;
23
+ #autoCompaction;
24
+ #articlePreviews = new ArticlePreviewBus((draft) => this.#publishArticleDraft(draft), () => this.#clock().getTime());
15
25
  #sessions = new Map();
16
26
  constructor(options, modelRuntime) {
17
27
  this.#agentDir = options.agentDir;
@@ -22,9 +32,11 @@ export class PiRuntimeAdapter {
22
32
  ((ref, runtime) => (ref ? runtime.getModel(ref.provider, ref.model) : undefined));
23
33
  this.#createTools = options.createTools ?? (() => []);
24
34
  this.#createSkillPaths = options.createSkillPaths ?? (() => []);
35
+ this.#createSkills = options.createSkills ?? (() => []);
25
36
  this.#toolGate = options.toolGate;
26
37
  this.#clock = options.clock ?? (() => new Date());
27
38
  this.#idFactory = options.idFactory ?? randomUUID;
39
+ this.#autoCompaction = options.autoCompaction ? { ...options.autoCompaction } : undefined;
28
40
  }
29
41
  static async create(options) {
30
42
  await mkdir(options.agentDir, { recursive: true, mode: 0o700 });
@@ -37,6 +49,9 @@ export class PiRuntimeAdapter {
37
49
  }));
38
50
  return new PiRuntimeAdapter(options, modelRuntime);
39
51
  }
52
+ articlePreview(toolCallId) {
53
+ return this.#articlePreviews.get(toolCallId);
54
+ }
40
55
  async create(spec) {
41
56
  this.#assertNewSession(spec.sessionId);
42
57
  const normalized = await this.#normalizeSpec(spec);
@@ -48,7 +63,7 @@ export class PiRuntimeAdapter {
48
63
  async resume(spec) {
49
64
  this.#assertNewSession(spec.sessionId);
50
65
  const normalized = await this.#normalizeSpec(spec);
51
- const sessionFile = await realpath(spec.sessionFile);
66
+ const sessionFile = await resolveSessionFile(spec.sessionFile, { allowMissing: true });
52
67
  const sessionDir = join(this.#sessionRoot, normalized.workspaceId);
53
68
  await mkdir(sessionDir, { recursive: true, mode: 0o700 });
54
69
  const sessionManager = SessionManager.open(sessionFile, sessionDir, normalized.cwd);
@@ -56,12 +71,14 @@ export class PiRuntimeAdapter {
56
71
  }
57
72
  async prompt(sessionId, input) {
58
73
  const record = this.#record(sessionId);
59
- if (record.runtime.session.isStreaming) {
74
+ if (record.runtime.session.isStreaming || record.activeTask) {
60
75
  throw new Error(`Session ${sessionId} is busy; use steer or followUp`);
61
76
  }
62
77
  if (!input.trim()) {
63
78
  throw new Error("Prompt cannot be empty");
64
79
  }
80
+ record.abortRequested = false;
81
+ record.lastError = undefined;
65
82
  const commandId = this.#idFactory();
66
83
  let resolveAccepted;
67
84
  let rejectAccepted;
@@ -69,19 +86,74 @@ export class PiRuntimeAdapter {
69
86
  resolveAccepted = resolve;
70
87
  rejectAccepted = reject;
71
88
  });
72
- const task = record.runtime.session
73
- .prompt(input, {
74
- source: "rpc",
75
- preflightResult: (success) => {
76
- if (success) {
77
- resolveAccepted({ accepted: true, commandId, acceptedAt: this.#clock().toISOString() });
89
+ // Own the logical task before any asynchronous compaction. Otherwise a
90
+ // restored session blocks prompt acceptance (and the serialized abort
91
+ // command behind it) for the entire summarization request.
92
+ const task = Promise.resolve()
93
+ .then(async () => {
94
+ if (this.#needsThresholdCompaction(record)) {
95
+ this.#startRun(record);
96
+ resolveAccepted({ accepted: true, commandId, acceptedAt: this.#clock().toISOString() });
97
+ }
98
+ await this.#compactAtConfiguredThreshold(record);
99
+ if (record.abortRequested) {
100
+ rejectAccepted(new Error("Prompt cancelled during context compaction"));
101
+ return;
102
+ }
103
+ await record.runtime.session.prompt(input, {
104
+ source: "rpc",
105
+ preflightResult: (success) => {
106
+ if (success)
107
+ resolveAccepted({ accepted: true, commandId, acceptedAt: this.#clock().toISOString() });
108
+ else
109
+ rejectAccepted(new Error("Pi rejected the prompt during preflight"));
110
+ },
111
+ });
112
+ while (!record.abortRequested) {
113
+ const resumeTask = record.compactionPending;
114
+ await this.#compactAtConfiguredThreshold(record);
115
+ if (record.abortRequested)
116
+ break;
117
+ await this.#restoreHeldQueue(record);
118
+ record.compactionPending = false;
119
+ if (resumeTask) {
120
+ await record.runtime.session.sendCustomMessage({
121
+ customType: CONTEXT_RESUME_MESSAGE,
122
+ content: "Continue the original user task after context compaction. Use the summary and retained tool results; do not repeat completed actions.",
123
+ display: false,
124
+ }, { triggerTurn: true });
78
125
  }
79
126
  else {
80
- rejectAccepted(new Error("Pi rejected the prompt during preflight"));
127
+ // A message can arrive while the *final answer* is compacting too.
128
+ // Start with that queued user input, rather than replying to the
129
+ // already-completed original request again.
130
+ const session = record.runtime.session;
131
+ if (!session.pendingMessageCount)
132
+ break;
133
+ const queue = session.clearQueue();
134
+ const nextInput = queue.steering.shift() ?? queue.followUp.shift();
135
+ if (nextInput === undefined)
136
+ break;
137
+ for (const message of queue.steering)
138
+ await session.steer(message);
139
+ for (const message of queue.followUp)
140
+ await session.followUp(message);
141
+ await session.prompt(nextInput, { source: "rpc", expandPromptTemplates: false });
81
142
  }
82
- },
143
+ }
144
+ if (record.compactionPending) {
145
+ await this.#restoreHeldQueue(record);
146
+ record.compactionPending = false;
147
+ }
83
148
  })
84
- .catch((error) => {
149
+ .catch(async (error) => {
150
+ await this.#restoreHeldQueue(record);
151
+ if (record.abortRequested) {
152
+ record.compactionPending = false;
153
+ rejectAccepted(new Error("Prompt cancelled during context compaction"));
154
+ return;
155
+ }
156
+ record.compactionPending = false;
85
157
  const normalizedError = toError(error);
86
158
  record.lastError = normalizedError.message;
87
159
  this.#emit(record, "runtime.error", { message: normalizedError.message }, false);
@@ -91,6 +163,9 @@ export class PiRuntimeAdapter {
91
163
  .finally(() => {
92
164
  if (record.activeTask === task) {
93
165
  record.activeTask = undefined;
166
+ record.runStarted = false;
167
+ if (!record.lastError)
168
+ this.#emit(record, "run.settled", {}, false);
94
169
  }
95
170
  });
96
171
  record.activeTask = task;
@@ -99,18 +174,57 @@ export class PiRuntimeAdapter {
99
174
  }
100
175
  async steer(sessionId, input) {
101
176
  const record = this.#record(sessionId);
177
+ if (record.compactionPending) {
178
+ record.heldQueue.steering.push(input);
179
+ this.#publishQueue(record);
180
+ return this.#accepted();
181
+ }
102
182
  await record.runtime.session.steer(input);
103
183
  return this.#accepted();
104
184
  }
105
185
  async followUp(sessionId, input) {
106
186
  const record = this.#record(sessionId);
187
+ if (record.compactionPending) {
188
+ record.heldQueue.followUp.push(input);
189
+ this.#publishQueue(record);
190
+ return this.#accepted();
191
+ }
107
192
  await record.runtime.session.followUp(input);
108
193
  return this.#accepted();
109
194
  }
195
+ async promoteFollowUp(sessionId, index) {
196
+ const record = this.#record(sessionId);
197
+ if (!Number.isSafeInteger(index) || index < 0 || index >= record.queue.followUp.length) {
198
+ throw new Error(`Follow-up queue index is out of range: ${index}`);
199
+ }
200
+ const selected = record.queue.followUp[index];
201
+ if (selected === undefined)
202
+ throw new Error(`Follow-up queue entry is unavailable: ${index}`);
203
+ if (record.compactionPending) {
204
+ record.heldQueue.followUp.splice(index, 1);
205
+ record.heldQueue.steering.push(selected);
206
+ this.#publishQueue(record);
207
+ return this.#accepted();
208
+ }
209
+ const queued = record.runtime.session.clearQueue();
210
+ for (const message of queued.steering)
211
+ await record.runtime.session.steer(message);
212
+ await record.runtime.session.steer(selected);
213
+ for (const [queuedIndex, message] of queued.followUp.entries()) {
214
+ if (queuedIndex !== index)
215
+ await record.runtime.session.followUp(message);
216
+ }
217
+ return this.#accepted();
218
+ }
110
219
  async abort(sessionId, _reason) {
111
- await this.#record(sessionId).runtime.session.abort();
220
+ const record = this.#record(sessionId);
221
+ record.abortRequested = true;
222
+ record.runtime.session.abortCompaction();
223
+ await record.runtime.session.abort();
224
+ await record.activeTask?.catch(() => undefined);
112
225
  }
113
226
  async compact(sessionId, instructions) {
227
+ await this.abort(sessionId, "manual compaction");
114
228
  const session = this.#record(sessionId).runtime.session;
115
229
  if (instructions === undefined) {
116
230
  await session.compact();
@@ -126,23 +240,21 @@ export class PiRuntimeAdapter {
126
240
  const model = session.model;
127
241
  const sessionFile = session.sessionFile;
128
242
  const lastError = record.lastError;
243
+ // Counting entries walks the branch but renders no text, so session state
244
+ // stays cheap no matter how long the conversation has grown.
245
+ const entries = this.#branchEntries(record);
246
+ const firstUserText = this.#firstUserText(record, entries);
129
247
  return {
130
248
  sessionId,
131
249
  upstreamSessionId: session.sessionId,
132
250
  ...(sessionFile ? { sessionFile } : {}),
133
251
  cwd: record.runtime.cwd,
134
- status: lastError ? "failed" : session.isStreaming ? "running" : "idle",
252
+ status: lastError ? "failed" : session.isStreaming || record.activeTask || record.thresholdCompaction ? "running" : "idle",
135
253
  ...(model ? { model: { provider: model.provider, model: model.id } } : {}),
136
254
  thinking: session.thinkingLevel,
137
- messages: session.messages.map(toRuntimeMessage),
138
- entries: session.sessionManager
139
- .getBranch()
140
- .filter((entry) => entry.type === "message")
141
- .map((entry) => ({
142
- id: entry.id,
143
- role: entry.message.role,
144
- text: messageText(entry.message),
145
- })),
255
+ ...(session.model ? { context: this.#contextUsage(record) } : {}),
256
+ messageCount: entries.length,
257
+ ...(firstUserText === undefined ? {} : { firstUserText }),
146
258
  queue: {
147
259
  steering: [...record.queue.steering],
148
260
  followUp: [...record.queue.followUp],
@@ -187,6 +299,72 @@ export class PiRuntimeAdapter {
187
299
  }
188
300
  return this.#handle(record);
189
301
  }
302
+ /**
303
+ * A slice of the persisted branch, which keeps entries a compaction has
304
+ * already dropped from the model's context. Only the requested slice is
305
+ * rendered to text: doing that for the whole branch is what made every
306
+ * streamed token cost as much as the conversation was long.
307
+ */
308
+ transcript(sessionId, options) {
309
+ const record = this.#record(sessionId);
310
+ const entries = this.#branchEntries(record);
311
+ const limit = Math.max(0, options.limit);
312
+ const from = Math.min(Math.max(0, options.from ?? Math.max(0, entries.length - limit)), entries.length);
313
+ return {
314
+ from,
315
+ count: entries.length,
316
+ messages: entries.slice(from, from + limit).map(toBranchMessage),
317
+ };
318
+ }
319
+ /**
320
+ * One message's full text, sliced. The transcript on the wire is bounded per
321
+ * string, so a message longer than that ceiling arrives cut; this is how a
322
+ * client asks for the rest of it without widening the ceiling for everything.
323
+ */
324
+ transcriptMessage(sessionId, options) {
325
+ const record = this.#record(sessionId);
326
+ const entries = this.#branchEntries(record);
327
+ const index = options.id === undefined
328
+ ? options.index ?? -1
329
+ : entries.findIndex((entry) => entry.id === options.id);
330
+ const entry = index >= 0 ? entries[index] : undefined;
331
+ if (!entry) {
332
+ throw new Error(`Transcript message is unavailable: ${options.id ?? String(options.index)}`);
333
+ }
334
+ const message = toBranchMessage(entry);
335
+ const offset = Math.min(Math.max(0, options.offset ?? 0), message.text.length);
336
+ return {
337
+ ...(message.id === undefined ? {} : { id: message.id }),
338
+ index,
339
+ role: message.role,
340
+ offset,
341
+ length: message.text.length,
342
+ text: message.text.slice(offset, offset + Math.max(0, options.limit)),
343
+ };
344
+ }
345
+ /** The persisted branch's message entries, unrendered. */
346
+ branch(sessionId) {
347
+ return this.#branchEntries(this.#record(sessionId)).map(toBranchMessage);
348
+ }
349
+ #branchEntries(record) {
350
+ return record.runtime.session.sessionManager
351
+ .getBranch()
352
+ .filter((entry) => entry.type === "message");
353
+ }
354
+ #firstUserText(record, entries) {
355
+ if (record.firstUserText !== undefined)
356
+ return record.firstUserText || undefined;
357
+ const first = entries.find((entry) => asRecord(entry.message)["role"] === "user");
358
+ // Nothing is cached until a user message exists: a session snapshotted at
359
+ // creation has none yet, and caching that absence would leave it untitled
360
+ // for the rest of its life.
361
+ if (!first)
362
+ return undefined;
363
+ // Only ever used as a session title, so it never needs the whole message.
364
+ const text = messageText(first.message).trim().slice(0, 200);
365
+ record.firstUserText = text;
366
+ return text || undefined;
367
+ }
190
368
  async fork(sessionId, entryId, position = "at") {
191
369
  const record = this.#record(sessionId);
192
370
  const result = await record.runtime.fork(entryId, { position });
@@ -197,6 +375,8 @@ export class PiRuntimeAdapter {
197
375
  }
198
376
  async dispose(sessionId) {
199
377
  const record = this.#record(sessionId);
378
+ await this.abort(sessionId, "session disposed");
379
+ this.#articlePreviews.clearSession(sessionId);
200
380
  record.unsubscribe?.();
201
381
  record.listeners.clear();
202
382
  await record.runtime.dispose();
@@ -212,6 +392,7 @@ export class PiRuntimeAdapter {
212
392
  agentDir: this.#agentDir,
213
393
  sessionManager,
214
394
  });
395
+ await this.#materializeSessionManager(runtime.session.sessionManager);
215
396
  const record = {
216
397
  spec,
217
398
  runtime,
@@ -219,22 +400,61 @@ export class PiRuntimeAdapter {
219
400
  listeners: new Set(),
220
401
  activeTask: undefined,
221
402
  queue: { steering: [], followUp: [] },
403
+ thresholdCompaction: false,
404
+ compactionPending: false,
405
+ runStarted: false,
406
+ abortRequested: false,
407
+ heldQueue: { steering: [], followUp: [] },
222
408
  lastError: undefined,
409
+ streaming: undefined,
410
+ itemSequence: 0,
411
+ firstUserText: undefined,
223
412
  };
224
413
  this.#sessions.set(spec.sessionId, record);
225
414
  this.#bind(record, runtime.session, false);
226
- runtime.setRebindSession(async (session) => this.#bind(record, session, true));
415
+ runtime.setRebindSession(async (session) => {
416
+ await this.#materializeSessionManager(session.sessionManager);
417
+ this.#bind(record, session, true);
418
+ });
227
419
  return this.#handle(record);
228
420
  }
421
+ async #materializeSessionManager(sessionManager) {
422
+ const sessionFile = sessionManager.getSessionFile();
423
+ if (!sessionFile)
424
+ return;
425
+ const contents = [sessionManager.getHeader(), ...sessionManager.getEntries()]
426
+ .map((entry) => JSON.stringify(entry))
427
+ .join("\n") + "\n";
428
+ try {
429
+ await writeFile(sessionFile, contents, { flag: "wx", mode: 0o600 });
430
+ }
431
+ catch (error) {
432
+ if (!isNodeError(error) || error.code !== "EEXIST")
433
+ throw error;
434
+ }
435
+ sessionManager.setSessionFile(await resolveSessionFile(sessionFile));
436
+ }
229
437
  #runtimeFactory(spec) {
230
438
  return async ({ cwd, agentDir, sessionManager, sessionStartEvent }) => {
231
439
  const policyExtension = this.#policyExtension(spec);
232
440
  const contextFiles = spec.contextFiles ? [...spec.contextFiles] : [];
233
441
  const skillPaths = [...await this.#createSkillPaths(spec.sessionId)].sort();
442
+ const resolvedSkills = [...await this.#createSkills(spec.sessionId)];
234
443
  const extensionFactories = policyExtension ? [policyExtension] : [];
235
444
  const services = await createAgentSessionServices({
236
445
  cwd,
237
446
  agentDir,
447
+ ...(this.#autoCompaction
448
+ ? {
449
+ settingsManager: SettingsManager.inMemory({
450
+ compaction: {
451
+ enabled: this.#autoCompaction.enabled,
452
+ reserveTokens: this.#autoCompaction.reserveTokens,
453
+ keepRecentTokens: this.#autoCompaction.keepRecentTokens,
454
+ },
455
+ }),
456
+ }
457
+ : {}),
238
458
  modelRuntime: this.#modelRuntime,
239
459
  resourceLoaderOptions: {
240
460
  noExtensions: true,
@@ -243,6 +463,9 @@ export class PiRuntimeAdapter {
243
463
  noThemes: true,
244
464
  noContextFiles: true,
245
465
  ...(skillPaths.length > 0 ? { additionalSkillPaths: skillPaths } : {}),
466
+ ...(resolvedSkills.length > 0
467
+ ? { skillsOverride: (base) => withResolvedSkills(base, resolvedSkills) }
468
+ : {}),
246
469
  ...(spec.systemPrompt ? { systemPrompt: spec.systemPrompt } : {}),
247
470
  ...(extensionFactories.length > 0 ? { extensionFactories } : {}),
248
471
  ...(contextFiles.length > 0
@@ -258,7 +481,13 @@ export class PiRuntimeAdapter {
258
481
  if (spec.model && !model) {
259
482
  throw new Error(`Configured model is unavailable: ${spec.model.provider}/${spec.model.model}`);
260
483
  }
261
- const customTools = [...this.#createTools(spec.sessionId)];
484
+ const customTools = this.#createTools(spec.sessionId, this.#modelRuntime).map((tool) => ({
485
+ ...tool,
486
+ execute: async (...args) => {
487
+ const result = await tool.execute(...args);
488
+ return { ...result, content: await boundToolResult(result.content, join(this.#sessionRoot, "artifacts", "tool-results")) };
489
+ },
490
+ }));
262
491
  const enabledTools = spec.enabledTools ? [...spec.enabledTools] : customTools.map((tool) => tool.name);
263
492
  const result = await createAgentSessionFromServices({
264
493
  services,
@@ -321,6 +550,25 @@ export class PiRuntimeAdapter {
321
550
  record.upstreamSessionId = session.sessionId;
322
551
  record.lastError = undefined;
323
552
  record.queue = { steering: [], followUp: [] };
553
+ record.streaming = undefined;
554
+ record.firstUserText = undefined;
555
+ const previousStop = session.agent.shouldStopAfterTurn;
556
+ session.agent.shouldStopAfterTurn = async (turn, signal) => {
557
+ if (await previousStop?.(turn, signal))
558
+ return true;
559
+ if (signal?.aborted || record.abortRequested || !this.#autoCompaction?.enabled)
560
+ return false;
561
+ if (turn.toolResults.length === 0 && session.pendingMessageCount === 0)
562
+ return false;
563
+ if (this.#contextUsage(record).tokens < this.#compactionThreshold(record))
564
+ return false;
565
+ record.compactionPending = true;
566
+ // Pi drains queues at agent_end, so hold them outside the SDK until the
567
+ // context has been compacted. New user messages join this same queue.
568
+ record.heldQueue = { steering: [...session.getSteeringMessages()], followUp: [...session.getFollowUpMessages()] };
569
+ session.clearQueue();
570
+ return true;
571
+ };
324
572
  record.unsubscribe = session.subscribe((event) => this.#onPiEvent(record, event));
325
573
  if (replacement) {
326
574
  this.#emit(record, "session.replaced", { previousUpstreamSessionId, upstreamSessionId: session.sessionId }, false);
@@ -329,50 +577,88 @@ export class PiRuntimeAdapter {
329
577
  #onPiEvent(record, event) {
330
578
  switch (event.type) {
331
579
  case "agent_start":
332
- this.#emit(record, "run.started", {}, false);
580
+ this.#startRun(record);
333
581
  break;
334
582
  case "agent_settled":
335
- this.#emit(record, "run.settled", {}, false);
583
+ this.#articlePreviews.clearSession(record.spec.sessionId);
584
+ // The adapter's logical task also includes automatic compaction and
585
+ // continuation. Publish settled only when that entire task finishes.
586
+ if (!record.activeTask)
587
+ this.#emit(record, "run.settled", {}, false);
336
588
  break;
337
- case "message_start":
338
- this.#emit(record, "message.started", messagePayload(event.message), false);
589
+ case "message_start": {
590
+ if (asRecord(event.message)["customType"] === CONTEXT_RESUME_MESSAGE)
591
+ break;
592
+ const streaming = this.#beginItem(record, event.message);
593
+ this.#emit(record, "transcript.item.started", { item: this.#startedItem(streaming) }, false);
339
594
  break;
595
+ }
340
596
  case "message_update": {
341
597
  const update = event.assistantMessageEvent;
342
- if (update.type === "text_delta") {
343
- this.#emit(record, "message.delta", { delta: update.delta }, true);
344
- }
345
- else if (update.type === "thinking_delta") {
346
- this.#emit(record, "message.thinking.delta", { delta: update.delta }, true);
598
+ if (update.type === "toolcall_delta" || update.type === "toolcall_end") {
599
+ this.#noteArticleDraft(record, event.message, update);
347
600
  }
601
+ if (update.type !== "text_delta" && update.type !== "thinking_delta")
602
+ break;
603
+ const kind = update.type === "text_delta" ? "text" : "thinking";
604
+ // A delta with no started item would have nowhere to land; opening one
605
+ // keeps the stream addressable even if Pi skips the start event.
606
+ const streaming = record.streaming ?? this.#beginItem(record, event.message);
607
+ if (kind === "text")
608
+ streaming.text += update.delta;
609
+ else
610
+ streaming.thinking += update.delta;
611
+ this.#emit(record, "transcript.delta", { itemId: streaming.id, kind, delta: update.delta }, true);
348
612
  break;
349
613
  }
350
- case "message_end":
351
- this.#emit(record, "message.completed", messagePayload(event.message), false);
614
+ case "message_end": {
615
+ if (asRecord(event.message)["customType"] === CONTEXT_RESUME_MESSAGE)
616
+ break;
617
+ const item = this.#finishItem(record, event.message);
618
+ this.#emit(record, "transcript.item.finished", { item }, false);
352
619
  break;
620
+ }
353
621
  case "tool_execution_start":
354
- this.#emit(record, "tool.started", { toolCallId: event.toolCallId, toolName: event.toolName, input: event.args }, false);
622
+ this.#emit(record, "tool.started", {
623
+ toolCallId: event.toolCallId,
624
+ toolName: event.toolName,
625
+ input: publishedArticleValue(event.toolName, event.args),
626
+ }, false);
355
627
  break;
356
- case "tool_execution_update":
357
- this.#emit(record, "tool.updated", { toolCallId: event.toolCallId, toolName: event.toolName, partialResult: event.partialResult }, true);
628
+ case "tool_execution_update": {
629
+ if (event.toolName === "article") {
630
+ const draft = articleDraftFromToolUpdate(record.spec.sessionId, event.toolCallId, event.partialResult);
631
+ if (draft) {
632
+ this.#articlePreviews.note(draft, this.#clock().getTime());
633
+ break;
634
+ }
635
+ }
636
+ this.#emit(record, "tool.updated", { toolCallId: event.toolCallId, toolName: event.toolName, partialResult: publishedArticleValue(event.toolName, event.partialResult) }, true);
358
637
  break;
638
+ }
359
639
  case "tool_execution_end":
640
+ this.#articlePreviews.clear(event.toolCallId);
360
641
  this.#emit(record, "tool.completed", {
361
642
  toolCallId: event.toolCallId,
362
643
  toolName: event.toolName,
363
644
  isError: event.isError,
364
- result: event.result,
645
+ result: publishedArticleValue(event.toolName, event.result),
365
646
  }, false);
366
647
  break;
367
648
  case "queue_update":
368
- record.queue = { steering: [...event.steering], followUp: [...event.followUp] };
369
- this.#emit(record, "queue.updated", record.queue, false);
649
+ this.#publishQueue(record);
370
650
  break;
371
651
  case "compaction_start":
372
- this.#emit(record, "compaction.started", { reason: event.reason }, false);
652
+ this.#emit(record, "compaction.started", { reason: record.thresholdCompaction && event.reason === "manual" ? "token_threshold" : event.reason }, false);
373
653
  break;
374
654
  case "compaction_end":
375
- this.#emit(record, "compaction.completed", { reason: event.reason, aborted: event.aborted, willRetry: event.willRetry }, false);
655
+ this.#emit(record, "compaction.completed", {
656
+ reason: record.thresholdCompaction && event.reason === "manual" ? "token_threshold" : event.reason,
657
+ aborted: event.aborted,
658
+ willRetry: event.willRetry,
659
+ ...(event.errorMessage ? { errorMessage: event.errorMessage } : {}),
660
+ ...(event.result ? { tokensBefore: event.result.tokensBefore, tokensAfter: event.result.estimatedTokensAfter } : {}),
661
+ }, false);
376
662
  break;
377
663
  case "auto_retry_start":
378
664
  this.#emit(record, "retry.started", { attempt: event.attempt, maxAttempts: event.maxAttempts, delayMs: event.delayMs }, false);
@@ -384,6 +670,60 @@ export class PiRuntimeAdapter {
384
670
  break;
385
671
  }
386
672
  }
673
+ #beginItem(record, message) {
674
+ const candidate = asRecord(message);
675
+ const streaming = {
676
+ id: `${record.upstreamSessionId}:${++record.itemSequence}`,
677
+ role: typeof candidate["role"] === "string" ? candidate["role"] : "assistant",
678
+ text: messageText(message),
679
+ thinking: "",
680
+ startedAt: this.#clock().toISOString(),
681
+ };
682
+ record.streaming = streaming;
683
+ return streaming;
684
+ }
685
+ /**
686
+ * The item a `transcript.item.started` event announces. It is the same shape
687
+ * a finished item has — clients and the desktop's frame validator both read a
688
+ * `TranscriptItem` — only still open, so the status is `streaming`.
689
+ */
690
+ #startedItem(streaming) {
691
+ return {
692
+ id: streaming.id,
693
+ role: streaming.role,
694
+ text: streaming.text,
695
+ ...(streaming.thinking ? { thinking: streaming.thinking } : {}),
696
+ status: "streaming",
697
+ ts: streaming.startedAt,
698
+ };
699
+ }
700
+ #finishItem(record, message) {
701
+ const streaming = record.streaming ?? this.#beginItem(record, message);
702
+ record.streaming = undefined;
703
+ return {
704
+ ...toRuntimeMessage(message),
705
+ id: streaming.id,
706
+ status: "complete",
707
+ ts: streaming.startedAt,
708
+ };
709
+ }
710
+ #noteArticleDraft(record, message, update) {
711
+ const draft = articleDraftFromUpdate(record.spec.sessionId, update, message);
712
+ if (!draft)
713
+ return;
714
+ this.#articlePreviews.note(draft, this.#clock().getTime());
715
+ }
716
+ #publishArticleDraft(draft) {
717
+ const record = this.#sessions.get(draft.sessionId);
718
+ if (!record)
719
+ return;
720
+ this.#emit(record, "article.preview", {
721
+ toolCallId: draft.toolCallId,
722
+ ...(draft.title === undefined ? {} : { title: draft.title }),
723
+ ...(draft.articleId === undefined ? {} : { articleId: draft.articleId }),
724
+ chars: draft.content?.length ?? 0,
725
+ }, true);
726
+ }
387
727
  #emit(record, type, payload, transient) {
388
728
  const event = {
389
729
  id: this.#idFactory(),
@@ -425,6 +765,107 @@ export class PiRuntimeAdapter {
425
765
  #accepted() {
426
766
  return { accepted: true, commandId: this.#idFactory(), acceptedAt: this.#clock().toISOString() };
427
767
  }
768
+ #startRun(record) {
769
+ if (record.runStarted)
770
+ return;
771
+ record.runStarted = true;
772
+ this.#emit(record, "run.started", {}, false);
773
+ }
774
+ #needsThresholdCompaction(record) {
775
+ const settings = this.#autoCompaction;
776
+ if (!settings?.enabled)
777
+ return false;
778
+ const session = record.runtime.session;
779
+ if (record.abortRequested || session.messages.length === 0)
780
+ return false;
781
+ // A just-compacted leaf cannot be compacted again without new work.
782
+ if (session.sessionManager.getBranch().at(-1)?.type === "compaction")
783
+ return false;
784
+ return this.#contextUsage(record).tokens >= this.#compactionThreshold(record);
785
+ }
786
+ async #compactAtConfiguredThreshold(record) {
787
+ if (!this.#needsThresholdCompaction(record))
788
+ return;
789
+ const session = record.runtime.session;
790
+ if (session.messages.at(-1)?.role === "toolResult") {
791
+ // Pi never cuts at a tool result. Add an invisible, user-like boundary
792
+ // after the completed batch so even the first long task can be compacted.
793
+ await session.sendCustomMessage({
794
+ customType: CONTEXT_RESUME_MESSAGE,
795
+ content: "The tool batch above has finished. Preserve the original user request, completed actions, evidence and remaining work in the context summary.",
796
+ display: false,
797
+ });
798
+ }
799
+ // Pi deliberately keeps whole recent messages. A single large retained
800
+ // answer can still exceed our threshold, yet leave nothing new to summarize.
801
+ // Use its public cut-point rules to avoid reporting that normal no-op as a
802
+ // failed task ("Nothing to compact").
803
+ const entries = session.sessionManager.getBranch();
804
+ const previous = getLatestCompactionEntry(entries);
805
+ const start = previous ? Math.max(0, entries.findIndex((entry) => entry.id === previous.firstKeptEntryId)) : 0;
806
+ const cut = findCutPoint(entries, start, entries.length, session.settingsManager.getCompactionSettings().keepRecentTokens);
807
+ if (!entries.slice(start, cut.firstKeptEntryIndex).some((entry) => entry.type !== "compaction" && sessionEntryToContextMessages(entry).length > 0))
808
+ return;
809
+ record.thresholdCompaction = true;
810
+ try {
811
+ await record.runtime.session.compact();
812
+ }
813
+ finally {
814
+ record.thresholdCompaction = false;
815
+ }
816
+ }
817
+ #compactionThreshold(record) {
818
+ const settings = this.#autoCompaction;
819
+ if (!settings)
820
+ return Number.POSITIVE_INFINITY;
821
+ const limit = record.runtime.session.model?.contextWindow ?? Number.POSITIVE_INFINITY;
822
+ return Math.max(1, Math.min(settings.triggerTokens, limit - settings.reserveTokens));
823
+ }
824
+ #publishQueue(record) {
825
+ record.queue = {
826
+ steering: [...record.runtime.session.getSteeringMessages(), ...record.heldQueue.steering],
827
+ followUp: [...record.runtime.session.getFollowUpMessages(), ...record.heldQueue.followUp],
828
+ };
829
+ this.#emit(record, "queue.updated", record.queue, false);
830
+ }
831
+ async #restoreHeldQueue(record) {
832
+ while (record.heldQueue.steering.length || record.heldQueue.followUp.length) {
833
+ const queue = record.heldQueue;
834
+ record.heldQueue = { steering: [], followUp: [] };
835
+ for (const message of queue.steering)
836
+ await record.runtime.session.steer(message);
837
+ for (const message of queue.followUp)
838
+ await record.runtime.session.followUp(message);
839
+ }
840
+ }
841
+ #contextUsage(record) {
842
+ const session = record.runtime.session;
843
+ const usage = session.getContextUsage();
844
+ let tokens = usage?.tokens;
845
+ const lastUsageIndex = session.messages.findLastIndex((message) => message.role === "assistant"
846
+ && message.stopReason !== "error" && message.stopReason !== "aborted"
847
+ && (message.usage.input + message.usage.output + message.usage.cacheRead + message.usage.cacheWrite) > 0);
848
+ if (tokens === undefined || tokens === null || lastUsageIndex < 0) {
849
+ tokens = estimateTextTokens(session.systemPrompt)
850
+ + estimateTextTokens(JSON.stringify(session.agent.state.tools.map((tool) => ({ name: tool.name, description: tool.description, parameters: tool.parameters }))));
851
+ for (const message of session.messages) {
852
+ tokens += estimateMessageTokens(message);
853
+ }
854
+ }
855
+ else {
856
+ // Pi estimates messages after the latest measured usage as chars / 4.
857
+ // Correct only that unmeasured tail; never add the entire history again.
858
+ for (const message of session.messages.slice(lastUsageIndex + 1)) {
859
+ tokens += estimateMessageTokens(message) - estimateTokens(message);
860
+ }
861
+ }
862
+ return {
863
+ tokens,
864
+ window: usage?.contextWindow ?? session.model?.contextWindow ?? 0,
865
+ estimated: true,
866
+ ...(this.#autoCompaction?.enabled ? { triggerTokens: this.#compactionThreshold(record) } : {}),
867
+ };
868
+ }
428
869
  #handle(record) {
429
870
  const sessionFile = record.runtime.session.sessionFile;
430
871
  return {
@@ -434,19 +875,73 @@ export class PiRuntimeAdapter {
434
875
  };
435
876
  }
436
877
  }
878
+ /**
879
+ * Adds skills resolved outside Pi to what Pi loaded from paths. A name Pi
880
+ * already has keeps its skill — the same first-wins rule Pi applies among its
881
+ * own paths — and the loser is reported the way Pi reports a collision.
882
+ */
883
+ function withResolvedSkills(base, resolved) {
884
+ const skills = [...base.skills];
885
+ const diagnostics = [...base.diagnostics];
886
+ for (const skill of resolved) {
887
+ const winner = skills.find((candidate) => candidate.name === skill.name);
888
+ if (winner) {
889
+ diagnostics.push({
890
+ type: "collision",
891
+ message: `name "${skill.name}" collision`,
892
+ path: skill.filePath,
893
+ collision: { resourceType: "skill", name: skill.name, winnerPath: winner.filePath, loserPath: skill.filePath },
894
+ });
895
+ continue;
896
+ }
897
+ skills.push({
898
+ ...skill,
899
+ sourceInfo: createSyntheticSourceInfo(skill.filePath, { source: "local", scope: "user", baseDir: skill.baseDir }),
900
+ });
901
+ }
902
+ return { skills, diagnostics };
903
+ }
904
+ function toBranchMessage(entry) {
905
+ return { id: entry.id, ...toRuntimeMessage(entry.message) };
906
+ }
437
907
  function toRuntimeMessage(message) {
438
908
  const candidate = asRecord(message);
909
+ const content = Array.isArray(candidate["content"]) ? candidate["content"].map(asRecord) : [];
910
+ const thinking = content.filter((block) => block["type"] === "thinking" && typeof block["thinking"] === "string")
911
+ .map((block) => block["thinking"]).join("\n");
912
+ const toolCalls = content.flatMap((block) => {
913
+ if (block["type"] !== "toolCall" || typeof block["id"] !== "string" || typeof block["name"] !== "string")
914
+ return [];
915
+ return [{ id: block["id"], name: block["name"], input: toolInputText(publishedArticleValue(block["name"], block["arguments"] ?? {})) }];
916
+ });
439
917
  return {
440
918
  role: typeof candidate["role"] === "string" ? candidate["role"] : "unknown",
441
919
  text: messageText(message),
920
+ ...(thinking ? { thinking } : {}),
921
+ ...(toolCalls.length ? { toolCalls } : {}),
922
+ ...(typeof candidate["toolCallId"] === "string" ? { toolCallId: candidate["toolCallId"] } : {}),
923
+ ...(typeof candidate["toolName"] === "string" ? { toolName: candidate["toolName"] } : {}),
924
+ ...(typeof candidate["isError"] === "boolean" ? { isError: candidate["isError"] } : {}),
442
925
  };
443
926
  }
444
- function messagePayload(message) {
445
- const candidate = asRecord(message);
446
- return {
447
- role: typeof candidate["role"] === "string" ? candidate["role"] : "unknown",
448
- text: messageText(message),
449
- };
927
+ function isNodeError(error) {
928
+ return error instanceof Error && "code" in error;
929
+ }
930
+ function estimateMessageTokens(message) {
931
+ let prose = messageText(message);
932
+ if (message.role === "compactionSummary" || message.role === "branchSummary")
933
+ prose += message.summary;
934
+ if (message.role === "bashExecution")
935
+ prose += message.output;
936
+ if (message.role === "assistant") {
937
+ for (const block of message.content) {
938
+ if (block.type === "thinking")
939
+ prose += block.thinking;
940
+ if (block.type === "toolCall")
941
+ prose += block.name + JSON.stringify(block.arguments);
942
+ }
943
+ }
944
+ return Math.max(estimateTokens(message), estimateTextTokens(prose));
450
945
  }
451
946
  function messageText(message) {
452
947
  const candidate = asRecord(message);
@@ -463,12 +958,12 @@ function messageText(message) {
463
958
  return content
464
959
  .map((block) => {
465
960
  const value = asRecord(block);
466
- if (typeof value["text"] === "string")
961
+ // Tool calls and hidden reasoning have dedicated runtime events. Mixing
962
+ // synthetic markers into assistant prose leaks strings such as
963
+ // `[tool:shell]` into Markdown and persisted history.
964
+ if ((value["type"] === undefined || value["type"] === "text") && typeof value["text"] === "string") {
467
965
  return value["text"];
468
- if (typeof value["thinking"] === "string")
469
- return value["thinking"];
470
- if (value["type"] === "toolCall" && typeof value["name"] === "string")
471
- return `[tool:${value["name"]}]`;
966
+ }
472
967
  return "";
473
968
  })
474
969
  .filter(Boolean)