@timurproko/a1 0.1.8-dev.3b54f21 → 0.1.8-dev.407

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 (643) hide show
  1. package/README.md +159 -65
  2. package/bin/cli.js +28 -10
  3. package/bin/guardian.js +12 -6
  4. package/bin/module-identity.js +69 -35
  5. package/bin/pi-tui.d.ts +6 -0
  6. package/bin/pi-tui.js +28 -0
  7. package/bin/release-cleanup.js +8 -0
  8. package/bin/supervisor.js +2 -2
  9. package/bin/sync-pi-tui-proxy.js +97 -0
  10. package/bin/ui.js +62 -21
  11. package/bin/update-recovery.js +274 -0
  12. package/bin/warmup.js +27 -0
  13. package/dist/cli/capabilities.d.ts +6 -0
  14. package/dist/{src/cli → cli}/capabilities.js +1 -1
  15. package/dist/{src/cli → cli}/dispatch.d.ts +15 -1
  16. package/dist/cli/dispatch.js +261 -0
  17. package/dist/cli/package-messages.d.ts +26 -0
  18. package/dist/cli/package-messages.js +71 -0
  19. package/dist/{src/cli → cli}/packages.d.ts +11 -2
  20. package/dist/{src/cli → cli}/packages.js +24 -27
  21. package/dist/{src/cli → cli}/version-stats.d.ts +6 -0
  22. package/dist/cli/version-stats.js +112 -0
  23. package/dist/composition/agent-engine-bridge.d.ts +3 -0
  24. package/dist/composition/agent-engine-bridge.js +108 -0
  25. package/dist/composition/index.d.ts +3 -0
  26. package/dist/composition/index.js +3 -0
  27. package/dist/composition/owned-ui.d.ts +35 -0
  28. package/dist/composition/owned-ui.js +118 -0
  29. package/dist/composition/process.d.ts +16 -0
  30. package/dist/composition/process.js +45 -0
  31. package/dist/composition/settings-route-host.d.ts +8 -0
  32. package/dist/composition/settings-route-host.js +75 -0
  33. package/dist/{src/foundation/agent-engine-contracts → contracts/agent-engine}/capability-ports.d.ts +2 -2
  34. package/dist/{src/foundation/agent-engine-contracts → contracts/agent-engine}/domain-validation.js +22 -4
  35. package/dist/{src/foundation/agent-engine-contracts → contracts/agent-engine}/domain.d.ts +17 -0
  36. package/dist/{src/foundation/agent-engine-contracts → contracts/agent-engine}/package-ports.d.ts +6 -0
  37. package/dist/contracts/owned-ui/image-attachments.d.ts +36 -0
  38. package/dist/contracts/owned-ui/image-attachments.js +67 -0
  39. package/dist/contracts/owned-ui/index.d.ts +8 -0
  40. package/dist/contracts/owned-ui/index.js +8 -0
  41. package/dist/{src/foundation/owned-ui-contracts → contracts/owned-ui}/model.d.ts +123 -0
  42. package/dist/contracts/owned-ui/prompt-history.d.ts +55 -0
  43. package/dist/contracts/owned-ui/prompt-history.js +23 -0
  44. package/dist/contracts/owned-ui/prompt-suggestions.d.ts +3 -0
  45. package/dist/contracts/owned-ui/prompt-suggestions.js +44 -0
  46. package/dist/contracts/owned-ui/suggestion-diagnostics.d.ts +22 -0
  47. package/dist/contracts/owned-ui/suggestion-diagnostics.js +9 -0
  48. package/dist/contracts/owned-ui/transcript-lifecycle.d.ts +5 -0
  49. package/dist/contracts/owned-ui/transcript-lifecycle.js +33 -0
  50. package/dist/{src/foundation/owned-ui-contracts → contracts/owned-ui}/validation.d.ts +5 -1
  51. package/dist/{src/foundation/owned-ui-contracts → contracts/owned-ui}/validation.js +129 -4
  52. package/dist/{src/foundation/presentation-contracts → contracts/presentation}/index.d.ts +8 -0
  53. package/dist/{src/features → features}/launch/development-launch.js +11 -3
  54. package/dist/{src/features → features}/launch/index.d.ts +0 -1
  55. package/dist/{src/features → features}/launch/index.js +0 -1
  56. package/dist/features/launch/intent.d.ts +10 -0
  57. package/dist/features/launch/intent.js +8 -0
  58. package/dist/{src/features → features}/launch/prepare-launch.d.ts +0 -2
  59. package/dist/{src/features → features}/launch/prepare-launch.js +2 -9
  60. package/dist/{src/features → features}/launch/profile-paths.d.ts +3 -2
  61. package/dist/{src/features → features}/launch/profile-paths.js +13 -8
  62. package/dist/{src/features → features}/launch/runtime-selection.d.ts +2 -5
  63. package/dist/{src/features → features}/owned-ui/customization.d.ts +3 -1
  64. package/dist/{src/features → features}/owned-ui/customization.js +3 -1
  65. package/dist/{src/features → features}/owned-ui/diagnostics.d.ts +2 -1
  66. package/dist/{src/features → features}/owned-ui/diagnostics.js +1 -0
  67. package/dist/{src/features → features}/owned-ui/index.d.ts +2 -0
  68. package/dist/{src/features → features}/owned-ui/index.js +2 -0
  69. package/dist/features/owned-ui/project-trust-prompt.d.ts +25 -0
  70. package/dist/features/owned-ui/project-trust-prompt.js +149 -0
  71. package/dist/{src/features → features}/owned-ui/run.d.ts +2 -2
  72. package/dist/features/owned-ui/run.js +32 -0
  73. package/dist/features/owned-ui/session-fork-prompt.d.ts +8 -0
  74. package/dist/features/owned-ui/session-fork-prompt.js +34 -0
  75. package/dist/{src/features → features}/owned-ui/settings-app.d.ts +3 -3
  76. package/dist/{src/features → features}/owned-ui/settings-app.js +105 -56
  77. package/dist/features/prompt-history/image-sidecar.d.ts +32 -0
  78. package/dist/features/prompt-history/image-sidecar.js +130 -0
  79. package/dist/features/prompt-history/index.d.ts +4 -0
  80. package/dist/features/prompt-history/index.js +3 -0
  81. package/dist/features/prompt-history/paths.d.ts +5 -0
  82. package/dist/features/prompt-history/paths.js +17 -0
  83. package/dist/features/prompt-history/service.d.ts +54 -0
  84. package/dist/features/prompt-history/service.js +425 -0
  85. package/dist/features/prompt-history/store.d.ts +16 -0
  86. package/dist/features/prompt-history/store.js +223 -0
  87. package/dist/features/prompt-history/worker.d.ts +9 -0
  88. package/dist/features/prompt-history/worker.js +45 -0
  89. package/dist/features/prompt-suggestions/diagnostics.d.ts +18 -0
  90. package/dist/features/prompt-suggestions/diagnostics.js +82 -0
  91. package/dist/features/prompt-suggestions/index.d.ts +1 -0
  92. package/dist/features/prompt-suggestions/index.js +1 -0
  93. package/dist/{src/features → features}/workspace/capabilities.d.ts +1 -1
  94. package/dist/{src/features → features}/workspace/reducer.d.ts +2 -1
  95. package/dist/{src/features → features}/workspace/reducer.js +2 -1
  96. package/dist/{src/features → features}/workspace/router.d.ts +2 -1
  97. package/dist/{src/features → features}/workspace/router.js +1 -0
  98. package/dist/{src/features → features}/workspace/store.d.ts +2 -1
  99. package/dist/{src/features → features}/workspace/store.js +1 -0
  100. package/dist/foundation/launch-context/index.d.ts +59 -0
  101. package/dist/foundation/launch-context/index.js +136 -0
  102. package/dist/{src/foundation → foundation}/launch-guardian/main.d.ts +2 -0
  103. package/dist/{src/foundation → foundation}/launch-guardian/main.js +23 -8
  104. package/dist/{src/foundation → foundation}/lifecycle/index.d.ts +2 -0
  105. package/dist/{src/foundation → foundation}/lifecycle/index.js +2 -0
  106. package/dist/{src/foundation → foundation}/lifecycle/model.d.ts +1 -1
  107. package/dist/{src/foundation → foundation}/lifecycle/model.js +1 -1
  108. package/dist/foundation/lifecycle/paths.d.ts +30 -0
  109. package/dist/{src/foundation → foundation}/lifecycle/paths.js +29 -1
  110. package/dist/foundation/lifecycle/session-selection.d.ts +9 -0
  111. package/dist/foundation/lifecycle/session-selection.js +39 -0
  112. package/dist/foundation/lifecycle/supervisor-startup.d.ts +30 -0
  113. package/dist/foundation/lifecycle/supervisor-startup.js +106 -0
  114. package/dist/{src/foundation → foundation}/native-host-protocol/codec.d.ts +2 -0
  115. package/dist/{src/foundation → foundation}/native-host-protocol/codec.js +2 -0
  116. package/dist/{src/foundation → foundation}/native-host-protocol/messages.d.ts +1 -1
  117. package/dist/{src/foundation → foundation}/native-host-protocol/messages.js +1 -1
  118. package/dist/foundation/process-containment/darwin-process-inspector.d.ts +5 -0
  119. package/dist/foundation/process-containment/darwin-process-inspector.js +7 -0
  120. package/dist/{src/foundation → foundation}/process-containment/index.d.ts +1 -0
  121. package/dist/{src/foundation → foundation}/process-containment/index.js +1 -0
  122. package/dist/{src/foundation → foundation}/process-containment/linux-process-inspector.d.ts +1 -0
  123. package/dist/{src/foundation → foundation}/process-containment/linux-process-inspector.js +1 -0
  124. package/dist/{src/foundation → foundation}/process-containment/native-guardian-containment.d.ts +1 -0
  125. package/dist/{src/foundation → foundation}/process-containment/native-guardian-containment.js +1 -0
  126. package/dist/{src/foundation → foundation}/process-containment/windows-process-inspector.d.ts +3 -1
  127. package/dist/{src/foundation → foundation}/process-containment/windows-process-inspector.js +5 -2
  128. package/dist/{src/foundation → foundation}/protocol/client.d.ts +1 -0
  129. package/dist/{src/foundation → foundation}/protocol/client.js +1 -0
  130. package/dist/{src/foundation → foundation}/protocol/messages.d.ts +1 -0
  131. package/dist/{src/foundation → foundation}/protocol/messages.js +1 -0
  132. package/dist/foundation/release/bootstrap.d.ts +55 -0
  133. package/dist/{src/foundation → foundation}/release/bootstrap.js +219 -46
  134. package/dist/foundation/release/cohort-state.d.ts +157 -0
  135. package/dist/foundation/release/cohort-state.js +472 -0
  136. package/dist/foundation/release/dependency-certification-retention.d.ts +7 -0
  137. package/dist/foundation/release/dependency-certification-retention.js +88 -0
  138. package/dist/foundation/release/dependency-certification.d.ts +24 -0
  139. package/dist/foundation/release/dependency-certification.js +266 -0
  140. package/dist/foundation/release/dependency-layer.d.ts +81 -0
  141. package/dist/foundation/release/dependency-layer.js +466 -0
  142. package/dist/foundation/release/endpoints.d.ts +20 -0
  143. package/dist/foundation/release/endpoints.js +50 -0
  144. package/dist/foundation/release/immutable-platform.d.ts +6 -0
  145. package/dist/foundation/release/immutable-platform.js +13 -0
  146. package/dist/{src/foundation → foundation}/release/index.d.ts +6 -0
  147. package/dist/{src/foundation → foundation}/release/index.js +6 -0
  148. package/dist/foundation/release/release-gc.d.ts +71 -0
  149. package/dist/foundation/release/release-gc.js +764 -0
  150. package/dist/{src/foundation → foundation}/release/release-store.d.ts +9 -3
  151. package/dist/{src/foundation → foundation}/release/release-store.js +74 -14
  152. package/dist/{src/foundation → foundation}/release/release.d.ts +6 -1
  153. package/dist/{src/foundation → foundation}/release/release.js +15 -4
  154. package/dist/foundation/release/restart-certification.d.ts +68 -0
  155. package/dist/foundation/release/restart-certification.js +227 -0
  156. package/dist/foundation/release/update-launch.d.ts +6 -0
  157. package/dist/foundation/release/update-launch.js +17 -0
  158. package/dist/foundation/release/update-recovery.d.ts +76 -0
  159. package/dist/foundation/release/update-recovery.js +356 -0
  160. package/dist/{src/foundation → foundation}/release/update-transaction.d.ts +11 -0
  161. package/dist/{src/foundation → foundation}/release/update-transaction.js +13 -1
  162. package/dist/{src/foundation → foundation}/release/update.d.ts +40 -4
  163. package/dist/{src/foundation → foundation}/release/update.js +196 -78
  164. package/dist/foundation/release/warmup.d.ts +3 -0
  165. package/dist/foundation/release/warmup.js +47 -0
  166. package/dist/foundation/startup/index.d.ts +1 -0
  167. package/dist/foundation/startup/index.js +1 -0
  168. package/dist/foundation/startup/startup-runtime.d.ts +36 -0
  169. package/dist/foundation/startup/startup-runtime.js +142 -0
  170. package/dist/{src/foundation → foundation}/storage/control-store.d.ts +2 -1
  171. package/dist/{src/foundation → foundation}/storage/control-store.js +39 -4
  172. package/dist/{src/foundation → foundation}/structured-agent-runtime/backpressure.d.ts +2 -1
  173. package/dist/{src/foundation → foundation}/structured-agent-runtime/backpressure.js +2 -1
  174. package/dist/{src/foundation → foundation}/structured-agent-runtime/commands.d.ts +2 -1
  175. package/dist/{src/foundation → foundation}/structured-agent-runtime/commands.js +2 -1
  176. package/dist/{src/foundation → foundation}/structured-agent-runtime/handshake.d.ts +1 -1
  177. package/dist/{src/foundation → foundation}/structured-agent-runtime/handshake.js +1 -1
  178. package/dist/{src/foundation → foundation}/structured-agent-runtime/reconnection.d.ts +2 -1
  179. package/dist/{src/foundation → foundation}/structured-agent-runtime/reconnection.js +2 -1
  180. package/dist/{src/foundation → foundation}/structured-agent-runtime/state.d.ts +2 -1
  181. package/dist/{src/foundation → foundation}/structured-agent-runtime/state.js +2 -1
  182. package/dist/foundation/supervision/index.d.ts +3 -0
  183. package/dist/foundation/supervision/index.js +3 -0
  184. package/dist/foundation/supervision/main.d.ts +1 -0
  185. package/dist/foundation/supervision/main.js +84 -0
  186. package/dist/foundation/supervision/paths.d.ts +1 -0
  187. package/dist/foundation/supervision/paths.js +1 -0
  188. package/dist/foundation/supervision/server.d.ts +51 -0
  189. package/dist/{src/foundation → foundation}/supervision/server.js +142 -20
  190. package/dist/foundation/terminal-cleanup/fatal-exit.d.ts +12 -0
  191. package/dist/foundation/terminal-cleanup/fatal-exit.js +85 -0
  192. package/dist/foundation/terminal-cleanup/index.d.ts +2 -0
  193. package/dist/foundation/terminal-cleanup/index.js +2 -0
  194. package/dist/foundation/terminal-cleanup/terminal-reset.d.ts +4 -0
  195. package/dist/foundation/terminal-cleanup/terminal-reset.js +40 -0
  196. package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/components.d.ts +1 -1
  197. package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/conformance.d.ts +1 -0
  198. package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/conformance.js +3 -2
  199. package/dist/integrations/pi/components/editor-interaction.d.ts +38 -0
  200. package/dist/integrations/pi/components/history-editor-loader.d.ts +5 -0
  201. package/dist/integrations/pi/components/history-editor-loader.js +5 -0
  202. package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/index.d.ts +2 -1
  203. package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/index.js +2 -1
  204. package/dist/integrations/pi/components/owned-editor-ux.d.ts +74 -0
  205. package/dist/integrations/pi/components/owned-editor-ux.js +1093 -0
  206. package/dist/integrations/pi/components/path-word-ranges.d.ts +2 -0
  207. package/dist/integrations/pi/components/path-word-ranges.js +37 -0
  208. package/dist/integrations/pi/components/prompt-input-port.d.ts +18 -0
  209. package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/shell-components.d.ts +1 -0
  210. package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/shell-components.js +1 -0
  211. package/dist/integrations/pi/components/shell-editor-autocomplete.js +297 -0
  212. package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/shell-extension-ui.d.ts +1 -1
  213. package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/shell-footer-status.d.ts +4 -4
  214. package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/shell-footer-status.js +58 -23
  215. package/dist/integrations/pi/components/shell-presenters-info.d.ts +43 -0
  216. package/dist/integrations/pi/components/shell-presenters-info.js +114 -0
  217. package/dist/integrations/pi/components/shell-presenters-transcript.d.ts +29 -0
  218. package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/shell-presenters-transcript.js +174 -170
  219. package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/shell-selectors-dialogs.d.ts +8 -4
  220. package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/shell-selectors-dialogs.js +10 -6
  221. package/dist/integrations/pi/components/shell-shared-facade.d.ts +213 -0
  222. package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/shell-shared-facade.js +5 -2
  223. package/dist/integrations/pi/components/submitted-prompt-adapter.d.ts +15 -0
  224. package/dist/integrations/pi/components/submitted-prompt-adapter.js +43 -0
  225. package/dist/integrations/pi/components/tool-image-presentation.d.ts +21 -0
  226. package/dist/integrations/pi/components/tool-image-presentation.js +86 -0
  227. package/dist/integrations/pi/components/tool-result-adapter.d.ts +6 -0
  228. package/dist/integrations/pi/components/tool-result-adapter.js +27 -0
  229. package/dist/integrations/pi/components/transcript-image-resolver.d.ts +7 -0
  230. package/dist/integrations/pi/components/transcript-image-resolver.js +19 -0
  231. package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/upstream/adjacent/core/keybindings.d.ts +11 -2
  232. package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/upstream/adjacent/core/keybindings.js +41 -3
  233. package/dist/integrations/pi/components/upstream/assets/earendil-image.json +12 -0
  234. package/dist/integrations/pi/components/upstream/components/earendil-announcement.js +42 -0
  235. package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +40 -0
  236. package/dist/integrations/pi/components/upstream/components/owned-editor.js +166 -0
  237. package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/upstream/components/scoped-models-selector.d.ts +1 -1
  238. package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/upstream/components/scoped-models-selector.js +3 -1
  239. package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/upstream/components/session-footer.d.ts +3 -2
  240. package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/upstream/components/session-footer.js +12 -7
  241. package/dist/integrations/pi/components/upstream/components/tool-execution.d.ts +61 -0
  242. package/dist/integrations/pi/components/upstream/components/tool-execution.js +344 -0
  243. package/dist/integrations/pi/components/upstream/history/editor-core.d.ts +280 -0
  244. package/dist/integrations/pi/components/upstream/history/editor-core.js +2062 -0
  245. package/dist/integrations/pi/components/upstream/history/kill-ring.d.ts +32 -0
  246. package/dist/integrations/pi/components/upstream/history/kill-ring.js +48 -0
  247. package/dist/integrations/pi/components/upstream/history/printable-key.d.ts +1 -0
  248. package/dist/integrations/pi/components/upstream/history/printable-key.js +40 -0
  249. package/dist/integrations/pi/components/upstream/history/text-helpers.d.ts +14 -0
  250. package/dist/integrations/pi/components/upstream/history/text-helpers.js +28 -0
  251. package/dist/integrations/pi/components/upstream/history/undo-stack.d.ts +22 -0
  252. package/dist/integrations/pi/components/upstream/history/undo-stack.js +36 -0
  253. package/dist/integrations/pi/components/upstream/history/word-navigation.d.ts +24 -0
  254. package/dist/integrations/pi/components/upstream/history/word-navigation.js +100 -0
  255. package/dist/{src/foundation/pi-engine-adapter → integrations/pi/engine}/adapter.d.ts +74 -6
  256. package/dist/{src/foundation/pi-engine-adapter → integrations/pi/engine}/adapter.js +1340 -228
  257. package/dist/integrations/pi/engine/changelog.d.ts +2 -0
  258. package/dist/integrations/pi/engine/changelog.js +63 -0
  259. package/dist/{src/foundation/pi-engine-adapter → integrations/pi/engine}/conformance.d.ts +4 -2
  260. package/dist/{src/foundation/pi-engine-adapter → integrations/pi/engine}/conformance.js +15 -5
  261. package/dist/integrations/pi/engine/http-dispatcher.d.ts +4 -0
  262. package/dist/integrations/pi/engine/http-dispatcher.js +25 -0
  263. package/dist/{src/foundation/pi-engine-adapter → integrations/pi/engine}/index.d.ts +4 -0
  264. package/dist/{src/foundation/pi-engine-adapter → integrations/pi/engine}/index.js +4 -0
  265. package/dist/{src/foundation/pi-engine-adapter → integrations/pi/engine}/model-auth-integration.d.ts +2 -1
  266. package/dist/{src/foundation/pi-engine-adapter → integrations/pi/engine}/model-auth-integration.js +1 -0
  267. package/dist/{src/foundation/pi-engine-adapter → integrations/pi/engine}/package-integration.d.ts +1 -1
  268. package/dist/{src/foundation/pi-engine-adapter → integrations/pi/engine}/package-integration.js +32 -26
  269. package/dist/integrations/pi/engine/pending-delivery.d.ts +30 -0
  270. package/dist/integrations/pi/engine/pending-delivery.js +158 -0
  271. package/dist/integrations/pi/engine/project-trust-preflight.d.ts +22 -0
  272. package/dist/integrations/pi/engine/project-trust-preflight.js +50 -0
  273. package/dist/{src/foundation/pi-engine-adapter → integrations/pi/engine}/resource-extension-integration.d.ts +2 -1
  274. package/dist/{src/foundation/pi-engine-adapter → integrations/pi/engine}/resource-extension-integration.js +1 -0
  275. package/dist/integrations/pi/engine/resources/changelog.json +12 -0
  276. package/dist/{src/foundation/pi-engine-adapter → integrations/pi/engine}/runtime-integration.d.ts +19 -1
  277. package/dist/{src/foundation/pi-engine-adapter → integrations/pi/engine}/runtime-integration.js +56 -5
  278. package/dist/{src/foundation/pi-engine-adapter → integrations/pi/engine}/session-integration.d.ts +4 -2
  279. package/dist/{src/foundation/pi-engine-adapter → integrations/pi/engine}/session-integration.js +19 -4
  280. package/dist/integrations/pi/engine/session-selection.d.ts +28 -0
  281. package/dist/integrations/pi/engine/session-selection.js +90 -0
  282. package/dist/integrations/pi/engine/settings-effects.d.ts +66 -0
  283. package/dist/integrations/pi/engine/settings-effects.js +249 -0
  284. package/dist/integrations/pi/engine/settings-integration.d.ts +43 -0
  285. package/dist/{src/foundation/pi-engine-adapter → integrations/pi/engine}/settings-integration.js +102 -108
  286. package/dist/integrations/pi/engine/tool-rendering.d.ts +13 -0
  287. package/dist/integrations/pi/engine/tool-rendering.js +145 -0
  288. package/dist/integrations/pi/engine/transcript-image-assets.d.ts +11 -0
  289. package/dist/integrations/pi/engine/transcript-image-assets.js +86 -0
  290. package/dist/integrations/pi/engine/windows-filesystem-hygiene.d.ts +21 -0
  291. package/dist/integrations/pi/engine/windows-filesystem-hygiene.js +55 -0
  292. package/dist/{src/foundation/pi-engine-adapter → integrations/pi/engine}/workflow-controllers.d.ts +3 -2
  293. package/dist/{src/foundation/pi-engine-adapter → integrations/pi/engine}/workflow-controllers.js +1 -0
  294. package/dist/{src/foundation/pi-engine-adapter → integrations/pi/engine}/workflows.d.ts +19 -2
  295. package/dist/integrations/pi/session-ui/clipboard-diagnostics.d.ts +16 -0
  296. package/dist/integrations/pi/session-ui/clipboard-diagnostics.js +65 -0
  297. package/dist/integrations/pi/session-ui/clipboard-image.d.ts +11 -0
  298. package/dist/integrations/pi/session-ui/clipboard-image.js +38 -0
  299. package/dist/integrations/pi/session-ui/editor-hyperlink-budget.d.ts +9 -0
  300. package/dist/integrations/pi/session-ui/editor-hyperlink-budget.js +23 -0
  301. package/dist/integrations/pi/session-ui/image-preparation-client.d.ts +20 -0
  302. package/dist/integrations/pi/session-ui/image-preparation-client.js +145 -0
  303. package/dist/integrations/pi/session-ui/image-preparation.d.ts +18 -0
  304. package/dist/integrations/pi/session-ui/image-preparation.js +126 -0
  305. package/dist/integrations/pi/session-ui/image-source.d.ts +13 -0
  306. package/dist/integrations/pi/session-ui/image-source.js +171 -0
  307. package/dist/integrations/pi/session-ui/image-worker.d.ts +17 -0
  308. package/dist/integrations/pi/session-ui/image-worker.js +23 -0
  309. package/dist/integrations/pi/session-ui/index.d.ts +5 -0
  310. package/dist/integrations/pi/session-ui/index.js +5 -0
  311. package/dist/integrations/pi/session-ui/paste-executor.d.ts +10 -0
  312. package/dist/integrations/pi/session-ui/paste-executor.js +232 -0
  313. package/dist/integrations/pi/session-ui/paste-helper.d.ts +1 -0
  314. package/dist/integrations/pi/session-ui/paste-helper.js +173 -0
  315. package/dist/integrations/pi/session-ui/paste-preparation-client.d.ts +16 -0
  316. package/dist/integrations/pi/session-ui/paste-preparation-client.js +159 -0
  317. package/dist/integrations/pi/session-ui/paste-protocol.d.ts +78 -0
  318. package/dist/integrations/pi/session-ui/paste-protocol.js +18 -0
  319. package/dist/integrations/pi/session-ui/paste-text-preparation.d.ts +20 -0
  320. package/dist/integrations/pi/session-ui/paste-text-preparation.js +134 -0
  321. package/dist/integrations/pi/session-ui/paste-text-worker.d.ts +1 -0
  322. package/dist/integrations/pi/session-ui/paste-text-worker.js +12 -0
  323. package/dist/integrations/pi/session-ui/path-chip-presentation.d.ts +7 -0
  324. package/dist/integrations/pi/session-ui/path-chip-presentation.js +24 -0
  325. package/dist/integrations/pi/session-ui/prompt-chips.d.ts +72 -0
  326. package/dist/integrations/pi/session-ui/prompt-chips.js +503 -0
  327. package/dist/integrations/pi/session-ui/prompt-history-controller.d.ts +36 -0
  328. package/dist/integrations/pi/session-ui/prompt-history-controller.js +170 -0
  329. package/dist/integrations/pi/session-ui/prompt-suggestion-controller.d.ts +35 -0
  330. package/dist/integrations/pi/session-ui/prompt-suggestion-controller.js +207 -0
  331. package/dist/integrations/pi/session-ui/response-copy-coordinator.d.ts +27 -0
  332. package/dist/integrations/pi/session-ui/response-copy-coordinator.js +189 -0
  333. package/dist/integrations/pi/session-ui/response-copy-helper.d.ts +1 -0
  334. package/dist/integrations/pi/session-ui/response-copy-helper.js +122 -0
  335. package/dist/integrations/pi/session-ui/response-copy-protocol.d.ts +56 -0
  336. package/dist/integrations/pi/session-ui/response-copy-protocol.js +5 -0
  337. package/dist/integrations/pi/session-ui/response-copy-transport.d.ts +31 -0
  338. package/dist/integrations/pi/session-ui/response-copy-transport.js +187 -0
  339. package/dist/integrations/pi/session-ui/route-host.d.ts +5 -0
  340. package/dist/integrations/pi/session-ui/route-host.js +1 -0
  341. package/dist/integrations/pi/session-ui/session-shell-root.d.ts +221 -0
  342. package/dist/integrations/pi/session-ui/session-shell-root.js +1305 -0
  343. package/dist/integrations/pi/session-ui/session-shell.d.ts +55 -0
  344. package/dist/integrations/pi/session-ui/session-shell.js +1792 -0
  345. package/dist/integrations/pi/session-ui/session-viewport-controller.d.ts +56 -0
  346. package/dist/integrations/pi/session-ui/session-viewport-controller.js +569 -0
  347. package/dist/integrations/pi/session-ui/stream-presentation-coalescer.d.ts +29 -0
  348. package/dist/integrations/pi/session-ui/stream-presentation-coalescer.js +79 -0
  349. package/dist/integrations/pi/session-ui/system-clipboard.d.ts +20 -0
  350. package/dist/integrations/pi/session-ui/system-clipboard.js +156 -0
  351. package/dist/integrations/pi/session-ui/text-paste.d.ts +5 -0
  352. package/dist/integrations/pi/session-ui/text-paste.js +16 -0
  353. package/dist/{src/foundation/pi-tui-runtime-adapter → integrations/pi/tui-runtime}/adapter.d.ts +10 -1
  354. package/dist/{src/foundation/pi-tui-runtime-adapter → integrations/pi/tui-runtime}/adapter.js +255 -11
  355. package/dist/{src/foundation/pi-tui-runtime-adapter → integrations/pi/tui-runtime}/contracts.d.ts +32 -6
  356. package/dist/integrations/pi/tui-runtime/contracts.js +1 -0
  357. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +81 -0
  358. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +424 -0
  359. package/dist/integrations/pi/tui-runtime/index.d.ts +7 -0
  360. package/dist/integrations/pi/tui-runtime/index.js +7 -0
  361. package/dist/integrations/pi/tui-runtime/input-presentation-coordinator.d.ts +39 -0
  362. package/dist/integrations/pi/tui-runtime/input-presentation-coordinator.js +139 -0
  363. package/dist/integrations/pi/tui-runtime/mouse-report-input.d.ts +13 -0
  364. package/dist/integrations/pi/tui-runtime/mouse-report-input.js +59 -0
  365. package/dist/integrations/pi/tui-runtime/overlay-geometry.d.ts +32 -0
  366. package/dist/integrations/pi/tui-runtime/overlay-geometry.js +159 -0
  367. package/dist/{src/foundation/pi-tui-runtime-adapter → integrations/pi/tui-runtime}/presentation-adapter.d.ts +1 -1
  368. package/dist/native/darwin-arm64/manifest.json +4 -4
  369. package/dist/native/darwin-arm64/process-guardian +0 -0
  370. package/dist/native/linux-x64/manifest.json +3 -3
  371. package/dist/native/linux-x64/process-guardian +0 -0
  372. package/dist/native/win32-x64/manifest.json +3 -3
  373. package/dist/native/win32-x64/process-guardian.exe +0 -0
  374. package/dist/{src/product-identity.d.ts → product-identity.d.ts} +4 -4
  375. package/dist/{src/product-identity.js → product-identity.js} +7 -38
  376. package/dist/{src/product-identity.json → product-identity.json} +13 -27
  377. package/dist/runtime-payload-inventory.json +122333 -0
  378. package/dist/{src/foundation/ui-apps → ui/apps}/contracts.d.ts +23 -1
  379. package/dist/{src/foundation/ui-apps → ui/apps}/host.d.ts +1 -1
  380. package/dist/{src/foundation/ui-apps → ui/apps}/host.js +14 -10
  381. package/dist/{src/foundation/ui-apps → ui/apps}/registry.d.ts +1 -0
  382. package/dist/{src/foundation/ui-apps → ui/apps}/registry.js +1 -0
  383. package/dist/{src/foundation/ui-components → ui/components}/dialog-panel.js +5 -3
  384. package/dist/{src/foundation/ui-components → ui/components}/frame.d.ts +1 -0
  385. package/dist/{src/foundation/ui-components → ui/components}/frame.js +1 -0
  386. package/dist/{src/foundation/ui-components → ui/components}/index.d.ts +7 -0
  387. package/dist/{src/foundation/ui-components → ui/components}/index.js +7 -0
  388. package/dist/{src/foundation/ui-components → ui/components}/line-input.d.ts +2 -8
  389. package/dist/{src/foundation/ui-components → ui/components}/line-input.js +7 -28
  390. package/dist/{src/foundation/ui-components → ui/components}/list-view.d.ts +0 -6
  391. package/dist/{src/foundation/ui-components → ui/components}/list-view.js +14 -7
  392. package/dist/{src/foundation/ui-components → ui/components}/mouse.d.ts +10 -0
  393. package/dist/{src/foundation/ui-components → ui/components}/mouse.js +24 -0
  394. package/dist/ui/components/progress-status.d.ts +2 -0
  395. package/dist/ui/components/progress-status.js +4 -0
  396. package/dist/ui/components/prompt-input.d.ts +32 -0
  397. package/dist/ui/components/prompt-input.js +54 -0
  398. package/dist/ui/components/prompt-row.d.ts +14 -0
  399. package/dist/ui/components/prompt-row.js +20 -0
  400. package/dist/{src/foundation/ui-components → ui/components}/scrollbar.d.ts +26 -0
  401. package/dist/{src/foundation/ui-components → ui/components}/scrollbar.js +35 -0
  402. package/dist/ui/components/selection-copy.d.ts +24 -0
  403. package/dist/ui/components/selection-copy.js +39 -0
  404. package/dist/{src/foundation/ui-components → ui/components}/shortcuts.d.ts +1 -0
  405. package/dist/{src/foundation/ui-components → ui/components}/shortcuts.js +1 -0
  406. package/dist/ui/components/spans.d.ts +28 -0
  407. package/dist/ui/components/spans.js +266 -0
  408. package/dist/ui/components/submitted-prompt.d.ts +23 -0
  409. package/dist/ui/components/submitted-prompt.js +50 -0
  410. package/dist/{src/foundation/ui-components → ui/components}/surface.d.ts +0 -5
  411. package/dist/{src/foundation/ui-components → ui/components}/surface.js +1 -5
  412. package/dist/ui/components/text-selection.d.ts +62 -0
  413. package/dist/ui/components/text-selection.js +210 -0
  414. package/dist/{src/foundation/ui-components → ui/components}/text.d.ts +14 -0
  415. package/dist/{src/foundation/ui-components → ui/components}/text.js +67 -5
  416. package/dist/ui/components/transcript-viewport.d.ts +173 -0
  417. package/dist/ui/components/transcript-viewport.js +753 -0
  418. package/dist/ui/components/visible-hyperlinks.d.ts +26 -0
  419. package/dist/ui/components/visible-hyperlinks.js +97 -0
  420. package/dist/{src/foundation/owned-ui-settings → ui/settings}/declarations.d.ts +8 -1
  421. package/dist/ui/settings/declarations.js +94 -0
  422. package/dist/{src/foundation/owned-ui-settings → ui/settings}/migrations.d.ts +1 -1
  423. package/dist/{src/foundation/owned-ui-settings → ui/settings}/migrations.js +28 -2
  424. package/dist/{src/foundation/owned-ui-settings → ui/settings}/sections.d.ts +6 -19
  425. package/dist/{src/foundation/owned-ui-settings → ui/settings}/sections.js +41 -22
  426. package/dist/{src/foundation/owned-ui-settings → ui/settings}/session.d.ts +9 -17
  427. package/dist/ui/settings/session.js +164 -0
  428. package/dist/{src/foundation/owned-ui-settings → ui/settings}/store.d.ts +1 -0
  429. package/dist/{src/foundation/owned-ui-settings → ui/settings}/store.js +2 -1
  430. package/docs/architecture/boundaries.md +14 -10
  431. package/docs/architecture/code-documentation.md +77 -0
  432. package/docs/architecture/history-editor-provenance.md +100 -0
  433. package/docs/architecture/internal-naming.md +91 -0
  434. package/docs/architecture/process-guardian-provenance.md +3 -1
  435. package/docs/architecture/project-structure.md +69 -30
  436. package/docs/architecture/prompt-suggestions.md +68 -0
  437. package/docs/architecture/resource-and-data-policy.md +28 -2
  438. package/docs/architecture/response-copy-delivery.md +83 -0
  439. package/docs/architecture/tool-image-presentation.md +62 -0
  440. package/docs/architecture/toolchain.md +24 -10
  441. package/docs/architecture/ui-reference-provenance.md +59 -5
  442. package/docs/ci-release-runbook.md +236 -79
  443. package/docs/features/launch-profiles.md +24 -17
  444. package/docs/features/modal-content-interaction.md +37 -0
  445. package/docs/features/prompt-history.md +149 -0
  446. package/docs/local-worktree-cleanup.md +128 -0
  447. package/docs/manual-code-streaming-cleanup.md +88 -0
  448. package/docs/manual-ghost-link-baseline.md +89 -0
  449. package/docs/manual-launch-instance-acceptance.md +12 -35
  450. package/docs/manual-owned-ui-checkpoint.md +78 -0
  451. package/docs/manual-pi-boundary-candidate.md +4 -5
  452. package/docs/manual-terminal-colour-check.md +4 -3
  453. package/docs/openspec-archive-automation.md +128 -0
  454. package/docs/repository-governance-live-acceptance.md +77 -0
  455. package/package.json +45 -28
  456. package/dist/src/cli/capabilities.d.ts +0 -15
  457. package/dist/src/cli/dispatch.js +0 -142
  458. package/dist/src/cli/version-stats.js +0 -72
  459. package/dist/src/composition/index.d.ts +0 -48
  460. package/dist/src/composition/index.js +0 -258
  461. package/dist/src/features/launch/intent.d.ts +0 -7
  462. package/dist/src/features/launch/intent.js +0 -4
  463. package/dist/src/features/launch/profiles.d.ts +0 -12
  464. package/dist/src/features/launch/profiles.js +0 -26
  465. package/dist/src/features/owned-ui/run.js +0 -15
  466. package/dist/src/foundation/lifecycle/paths.d.ts +0 -10
  467. package/dist/src/foundation/owned-ui-contracts/index.d.ts +0 -3
  468. package/dist/src/foundation/owned-ui-contracts/index.js +0 -3
  469. package/dist/src/foundation/owned-ui-settings/declarations.js +0 -39
  470. package/dist/src/foundation/owned-ui-settings/session.js +0 -145
  471. package/dist/src/foundation/pi-component-adapter/shell-editor-autocomplete.js +0 -162
  472. package/dist/src/foundation/pi-component-adapter/shell-presenters-transcript.d.ts +0 -57
  473. package/dist/src/foundation/pi-component-adapter/shell-shared-facade.d.ts +0 -116
  474. package/dist/src/foundation/pi-component-adapter/upstream/components/earendil-announcement.js +0 -19
  475. package/dist/src/foundation/pi-component-adapter/upstream/components/owned-editor.d.ts +0 -18
  476. package/dist/src/foundation/pi-component-adapter/upstream/components/owned-editor.js +0 -55
  477. package/dist/src/foundation/pi-engine-adapter/settings-integration.d.ts +0 -55
  478. package/dist/src/foundation/pi-owned-ui-integration/index.d.ts +0 -2
  479. package/dist/src/foundation/pi-owned-ui-integration/index.js +0 -2
  480. package/dist/src/foundation/pi-owned-ui-integration/route-host.d.ts +0 -29
  481. package/dist/src/foundation/pi-owned-ui-integration/session-shell.d.ts +0 -106
  482. package/dist/src/foundation/pi-owned-ui-integration/session-shell.js +0 -1518
  483. package/dist/src/foundation/pi-tui-runtime-adapter/index.d.ts +0 -4
  484. package/dist/src/foundation/pi-tui-runtime-adapter/index.js +0 -4
  485. package/dist/src/foundation/release/bootstrap.d.ts +0 -29
  486. package/dist/src/foundation/release/cohort-state.d.ts +0 -70
  487. package/dist/src/foundation/release/cohort-state.js +0 -202
  488. package/dist/src/foundation/release/release-gc.d.ts +0 -5
  489. package/dist/src/foundation/release/release-gc.js +0 -16
  490. package/dist/src/foundation/supervision/index.d.ts +0 -3
  491. package/dist/src/foundation/supervision/index.js +0 -3
  492. package/dist/src/foundation/supervision/main.d.ts +0 -1
  493. package/dist/src/foundation/supervision/main.js +0 -41
  494. package/dist/src/foundation/supervision/paths.d.ts +0 -1
  495. package/dist/src/foundation/supervision/paths.js +0 -1
  496. package/dist/src/foundation/supervision/server.d.ts +0 -22
  497. package/dist/src/foundation/ui-components/spans.d.ts +0 -7
  498. package/dist/src/foundation/ui-components/spans.js +0 -59
  499. package/docs/manual-transparent-checkpoint.md +0 -53
  500. /package/dist/{src/cli → cli}/index.d.ts +0 -0
  501. /package/dist/{src/cli → cli}/index.js +0 -0
  502. /package/dist/{src/cli → cli}/version.d.ts +0 -0
  503. /package/dist/{src/cli → cli}/version.js +0 -0
  504. /package/dist/{src/foundation/agent-engine-contracts → contracts/agent-engine}/capability-ports.js +0 -0
  505. /package/dist/{src/foundation/agent-engine-contracts → contracts/agent-engine}/domain-validation.d.ts +0 -0
  506. /package/dist/{src/foundation/agent-engine-contracts → contracts/agent-engine}/domain.js +0 -0
  507. /package/dist/{src/foundation/agent-engine-contracts → contracts/agent-engine}/index.d.ts +0 -0
  508. /package/dist/{src/foundation/agent-engine-contracts → contracts/agent-engine}/index.js +0 -0
  509. /package/dist/{src/foundation/agent-engine-contracts → contracts/agent-engine}/model.d.ts +0 -0
  510. /package/dist/{src/foundation/agent-engine-contracts → contracts/agent-engine}/model.js +0 -0
  511. /package/dist/{src/foundation/agent-engine-contracts → contracts/agent-engine}/package-ports.js +0 -0
  512. /package/dist/{src/foundation/agent-engine-contracts → contracts/agent-engine}/ports.d.ts +0 -0
  513. /package/dist/{src/foundation/agent-engine-contracts → contracts/agent-engine}/ports.js +0 -0
  514. /package/dist/{src/foundation/agent-engine-contracts → contracts/agent-engine}/serialization.d.ts +0 -0
  515. /package/dist/{src/foundation/agent-engine-contracts → contracts/agent-engine}/serialization.js +0 -0
  516. /package/dist/{src/foundation/agent-engine-contracts → contracts/agent-engine}/validation.d.ts +0 -0
  517. /package/dist/{src/foundation/agent-engine-contracts → contracts/agent-engine}/validation.js +0 -0
  518. /package/dist/{src/foundation/owned-ui-contracts → contracts/owned-ui}/extension-ui.d.ts +0 -0
  519. /package/dist/{src/foundation/owned-ui-contracts → contracts/owned-ui}/extension-ui.js +0 -0
  520. /package/dist/{src/foundation/owned-ui-contracts → contracts/owned-ui}/model.js +0 -0
  521. /package/dist/{src/foundation/presentation-contracts → contracts/presentation}/index.js +0 -0
  522. /package/dist/{src/foundation/workspace-contracts → contracts/workspace}/index.d.ts +0 -0
  523. /package/dist/{src/foundation/workspace-contracts → contracts/workspace}/index.js +0 -0
  524. /package/dist/{src/foundation/workspace-contracts → contracts/workspace}/model.d.ts +0 -0
  525. /package/dist/{src/foundation/workspace-contracts → contracts/workspace}/model.js +0 -0
  526. /package/dist/{src/foundation/workspace-contracts → contracts/workspace}/validation.d.ts +0 -0
  527. /package/dist/{src/foundation/workspace-contracts → contracts/workspace}/validation.js +0 -0
  528. /package/dist/{src/features → features}/launch/development-launch.d.ts +0 -0
  529. /package/dist/{src/features → features}/launch/initialize-profile.d.ts +0 -0
  530. /package/dist/{src/features → features}/launch/initialize-profile.js +0 -0
  531. /package/dist/{src/features → features}/launch/runtime-selection.js +0 -0
  532. /package/dist/{src/features → features}/workspace/capabilities.js +0 -0
  533. /package/dist/{src/features → features}/workspace/index.d.ts +0 -0
  534. /package/dist/{src/features → features}/workspace/index.js +0 -0
  535. /package/dist/{src/features → features}/workspace/presentation.d.ts +0 -0
  536. /package/dist/{src/features → features}/workspace/presentation.js +0 -0
  537. /package/dist/{src/features → features}/workspace/reconciliation.d.ts +0 -0
  538. /package/dist/{src/features → features}/workspace/reconciliation.js +0 -0
  539. /package/dist/{src/foundation → foundation}/launch-guardian/index.d.ts +0 -0
  540. /package/dist/{src/foundation → foundation}/launch-guardian/index.js +0 -0
  541. /package/dist/{src/foundation → foundation}/lifecycle/commands.d.ts +0 -0
  542. /package/dist/{src/foundation → foundation}/lifecycle/commands.js +0 -0
  543. /package/dist/{src/foundation → foundation}/lifecycle/launch-instance.d.ts +0 -0
  544. /package/dist/{src/foundation → foundation}/lifecycle/launch-instance.js +0 -0
  545. /package/dist/{src/foundation → foundation}/native-host-protocol/evidence.d.ts +0 -0
  546. /package/dist/{src/foundation → foundation}/native-host-protocol/evidence.js +0 -0
  547. /package/dist/{src/foundation → foundation}/native-host-protocol/index.d.ts +0 -0
  548. /package/dist/{src/foundation → foundation}/native-host-protocol/index.js +0 -0
  549. /package/dist/{src/foundation → foundation}/native-host-protocol/proof-gate.d.ts +0 -0
  550. /package/dist/{src/foundation → foundation}/native-host-protocol/proof-gate.js +0 -0
  551. /package/dist/{src/foundation → foundation}/process-containment/artifact.d.ts +0 -0
  552. /package/dist/{src/foundation → foundation}/process-containment/artifact.js +0 -0
  553. /package/dist/{src/foundation → foundation}/process-containment/contracts.d.ts +0 -0
  554. /package/dist/{src/foundation → foundation}/process-containment/contracts.js +0 -0
  555. /package/dist/{src/foundation → foundation}/protocol/index.d.ts +0 -0
  556. /package/dist/{src/foundation → foundation}/protocol/index.js +0 -0
  557. /package/dist/{src/foundation → foundation}/release/cohort-selection.d.ts +0 -0
  558. /package/dist/{src/foundation → foundation}/release/cohort-selection.js +0 -0
  559. /package/dist/{src/foundation → foundation}/release/concurrency.d.ts +0 -0
  560. /package/dist/{src/foundation → foundation}/release/concurrency.js +0 -0
  561. /package/dist/{src/foundation → foundation}/release/process-cleanup.d.ts +0 -0
  562. /package/dist/{src/foundation → foundation}/release/process-cleanup.js +0 -0
  563. /package/dist/{src/foundation → foundation}/release/stable-release.d.ts +0 -0
  564. /package/dist/{src/foundation → foundation}/release/stable-release.js +0 -0
  565. /package/dist/{src/foundation → foundation}/storage/index.d.ts +0 -0
  566. /package/dist/{src/foundation → foundation}/storage/index.js +0 -0
  567. /package/dist/{src/foundation → foundation}/structured-agent-runtime/index.d.ts +0 -0
  568. /package/dist/{src/foundation → foundation}/structured-agent-runtime/index.js +0 -0
  569. /package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/components.js +0 -0
  570. /package/dist/{src/foundation/pi-owned-ui-integration/route-host.js → integrations/pi/components/editor-interaction.js} +0 -0
  571. /package/dist/{src/foundation/pi-tui-runtime-adapter/contracts.js → integrations/pi/components/prompt-input-port.js} +0 -0
  572. /package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/resources/builtin-themes.d.ts +0 -0
  573. /package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/resources/builtin-themes.js +0 -0
  574. /package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/shell-editor-autocomplete.d.ts +0 -0
  575. /package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/shell-extension-ui.js +0 -0
  576. /package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/theme.d.ts +0 -0
  577. /package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/theme.js +0 -0
  578. /package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/upstream/components/countdown-timer.d.ts +0 -0
  579. /package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/upstream/components/countdown-timer.js +0 -0
  580. /package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/upstream/components/custom-entry.d.ts +0 -0
  581. /package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/upstream/components/custom-entry.js +0 -0
  582. /package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/upstream/components/daxnuts.d.ts +0 -0
  583. /package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/upstream/components/daxnuts.js +0 -0
  584. /package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/upstream/components/earendil-announcement.d.ts +0 -0
  585. /package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/upstream/components/extension-editor.d.ts +0 -0
  586. /package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/upstream/components/extension-editor.js +0 -0
  587. /package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/upstream/components/first-time-setup.d.ts +0 -0
  588. /package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/upstream/components/first-time-setup.js +0 -0
  589. /package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/upstream/components/markdown-transform.d.ts +0 -0
  590. /package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/upstream/components/markdown-transform.js +0 -0
  591. /package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/upstream/components/mermaid.d.ts +0 -0
  592. /package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/upstream/components/mermaid.js +0 -0
  593. /package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/upstream/components/session-selector-search.d.ts +0 -0
  594. /package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/upstream/components/session-selector-search.js +0 -0
  595. /package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/upstream/components/session-selector.d.ts +0 -0
  596. /package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/upstream/components/session-selector.js +0 -0
  597. /package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/upstream/components/skill-invocation-message.d.ts +0 -0
  598. /package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/upstream/components/skill-invocation-message.js +0 -0
  599. /package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/upstream/components/status-indicator.d.ts +0 -0
  600. /package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/upstream/components/status-indicator.js +0 -0
  601. /package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/upstream/components/tree-selector.d.ts +0 -0
  602. /package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/upstream/components/tree-selector.js +0 -0
  603. /package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/upstream/components/trust-selector.d.ts +0 -0
  604. /package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/upstream/components/trust-selector.js +0 -0
  605. /package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/upstream/external-editor.d.ts +0 -0
  606. /package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/upstream/external-editor.js +0 -0
  607. /package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/upstream/model-search.d.ts +0 -0
  608. /package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/upstream/model-search.js +0 -0
  609. /package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/upstream/theme/theme-controller.d.ts +0 -0
  610. /package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/upstream/theme/theme-controller.js +0 -0
  611. /package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/upstream/theme/theme.d.ts +0 -0
  612. /package/dist/{src/foundation/pi-component-adapter → integrations/pi/components}/upstream/theme/theme.js +0 -0
  613. /package/dist/{src/foundation/pi-engine-adapter → integrations/pi/engine}/pi-settings-metadata.json +0 -0
  614. /package/dist/{src/foundation/pi-engine-adapter → integrations/pi/engine}/public-main-entry.d.ts +0 -0
  615. /package/dist/{src/foundation/pi-engine-adapter → integrations/pi/engine}/public-main-entry.js +0 -0
  616. /package/dist/{src/foundation/pi-engine-adapter → integrations/pi/engine}/workflows.js +0 -0
  617. /package/dist/{src/foundation/pi-tui-runtime-adapter → integrations/pi/tui-runtime}/conformance.d.ts +0 -0
  618. /package/dist/{src/foundation/pi-tui-runtime-adapter → integrations/pi/tui-runtime}/conformance.js +0 -0
  619. /package/dist/{src/foundation/pi-tui-runtime-adapter → integrations/pi/tui-runtime}/presentation-adapter.js +0 -0
  620. /package/dist/{src/foundation/ui-apps → ui/apps}/contracts.js +0 -0
  621. /package/dist/{src/foundation/ui-apps → ui/apps}/index.d.ts +0 -0
  622. /package/dist/{src/foundation/ui-apps → ui/apps}/index.js +0 -0
  623. /package/dist/{src/foundation/ui-components → ui/components}/dialog-panel.d.ts +0 -0
  624. /package/dist/{src/foundation/ui-components → ui/components}/label.d.ts +0 -0
  625. /package/dist/{src/foundation/ui-components → ui/components}/label.js +0 -0
  626. /package/dist/{src/foundation/ui-components → ui/components}/list-block.d.ts +0 -0
  627. /package/dist/{src/foundation/ui-components → ui/components}/list-block.js +0 -0
  628. /package/dist/{src/foundation/ui-components → ui/components}/pane.d.ts +0 -0
  629. /package/dist/{src/foundation/ui-components → ui/components}/pane.js +0 -0
  630. /package/dist/{src/foundation/ui-components → ui/components}/revision.d.ts +0 -0
  631. /package/dist/{src/foundation/ui-components → ui/components}/revision.js +0 -0
  632. /package/dist/{src/foundation/ui-components → ui/components}/status-line.d.ts +0 -0
  633. /package/dist/{src/foundation/ui-components → ui/components}/status-line.js +0 -0
  634. /package/dist/{src/foundation/ui-components → ui/components}/stepper.d.ts +0 -0
  635. /package/dist/{src/foundation/ui-components → ui/components}/stepper.js +0 -0
  636. /package/dist/{src/foundation/ui-components → ui/components}/theme.d.ts +0 -0
  637. /package/dist/{src/foundation/ui-components → ui/components}/theme.js +0 -0
  638. /package/dist/{src/foundation/ui-components → ui/components}/value-menu.d.ts +0 -0
  639. /package/dist/{src/foundation/ui-components → ui/components}/value-menu.js +0 -0
  640. /package/dist/{src/foundation/owned-ui-settings → ui/settings}/index.d.ts +0 -0
  641. /package/dist/{src/foundation/owned-ui-settings → ui/settings}/index.js +0 -0
  642. /package/dist/{src/foundation/owned-ui-settings → ui/settings}/resolution.d.ts +0 -0
  643. /package/dist/{src/foundation/owned-ui-settings → ui/settings}/resolution.js +0 -0
@@ -1,31 +1,99 @@
1
1
  import { execFile } from "node:child_process";
2
+ import { existsSync, realpathSync } from "node:fs";
2
3
  import { mkdir, rm, writeFile } from "node:fs/promises";
3
4
  import { tmpdir } from "node:os";
4
5
  import { dirname, join, resolve } from "node:path";
5
6
  import { promisify } from "node:util";
6
- import { PRODUCT_IDENTITY } from "../../product-identity.js";
7
- import { copyToClipboard, DefaultPackageManager, getAgentDir, ProjectTrustStore, SessionManager, } from "@earendil-works/pi-coding-agent";
8
- import { OWNED_UI_EXTENSION_CONTRACT_VERSION, OWNED_UI_EXTENSION_RENDER_CALLBACKS, OWNED_UI_EXTENSION_UI_CALLBACKS, OWNED_UI_EXTENSION_UI_PROPERTIES, assertOwnedUiCommand, assertOwnedUiExtensionUiPort, assertOwnedUiSnapshot, } from "../owned-ui-contracts/index.js";
7
+ import { PRODUCT_IDENTITY } from "../../../product-identity.js";
8
+ import { configureOwnedHttpDispatcher } from "./http-dispatcher.js";
9
+ import { readPinnedCommandChangelog } from "./changelog.js";
10
+ import { toolRenderingInput } from "./tool-rendering.js";
11
+ import { TranscriptImageAssets } from "./transcript-image-assets.js";
12
+ import { copyToClipboard, CredentialSynchronizationError, DefaultPackageManager, getAgentDir, ProjectTrustStore, SessionManager, VERSION, } from "@earendil-works/pi-coding-agent";
13
+ import { OWNED_UI_EXTENSION_CONTRACT_VERSION, OWNED_UI_EXTENSION_RENDER_CALLBACKS, OWNED_UI_EXTENSION_UI_CALLBACKS, OWNED_UI_EXTENSION_UI_PROPERTIES, CONTEXTUAL_PROMPT_SUGGESTION_INSTRUCTION, acceptsTranscriptUpdate, transcriptToolState, assertOwnedUiCommand, assertOwnedUiExtensionUiPort, assertOwnedUiPromptSuggestionRequest, assertOwnedUiPromptSuggestionResult, assertOwnedUiSnapshot, normalizePromptSuggestionCandidate, } from "../../../contracts/owned-ui/index.js";
9
14
  import { PINNED_PI_SETTINGS_CALLBACKS, PINNED_PI_WORKFLOW_COMMAND_NAMES, } from "./workflows.js";
10
15
  import { createPiRuntimeIntegration } from "./runtime-integration.js";
11
16
  import { PiSessionCommandIntegration } from "./session-integration.js";
12
17
  import { PiSettingsIntegration } from "./settings-integration.js";
18
+ import { PendingEngineDelivery } from "./pending-delivery.js";
19
+ /** Explicit flush failure when required delivery was interrupted rather than completed. */
20
+ export class EngineDeliveryError extends Error {
21
+ constructor() { super("Engine delivery did not complete"); this.name = "EngineDeliveryError"; }
22
+ }
13
23
  const execFileAsync = promisify(execFile);
24
+ /**
25
+ * Engine events delivered before the queue hands the event loop a turn. Small enough
26
+ * that a streaming burst never holds input, large enough that an ordinary turn is one
27
+ * batch.
28
+ */
29
+ // Performance: deliver at most one engine event per event-loop turn. Transcript updates can
30
+ // be expensive in long sessions; a larger synchronous batch starves terminal
31
+ // input and makes an in-progress mouse selection appear frozen.
32
+ const EVENT_DELIVERY_BATCH = 1;
33
+ const AUTH_REFRESH_TIMEOUT_MS = 15_000;
34
+ // Provenance: Pi 0.84.2 core/model-resolver.ts defaultModelPerProvider.
35
+ const PINNED_DEFAULT_MODEL_BY_PROVIDER = Object.freeze({
36
+ "amazon-bedrock": "us.anthropic.claude-opus-4-6-v1",
37
+ "ant-ling": "Ring-2.6-1T",
38
+ anthropic: "claude-opus-4-8",
39
+ openai: "gpt-5.5",
40
+ "azure-openai-responses": "gpt-5.4",
41
+ "openai-codex": "gpt-5.5",
42
+ radius: "auto",
43
+ nvidia: "nvidia/nemotron-3-super-120b-a12b",
44
+ deepseek: "deepseek-v4-pro",
45
+ google: "gemini-3.1-pro-preview",
46
+ "google-vertex": "gemini-3.1-pro-preview",
47
+ "github-copilot": "gpt-5.4",
48
+ openrouter: "moonshotai/kimi-k2.6",
49
+ "vercel-ai-gateway": "zai/glm-5.1",
50
+ xai: "grok-4.5",
51
+ groq: "openai/gpt-oss-120b",
52
+ cerebras: "zai-glm-4.7",
53
+ zai: "glm-5.1",
54
+ "zai-coding-cn": "glm-5.1",
55
+ mistral: "devstral-medium-latest",
56
+ minimax: "MiniMax-M2.7",
57
+ "minimax-cn": "MiniMax-M2.7",
58
+ moonshotai: "kimi-k2.6",
59
+ "moonshotai-cn": "kimi-k2.6",
60
+ huggingface: "moonshotai/Kimi-K2.6",
61
+ fireworks: "accounts/fireworks/models/kimi-k2p6",
62
+ together: "moonshotai/Kimi-K2.6",
63
+ baseten: "zai-org/GLM-5.2",
64
+ opencode: "kimi-k2.6",
65
+ "opencode-go": "kimi-k2.6",
66
+ "kimi-coding": "kimi-for-coding",
67
+ "cloudflare-workers-ai": "@cf/moonshotai/kimi-k2.6",
68
+ "cloudflare-ai-gateway": "workers-ai/@cf/moonshotai/kimi-k2.6",
69
+ "qwen-token-plan": "qwen3.7-max",
70
+ "qwen-token-plan-cn": "qwen3.7-max",
71
+ "qwen-token-plan-individual": "qwen3.8-max",
72
+ xiaomi: "mimo-v2.5-pro",
73
+ "xiaomi-token-plan-cn": "mimo-v2.5-pro",
74
+ "xiaomi-token-plan-ams": "mimo-v2.5-pro",
75
+ "xiaomi-token-plan-sgp": "mimo-v2.5-pro",
76
+ });
14
77
  const DEFAULT_SURFACE = {
15
78
  columns: 100,
16
79
  rows: 32,
17
80
  focusedRegion: "editor",
18
81
  hardwareCursor: false,
19
82
  };
83
+ /** Owns the pinned Pi session lifecycle and translates its events into the neutral agent-engine contract. */
20
84
  export class PiEngineAdapter {
21
85
  #runtimeFactory;
22
86
  #checkPackageUpdates;
23
87
  #cwd;
24
88
  #agentDir;
25
89
  #sessionId;
90
+ #sessionPath;
91
+ #sessionSelection;
92
+ #sessionForkPrompt;
26
93
  #workflowHost;
94
+ #clipboardWriter;
27
95
  #workflowInteraction;
28
- #listeners = new Set();
96
+ #listeners = new Map();
29
97
  #runtime;
30
98
  #session;
31
99
  #unsubscribe;
@@ -41,6 +109,7 @@ export class PiEngineAdapter {
41
109
  submitEnabled: false,
42
110
  };
43
111
  #sessionGeneration = 0;
112
+ #sessionBindingGeneration = 0;
44
113
  #status = {
45
114
  title: "Pi",
46
115
  workingMessage: null,
@@ -53,26 +122,48 @@ export class PiEngineAdapter {
53
122
  #activeCommandIds = [];
54
123
  #completedCommands = new Map();
55
124
  #transcript = [];
125
+ #transcriptIndex = new Map();
126
+ #transcriptSnapshot;
56
127
  #messageBlockIds = new WeakMap();
57
128
  #messageFallbackIds = new Map();
58
129
  #toolBlockIds = new Map();
130
+ #transcriptImageAssets = new TranscriptImageAssets();
131
+ #usageCache;
59
132
  #nextBlockSequence = 0;
60
133
  #diagnostics = [];
61
- #eventQueue = [];
134
+ #eventQueue = new PendingEngineDelivery(event => this.#transcriptImageAssets.retainEvent(event));
62
135
  #eventQueueProcessing;
63
- #droppedEventCount = 0;
136
+ #overload;
137
+ #overloads = 0;
138
+ #deliveryFailed = false;
139
+ #admissionStopped = false;
140
+ #runningCommands = 0;
141
+ #invalidatedEvents = 0;
142
+ #pendingCommands = new Map();
143
+ #reservedOutcomes = new Map();
144
+ #pendingWorkflows = new Set();
145
+ #agentRunActive = false;
146
+ #agentRunSequence = 0;
147
+ #assistantResponseSequence = 0;
148
+ #statusKind = null;
64
149
  #sessionCommands;
65
150
  #gitBranch = null;
66
151
  #extensionUi;
67
152
  #extensionShutdown;
68
153
  #extensionBound = false;
69
154
  #disposed = false;
70
- /** Lists the themes installed on this machine, when the caller supplies one. */
155
+ #settingsProductMode;
156
+ #projectTrustPrompt;
157
+ #settingsIntegration;
158
+ #settingsIntegrationManager;
71
159
  #availableThemes;
72
160
  constructor(options = {}) {
73
161
  this.#cwd = options.cwd ?? process.cwd();
74
162
  this.#agentDir = options.agentDir ?? getAgentDir();
75
163
  this.#sessionId = options.sessionId ?? "owned-session-1";
164
+ this.#sessionPath = options.sessionPath;
165
+ this.#sessionSelection = options.sessionSelection;
166
+ this.#sessionForkPrompt = options.sessionForkPrompt;
76
167
  this.#runtimeFactory = options.createRuntime ?? createDefaultPiRuntime;
77
168
  this.#checkPackageUpdates = options.checkPackageUpdates
78
169
  ?? (options.createRuntime
@@ -80,6 +171,8 @@ export class PiEngineAdapter {
80
171
  : settingsManager => checkDefaultPiPackageUpdates(this.#cwd, this.#agentDir, settingsManager));
81
172
  this.#workflowHost = options.workflowHost ?? defaultWorkflowHost();
82
173
  this.#availableThemes = options.availableThemes ?? null;
174
+ this.#settingsProductMode = options.settingsProductMode ?? "bare";
175
+ this.#projectTrustPrompt = options.projectTrustPrompt;
83
176
  this.#workflowInteraction = { prompt: async () => null, notify() { } };
84
177
  }
85
178
  setWorkflowInteractionHost(interaction) {
@@ -91,12 +184,112 @@ export class PiEngineAdapter {
91
184
  get sessionGeneration() {
92
185
  return this.#sessionGeneration;
93
186
  }
187
+ /** Actual session replacements, excluding delivery-only invalidation of callbacks. */
188
+ get sessionBindingGeneration() { return this.#sessionBindingGeneration; }
189
+ get cwd() {
190
+ return this.#runtime?.cwd ?? this.#cwd;
191
+ }
94
192
  get agentDir() {
95
193
  return this.#agentDir;
96
194
  }
195
+ resolveTranscriptImage(assetId) {
196
+ return this.#transcriptImageAssets.resolve(assetId);
197
+ }
198
+ currentSessionFile() {
199
+ const value = this.#session?.sessionManager?.getSessionFile?.();
200
+ return typeof value === "string" && value.length > 0 ? value : null;
201
+ }
202
+ currentSessionResumeMetadata() {
203
+ const manager = this.#session?.sessionManager;
204
+ if (manager === undefined
205
+ || typeof manager.isPersisted !== "function"
206
+ || typeof manager.getSessionId !== "function"
207
+ || typeof manager.getSessionDir !== "function"
208
+ || typeof manager.usesDefaultSessionDir !== "function"
209
+ || manager.isPersisted() !== true
210
+ || !existsSync(this.currentSessionFile() ?? ""))
211
+ return null;
212
+ const sessionId = manager.getSessionId();
213
+ const sessionDir = manager.getSessionDir();
214
+ if (!sessionId || !sessionDir)
215
+ return null;
216
+ return { sessionId, sessionDir, usesDefaultSessionDir: manager.usesDefaultSessionDir() };
217
+ }
97
218
  get disposed() {
98
219
  return this.#disposed;
99
220
  }
221
+ suggestionReasoningPolicy() {
222
+ const session = this.#session;
223
+ if (!session?.model)
224
+ return "unavailable";
225
+ if (!session.model.reasoning)
226
+ return "ordinary";
227
+ const levels = session.getAvailableThinkingLevels?.() ?? [];
228
+ // Compatibility: capability order, not the user's current setting or a provider-name heuristic.
229
+ return ["off", "minimal", "low", "medium", "high", "xhigh", "max"]
230
+ .find(level => levels.includes(level)) ?? "unavailable";
231
+ }
232
+ async generate(request) {
233
+ assertOwnedUiPromptSuggestionRequest(request);
234
+ const identity = request.identity;
235
+ const session = this.#session;
236
+ const runtime = this.#runtime;
237
+ const activeModel = this.#activeModel;
238
+ if (this.#disposed || this.#overload !== undefined || this.#admissionStopped || session === undefined || runtime === undefined || request.signal.aborted
239
+ || identity.sessionId !== this.#sessionId
240
+ || identity.sessionGeneration !== this.#sessionGeneration
241
+ || identity.runSequence !== this.#agentRunSequence
242
+ || identity.responseSequence !== this.#assistantResponseSequence
243
+ || activeModel === null
244
+ || identity.model.providerId !== activeModel.providerId
245
+ || identity.model.modelId !== activeModel.modelId) {
246
+ return { identity, outcome: request.signal.aborted ? "cancelled" : "unavailable", text: null };
247
+ }
248
+ const model = session.model;
249
+ const agentState = session.agent.state;
250
+ const policy = this.suggestionReasoningPolicy();
251
+ if (model === undefined || policy === "unavailable" || typeof runtime.services.modelRuntime.completeSimple !== "function") {
252
+ return { identity, outcome: "unavailable", text: null };
253
+ }
254
+ const messages = agentState.messages.filter(message => message.role === "user" || message.role === "assistant" || message.role === "toolResult");
255
+ const reasoning = policy === "ordinary" || policy === "off" ? undefined : policy;
256
+ let response;
257
+ try {
258
+ response = await runtime.services.modelRuntime.completeSimple(model, {
259
+ systemPrompt: agentState.systemPrompt,
260
+ messages: [
261
+ ...messages,
262
+ { role: "user", content: CONTEXTUAL_PROMPT_SUGGESTION_INSTRUCTION, timestamp: Date.now() },
263
+ ],
264
+ tools: agentState.tools,
265
+ }, {
266
+ signal: request.signal,
267
+ ...(reasoning === undefined ? {} : { reasoning }),
268
+ });
269
+ }
270
+ catch {
271
+ return { identity, outcome: request.signal.aborted ? "cancelled" : "provider-failure", text: null };
272
+ }
273
+ if (request.signal.aborted || (isRecord(response) && response.stopReason === "aborted")) {
274
+ return { identity, outcome: "cancelled", text: null };
275
+ }
276
+ if (!isRecord(response) || stringValue(response.errorMessage) !== undefined || response.stopReason === "error") {
277
+ return { identity, outcome: "provider-failure", text: null };
278
+ }
279
+ const content = Array.isArray(response.content) ? response.content : [];
280
+ if (response.stopReason === "length" || content.some(block => isRecord(block) && block.type === "toolCall")) {
281
+ return { identity, outcome: "rejected", text: null };
282
+ }
283
+ // Security: validate all text, not just the first block of a multi-part response.
284
+ const rawText = content.filter(block => isRecord(block) && block.type === "text")
285
+ .map(block => stringValue(block.text) ?? "").join("\n");
286
+ const text = normalizePromptSuggestionCandidate(rawText);
287
+ const result = text === null
288
+ ? { identity, outcome: rawText.trim() ? "rejected" : "empty", text: null }
289
+ : { identity, outcome: "candidate", text };
290
+ assertOwnedUiPromptSuggestionResult(result);
291
+ return result;
292
+ }
100
293
  async start() {
101
294
  if (this.#runtime)
102
295
  return this.view();
@@ -104,18 +297,25 @@ export class PiEngineAdapter {
104
297
  cwd: this.#cwd,
105
298
  agentDir: this.#agentDir,
106
299
  sessionId: this.#sessionId,
300
+ ...(this.#sessionPath === undefined ? {} : { sessionPath: this.#sessionPath }),
301
+ ...(this.#sessionSelection === undefined ? {} : { sessionSelection: this.#sessionSelection }),
302
+ ...(this.#sessionForkPrompt === undefined ? {} : { sessionForkPrompt: this.#sessionForkPrompt }),
303
+ ...(this.#projectTrustPrompt === undefined ? {} : { projectTrustPrompt: this.#projectTrustPrompt }),
107
304
  }).catch(error => {
108
305
  this.#lifecycle = "failed";
109
306
  this.#addDiagnostic("error", "engine-startup", error instanceof Error ? error.message : String(error), false);
110
307
  throw error;
111
308
  });
112
309
  this.#runtime = runtime;
310
+ this.#cwd = runtime.cwd ?? this.#cwd;
113
311
  this.#gitBranch = await readGitBranch(this.#cwd);
114
312
  this.#terminal = {
115
313
  ...this.#terminal,
116
314
  hardwareCursor: runtime.services.settingsManager?.getShowHardwareCursor?.() ?? this.#terminal.hardwareCursor,
117
315
  };
118
316
  runtime.setRebindSession(async (session) => {
317
+ if (this.#overload !== undefined || this.#admissionStopped || this.#disposed)
318
+ return;
119
319
  this.#bindSession(session);
120
320
  this.#emitView();
121
321
  });
@@ -123,6 +323,7 @@ export class PiEngineAdapter {
123
323
  this.#addDiagnostic(diagnostic.type === "error" ? "error" : diagnostic.type === "warning" ? "warning" : "info", "engine-startup", diagnostic.message, diagnostic.type !== "error");
124
324
  }
125
325
  this.#bindSession(runtime.session);
326
+ await this.#announceChangelog(runtime.services.settingsManager);
126
327
  this.#lifecycle = "ready";
127
328
  this.#editor = { ...this.#editor, submitEnabled: true };
128
329
  this.#emitEvent({ type: "session-lifecycle", lifecycle: "ready", reason: null });
@@ -130,6 +331,27 @@ export class PiEngineAdapter {
130
331
  void this.#announcePackageUpdates(runtime.services.settingsManager);
131
332
  return this.view();
132
333
  }
334
+ async #announceChangelog(settingsManager) {
335
+ if (!settingsManager || typeof settingsManager.getLastChangelogVersion !== "function"
336
+ || typeof settingsManager.setLastChangelogVersion !== "function")
337
+ return;
338
+ if ((this.#session?.messages.length ?? 0) > 0)
339
+ return;
340
+ const lastVersion = settingsManager.getLastChangelogVersion();
341
+ if (lastVersion === VERSION)
342
+ return;
343
+ if (!lastVersion) {
344
+ settingsManager.setLastChangelogVersion(VERSION);
345
+ await settingsManager.flush();
346
+ return;
347
+ }
348
+ const markdown = await this.#workflowHost.readChangelog(lastVersion).catch(() => "");
349
+ if (markdown.trim().length > 0) {
350
+ this.#addDiagnostic("info", settingsManager.getCollapseChangelog() ? "changelog-collapsed" : "changelog-expanded", markdown, true);
351
+ settingsManager.setLastChangelogVersion(VERSION);
352
+ await settingsManager.flush();
353
+ }
354
+ }
133
355
  async #announcePackageUpdates(settingsManager) {
134
356
  if (process.env.PI_OFFLINE)
135
357
  return;
@@ -143,17 +365,28 @@ export class PiEngineAdapter {
143
365
  if (this.#disposed || updates.length === 0)
144
366
  return;
145
367
  const packages = updates.map(name => `- ${name}`).join("\n");
146
- this.#addDiagnostic("info", "package-updates", `Package updates are available. Run pi update --extensions\nPackages:\n${packages}`, true);
368
+ this.#addDiagnostic("info", "package-updates", `Package updates are available. Run ${PRODUCT_IDENTITY.commandName} pi update --extensions\nPackages:\n${packages}`, true);
147
369
  this.#emitView();
148
370
  }
149
371
  onEvent(listener) {
150
- this.#listeners.add(listener);
151
- listener(this.#event({ type: "session-view", view: this.view() }));
372
+ const initial = this.#event({ type: "session-view", view: this.view() });
373
+ this.#listeners.set(listener, initial.sequence);
374
+ listener(initial);
152
375
  return () => this.#listeners.delete(listener);
153
376
  }
377
+ /** Developer-only pressure evidence; never mirrored into visible diagnostic/status arrays. */
378
+ deliveryDiagnostics() {
379
+ return { ...this.#eventQueue.diagnostics(), overloads: this.#overloads, recovering: this.#overload !== undefined,
380
+ pendingCommands: this.#pendingCommands.size + this.#pendingWorkflows.size, reservedOutcomes: this.#reservedOutcomes.size, invalidatedEvents: this.#invalidatedEvents };
381
+ }
154
382
  async flushEvents() {
383
+ const failed = this.#deliveryFailed;
155
384
  while (this.#eventQueueProcessing)
156
385
  await this.#eventQueueProcessing;
386
+ if (failed || this.#deliveryFailed || this.#disposed && this.#lifecycle !== "stopped") {
387
+ this.#deliveryFailed = false;
388
+ throw new EngineDeliveryError();
389
+ }
157
390
  }
158
391
  nonVisualResources() {
159
392
  const loader = this.#runtime?.services.resourceLoader;
@@ -278,6 +511,7 @@ export class PiEngineAdapter {
278
511
  id: `extension-${resources.length}`,
279
512
  sourcePath: extension.path,
280
513
  resolvedPath: extension.resolvedPath,
514
+ sourceInfo: extensionSourceSummary(extension.sourceInfo),
281
515
  loaded: true,
282
516
  hidden: extension.hidden === true,
283
517
  diagnostic: null,
@@ -373,9 +607,10 @@ export class PiEngineAdapter {
373
607
  }
374
608
  const extensionCommands = this.#session?.extensionRunner?.getRegisteredCommands?.();
375
609
  if (Array.isArray(extensionCommands)) {
376
- for (const command of extensionCommands.filter(isRecord)) {
377
- const name = stringProperty(command, "name");
378
- if (!name || usedNames.has(name))
610
+ const registered = extensionCommands.filter(isRecord);
611
+ for (const command of registered) {
612
+ const name = stringProperty(command, "invocationName") ?? stringProperty(command, "name");
613
+ if (!name || usedNames.has(name) || isPiPrefixedCompatibilityAlias(command, registered))
379
614
  continue;
380
615
  commands.push({ name, description: stringProperty(command, "description") ?? "Extension command", source: "extension" });
381
616
  usedNames.add(name);
@@ -388,7 +623,8 @@ export class PiEngineAdapter {
388
623
  const session = this.#requireWorkflowSession();
389
624
  const result = await requireCapability(session.cycleModel, "cycleModel").call(session, direction);
390
625
  if (!isRecord(result) || !isRecord(result.model)) {
391
- return workflowResult("model", "cancelled", "No other model is available");
626
+ const scoped = Array.isArray(session.scopedModels) && session.scopedModels.length > 0;
627
+ return workflowResult("model", "completed", scoped ? "Only one model in scope" : "Only one model available", undefined, "status");
392
628
  }
393
629
  const model = readModel(result.model);
394
630
  if (model)
@@ -396,7 +632,11 @@ export class PiEngineAdapter {
396
632
  if (result.thinkingLevel !== undefined)
397
633
  this.#thinkingLevel = readThinkingLevel(result.thinkingLevel);
398
634
  this.#emitView();
399
- return workflowResult("model", "completed", model ? `Selected model ${model.providerId}/${model.modelId}` : "Selected model");
635
+ const modelName = stringProperty(result.model, "name") ?? stringProperty(result.model, "id") ?? "model";
636
+ const reasoning = result.model.reasoning === true;
637
+ const thinking = this.#thinkingLevel;
638
+ const suffix = reasoning && thinking !== "off" ? ` (thinking: ${thinking})` : "";
639
+ return workflowResult("model", "completed", `Switched to ${modelName}${suffix}`, undefined, "status");
400
640
  }
401
641
  catch (error) {
402
642
  return workflowResult("model", "failed", error instanceof Error ? error.message : String(error));
@@ -412,16 +652,107 @@ export class PiEngineAdapter {
412
652
  /** Settings port for the live runtime, or null before the runtime is available. */
413
653
  settingsPort() {
414
654
  const settings = this.#runtime?.services.settingsManager;
415
- if (!settings)
655
+ if (!settings || typeof settings.getCompactionEnabled !== "function")
416
656
  return null;
417
- const session = this.#runtime?.session;
418
- return new PiSettingsIntegration(settings, {
419
- ...(this.#availableThemes === null ? {} : { themes: this.#availableThemes }),
420
- thinkingLevels: () => {
421
- const levels = session?.getAvailableThinkingLevels?.();
422
- return Array.isArray(levels) ? levels.map(level => String(level)) : [];
423
- },
424
- });
657
+ if (this.#settingsIntegration === undefined || this.#settingsIntegrationManager !== settings) {
658
+ this.#settingsIntegrationManager = settings;
659
+ configureOwnedHttpDispatcher(settings.getHttpIdleTimeoutMs());
660
+ this.#settingsIntegration = new PiSettingsIntegration(settings, {
661
+ ...(this.#availableThemes === null ? {} : { themes: this.#availableThemes }),
662
+ thinkingLevels: () => {
663
+ const levels = this.#runtime?.session?.getAvailableThinkingLevels?.();
664
+ return Array.isArray(levels) ? levels.map(level => String(level)) : [];
665
+ },
666
+ productMode: this.#settingsProductMode,
667
+ });
668
+ this.#settingsIntegration.bindOwner("shell", {
669
+ enableSkillCommands: { apply: value => {
670
+ if (typeof value !== "boolean")
671
+ throw new TypeError("Skill commands value is invalid");
672
+ settings.setEnableSkillCommands(value);
673
+ } },
674
+ doubleEscapeAction: { apply: value => {
675
+ if (value !== "tree" && value !== "fork" && value !== "none")
676
+ throw new TypeError("Double-Escape action is invalid");
677
+ settings.setDoubleEscapeAction(value);
678
+ } },
679
+ treeFilterMode: { apply: value => {
680
+ if (value !== "default" && value !== "no-tools" && value !== "user-only" && value !== "labeled-only" && value !== "all")
681
+ throw new TypeError("Tree filter mode is invalid");
682
+ settings.setTreeFilterMode(value);
683
+ } },
684
+ showCacheMissNotices: { apply: value => {
685
+ if (typeof value !== "boolean")
686
+ throw new TypeError("Cache-miss notice setting is invalid");
687
+ settings.setShowCacheMissNotices(value);
688
+ } },
689
+ });
690
+ this.#settingsIntegration.bindOwner("startup", {
691
+ // Invariant: deferred application is the owner operation: the next preflight reads
692
+ // the persisted default before constructing project-backed services.
693
+ defaultProjectTrust: { apply() { } },
694
+ collapseChangelog: { apply() { } },
695
+ });
696
+ this.#settingsIntegration.bindOwner("agent", {
697
+ autoCompact: { apply: value => {
698
+ if (typeof value !== "boolean")
699
+ throw new TypeError("Auto compact value is invalid");
700
+ this.#requireWorkflowSession().setAutoCompactionEnabled(value);
701
+ } },
702
+ autoResizeImages: { apply: value => {
703
+ if (typeof value !== "boolean")
704
+ throw new TypeError("Auto-resize images value is invalid");
705
+ settings.setImageAutoResize(value);
706
+ } },
707
+ blockImages: { apply: value => {
708
+ if (typeof value !== "boolean")
709
+ throw new TypeError("Block images value is invalid");
710
+ settings.setBlockImages(value);
711
+ } },
712
+ steeringMode: { apply: value => {
713
+ if (value !== "all" && value !== "one-at-a-time")
714
+ throw new TypeError("Steering mode is invalid");
715
+ this.#requireWorkflowSession().setSteeringMode(value);
716
+ } },
717
+ followUpMode: { apply: value => {
718
+ if (value !== "all" && value !== "one-at-a-time")
719
+ throw new TypeError("Follow-up mode is invalid");
720
+ this.#requireWorkflowSession().setFollowUpMode(value);
721
+ } },
722
+ transport: { apply: value => {
723
+ if (value !== "sse" && value !== "websocket" && value !== "websocket-cached" && value !== "auto")
724
+ throw new TypeError("Transport is invalid");
725
+ this.#requireWorkflowSession().agent.transport = value;
726
+ } },
727
+ httpIdleTimeoutMs: { apply: value => {
728
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0)
729
+ throw new TypeError("HTTP idle timeout is invalid");
730
+ // Invariant: provider streaming reads the manager per request; global fetch uses
731
+ // the matching owned dispatcher and zero maps to disabled semantics.
732
+ configureOwnedHttpDispatcher(value);
733
+ settings.setHttpIdleTimeoutMs(value);
734
+ } },
735
+ thinkingLevel: { apply: value => {
736
+ if (!isThinkingLevel(value))
737
+ throw new TypeError("Thinking level is invalid");
738
+ this.#requireWorkflowSession().setThinkingLevel(value);
739
+ this.#thinkingLevel = readThinkingLevel(this.#requireWorkflowSession().thinkingLevel);
740
+ this.#emitView();
741
+ } },
742
+ warnings: { apply: value => {
743
+ if (!isRecord(value) || Object.values(value).some(flag => typeof flag !== "boolean"))
744
+ throw new TypeError("Warnings setting is invalid");
745
+ settings.setWarnings(value);
746
+ } },
747
+ });
748
+ }
749
+ return this.#settingsIntegration;
750
+ }
751
+ bindSettingsOwner(owner, handlers) {
752
+ const settings = this.settingsPort();
753
+ if (settings === null)
754
+ return () => { };
755
+ return settings.bindOwner(owner, handlers);
425
756
  }
426
757
  pinnedModelSelectorContext() {
427
758
  const session = this.#requireWorkflowSession();
@@ -454,17 +785,25 @@ export class PiEngineAdapter {
454
785
  if (!runtime)
455
786
  throw new Error("engine runtime is unavailable");
456
787
  const cwd = resolve(this.#cwd);
457
- const parent = dirname(cwd);
788
+ let trustPath = cwd;
789
+ try {
790
+ trustPath = realpathSync(cwd);
791
+ }
792
+ catch {
793
+ // Compatibility: pinned Pi retains the resolved path when real-path lookup fails.
794
+ }
795
+ // Security: trust the target's parent, not the parent containing a directory alias.
796
+ const parent = dirname(trustPath);
458
797
  const trustStore = new ProjectTrustStore(this.#agentDir);
459
798
  const trustOptions = [
460
- { label: "Trust", trusted: true, updates: [{ path: cwd, decision: true }], savedPath: cwd },
461
- ...(parent === cwd ? [] : [{
799
+ { label: "Trust", trusted: true, updates: [{ path: trustPath, decision: true }], savedPath: trustPath },
800
+ ...(parent === trustPath ? [] : [{
462
801
  label: `Trust parent folder (${parent})`,
463
802
  trusted: true,
464
- updates: [{ path: parent, decision: true }, { path: cwd, decision: null }],
803
+ updates: [{ path: parent, decision: true }, { path: trustPath, decision: null }],
465
804
  savedPath: parent,
466
805
  }]),
467
- { label: "Do not trust", trusted: false, updates: [{ path: cwd, decision: false }], savedPath: cwd },
806
+ { label: "Do not trust", trusted: false, updates: [{ path: trustPath, decision: false }], savedPath: trustPath },
468
807
  ];
469
808
  return {
470
809
  cwd,
@@ -591,6 +930,17 @@ export class PiEngineAdapter {
591
930
  }));
592
931
  return { title: `Select authentication method for ${providerName}:`, options };
593
932
  }
933
+ pinnedAmbientAuthentication(selection) {
934
+ if (!selection.startsWith("api_key:"))
935
+ return null;
936
+ const providerId = selection.slice("api_key:".length);
937
+ const provider = this.#runtime?.services.modelRuntime.getProvider?.(providerId);
938
+ const method = dynamicObject(dynamicObject(provider, "auth"), "apiKey");
939
+ if (!method || typeof method.login === "function")
940
+ return null;
941
+ const providerName = stringProperty(provider, "name") ?? providerId;
942
+ return { providerId, providerName, title: `${providerName} setup`, message: `${stringProperty(method, "name") ?? "Authentication"} is configured outside pi.` };
943
+ }
594
944
  pinnedLogoutOptions() {
595
945
  return this.#logoutOptions();
596
946
  }
@@ -671,9 +1021,27 @@ export class PiEngineAdapter {
671
1021
  pinnedMessageRenderer(customType) {
672
1022
  return this.#requireWorkflowSession().extensionRunner?.getMessageRenderer?.(customType);
673
1023
  }
1024
+ pinnedShortcutDescriptions(bindings) {
1025
+ const shortcuts = this.#requireWorkflowSession().extensionRunner?.getShortcuts?.(bindings);
1026
+ return shortcuts === undefined ? [] : [...shortcuts].map(([key, shortcut]) => ({ key, description: shortcut.description ?? shortcut.extensionPath }));
1027
+ }
674
1028
  pinnedToolDefinition(toolName) {
675
1029
  return this.#requireWorkflowSession().extensionRunner?.getToolDefinition?.(toolName);
676
1030
  }
1031
+ /** Bind the owned UI's clipboard lifecycle without changing the comparison host. True means acknowledged delivery. */
1032
+ bindClipboardWriter(writer) {
1033
+ const previous = this.#clipboardWriter;
1034
+ this.#clipboardWriter = writer;
1035
+ return () => { if (this.#clipboardWriter === writer)
1036
+ this.#clipboardWriter = previous; };
1037
+ }
1038
+ /** Workflow and tree copying share the active owner's write fence and delivery acknowledgment. */
1039
+ async copyWorkflowText(text) {
1040
+ if (this.#clipboardWriter !== undefined)
1041
+ return this.#clipboardWriter(text);
1042
+ await this.#workflowHost.copyText(text);
1043
+ return true;
1044
+ }
677
1045
  clearQueuedWorkflows() {
678
1046
  const session = this.#requireWorkflowSession();
679
1047
  const result = session.clearQueue?.();
@@ -698,23 +1066,55 @@ export class PiEngineAdapter {
698
1066
  excludeFromContext,
699
1067
  };
700
1068
  }
701
- applyPinnedSettingValue(callback, value) {
1069
+ async applyPinnedSettingValue(callback, value) {
702
1070
  try {
703
- return this.#applyPinnedSetting(callback, value, true);
1071
+ return await this.#applyPinnedSetting(callback, value, true);
704
1072
  }
705
1073
  catch (error) {
706
1074
  return workflowResult("settings", "failed", error instanceof Error ? error.message : String(error));
707
1075
  }
708
1076
  }
1077
+ reloadBlockedResult() {
1078
+ const session = this.#requireWorkflowSession();
1079
+ if (session.isStreaming)
1080
+ return workflowResult("reload", "failed", "Wait for the current response to finish before reloading.", undefined, "warning");
1081
+ if (session.isCompacting)
1082
+ return workflowResult("reload", "failed", "Wait for compaction to finish before reloading.", undefined, "warning");
1083
+ return null;
1084
+ }
709
1085
  async executeWorkflow(request) {
1086
+ const cancelled = workflowResult(request.command, "cancelled", "", undefined, "silent");
1087
+ if (this.#overload !== undefined || this.#admissionStopped || this.#disposed
1088
+ || this.#pendingCommands.size + this.#pendingWorkflows.size >= 32)
1089
+ return cancelled;
1090
+ let cancel;
1091
+ const cancellation = new Promise(resolve => { cancel = () => resolve(cancelled); });
1092
+ const pending = { command: request.command, cancel };
1093
+ this.#pendingWorkflows.add(pending);
1094
+ this.#runningCommands++;
1095
+ const operation = this.#runWorkflow(request).finally(() => { this.#runningCommands--; });
1096
+ try {
1097
+ return await Promise.race([operation, cancellation]);
1098
+ }
1099
+ finally {
1100
+ this.#pendingWorkflows.delete(pending);
1101
+ }
1102
+ }
1103
+ async #runWorkflow(request) {
710
1104
  try {
711
1105
  return await this.#performWorkflow(request);
712
1106
  }
713
1107
  catch (error) {
714
1108
  const message = error instanceof Error ? error.message : String(error);
715
1109
  const contextualMessage = request.command === "export"
716
- ? `Failed to export session: ${message}`
717
- : request.command === "reload" ? `Reload failed: ${message}` : message;
1110
+ ? `Failed to export session: ${errorMessage(error, "Unknown error")}`
1111
+ : request.command === "import"
1112
+ ? `Failed to import session: ${message}`
1113
+ : request.command === "new"
1114
+ ? `Failed to create session: ${message}`
1115
+ : request.command === "resume"
1116
+ ? `Failed to resume session: ${message}`
1117
+ : request.command === "reload" ? `Reload failed: ${message}` : message;
718
1118
  return workflowResult(request.command, "failed", contextualMessage);
719
1119
  }
720
1120
  }
@@ -724,13 +1124,13 @@ export class PiEngineAdapter {
724
1124
  sessionId: this.#sessionId,
725
1125
  revision: this.#viewRevision,
726
1126
  lifecycle: this.#lifecycle,
727
- transcript: this.#transcript.map(block => ({ ...block })),
1127
+ transcript: this.#transcriptSnapshot ??= Object.freeze([...this.#transcript]),
728
1128
  editor: { ...this.#editor, queuedSubmissions: [...this.#editor.queuedSubmissions] },
729
1129
  status: {
730
1130
  ...this.#status,
731
1131
  diagnostics: [...this.#status.diagnostics],
732
1132
  badges: [...this.#status.badges],
733
- usage: this.#readUsage(),
1133
+ usage: this.#usageCache ??= this.#readUsage(),
734
1134
  footer: {
735
1135
  branch: this.#gitBranch,
736
1136
  sessionName: this.#session?.sessionManager?.getSessionName() ?? null,
@@ -767,6 +1167,9 @@ export class PiEngineAdapter {
767
1167
  if (this.#disposed || !this.#runtime || !this.#session) {
768
1168
  return this.#finishCommand(command, "rejected", "engine adapter is not running");
769
1169
  }
1170
+ // Invariant: the bounded out-of-band cancellation path has one slot per admitted command.
1171
+ if (this.#overload !== undefined || this.#admissionStopped || this.#pendingCommands.size + this.#pendingWorkflows.size >= 32)
1172
+ return { outcome: "rejected", diagnostic: null };
770
1173
  const existing = this.#completedCommands.get(command.correlationId);
771
1174
  if (existing)
772
1175
  return existing;
@@ -774,28 +1177,65 @@ export class PiEngineAdapter {
774
1177
  return this.#finishCommand(command, "rejected", "duplicate engine command correlation id");
775
1178
  }
776
1179
  this.#activeCommandIds.push(command.correlationId);
777
- this.#emitEvent({
778
- type: "command-outcome",
779
- correlationId: command.correlationId,
780
- outcome: "accepted",
781
- diagnostic: null,
1180
+ const generation = this.#sessionGeneration;
1181
+ let cancelled = false;
1182
+ const cancellation = new Promise(resolve => {
1183
+ this.#pendingCommands.set(command.correlationId, { type: command.type, cancel: () => {
1184
+ if (cancelled)
1185
+ return;
1186
+ cancelled = true;
1187
+ resolve(this.#recordCommand(command, "failed", null));
1188
+ } });
782
1189
  });
1190
+ this.#emitEvent({ type: "command-outcome", correlationId: command.correlationId, outcome: "accepted", diagnostic: null });
1191
+ const operation = async () => {
1192
+ try {
1193
+ if (cancelled)
1194
+ return { outcome: "failed", diagnostic: null };
1195
+ this.#runningCommands++;
1196
+ try {
1197
+ await this.#perform(command);
1198
+ }
1199
+ finally {
1200
+ this.#runningCommands--;
1201
+ }
1202
+ if (cancelled)
1203
+ return { outcome: "failed", diagnostic: null };
1204
+ if (generation === this.#sessionGeneration)
1205
+ this.#emitView();
1206
+ if (cancelled)
1207
+ return { outcome: "failed", diagnostic: null };
1208
+ return this.#recordCommand(command, "completed", null);
1209
+ }
1210
+ catch (error) {
1211
+ if (cancelled)
1212
+ return { outcome: "failed", diagnostic: null };
1213
+ const diagnostic = error instanceof Error ? error.message : String(error);
1214
+ this.#addDiagnostic("error", "engine-command", diagnostic, true);
1215
+ if (generation === this.#sessionGeneration)
1216
+ this.#emitView();
1217
+ return this.#recordCommand(command, "failed", diagnostic);
1218
+ }
1219
+ };
783
1220
  try {
784
- await this.#perform(command);
785
- this.#emitView();
786
- return this.#recordCommand(command, "completed", null);
1221
+ return await Promise.race([operation(), cancellation]);
787
1222
  }
788
- catch (error) {
789
- const diagnostic = error instanceof Error ? error.message : String(error);
790
- this.#addDiagnostic("error", "engine-command", diagnostic, true);
791
- this.#emitView();
792
- return this.#recordCommand(command, "failed", diagnostic);
1223
+ finally {
1224
+ this.#pendingCommands.delete(command.correlationId);
793
1225
  }
794
1226
  }
795
1227
  async dispose() {
796
1228
  if (this.#disposed)
797
1229
  return;
798
1230
  this.#disposed = true;
1231
+ for (const pending of this.#pendingCommands.values())
1232
+ if (pending.type !== "shutdown")
1233
+ pending.cancel();
1234
+ // Compatibility: /quit owns normal disposal and must report its actual completion, not cancel itself.
1235
+ for (const pending of this.#pendingWorkflows)
1236
+ if (pending.command !== "quit")
1237
+ pending.cancel();
1238
+ this.#transcriptImageAssets.clear();
799
1239
  this.#extensionBound = false;
800
1240
  this.#extensionUi = undefined;
801
1241
  this.#extensionShutdown = undefined;
@@ -807,7 +1247,13 @@ export class PiEngineAdapter {
807
1247
  this.#lifecycle = "stopped";
808
1248
  this.#emitEvent({ type: "session-lifecycle", lifecycle: "stopped", reason: null });
809
1249
  this.#emitView();
810
- await this.flushEvents();
1250
+ try {
1251
+ await this.flushEvents();
1252
+ }
1253
+ catch (error) {
1254
+ if (!(error instanceof EngineDeliveryError))
1255
+ throw error;
1256
+ }
811
1257
  }
812
1258
  async #performWorkflow(request) {
813
1259
  const session = this.#requireWorkflowSession();
@@ -821,22 +1267,88 @@ export class PiEngineAdapter {
821
1267
  case "settings": {
822
1268
  if (!selection)
823
1269
  return workflowResult(request.command, "failed", "Settings requires the owned settings controller");
824
- return this.#applyPinnedSetting(selection);
1270
+ return await this.#applyPinnedSetting(selection);
825
1271
  }
826
1272
  case "model": {
827
1273
  const reference = selection ?? argument;
828
1274
  if (!reference)
829
1275
  return workflowResult(request.command, "failed", "Model requires the owned model controller");
830
- const [providerId, modelId] = reference.split("/", 2);
831
- if (!providerId || !modelId)
832
- return workflowResult(request.command, "failed", "Model requires provider/model");
833
- const model = runtime.services.modelRuntime.getModel(providerId, modelId);
834
- if (!model)
835
- return workflowResult(request.command, "failed", `Model is unavailable: ${reference}`);
836
- await session.setModel(model);
1276
+ const scopedModels = Array.isArray(session.scopedModels)
1277
+ ? session.scopedModels.map(item => item.model)
1278
+ : [];
1279
+ let availableModels = scopedModels.length > 0
1280
+ ? scopedModels
1281
+ : [...(runtime.services.modelRuntime.getAvailableSnapshot?.() ?? [])];
1282
+ let model = findExactWorkflowModel(reference, availableModels);
1283
+ const messages = [];
1284
+ const generation = this.#sessionGeneration;
1285
+ const current = () => !this.#disposed && generation === this.#sessionGeneration;
1286
+ const publish = (message) => {
1287
+ if (!current())
1288
+ return;
1289
+ if (this.#workflowInteraction.publish)
1290
+ this.#workflowInteraction.publish(message);
1291
+ else
1292
+ messages.push(message);
1293
+ };
1294
+ if (model === undefined && scopedModels.length === 0) {
1295
+ publish({ kind: "status", message: "Refreshing model catalogs…" });
1296
+ const controller = new AbortController();
1297
+ let timedOut = false;
1298
+ const timeout = setTimeout(() => {
1299
+ timedOut = true;
1300
+ controller.abort();
1301
+ }, AUTH_REFRESH_TIMEOUT_MS);
1302
+ try {
1303
+ const refreshed = await runtime.services.modelRuntime.refresh?.({ signal: controller.signal });
1304
+ if (isRecord(refreshed) && refreshed.aborted === true && timedOut) {
1305
+ publish({ kind: "warning", message: "Model refresh timed out; searching cached models." });
1306
+ }
1307
+ else if (isRecord(refreshed) && refreshed.errors instanceof Map && refreshed.errors.size > 0) {
1308
+ publish({ kind: "warning", message: `Could not refresh ${[...refreshed.errors.keys()].join(", ")}; searching cached models.` });
1309
+ }
1310
+ }
1311
+ catch (error) {
1312
+ publish({
1313
+ kind: "warning",
1314
+ message: timedOut
1315
+ ? "Model refresh timed out; searching cached models."
1316
+ : `Could not refresh model catalogs: ${error instanceof Error ? error.message : String(error)}`,
1317
+ });
1318
+ }
1319
+ finally {
1320
+ clearTimeout(timeout);
1321
+ }
1322
+ availableModels = [...(runtime.services.modelRuntime.getAvailableSnapshot?.() ?? [])];
1323
+ model = findExactWorkflowModel(reference, availableModels);
1324
+ }
1325
+ if (!current())
1326
+ return workflowResult(request.command, "cancelled", "Model selection cancelled", undefined, "silent");
1327
+ if (model === undefined) {
1328
+ return {
1329
+ ...workflowResult(request.command, "requires-selection", "Select a model", reference, "silent"),
1330
+ ...(messages.length === 0 ? {} : { messages: Object.freeze(messages) }),
1331
+ };
1332
+ }
1333
+ try {
1334
+ await session.setModel(model);
1335
+ }
1336
+ catch (error) {
1337
+ if (!current())
1338
+ return workflowResult(request.command, "cancelled", "Model selection cancelled", undefined, "silent");
1339
+ const failure = { kind: "error", message: error instanceof Error ? error.message : String(error) };
1340
+ return workflowResult(request.command, "failed", failure.message, undefined, "error", messages.length === 0 ? undefined : [...messages, failure]);
1341
+ }
1342
+ if (!current())
1343
+ return workflowResult(request.command, "cancelled", "Model selection cancelled", undefined, "silent");
1344
+ const providerId = stringProperty(model, "provider") ?? "unknown";
1345
+ const modelId = stringProperty(model, "id") ?? reference;
837
1346
  this.#activeModel = { providerId, modelId, displayName: stringProperty(model, "name") ?? modelId };
838
1347
  this.#emitView();
839
- return workflowResult(request.command, "completed", `Model: ${modelId}`);
1348
+ const resultMessage = { kind: "status", message: `Model: ${modelId}` };
1349
+ return messages.length === 0
1350
+ ? workflowResult(request.command, "completed", resultMessage.message)
1351
+ : workflowResult(request.command, "completed", resultMessage.message, undefined, "status", [...messages, resultMessage]);
840
1352
  }
841
1353
  case "scoped-models": {
842
1354
  if (!selection)
@@ -863,27 +1375,64 @@ export class PiEngineAdapter {
863
1375
  return workflowConfirmation(request.command, `Replace current session with ${path}?`);
864
1376
  }
865
1377
  if (!request.confirmed)
866
- return workflowResult(request.command, "cancelled", "Import cancelled");
867
- const result = await requireCapability(runtime.importFromJsonl, "importFromJsonl").call(runtime, path);
868
- if (isRecord(result) && result.cancelled === true)
869
- return workflowResult(request.command, "cancelled", "Import cancelled");
870
- return workflowResult(request.command, "completed", `Session imported from: ${path}`);
1378
+ return workflowResult(request.command, "cancelled", "Import cancelled", undefined, "status");
1379
+ try {
1380
+ const result = await requireCapability(runtime.importFromJsonl, "importFromJsonl").call(runtime, path, request.cwdOverride);
1381
+ if (isRecord(result) && result.cancelled === true)
1382
+ return workflowResult(request.command, "cancelled", "Import cancelled", undefined, "status");
1383
+ return workflowResult(request.command, "completed", `Session imported from: ${path}`);
1384
+ }
1385
+ catch (error) {
1386
+ const issue = isRecord(error) && isRecord(error.issue) ? error.issue : undefined;
1387
+ const fallbackCwd = issue === undefined ? undefined : stringProperty(issue, "fallbackCwd");
1388
+ if (fallbackCwd !== undefined && request.cwdOverride === undefined) {
1389
+ const sessionCwd = stringProperty(issue, "sessionCwd") ?? "the session working directory";
1390
+ return workflowConfirmation(request.command, `cwd from session file does not exist\n${sessionCwd}\n\ncontinue in current cwd\n${fallbackCwd}`, fallbackCwd);
1391
+ }
1392
+ throw error;
1393
+ }
871
1394
  }
872
1395
  case "share": {
873
- const auth = await this.#workflowHost.runCommand("gh", ["auth", "status"]).catch(error => {
874
- throw new Error(`GitHub CLI is unavailable or not logged in: ${error instanceof Error ? error.message : String(error)}`);
875
- });
876
- if (auth.stderr && !auth.stdout)
877
- throw new Error(auth.stderr.trim());
1396
+ const signal = request.signal;
1397
+ if (signal?.aborted)
1398
+ return workflowResult(request.command, "cancelled", "Share cancelled", undefined, "status");
1399
+ try {
1400
+ await this.#workflowHost.runCommand("gh", ["auth", "status"], signal === undefined ? undefined : { signal });
1401
+ }
1402
+ catch (error) {
1403
+ if (signal?.aborted || isAbortError(error))
1404
+ return workflowResult(request.command, "cancelled", "Share cancelled", undefined, "status");
1405
+ const message = commandMissing(error)
1406
+ ? "GitHub CLI (gh) is not installed. Install it from https://cli.github.com/"
1407
+ : "GitHub CLI is not logged in. Run 'gh auth login' first.";
1408
+ return workflowResult(request.command, "failed", message);
1409
+ }
878
1410
  const temporary = join(tmpdir(), `${PRODUCT_IDENTITY.filesystem.temporaryPrefix}pi-session-${process.pid}.html`);
879
1411
  try {
880
- await requireCapability(session.exportToHtml, "exportToHtml").call(session, temporary);
881
- const gist = await this.#workflowHost.runCommand("gh", ["gist", "create", "--public=false", temporary]);
1412
+ try {
1413
+ await requireCapability(session.exportToHtml, "exportToHtml").call(session, temporary);
1414
+ }
1415
+ catch (error) {
1416
+ return workflowResult(request.command, "failed", `Failed to export session: ${errorMessage(error, "Unknown error")}`);
1417
+ }
1418
+ if (signal?.aborted)
1419
+ return workflowResult(request.command, "cancelled", "Share cancelled", undefined, "status");
1420
+ let gist;
1421
+ try {
1422
+ gist = await this.#workflowHost.runCommand("gh", ["gist", "create", "--public=false", temporary], signal === undefined ? undefined : { signal });
1423
+ }
1424
+ catch (error) {
1425
+ if (signal?.aborted || isAbortError(error))
1426
+ return workflowResult(request.command, "cancelled", "Share cancelled", undefined, "status");
1427
+ return workflowResult(request.command, "failed", `Failed to create gist: ${shareCommandFailureDetail(error)}`);
1428
+ }
1429
+ if (signal?.aborted)
1430
+ return workflowResult(request.command, "cancelled", "Share cancelled", undefined, "status");
882
1431
  const gistUrl = gist.stdout.trim();
883
1432
  const gistId = gistUrl.split("/").at(-1);
884
1433
  if (!gistId)
885
- throw new Error("Failed to parse gist ID from gh output");
886
- return workflowResult(request.command, "completed", `Share URL: https://pi.gptscript.ai/gist/${gistId}`, gistUrl);
1434
+ return workflowResult(request.command, "failed", "Failed to parse gist ID from gh output");
1435
+ return workflowResult(request.command, "completed", `Share URL: ${shareViewerUrl(gistId)}`, gistUrl);
887
1436
  }
888
1437
  finally {
889
1438
  await rm(temporary, { force: true });
@@ -893,8 +1442,8 @@ export class PiEngineAdapter {
893
1442
  const text = requireCapability(session.getLastAssistantText, "getLastAssistantText").call(session);
894
1443
  if (typeof text !== "string" || text.length === 0)
895
1444
  return workflowResult(request.command, "failed", "No agent messages to copy yet.");
896
- await this.#workflowHost.copyText(text);
897
- return workflowResult(request.command, "completed", "Copied last agent message to clipboard");
1445
+ const acknowledged = await this.copyWorkflowText(text);
1446
+ return workflowResult(request.command, "completed", acknowledged ? "Copied last agent message to clipboard" : "Submitted last agent message to clipboard");
898
1447
  }
899
1448
  case "name": {
900
1449
  const manager = session.sessionManager;
@@ -902,7 +1451,7 @@ export class PiEngineAdapter {
902
1451
  const current = manager?.getSessionName();
903
1452
  return typeof current === "string"
904
1453
  ? workflowResult(request.command, "completed", `Session name: ${current}`)
905
- : workflowResult(request.command, "failed", "Usage: /name <name>");
1454
+ : workflowResult(request.command, "failed", "Usage: /name <name>", undefined, "warning");
906
1455
  }
907
1456
  requireCapability(session.setSessionName, "setSessionName").call(session, argument);
908
1457
  const normalized = manager?.getSessionName();
@@ -929,17 +1478,17 @@ export class PiEngineAdapter {
929
1478
  return workflowResult(request.command, "failed", "Fork requires the owned user-message controller");
930
1479
  const result = await requireCapability(runtime.fork, "fork").call(runtime, selection, { position: "before" });
931
1480
  if (isRecord(result) && result.cancelled === true)
932
- return workflowResult(request.command, "cancelled", "Fork cancelled");
1481
+ return workflowResult(request.command, "cancelled", "Fork cancelled", undefined, "silent");
933
1482
  return workflowResult(request.command, "completed", "Forked to new session");
934
1483
  }
935
1484
  case "clone": {
936
1485
  const manager = session.sessionManager;
937
1486
  const leaf = manager?.getLeafId?.();
938
1487
  if (typeof leaf !== "string")
939
- return workflowResult(request.command, "failed", "No session position is available to clone");
1488
+ return workflowResult(request.command, "completed", "Nothing to clone yet", undefined, "status");
940
1489
  const result = await requireCapability(runtime.fork, "fork").call(runtime, leaf, { position: "at" });
941
1490
  if (isRecord(result) && result.cancelled === true)
942
- return workflowResult(request.command, "cancelled", "Clone cancelled");
1491
+ return workflowResult(request.command, "cancelled", "Clone cancelled", undefined, "silent");
943
1492
  return workflowResult(request.command, "completed", "Cloned to new session");
944
1493
  }
945
1494
  case "tree": {
@@ -953,9 +1502,9 @@ export class PiEngineAdapter {
953
1502
  ...(request.treeSummary.customInstructions === undefined ? {} : { customInstructions: request.treeSummary.customInstructions }),
954
1503
  });
955
1504
  if (isRecord(result) && result.aborted === true)
956
- return workflowResult(request.command, "cancelled", "Branch summarization cancelled");
1505
+ return workflowResult(request.command, "cancelled", "Branch summarization cancelled", undefined, "status");
957
1506
  if (isRecord(result) && result.cancelled === true)
958
- return workflowResult(request.command, "cancelled", "Navigation cancelled");
1507
+ return workflowResult(request.command, "cancelled", "Navigation cancelled", undefined, "status");
959
1508
  return workflowResult(request.command, "completed", "Navigated to selected point");
960
1509
  }
961
1510
  case "trust": {
@@ -990,6 +1539,7 @@ export class PiEngineAdapter {
990
1539
  const authType = authTypeValue === "api_key" ? "api_key" : "oauth";
991
1540
  const provider = modelRuntime.getProvider?.(providerId);
992
1541
  const providerName = stringProperty(provider, "name") ?? providerId;
1542
+ const previousModel = session.model;
993
1543
  this.#workflowInteraction.startLogin?.({ providerId, providerName, authType });
994
1544
  try {
995
1545
  await requireCapability(modelRuntime.login, "login").call(modelRuntime, providerId, authType, {
@@ -1027,25 +1577,40 @@ export class PiEngineAdapter {
1027
1577
  });
1028
1578
  }
1029
1579
  catch (error) {
1030
- if (error instanceof Error && error.message === "Login cancelled")
1031
- return workflowResult(request.command, "cancelled", "Login cancelled");
1032
- throw error;
1580
+ if (error instanceof Error && error.message === "Login cancelled") {
1581
+ return workflowResult(request.command, "cancelled", "Login cancelled", undefined, "silent");
1582
+ }
1583
+ const detail = error instanceof Error ? error.message : String(error);
1584
+ const actionLabel = authType === "api_key" ? `Saved API key for ${providerName}` : `Logged in to ${providerName}`;
1585
+ const message = error instanceof CredentialSynchronizationError
1586
+ ? `${actionLabel}, but local model state could not be synchronized: ${detail}`
1587
+ : authType === "api_key"
1588
+ ? `Failed to save API key for ${providerName}: ${detail}`
1589
+ : `Failed to login to ${providerName}: ${detail}`;
1590
+ return workflowResult(request.command, "failed", message);
1033
1591
  }
1034
1592
  finally {
1035
1593
  this.#workflowInteraction.finishLogin?.();
1036
1594
  }
1037
- this.#reconcileActiveModelAvailability();
1038
- this.#emitView();
1039
- return workflowResult(request.command, "completed", `Logged in to ${providerName}. Credentials saved to ${join(this.#agentDir, "auth.json")}`);
1595
+ return await this.#completeProviderAuthentication(providerId, providerName, authType, previousModel);
1040
1596
  }
1041
1597
  case "logout": {
1042
1598
  if (!selection)
1043
1599
  return workflowResult(request.command, "failed", "Logout requires the owned authentication controller");
1044
1600
  const [credentialType = "oauth", providerId = selection] = selection.includes(":") ? selection.split(":", 2) : ["oauth", selection];
1045
1601
  const modelRuntime = runtime.services.modelRuntime;
1046
- await requireCapability(modelRuntime.logout, "logout").call(modelRuntime, providerId, { signal: AbortSignal.timeout(15_000) });
1047
1602
  const provider = modelRuntime.getProvider?.(providerId);
1048
1603
  const providerName = stringProperty(provider, "name") ?? providerId;
1604
+ try {
1605
+ await requireCapability(modelRuntime.logout, "logout").call(modelRuntime, providerId, { signal: AbortSignal.timeout(15_000) });
1606
+ }
1607
+ catch (error) {
1608
+ const detail = error instanceof Error ? error.message : String(error);
1609
+ const message = error instanceof CredentialSynchronizationError
1610
+ ? `Credentials removed for ${providerName}, but local model state could not be synchronized: ${detail}`
1611
+ : `Logout failed: ${detail}`;
1612
+ return workflowResult(request.command, "failed", message);
1613
+ }
1049
1614
  this.#reconcileActiveModelAvailability();
1050
1615
  this.#emitView();
1051
1616
  return workflowResult(request.command, "completed", credentialType === "api_key"
@@ -1055,12 +1620,17 @@ export class PiEngineAdapter {
1055
1620
  case "new": {
1056
1621
  const result = await runtime.newSession();
1057
1622
  if (isRecord(result) && result.cancelled === true)
1058
- return workflowResult(request.command, "cancelled", "New session cancelled");
1059
- return workflowResult(request.command, "completed", "✓ New session started");
1623
+ return workflowResult(request.command, "cancelled", "New session cancelled", undefined, "silent");
1624
+ return workflowResult(request.command, "completed", "✓ New session started", undefined, "accent");
1060
1625
  }
1061
1626
  case "compact": {
1062
- await session.compact(argument || undefined);
1063
- return workflowResult(request.command, "completed", "Compaction requested");
1627
+ try {
1628
+ await session.compact(argument || undefined);
1629
+ return workflowResult(request.command, "completed", "Compaction requested", undefined, "silent");
1630
+ }
1631
+ catch (error) {
1632
+ return workflowResult(request.command, "failed", error instanceof Error ? error.message : String(error), undefined, "silent");
1633
+ }
1064
1634
  }
1065
1635
  case "resume": {
1066
1636
  if (!selection && !argument)
@@ -1069,7 +1639,7 @@ export class PiEngineAdapter {
1069
1639
  try {
1070
1640
  const result = await runtime.switchSession(sessionPath);
1071
1641
  if (isRecord(result) && result.cancelled === true)
1072
- return workflowResult(request.command, "cancelled", "Resume cancelled");
1642
+ return workflowResult(request.command, "cancelled", "Resume cancelled", undefined, "silent");
1073
1643
  return workflowResult(request.command, "completed", "Resumed session");
1074
1644
  }
1075
1645
  catch (error) {
@@ -1082,21 +1652,25 @@ export class PiEngineAdapter {
1082
1652
  return workflowConfirmation(request.command, `cwd from session file does not exist\n${sessionCwd}\n\ncontinue in current cwd\n${fallbackCwd}`);
1083
1653
  }
1084
1654
  if (!request.confirmed)
1085
- return workflowResult(request.command, "cancelled", "Resume cancelled");
1655
+ return workflowResult(request.command, "cancelled", "Resume cancelled", undefined, "status");
1086
1656
  const result = await runtime.switchSession(sessionPath, { cwdOverride: fallbackCwd });
1087
1657
  if (isRecord(result) && result.cancelled === true)
1088
- return workflowResult(request.command, "cancelled", "Resume cancelled");
1658
+ return workflowResult(request.command, "cancelled", "Resume cancelled", undefined, "silent");
1089
1659
  return workflowResult(request.command, "completed", "Resumed session in current cwd");
1090
1660
  }
1091
1661
  }
1092
1662
  case "reload": {
1093
- if (session.isStreaming)
1094
- return workflowResult(request.command, "failed", "Wait for the current response to finish before reloading.");
1095
- if (session.isCompacting)
1096
- return workflowResult(request.command, "failed", "Wait for compaction to finish before reloading.");
1663
+ const blocked = this.reloadBlockedResult();
1664
+ if (blocked)
1665
+ return blocked;
1097
1666
  await requireCapability(session.reload, "reload").call(session);
1098
1667
  await this.#bindExtensionUiToSession();
1099
- return workflowResult(request.command, "completed", "Reloaded keybindings, extensions, skills, prompts, themes, and context files");
1668
+ const message = "Reloaded keybindings, extensions, skills, prompts, themes, and context files";
1669
+ const modelError = runtime.services.modelRuntime.getError?.();
1670
+ return workflowResult(request.command, "completed", message, undefined, "status", modelError ? [
1671
+ { kind: "error", message: `models.json error: ${modelError}` },
1672
+ { kind: "status", message },
1673
+ ] : undefined);
1100
1674
  }
1101
1675
  case "quit": {
1102
1676
  await this.dispose();
@@ -1114,6 +1688,91 @@ export class PiEngineAdapter {
1114
1688
  return workflowResult(request.command, "completed", "Demented elves announcement");
1115
1689
  }
1116
1690
  }
1691
+ async #completeProviderAuthentication(providerId, providerName, authType, previousModel) {
1692
+ const session = this.#requireWorkflowSession();
1693
+ const modelRuntime = this.#runtime?.services.modelRuntime;
1694
+ if (!modelRuntime)
1695
+ throw new Error("engine runtime is unavailable");
1696
+ const actionLabel = authType === "oauth" ? `Logged in to ${providerName}` : `Saved API key for ${providerName}`;
1697
+ let selectedModelId;
1698
+ let selectionError;
1699
+ if (isUnknownModel(previousModel)) {
1700
+ const available = modelRuntime.getAvailableSnapshot?.() ?? [];
1701
+ const providerModels = available.filter(model => stringProperty(model, "provider") === providerId);
1702
+ const defaultModelId = PINNED_DEFAULT_MODEL_BY_PROVIDER[providerId];
1703
+ if (defaultModelId === undefined) {
1704
+ selectionError = `${actionLabel}, but no default model is configured for provider "${providerId}". Use /model to select a model.`;
1705
+ }
1706
+ else if (providerModels.length === 0) {
1707
+ selectionError = `${actionLabel}, but no models are available for that provider. Use /model to select a model.`;
1708
+ }
1709
+ else {
1710
+ const selectedModel = providerModels.find(model => stringProperty(model, "id") === defaultModelId);
1711
+ if (selectedModel === undefined) {
1712
+ selectionError = `${actionLabel}, but its default model "${defaultModelId}" is not available. Use /model to select a model.`;
1713
+ }
1714
+ else {
1715
+ try {
1716
+ await session.setModel(selectedModel);
1717
+ selectedModelId = stringProperty(selectedModel, "id") ?? defaultModelId;
1718
+ this.#activeModel = {
1719
+ providerId,
1720
+ modelId: selectedModelId,
1721
+ displayName: stringProperty(selectedModel, "name") ?? selectedModelId,
1722
+ };
1723
+ }
1724
+ catch (error) {
1725
+ selectionError = `${actionLabel}, but selecting its default model failed: ${error instanceof Error ? error.message : String(error)}. Use /model to select a model.`;
1726
+ }
1727
+ }
1728
+ }
1729
+ }
1730
+ this.#reconcileActiveModelAvailability();
1731
+ this.#emitView();
1732
+ const status = `${actionLabel}.${selectedModelId ? ` Selected ${selectedModelId}.` : ""} Credentials saved to ${join(this.#agentDir, "auth.json")}`;
1733
+ const messages = [
1734
+ { kind: "status", message: status },
1735
+ ...(selectionError === undefined ? [] : [{ kind: "error", message: selectionError }]),
1736
+ ];
1737
+ this.#scheduleAuthenticatedProviderRefresh(providerId, actionLabel);
1738
+ return workflowResult("login", "completed", status, undefined, "status", messages);
1739
+ }
1740
+ #scheduleAuthenticatedProviderRefresh(providerId, actionLabel) {
1741
+ const runtime = this.#runtime;
1742
+ const refresh = runtime?.services.modelRuntime.refresh;
1743
+ if (!runtime || typeof refresh !== "function")
1744
+ return;
1745
+ const generation = this.#sessionGeneration;
1746
+ const publish = this.#workflowInteraction.publish;
1747
+ const controller = new AbortController();
1748
+ const timeout = setTimeout(() => controller.abort(), AUTH_REFRESH_TIMEOUT_MS);
1749
+ // Compatibility: post-authentication refresh starts on the next turn so its notices follow the saved-credential status.
1750
+ void new Promise(resolve => setTimeout(resolve, 0))
1751
+ .then(() => refresh.call(runtime.services.modelRuntime, { providers: [providerId], signal: controller.signal }))
1752
+ .then(result => {
1753
+ if (this.#disposed || this.#sessionGeneration !== generation)
1754
+ return;
1755
+ if (isRecord(result) && result.aborted === true) {
1756
+ publish?.({ kind: "warning", message: `${actionLabel}, but its model catalog refresh timed out; using cached models.` });
1757
+ }
1758
+ else if (isRecord(result) && result.errors instanceof Map && result.errors.size > 0) {
1759
+ publish?.({ kind: "warning", message: `${actionLabel}, but its model catalog could not be refreshed; using cached models.` });
1760
+ }
1761
+ this.#reconcileActiveModelAvailability();
1762
+ this.#emitView();
1763
+ })
1764
+ .catch(error => {
1765
+ if (this.#disposed || this.#sessionGeneration !== generation)
1766
+ return;
1767
+ publish?.({
1768
+ kind: "warning",
1769
+ message: controller.signal.aborted
1770
+ ? `${actionLabel}, but its model catalog refresh timed out; using cached models.`
1771
+ : `${actionLabel}, but its model catalog could not be refreshed: ${error instanceof Error ? error.message : String(error)}`,
1772
+ });
1773
+ })
1774
+ .finally(() => clearTimeout(timeout));
1775
+ }
1117
1776
  #requireWorkflowSession() {
1118
1777
  if (this.#disposed || !this.#runtime || !this.#session)
1119
1778
  throw new Error("engine adapter is not running");
@@ -1211,7 +1870,7 @@ export class PiEngineAdapter {
1211
1870
  return [];
1212
1871
  return sessionInfoOptions(await SessionManager.list(cwd, typeof sessionDir === "string" ? sessionDir : undefined));
1213
1872
  }
1214
- #applyPinnedSetting(selection, selectedValue, hasSelectedValue = false) {
1873
+ async #applyPinnedSetting(selection, selectedValue, hasSelectedValue = false) {
1215
1874
  if (!PINNED_PI_SETTINGS_CALLBACKS.includes(selection)) {
1216
1875
  return workflowResult("settings", "failed", `Unknown setting callback: ${selection}`);
1217
1876
  }
@@ -1258,33 +1917,24 @@ export class PiEngineAdapter {
1258
1917
  selectedValue = currentValues[callback];
1259
1918
  hasSelectedValue = true;
1260
1919
  }
1261
- const settings = this.#runtime?.services.settingsManager;
1262
- if (!settings)
1920
+ const port = this.settingsPort();
1921
+ if (port === null)
1263
1922
  return workflowResult("settings", "failed", "Settings are unavailable");
1264
- const session = this.#requireWorkflowSession();
1265
- if (callback === "onThinkingLevelChange") {
1266
- if (!isThinkingLevel(selectedValue))
1267
- return workflowResult("settings", "failed", "Thinking level is invalid");
1268
- session.setThinkingLevel(selectedValue);
1269
- }
1270
- else {
1271
- const key = settingKeyForCallback(callback);
1272
- if (key === null)
1273
- return workflowResult("settings", "failed", `${settingLabel(callback)} is unavailable in this runtime`);
1274
- const value = agentJsonValue(selectedValue);
1275
- if (callback === "onAutoCompactChange") {
1276
- if (typeof value !== "boolean")
1277
- return workflowResult("settings", "failed", "Auto compact value is invalid");
1278
- session.setAutoCompactionEnabled?.(value);
1279
- }
1280
- new PiSettingsIntegration(settings).writeSettingNow(key, value);
1923
+ const key = settingKeyForCallback(callback);
1924
+ if (key === null)
1925
+ return workflowResult("settings", "failed", `${settingLabel(callback)} is unavailable in this runtime`);
1926
+ const result = await port.writeSetting(key, agentJsonValue(selectedValue));
1927
+ if (result.status === "failed" || result.status === "unavailable") {
1928
+ return workflowResult("settings", "failed", result.failure ?? result.limitationReason ?? `${settingLabel(callback)} is unavailable`);
1281
1929
  }
1282
1930
  this.#emitView();
1283
- return workflowResult("settings", "completed", `${settingLabel(callback)}: ${String(selectedValue)}`);
1931
+ const suffix = result.status === "deferred" ? ` (${result.application})` : "";
1932
+ return workflowResult("settings", "completed", `${settingLabel(callback)}: ${String(selectedValue)}${suffix}`);
1284
1933
  }
1285
1934
  async #perform(command) {
1286
1935
  const runtime = this.#runtime;
1287
1936
  const session = this.#session;
1937
+ const generation = this.#sessionGeneration;
1288
1938
  if (!runtime || !session)
1289
1939
  throw new Error("engine session is unavailable");
1290
1940
  switch (command.type) {
@@ -1295,7 +1945,11 @@ export class PiEngineAdapter {
1295
1945
  case "retry":
1296
1946
  case "compact": {
1297
1947
  const result = await this.#sessionCommands?.execute(command.type === "prompt" || command.type === "steer" || command.type === "follow-up"
1298
- ? { type: command.type, text: command.text }
1948
+ ? {
1949
+ type: command.type,
1950
+ text: command.text,
1951
+ ...(command.images === undefined ? {} : { images: [...command.images] }),
1952
+ }
1299
1953
  : { type: command.type });
1300
1954
  if (!result || result.outcome === "rejected" || result.outcome === "failed") {
1301
1955
  throw new Error(command.type === "retry" ? "no previous prompt is available to retry" : `Pi session command failed: ${command.type}`);
@@ -1308,7 +1962,8 @@ export class PiEngineAdapter {
1308
1962
  throw new Error(`model is unavailable: ${command.model.providerId}/${command.model.modelId}`);
1309
1963
  }
1310
1964
  await session.setModel(model);
1311
- this.#activeModel = { ...command.model };
1965
+ if (generation === this.#sessionGeneration && this.#overload === undefined && !this.#admissionStopped)
1966
+ this.#activeModel = { ...command.model };
1312
1967
  return;
1313
1968
  }
1314
1969
  case "set-thinking-level":
@@ -1336,7 +1991,13 @@ export class PiEngineAdapter {
1336
1991
  }
1337
1992
  #bindSession(session) {
1338
1993
  this.#unsubscribe?.();
1994
+ for (const pending of this.#pendingCommands.values()) {
1995
+ if (pending.type !== "new-session" && pending.type !== "resume-session")
1996
+ pending.cancel();
1997
+ }
1339
1998
  this.#sessionGeneration += 1;
1999
+ this.#sessionBindingGeneration += 1;
2000
+ this.#invalidatedEvents += this.#eventQueue.discardObsolete(this.#sessionGeneration);
1340
2001
  this.#session = session;
1341
2002
  this.#activeCommandIds = [];
1342
2003
  this.#completedCommands.clear();
@@ -1350,11 +2011,22 @@ export class PiEngineAdapter {
1350
2011
  submitEnabled: true,
1351
2012
  };
1352
2013
  this.#status = { ...this.#status, workingMessage: null, badges: [] };
2014
+ this.#statusKind = null;
2015
+ this.#agentRunActive = false;
2016
+ this.#agentRunSequence = 0;
2017
+ this.#assistantResponseSequence = 0;
1353
2018
  this.#activeModel = readModel(session.model);
1354
2019
  this.#reconcileActiveModelAvailability();
1355
2020
  this.#thinkingLevel = readThinkingLevel(session.thinkingLevel);
2021
+ this.#transcriptImageAssets.clear();
2022
+ // Invariant: a new binding cannot inherit arguments/results from a reused invocation id.
2023
+ this.#setTranscript([]);
1356
2024
  this.#rebuildTranscript(session.messages, "finalized");
1357
- this.#unsubscribe = session.subscribe(event => this.#handlePiEvent(event));
2025
+ const generation = this.#sessionGeneration;
2026
+ this.#unsubscribe = session.subscribe(event => {
2027
+ if (generation === this.#sessionGeneration && !this.#disposed)
2028
+ this.#handlePiEvent(event);
2029
+ });
1358
2030
  if (this.#extensionUi !== undefined)
1359
2031
  void this.#bindExtensionUiToSession();
1360
2032
  }
@@ -1402,33 +2074,122 @@ export class PiEngineAdapter {
1402
2074
  this.#emitView();
1403
2075
  }
1404
2076
  }
2077
+ // Performance: replacing the transcript rebuilds its index so block lookup stays constant-time.
2078
+ #setTranscript(blocks) {
2079
+ // Invariant: lazy delivery snapshots must freeze before their authoritative source can be removed.
2080
+ if (!this.#eventQueue.seal()) {
2081
+ this.#beginOverload();
2082
+ return;
2083
+ }
2084
+ for (const block of blocks)
2085
+ this.#transcriptImageAssets.retain(block);
2086
+ for (const block of this.#transcript)
2087
+ this.#transcriptImageAssets.release(block);
2088
+ this.#transcript = blocks;
2089
+ this.#transcriptIndex.clear();
2090
+ for (const [index, block] of blocks.entries())
2091
+ this.#transcriptIndex.set(block.id, index);
2092
+ this.#transcriptImageAssets.discardUnowned();
2093
+ this.#transcriptSnapshot = undefined;
2094
+ }
2095
+ #transcriptBlock(id) {
2096
+ const index = this.#transcriptIndex.get(id);
2097
+ return index === undefined ? undefined : this.#transcript[index];
2098
+ }
2099
+ // Invariant: the named work state is the only state a matching end may clear.
2100
+ #enterWorkState(kind, message) {
2101
+ const wasBusy = this.#lifecycle === "busy";
2102
+ this.#statusKind = kind;
2103
+ this.#lifecycle = "busy";
2104
+ this.#status = { ...this.#status, workingMessage: message };
2105
+ if (!wasBusy)
2106
+ this.#emitEvent({ type: "session-lifecycle", lifecycle: "busy", reason: null });
2107
+ this.#emitEvent({ type: "status", status: this.#status });
2108
+ }
2109
+ // Invariant: ending retry or compaction cannot clear a different active work state.
2110
+ #endWorkState(kind) {
2111
+ if (this.#statusKind !== kind)
2112
+ return;
2113
+ if (this.#agentRunActive) {
2114
+ this.#enterWorkState("working", "Working");
2115
+ return;
2116
+ }
2117
+ this.#leaveWorkStates();
2118
+ }
2119
+ #leaveWorkStates() {
2120
+ this.#statusKind = null;
2121
+ this.#lifecycle = "ready";
2122
+ this.#status = { ...this.#status, workingMessage: null };
2123
+ this.#emitEvent({ type: "session-lifecycle", lifecycle: "ready", reason: null });
2124
+ this.#emitEvent({ type: "status", status: this.#status });
2125
+ }
1405
2126
  #handlePiEvent(event) {
2127
+ try {
2128
+ this.#applyPiEvent(event);
2129
+ }
2130
+ finally {
2131
+ this.#transcriptImageAssets.discardUnowned();
2132
+ }
2133
+ }
2134
+ #applyPiEvent(event) {
1406
2135
  if (!isRecord(event) || typeof event.type !== "string")
1407
2136
  return;
2137
+ // Invariant: usage moves at message and lifecycle boundaries, not with stream chunks, so the
2138
+ // two streaming event kinds keep the memo and everything else drops it.
2139
+ if (event.type !== "message_update" && event.type !== "tool_execution_update")
2140
+ this.#usageCache = undefined;
1408
2141
  switch (event.type) {
1409
2142
  case "agent_start":
1410
- this.#lifecycle = "busy";
1411
- this.#status = { ...this.#status, workingMessage: "Working..." };
1412
- this.#emitEvent({ type: "session-lifecycle", lifecycle: "busy", reason: null });
1413
- this.#emitEvent({ type: "status", status: this.#status });
2143
+ this.#agentRunActive = true;
2144
+ this.#agentRunSequence += 1;
2145
+ this.#emitEvent({ type: "agent-run-started" });
2146
+ this.#enterWorkState("working", "Working");
1414
2147
  return;
1415
2148
  case "message_start":
1416
2149
  this.#upsertMessageBlock(event.message, "live");
1417
2150
  return;
1418
2151
  case "message_update": {
1419
- const block = this.#upsertMessageBlock(event.message, "live");
1420
- if (block && isRecord(event.assistantMessageEvent) && typeof event.assistantMessageEvent.delta === "string") {
1421
- this.#upsertTranscriptBlock({
1422
- ...block,
1423
- text: block.text.endsWith(event.assistantMessageEvent.delta)
1424
- ? block.text
1425
- : `${block.text}${event.assistantMessageEvent.delta}`,
1426
- });
2152
+ const delta = isRecord(event.assistantMessageEvent) && typeof event.assistantMessageEvent.delta === "string"
2153
+ ? event.assistantMessageEvent.delta
2154
+ : undefined;
2155
+ // Invariant: the delta is folded in before the block is stored, so a chunk is one update to
2156
+ // one block rather than a store without the delta followed by a store with it.
2157
+ const blocks = this.#messageBlocks(event.message, "live", this.#transcript.length);
2158
+ for (const [index, block] of blocks.entries()) {
2159
+ this.#upsertTranscriptBlock(index === 0 && delta !== undefined && !block.text.endsWith(delta)
2160
+ ? { ...block, text: `${block.text}${delta}` }
2161
+ : block);
1427
2162
  }
1428
2163
  return;
1429
2164
  }
1430
2165
  case "message_end":
1431
2166
  this.#upsertMessageBlock(event.message, "finalized");
2167
+ this.#settleFailedDeclarations(event.message);
2168
+ // Compatibility: preserve the same semantic boundary v2 counted. Transcript block
2169
+ // finalization is intentionally not a substitute: rebuilds, retries,
2170
+ // thinking parts, and tool rows can all finalize independently.
2171
+ if (isRecord(event.message) && event.message.role === "assistant") {
2172
+ this.#assistantResponseSequence += 1;
2173
+ const content = Array.isArray(event.message.content) ? event.message.content : [];
2174
+ const stopReason = stringValue(event.message.stopReason) ?? null;
2175
+ const toolContinuation = stopReason === "toolUse"
2176
+ || content.some(item => isRecord(item) && item.type === "toolCall");
2177
+ const successful = stringValue(event.message.errorMessage) === undefined
2178
+ && stopReason !== "error"
2179
+ && stopReason !== "aborted"
2180
+ && textFromContent(content).trim().length > 0;
2181
+ this.#emitEvent({
2182
+ type: "assistant-message-completed",
2183
+ sessionGeneration: this.#sessionGeneration,
2184
+ runSequence: this.#agentRunSequence,
2185
+ responseSequence: this.#assistantResponseSequence,
2186
+ model: this.#activeModel,
2187
+ assistantMessageCount: this.#transcript.filter(block => block.kind === "assistant").length,
2188
+ successful,
2189
+ stopReason,
2190
+ toolContinuation,
2191
+ });
2192
+ }
1432
2193
  return;
1433
2194
  case "turn_end":
1434
2195
  this.#upsertMessageBlock(event.message, "finalized");
@@ -1438,26 +2199,58 @@ export class PiEngineAdapter {
1438
2199
  }
1439
2200
  return;
1440
2201
  case "tool_execution_start":
2202
+ case "tool_execution_end": {
2203
+ this.#upsertToolExecutionBlock(event);
2204
+ return;
2205
+ }
1441
2206
  case "tool_execution_update":
1442
- case "tool_execution_end":
1443
2207
  this.#upsertToolExecutionBlock(event);
1444
2208
  return;
1445
2209
  case "agent_settled":
1446
2210
  case "agent_end": {
1447
2211
  if (event.type === "agent_end" && event.willRetry === true)
1448
2212
  return;
1449
- const finalMessages = Array.isArray(event.messages) && event.messages.length > 0
1450
- ? event.messages
1451
- : this.#session?.messages ?? [];
1452
- if (finalMessages.length > 0)
2213
+ // Protocol: agent_end is run-local; only settlement reads the complete session scope.
2214
+ const finalMessages = event.type === "agent_settled"
2215
+ ? this.#session?.messages ?? []
2216
+ : Array.isArray(event.messages) ? event.messages : [];
2217
+ if (event.type === "agent_end")
2218
+ this.#mergeRunTranscript(finalMessages);
2219
+ else if (finalMessages.length > 0)
1453
2220
  this.#rebuildTranscript(finalMessages, "finalized");
1454
- else
1455
- this.#transcript = this.#transcript.map(block => block.status === "live" ? { ...block, status: "finalized" } : block);
1456
- this.#lifecycle = "ready";
1457
- this.#status = { ...this.#status, workingMessage: null };
1458
- this.#emitEvent({ type: "session-lifecycle", lifecycle: "ready", reason: null });
1459
- this.#emitEvent({ type: "status", status: this.#status });
2221
+ // Invariant: missing final messages must not erase accumulated content or invent tool outcomes.
2222
+ if (finalMessages.length === 0)
2223
+ this.#setTranscript(this.#transcript.map(block => block.status === "live" && transcriptToolState(block) === undefined
2224
+ ? { ...block, status: "finalized", revision: block.revision + 1 } : block));
2225
+ // Compatibility: ending a turn leaves the working state, as the recorded pinned baseline does, but
2226
+ // it leaves only that state: a compaction or retry being shown outlives the turn
2227
+ // that ended under it. Settlement ends the run, and with it every state — the
2228
+ // engine ends a turn for each continuation it makes and settles once.
2229
+ if (event.type === "agent_settled") {
2230
+ this.#agentRunActive = false;
2231
+ this.#leaveWorkStates();
2232
+ }
2233
+ else if (this.#statusKind === null || this.#statusKind === "working") {
2234
+ this.#leaveWorkStates();
2235
+ }
1460
2236
  this.#emitView();
2237
+ if (event.type === "agent_settled") {
2238
+ const assistants = finalMessages.filter(message => isRecord(message) && message.role === "assistant");
2239
+ const lastAssistant = assistants.at(-1);
2240
+ const successful = lastAssistant !== undefined
2241
+ && stringValue(lastAssistant.errorMessage) === undefined
2242
+ && stringValue(lastAssistant.stopReason) !== "error"
2243
+ && stringValue(lastAssistant.stopReason) !== "aborted";
2244
+ this.#emitEvent({
2245
+ type: "agent-run-settled",
2246
+ sessionGeneration: this.#sessionGeneration,
2247
+ runSequence: this.#agentRunSequence,
2248
+ responseSequence: this.#assistantResponseSequence,
2249
+ model: this.#activeModel,
2250
+ assistantMessageCount: assistants.length,
2251
+ successful,
2252
+ });
2253
+ }
1461
2254
  return;
1462
2255
  }
1463
2256
  case "queue_update": {
@@ -1472,20 +2265,16 @@ export class PiEngineAdapter {
1472
2265
  return;
1473
2266
  }
1474
2267
  case "auto_retry_start":
1475
- this.#lifecycle = "busy";
1476
- this.#status = { ...this.#status, workingMessage: "Retrying…" };
1477
- this.#emitEvent({ type: "status", status: this.#status });
2268
+ this.#enterWorkState("retry", "Retrying");
1478
2269
  return;
1479
2270
  case "auto_retry_end":
1480
- case "compaction_end":
1481
- this.#lifecycle = "ready";
1482
- this.#status = { ...this.#status, workingMessage: null };
1483
- this.#emitEvent({ type: "status", status: this.#status });
2271
+ this.#endWorkState("retry");
1484
2272
  return;
1485
2273
  case "compaction_start":
1486
- this.#lifecycle = "busy";
1487
- this.#status = { ...this.#status, workingMessage: "Compacting…" };
1488
- this.#emitEvent({ type: "status", status: this.#status });
2274
+ this.#enterWorkState("compaction", "Compacting");
2275
+ return;
2276
+ case "compaction_end":
2277
+ this.#endWorkState("compaction");
1489
2278
  return;
1490
2279
  case "thinking_level_changed":
1491
2280
  this.#thinkingLevel = readThinkingLevel(event.level);
@@ -1548,6 +2337,29 @@ export class PiEngineAdapter {
1548
2337
  autoCompactEnabled: this.#runtime?.services.settingsManager?.getCompactionEnabled?.() ?? true,
1549
2338
  };
1550
2339
  }
2340
+ // Invariant: run-local completion may restate messages, but never owns transcript membership.
2341
+ #mergeRunTranscript(messages) {
2342
+ const positions = new Map();
2343
+ const sessionOccurrences = new Map();
2344
+ for (const [index, message] of (this.#session?.messages ?? []).entries()) {
2345
+ const key = messageFallbackKey(message, index);
2346
+ const occurrence = sessionOccurrences.get(key) ?? 0;
2347
+ sessionOccurrences.set(key, occurrence + 1);
2348
+ positions.set(message, { index, occurrence });
2349
+ }
2350
+ const runOccurrences = new Map();
2351
+ for (const [index, message] of messages.entries()) {
2352
+ const key = messageFallbackKey(message, index);
2353
+ const occurrence = runOccurrences.get(key) ?? 0;
2354
+ runOccurrences.set(key, occurrence + 1);
2355
+ const position = positions.get(message) ?? { index, occurrence };
2356
+ for (const block of this.#messageBlocks(message, "finalized", position.index, position.occurrence)) {
2357
+ this.#upsertTranscriptBlock(block);
2358
+ }
2359
+ this.#settleFailedDeclarations(message);
2360
+ }
2361
+ }
2362
+ // Invariant: full replacement is reserved for session-authoritative scope (bind/settlement).
1551
2363
  #rebuildTranscript(messages, status) {
1552
2364
  const blocks = [];
1553
2365
  const blockIndexes = new Map();
@@ -1556,7 +2368,7 @@ export class PiEngineAdapter {
1556
2368
  const key = messageFallbackKey(message, index);
1557
2369
  const occurrence = occurrences.get(key) ?? 0;
1558
2370
  occurrences.set(key, occurrence + 1);
1559
- for (const block of this.#messageBlocks(message, status, index, occurrence)) {
2371
+ for (const block of this.#messageBlocks(message, status, index, occurrence, id => blocks[blockIndexes.get(id) ?? -1] ?? this.#transcriptBlock(id))) {
1560
2372
  const existingIndex = blockIndexes.get(block.id);
1561
2373
  if (existingIndex === undefined) {
1562
2374
  blockIndexes.set(block.id, blocks.length);
@@ -1575,7 +2387,14 @@ export class PiEngineAdapter {
1575
2387
  }
1576
2388
  }
1577
2389
  }
1578
- this.#transcript = blocks;
2390
+ // Performance: an authoritative rebuild restates most of what is already there. Reusing the block
2391
+ // that already says it keeps its revision, and with it the rows the shell rendered for
2392
+ // it — otherwise every turn that ends re-renders the whole session.
2393
+ this.#setTranscript(blocks.map(block => {
2394
+ const existing = this.#transcriptBlock(block.id);
2395
+ const next = existing === undefined ? block : retainCompletedArguments(existing, block);
2396
+ return existing !== undefined && sameBlockContent(existing, next) ? existing : next;
2397
+ }));
1579
2398
  }
1580
2399
  #upsertMessageBlock(message, status) {
1581
2400
  const blocks = this.#messageBlocks(message, status, this.#transcript.length);
@@ -1584,7 +2403,7 @@ export class PiEngineAdapter {
1584
2403
  this.#upsertTranscriptBlock(block);
1585
2404
  return first;
1586
2405
  }
1587
- #messageBlocks(message, status, fallbackIndex, occurrence) {
2406
+ #messageBlocks(message, status, fallbackIndex, occurrence, currentBlock = id => this.#transcriptBlock(id)) {
1588
2407
  if (!isRecord(message) || typeof message.role !== "string")
1589
2408
  return [];
1590
2409
  const baseId = this.#messageBlockId(message, fallbackIndex, status, occurrence);
@@ -1596,7 +2415,12 @@ export class PiEngineAdapter {
1596
2415
  revision: this.#nextBlockRevision(baseId),
1597
2416
  title: "User",
1598
2417
  text: textFromContent(message.content),
1599
- payload: { role: "user", imageCount: contentImageCount(message.content) },
2418
+ imageReferences: this.#imageReferences(message.content, "user"),
2419
+ payload: {
2420
+ role: "user",
2421
+ imageCount: contentImageCount(message.content),
2422
+ timestamp: typeof message.timestamp === "number" && Number.isFinite(message.timestamp) ? message.timestamp : null,
2423
+ },
1600
2424
  }];
1601
2425
  }
1602
2426
  if (message.role === "bashExecution") {
@@ -1660,24 +2484,24 @@ export class PiEngineAdapter {
1660
2484
  : this.#toolBlockIds.get(toolCallId) ?? `tool-${toolCallId}`;
1661
2485
  if (toolCallId !== undefined)
1662
2486
  this.#toolBlockIds.set(toolCallId, blockId);
1663
- const existing = this.#transcript.find(block => block.id === blockId);
2487
+ const existing = currentBlock(blockId);
1664
2488
  const existingPayload = isRecord(existing?.payload) ? existing.payload : undefined;
2489
+ const payload = {
2490
+ role: "toolResult", toolCallId: toolCallId ?? null,
2491
+ toolName: stringValue(message.toolName) ?? stringValue(existingPayload?.toolName) ?? "unknown",
2492
+ argsComplete: true, partialResult: status === "live", isError: message.isError === true,
2493
+ };
2494
+ const rendering = toolRenderingInput({ args: undefined, previousArgs: existing?.toolRendering,
2495
+ result: message, payload, image: part => this.#imageReferences([part], "tool-result")[0] });
1665
2496
  return [{
1666
2497
  id: blockId,
1667
2498
  kind: "tool-result",
1668
2499
  status,
2500
+ toolState: { argsComplete: true, execution: status === "live" ? "running" : message.isError === true ? "failed" : "succeeded" },
1669
2501
  revision: this.#nextBlockRevision(blockId),
1670
2502
  title: stringValue(message.toolName) ?? existing?.title ?? "Tool result",
1671
- text: textFromContent(message.content),
1672
- payload: {
1673
- ...(existingPayload ?? {}),
1674
- role: "toolResult",
1675
- toolCallId: toolCallId ?? null,
1676
- toolName: stringValue(message.toolName) ?? stringValue(existingPayload?.toolName) ?? "unknown",
1677
- argsComplete: true,
1678
- isError: message.isError === true,
1679
- details: jsonSummary(message.details),
1680
- },
2503
+ ...rendering,
2504
+ payload,
1681
2505
  }];
1682
2506
  }
1683
2507
  if (message.role !== "assistant" || !Array.isArray(message.content))
@@ -1707,23 +2531,74 @@ export class PiEngineAdapter {
1707
2531
  const toolCallId = stringValue(item.id) ?? `${baseId}:${blocks.length}`;
1708
2532
  const blockId = this.#toolBlockIds.get(toolCallId) ?? `tool-${toolCallId}`;
1709
2533
  this.#toolBlockIds.set(toolCallId, blockId);
2534
+ const failure = status === "finalized" ? this.#declarationFailure(message) : undefined;
2535
+ const payload = { toolCallId, toolName: stringValue(item.name) ?? "unknown",
2536
+ argsComplete: failure === undefined && status === "finalized",
2537
+ ...(failure === undefined ? {} : { isError: true }) };
2538
+ const rendering = toolRenderingInput({ args: item.arguments, payload,
2539
+ ...(failure === undefined ? {} : { result: { content: [{ type: "text", text: failure.text }] } }),
2540
+ image: () => undefined });
1710
2541
  blocks.push({
1711
2542
  id: blockId,
1712
- kind: "tool-call",
1713
- status,
2543
+ kind: failure === undefined ? "tool-call" : "tool-result",
2544
+ status: failure === undefined ? "live" : "finalized",
2545
+ toolState: {
2546
+ argsComplete: failure === undefined && status === "finalized",
2547
+ execution: failure?.execution ?? "pending",
2548
+ },
1714
2549
  revision: this.#nextBlockRevision(blockId),
1715
2550
  title: stringValue(item.name) ?? "Tool",
1716
- text: jsonSummary(item.arguments).summary,
1717
- payload: {
1718
- toolCallId,
1719
- toolName: stringValue(item.name) ?? "unknown",
1720
- arguments: jsonSummary(item.arguments),
1721
- argsComplete: status === "finalized",
1722
- },
2551
+ ...rendering,
2552
+ text: failure?.text ?? jsonSummary(item.arguments).summary,
2553
+ payload,
1723
2554
  });
1724
2555
  }
1725
2556
  return blocks;
1726
2557
  }
2558
+ #declarationFailure(message) {
2559
+ // Provenance: pinned interactive-mode message_end and history reconstruction settle pending tools.
2560
+ if (message.role !== "assistant")
2561
+ return undefined;
2562
+ if (message.stopReason === "aborted") {
2563
+ const attempts = this.#session?.retryAttempt ?? 0;
2564
+ return { execution: "aborted", text: attempts > 0
2565
+ ? `Aborted after ${attempts} retry attempt${attempts > 1 ? "s" : ""}` : "Operation aborted" };
2566
+ }
2567
+ return message.stopReason === "error"
2568
+ ? { execution: "failed", text: stringValue(message.errorMessage) || "Error" } : undefined;
2569
+ }
2570
+ #settleFailedDeclarations(message) {
2571
+ if (!isRecord(message))
2572
+ return;
2573
+ const failure = this.#declarationFailure(message);
2574
+ if (failure === undefined)
2575
+ return;
2576
+ for (const block of this.#transcript) {
2577
+ const state = transcriptToolState(block);
2578
+ if (state === undefined || state.execution !== "pending" && state.execution !== "running")
2579
+ continue;
2580
+ const payload = { ...(isRecord(block.payload) ? block.payload : {}), partialResult: false, isError: true };
2581
+ this.#upsertTranscriptBlock({
2582
+ ...block, kind: "tool-result", status: "finalized", revision: block.revision + 1,
2583
+ ...toolRenderingInput({ args: undefined, previousArgs: block.toolRendering, payload,
2584
+ result: { content: [{ type: "text", text: failure.text }] }, image: () => undefined }),
2585
+ toolState: { ...state, execution: failure.execution }, payload,
2586
+ });
2587
+ }
2588
+ }
2589
+ #imageReferences(content, source) {
2590
+ if (!Array.isArray(content))
2591
+ return [];
2592
+ const references = [];
2593
+ for (const item of content) {
2594
+ if (references.length >= 16)
2595
+ break;
2596
+ const reference = this.#transcriptImageAssets.reference(item, source);
2597
+ if (reference !== undefined)
2598
+ references.push(reference);
2599
+ }
2600
+ return references;
2601
+ }
1727
2602
  #upsertToolExecutionBlock(event) {
1728
2603
  const toolCallId = stringValue(event.toolCallId);
1729
2604
  if (!toolCallId)
@@ -1731,31 +2606,52 @@ export class PiEngineAdapter {
1731
2606
  const blockId = this.#toolBlockIds.get(toolCallId) ?? `tool-${toolCallId}`;
1732
2607
  this.#toolBlockIds.set(toolCallId, blockId);
1733
2608
  const ended = event.type === "tool_execution_end";
2609
+ const existing = this.#transcriptBlock(blockId);
2610
+ const state = existing === undefined ? undefined : transcriptToolState(existing);
2611
+ // Invariant: duplicate starts cannot blank accumulated output; late events cannot reopen a settled invocation.
2612
+ if (state !== undefined && (state.execution !== "pending" && state.execution !== "running"
2613
+ || event.type === "tool_execution_start" && state.execution === "running"))
2614
+ return;
1734
2615
  const source = ended ? event.result : event.partialResult;
2616
+ const payload = { toolCallId, toolName: stringValue(event.toolName) ?? "unknown",
2617
+ partialResult: event.type === "tool_execution_update", argsComplete: true, isError: event.isError === true };
2618
+ const rendering = toolRenderingInput({ args: event.args, previousArgs: existing?.toolRendering, result: source,
2619
+ payload, image: part => this.#imageReferences([part], "tool-result")[0] });
1735
2620
  this.#upsertTranscriptBlock({
1736
2621
  id: blockId,
1737
2622
  kind: ended ? "tool-result" : "tool-call",
1738
2623
  status: ended ? "finalized" : "live",
2624
+ toolState: { argsComplete: true, execution: ended ? event.isError === true ? "failed" : "succeeded" : "running" },
1739
2625
  revision: this.#nextBlockRevision(blockId),
1740
2626
  title: stringValue(event.toolName) ?? "Tool",
1741
- text: textFromContent(isRecord(source) ? source.content : source),
1742
- payload: {
1743
- toolCallId,
1744
- toolName: stringValue(event.toolName) ?? "unknown",
1745
- arguments: jsonSummary(event.args),
1746
- result: jsonSummary(source),
1747
- partialResult: event.type === "tool_execution_update",
1748
- argsComplete: ended,
1749
- isError: event.isError === true,
1750
- },
2627
+ ...rendering,
2628
+ payload,
1751
2629
  });
1752
2630
  }
1753
2631
  #upsertTranscriptBlock(block) {
1754
- const index = this.#transcript.findIndex(existing => existing.id === block.id);
1755
- if (index >= 0)
2632
+ const index = this.#transcriptIndex.get(block.id);
2633
+ if (index !== undefined) {
2634
+ const existing = this.#transcript[index];
2635
+ // Invariant: argument completion is not execution finality; stale phases still stay rejected.
2636
+ if (existing !== undefined && !acceptsTranscriptUpdate(existing, block))
2637
+ return;
2638
+ if (existing !== undefined)
2639
+ block = retainCompletedArguments(existing, block);
2640
+ // Performance: nothing is emitted for a block that repeats itself, and keeping the
2641
+ // revision keeps the rows it already rendered.
2642
+ if (existing !== undefined && sameBlockContent(existing, block))
2643
+ return;
2644
+ this.#transcriptImageAssets.retain(block);
2645
+ if (existing !== undefined)
2646
+ this.#transcriptImageAssets.release(existing);
1756
2647
  this.#transcript[index] = block;
1757
- else
2648
+ }
2649
+ else {
2650
+ this.#transcriptImageAssets.retain(block);
2651
+ this.#transcriptIndex.set(block.id, this.#transcript.length);
1758
2652
  this.#transcript.push(block);
2653
+ }
2654
+ this.#transcriptSnapshot = undefined;
1759
2655
  this.#emitEvent({ type: "transcript-block", block });
1760
2656
  }
1761
2657
  #messageBlockId(message, fallbackIndex, status, occurrence) {
@@ -1773,7 +2669,7 @@ export class PiEngineAdapter {
1773
2669
  }
1774
2670
  }
1775
2671
  else {
1776
- id = [...cached].reverse().find(candidate => this.#transcript.some(block => block.id === candidate && block.status === "live"));
2672
+ id = [...cached].reverse().find(candidate => this.#transcriptBlock(candidate)?.status === "live");
1777
2673
  if (id === undefined && status === "finalized")
1778
2674
  id = cached.at(-1);
1779
2675
  if (id === undefined) {
@@ -1786,7 +2682,7 @@ export class PiEngineAdapter {
1786
2682
  return id;
1787
2683
  }
1788
2684
  #nextBlockRevision(id) {
1789
- const existing = this.#transcript.find(block => block.id === id);
2685
+ const existing = this.#transcriptBlock(id);
1790
2686
  if (existing)
1791
2687
  return existing.revision + 1;
1792
2688
  this.#nextBlockSequence += 1;
@@ -1817,6 +2713,7 @@ export class PiEngineAdapter {
1817
2713
  this.#emitEvent({ type: "diagnostic", diagnostic });
1818
2714
  }
1819
2715
  #emitView() {
2716
+ this.#usageCache = undefined;
1820
2717
  this.#viewRevision += 1;
1821
2718
  this.#emitEvent({ type: "session-view", view: this.view() });
1822
2719
  }
@@ -1827,43 +2724,127 @@ export class PiEngineAdapter {
1827
2724
  this.#enqueueEvent(event);
1828
2725
  }
1829
2726
  #enqueueEvent(event) {
1830
- const capacity = 1_024;
1831
- if (this.#eventQueue.length >= capacity) {
1832
- const coalescible = this.#eventQueue.findIndex(queued => queued.type === "session-view"
1833
- || queued.type === "status"
1834
- || queued.type === "editor-state"
1835
- || queued.type === "transcript-block");
1836
- if (coalescible >= 0)
1837
- this.#eventQueue.splice(coalescible, 1);
1838
- else
1839
- this.#eventQueue.shift();
1840
- this.#droppedEventCount += 1;
1841
- if (this.#droppedEventCount === 1 || this.#droppedEventCount % 128 === 0) {
1842
- this.#recordDiagnostic("warning", "event-backpressure", `owned UI coalesced ${this.#droppedEventCount} engine events under backpressure`, true);
2727
+ if (this.#overload !== undefined) {
2728
+ if (event.type === "command-outcome" && event.outcome !== "accepted" && this.#pendingCommands.has(event.correlationId)) {
2729
+ this.#reservedOutcomes.set(event.correlationId, { outcome: event.outcome, diagnostic: event.diagnostic });
2730
+ }
2731
+ return;
2732
+ }
2733
+ if (!this.#eventQueue.push(event, this.#sessionGeneration, event.type === "transcript-block"
2734
+ ? this.#blockReconciliation(event.block.id, event.sequence) : undefined)) {
2735
+ this.#beginOverload();
2736
+ if (event.type === "command-outcome" && event.outcome !== "accepted" && this.#pendingCommands.has(event.correlationId)
2737
+ && !this.#reservedOutcomes.has(event.correlationId)) {
2738
+ this.#reservedOutcomes.set(event.correlationId, { outcome: event.outcome, diagnostic: event.diagnostic });
1843
2739
  }
1844
2740
  }
1845
- this.#eventQueue.push(event);
1846
2741
  this.#eventQueueProcessing ??= Promise.resolve().then(() => this.#processEventQueue());
1847
2742
  }
2743
+ // Performance: capture only identity, not the complete block/event retained by an earlier revision.
2744
+ #blockReconciliation(id, sequence) {
2745
+ return () => ({ type: "transcript-block", sessionId: this.#sessionId, sequence, block: this.#transcriptBlock(id) });
2746
+ }
2747
+ #beginOverload() {
2748
+ if (this.#overload !== undefined)
2749
+ return;
2750
+ this.#overloads = Math.min(Number.MAX_SAFE_INTEGER, this.#overloads + 1);
2751
+ this.#deliveryFailed = true;
2752
+ // Concurrency: reserve before cancellation; outcomes produced reentrantly must not enter the saturated queue.
2753
+ this.#overload = Promise.resolve(false);
2754
+ for (const pending of this.#pendingCommands.values())
2755
+ pending.cancel();
2756
+ for (const pending of this.#pendingWorkflows)
2757
+ pending.cancel();
2758
+ const session = this.#session;
2759
+ this.#overload = new Promise(resolve => {
2760
+ const timer = setTimeout(() => resolve(false), 2000);
2761
+ void Promise.resolve().then(() => session?.abort()).then(() => {
2762
+ clearTimeout(timer);
2763
+ resolve(true);
2764
+ }, () => { clearTimeout(timer); resolve(false); });
2765
+ });
2766
+ }
2767
+ #deliver(event) {
2768
+ for (const [listener, subscribedAt] of this.#listeners) {
2769
+ if (event.sequence <= subscribedAt)
2770
+ continue;
2771
+ try {
2772
+ listener(event);
2773
+ }
2774
+ catch (error) {
2775
+ this.#deliveryFailed = true;
2776
+ this.#recordDiagnostic("warning", "event-listener", error instanceof Error ? error.message : String(error), true);
2777
+ }
2778
+ }
2779
+ }
2780
+ async #reconcileOverload() {
2781
+ const cancelled = await this.#overload;
2782
+ const canResume = cancelled === true && this.#runningCommands === 0;
2783
+ this.#admissionStopped = !canResume;
2784
+ this.#unsubscribe?.();
2785
+ this.#unsubscribe = undefined;
2786
+ ++this.#sessionGeneration;
2787
+ this.#agentRunActive = false;
2788
+ this.#statusKind = null;
2789
+ this.#status = { ...this.#status, workingMessage: null };
2790
+ this.#lifecycle = this.#disposed ? "stopped" : canResume ? "ready" : "failed";
2791
+ this.#editor = { ...this.#editor, submitEnabled: canResume && !this.#disposed };
2792
+ this.#viewRevision += 1;
2793
+ // Invariant: overload publishes one authoritative view; settle tool phases before finalizing other live blocks.
2794
+ this.#settleFailedDeclarations({ role: "assistant", stopReason: cancelled ? "aborted" : "error",
2795
+ errorMessage: "Tool result unavailable after UI delivery overload" });
2796
+ this.#setTranscript(this.#transcript.map(block => block.status === "live" ? { ...block, status: "finalized", revision: block.revision + 1 } : block));
2797
+ for (const [correlationId, result] of this.#reservedOutcomes) {
2798
+ this.#deliver(this.#event({ type: "command-outcome", correlationId, ...result }));
2799
+ await new Promise(resolve => setImmediate(resolve));
2800
+ }
2801
+ this.#reservedOutcomes.clear();
2802
+ // Invariant: one out-of-band authoritative reconciliation, not an emergency transcript/event log.
2803
+ this.#deliver(this.#event({ type: "session-view", view: this.view() }));
2804
+ if (this.#disposed)
2805
+ this.#deliver(this.#event({ type: "session-lifecycle", lifecycle: "stopped", reason: null }));
2806
+ if (canResume && !this.#disposed && this.#session !== undefined) {
2807
+ const generation = this.#sessionGeneration;
2808
+ this.#unsubscribe = this.#session.subscribe(event => {
2809
+ if (generation === this.#sessionGeneration && !this.#disposed)
2810
+ this.#handlePiEvent(event);
2811
+ });
2812
+ }
2813
+ this.#overload = undefined;
2814
+ }
1848
2815
  async #processEventQueue() {
1849
2816
  try {
1850
- while (this.#eventQueue.length > 0) {
1851
- const event = this.#eventQueue.shift();
1852
- if (!event)
2817
+ let deliveredSinceYield = 0;
2818
+ while (this.#eventQueue.size > 0) {
2819
+ const pending = this.#eventQueue.shift();
2820
+ if (pending === undefined)
1853
2821
  continue;
1854
- for (const listener of this.#listeners) {
1855
- try {
1856
- listener(event);
1857
- }
1858
- catch (error) {
1859
- this.#recordDiagnostic("warning", "event-listener", error instanceof Error ? error.message : String(error), true);
1860
- }
2822
+ if (pending.generation !== this.#sessionGeneration && pending.event.type !== "command-outcome") {
2823
+ this.#invalidatedEvents = Math.min(Number.MAX_SAFE_INTEGER, this.#invalidatedEvents + 1);
2824
+ pending.release?.();
2825
+ continue;
2826
+ }
2827
+ try {
2828
+ this.#deliver(pending.event);
2829
+ }
2830
+ finally {
2831
+ pending.release?.();
2832
+ }
2833
+ deliveredSinceYield += 1;
2834
+ // Concurrency: a microtask chain runs to exhaustion before the loop turns, so a streaming
2835
+ // burst would hold typed input, pointer reports, and timed indicators until it
2836
+ // drained. Yielding on a macrotask hands those their turn between batches.
2837
+ if (deliveredSinceYield >= EVENT_DELIVERY_BATCH && this.#eventQueue.size > 0) {
2838
+ deliveredSinceYield = 0;
2839
+ await new Promise(resolve => { setImmediate(resolve); });
1861
2840
  }
1862
2841
  }
2842
+ if (this.#overload !== undefined)
2843
+ await this.#reconcileOverload();
1863
2844
  }
1864
2845
  finally {
1865
2846
  this.#eventQueueProcessing = undefined;
1866
- if (this.#eventQueue.length > 0) {
2847
+ if (this.#eventQueue.size > 0) {
1867
2848
  this.#eventQueueProcessing = Promise.resolve().then(() => this.#processEventQueue());
1868
2849
  }
1869
2850
  }
@@ -1896,7 +2877,14 @@ export async function createPiEngineAdapter(options = {}) {
1896
2877
  return adapter;
1897
2878
  }
1898
2879
  async function createDefaultPiRuntime(input) {
1899
- return createPiRuntimeIntegration({ cwd: input.cwd, agentDir: input.agentDir });
2880
+ return createPiRuntimeIntegration({
2881
+ cwd: input.cwd,
2882
+ agentDir: input.agentDir,
2883
+ ...(input.sessionPath === undefined ? {} : { sessionPath: input.sessionPath }),
2884
+ ...(input.sessionSelection === undefined ? {} : { sessionSelection: input.sessionSelection }),
2885
+ ...(input.sessionForkPrompt === undefined ? {} : { sessionForkPrompt: input.sessionForkPrompt }),
2886
+ ...(input.projectTrustPrompt === undefined ? {} : { projectTrustPrompt: input.projectTrustPrompt }),
2887
+ });
1900
2888
  }
1901
2889
  async function checkDefaultPiPackageUpdates(cwd, agentDir, settingsManager) {
1902
2890
  const packageManager = new DefaultPackageManager({ cwd, agentDir, settingsManager });
@@ -2019,11 +3007,11 @@ function pinnedCacheWaste(entries, modelRuntime) {
2019
3007
  function defaultWorkflowHost() {
2020
3008
  return {
2021
3009
  copyText: copyToClipboard,
2022
- async runCommand(command, arguments_) {
2023
- const result = await execFileAsync(command, [...arguments_], { encoding: "utf8" });
3010
+ async runCommand(command, arguments_, options) {
3011
+ const result = await execFileAsync(command, [...arguments_], { encoding: "utf8", signal: options?.signal });
2024
3012
  return { stdout: result.stdout, stderr: result.stderr };
2025
3013
  },
2026
- readChangelog: async () => "No changelog entries found.",
3014
+ readChangelog: readPinnedCommandChangelog,
2027
3015
  };
2028
3016
  }
2029
3017
  function workflowLoginNotification(event) {
@@ -2056,14 +3044,22 @@ function workflowLoginNotification(event) {
2056
3044
  }
2057
3045
  return { type: event.type === "waiting" ? "waiting" : "progress", message };
2058
3046
  }
2059
- function workflowResult(command, outcome, message, detail) {
2060
- return { command, outcome, message, ...(detail === undefined ? {} : { detail }) };
3047
+ function workflowResult(command, outcome, message, detail, messageKind, messages) {
3048
+ return {
3049
+ command,
3050
+ outcome,
3051
+ message,
3052
+ ...(detail === undefined ? {} : { detail }),
3053
+ ...(messageKind === undefined ? {} : { messageKind }),
3054
+ ...(messages === undefined ? {} : { messages: Object.freeze([...messages]) }),
3055
+ };
2061
3056
  }
2062
- function workflowConfirmation(command, message) {
3057
+ function workflowConfirmation(command, message, detail) {
2063
3058
  return {
2064
3059
  command,
2065
3060
  outcome: "requires-confirmation",
2066
3061
  message,
3062
+ ...(detail === undefined ? {} : { detail }),
2067
3063
  selectorTitle: "Confirm",
2068
3064
  options: [
2069
3065
  { id: "yes", label: "Yes" },
@@ -2071,6 +3067,26 @@ function workflowConfirmation(command, message) {
2071
3067
  ],
2072
3068
  };
2073
3069
  }
3070
+ function errorMessage(error, fallback) {
3071
+ return error instanceof Error ? error.message : fallback;
3072
+ }
3073
+ function shareCommandFailureDetail(error) {
3074
+ // Compatibility: Pi reports a failed child's stderr, not execFile's command wrapper.
3075
+ if (isRecord(error) && (typeof error.code === "number" || typeof error.signal === "string") && typeof error.stderr === "string") {
3076
+ return error.stderr.trim() || "Unknown error";
3077
+ }
3078
+ return errorMessage(error, "Unknown error");
3079
+ }
3080
+ function commandMissing(error) {
3081
+ return isRecord(error) && error.code === "ENOENT";
3082
+ }
3083
+ function isAbortError(error) {
3084
+ return isRecord(error) && (error.name === "AbortError" || error.code === "ABORT_ERR");
3085
+ }
3086
+ function shareViewerUrl(gistId) {
3087
+ const baseUrl = process.env.PI_SHARE_VIEWER_URL || "https://pi.dev/session/";
3088
+ return `${baseUrl}#${gistId}`;
3089
+ }
2074
3090
  function workflowOptions(value, idKey, labelKey) {
2075
3091
  if (!Array.isArray(value))
2076
3092
  return [];
@@ -2122,7 +3138,7 @@ function settingKeyForCallback(callback) {
2122
3138
  onAutoCompactChange: "autoCompact", onShowImagesChange: "showImages", onImageWidthCellsChange: "imageWidthCells",
2123
3139
  onAutoResizeImagesChange: "autoResizeImages", onBlockImagesChange: "blockImages", onEnableSkillCommandsChange: "enableSkillCommands",
2124
3140
  onSteeringModeChange: "steeringMode", onFollowUpModeChange: "followUpMode", onTransportChange: "transport",
2125
- onHttpIdleTimeoutMsChange: "httpIdleTimeoutMs", onThemeChange: "theme", onThemePreview: "theme",
3141
+ onHttpIdleTimeoutMsChange: "httpIdleTimeoutMs", onThinkingLevelChange: "thinkingLevel", onThemeChange: "theme", onThemePreview: "theme",
2126
3142
  onHideThinkingBlockChange: "hideThinkingBlock", onMermaidRenderingModeChange: "mermaidRenderingMode",
2127
3143
  onShowCacheMissNoticesChange: "showCacheMissNotices", onCollapseChangelogChange: "collapseChangelog",
2128
3144
  onEnableInstallTelemetryChange: "enableInstallTelemetry", onQuietStartupChange: "quietStartup",
@@ -2245,11 +3261,26 @@ function extensionResourceDiagnostic(index, sourcePath, diagnostic) {
2245
3261
  id: `extension-diagnostic-${index}`,
2246
3262
  sourcePath,
2247
3263
  resolvedPath: null,
3264
+ sourceInfo: null,
2248
3265
  loaded: false,
2249
3266
  hidden: false,
2250
3267
  diagnostic,
2251
3268
  };
2252
3269
  }
3270
+ function extensionSourceSummary(value) {
3271
+ if (!isRecord(value))
3272
+ return null;
3273
+ const source = stringProperty(value, "source");
3274
+ const scope = value.scope;
3275
+ const origin = value.origin;
3276
+ const baseDir = value.baseDir;
3277
+ if (!source
3278
+ || (scope !== "user" && scope !== "project" && scope !== "temporary")
3279
+ || (origin !== "package" && origin !== "top-level")
3280
+ || (baseDir !== undefined && typeof baseDir !== "string"))
3281
+ return null;
3282
+ return { source, scope, origin, baseDir: baseDir ?? null };
3283
+ }
2253
3284
  function collectionResult(value, key) {
2254
3285
  if (!isRecord(value))
2255
3286
  return { values: [], diagnostics: [] };
@@ -2274,10 +3305,91 @@ function stringProperty(value, key) {
2274
3305
  const item = value[key];
2275
3306
  return typeof item === "string" && item.length > 0 ? item : undefined;
2276
3307
  }
3308
+ function isUnknownModel(value) {
3309
+ return isRecord(value)
3310
+ && value.provider === "unknown"
3311
+ && value.id === "unknown"
3312
+ && value.api === "unknown";
3313
+ }
3314
+ function findExactWorkflowModel(reference, models) {
3315
+ const normalized = reference.trim().toLowerCase();
3316
+ const canonical = models.filter(model => {
3317
+ const provider = stringProperty(model, "provider");
3318
+ const id = stringProperty(model, "id");
3319
+ return provider !== undefined && id !== undefined && `${provider}/${id}`.toLowerCase() === normalized;
3320
+ });
3321
+ if (canonical.length === 1)
3322
+ return canonical[0];
3323
+ const bare = models.filter(model => stringProperty(model, "id")?.toLowerCase() === normalized);
3324
+ return bare.length === 1 ? bare[0] : undefined;
3325
+ }
3326
+ /**
3327
+ * Some ecosystem extensions retain a `pi-<name>` slash-command alias beside
3328
+ * their unprefixed command. A1 presents the product-neutral command once while
3329
+ * leaving Pi's runner free to accept the compatibility alias when typed.
3330
+ */
3331
+ function isPiPrefixedCompatibilityAlias(command, commands) {
3332
+ const name = stringProperty(command, "name");
3333
+ if (!name?.startsWith("pi-") || name.length === 3)
3334
+ return false;
3335
+ const canonicalName = name.slice(3);
3336
+ const description = stringProperty(command, "description");
3337
+ const sourcePath = extensionCommandSourcePath(command);
3338
+ return commands.some(candidate => candidate !== command
3339
+ && stringProperty(candidate, "name") === canonicalName
3340
+ && stringProperty(candidate, "description") === description
3341
+ && extensionCommandSourcePath(candidate) === sourcePath);
3342
+ }
3343
+ function extensionCommandSourcePath(command) {
3344
+ return isRecord(command) ? stringProperty(command.sourceInfo, "path") : undefined;
3345
+ }
2277
3346
  function compactResourceLabel(path) {
2278
3347
  const segments = path.replaceAll("\\", "/").split("/").filter(Boolean);
2279
3348
  return segments.at(-1) ?? path;
2280
3349
  }
3350
+ /** A later error may restate a declaration, but cannot make completed arguments incomplete. */
3351
+ function retainCompletedArguments(current, next) {
3352
+ if (current.toolState?.argsComplete !== true || next.toolState === undefined || next.toolState.argsComplete)
3353
+ return next;
3354
+ return { ...next, toolState: { ...next.toolState, argsComplete: true },
3355
+ ...(next.toolRendering === undefined || current.toolRendering === undefined ? {} : {
3356
+ toolRendering: { ...next.toolRendering, arguments: current.toolRendering.arguments },
3357
+ }),
3358
+ payload: { ...(isRecord(next.payload) ? next.payload : {}), argsComplete: true } };
3359
+ }
3360
+ /**
3361
+ * Whether two blocks say the same thing. A block that says what it already said is not a
3362
+ * new revision: the shell renders a block once per revision, so bumping one it did not
3363
+ * need re-renders it for nothing.
3364
+ */
3365
+ function sameBlockContent(left, right) {
3366
+ return left.kind === right.kind
3367
+ && left.status === right.status
3368
+ && left.title === right.title
3369
+ && left.text === right.text
3370
+ && sameValue(left.toolState, right.toolState)
3371
+ && sameValue(left.toolRendering, right.toolRendering)
3372
+ && sameValue(left.payload, right.payload)
3373
+ && sameValue(left.imageReferences ?? [], right.imageReferences ?? []);
3374
+ }
3375
+ function sameValue(left, right) {
3376
+ if (left === right)
3377
+ return true;
3378
+ if (typeof left !== typeof right || left === null || right === null)
3379
+ return false;
3380
+ if (Array.isArray(left) || Array.isArray(right)) {
3381
+ if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length)
3382
+ return false;
3383
+ return left.every((value, index) => sameValue(value, right[index]));
3384
+ }
3385
+ if (typeof left !== "object")
3386
+ return false;
3387
+ const leftKeys = Object.keys(left);
3388
+ const rightRecord = right;
3389
+ if (leftKeys.length !== Object.keys(rightRecord).length)
3390
+ return false;
3391
+ return leftKeys.every(key => Object.hasOwn(rightRecord, key) && sameValue(left[key], rightRecord[key]));
3392
+ }
2281
3393
  function textFromContent(content) {
2282
3394
  if (typeof content === "string")
2283
3395
  return content;