pi-ui-extend 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (420) hide show
  1. package/README.md +307 -0
  2. package/bin/pix.mjs +219 -0
  3. package/dist/app/app.d.ts +96 -0
  4. package/dist/app/app.js +871 -0
  5. package/dist/app/blink-controller.d.ts +23 -0
  6. package/dist/app/blink-controller.js +82 -0
  7. package/dist/app/cli.d.ts +8 -0
  8. package/dist/app/cli.js +83 -0
  9. package/dist/app/clipboard.d.ts +1 -0
  10. package/dist/app/clipboard.js +24 -0
  11. package/dist/app/command-controller.d.ts +18 -0
  12. package/dist/app/command-controller.js +58 -0
  13. package/dist/app/command-host.d.ts +44 -0
  14. package/dist/app/command-host.js +1 -0
  15. package/dist/app/command-model-actions.d.ts +12 -0
  16. package/dist/app/command-model-actions.js +176 -0
  17. package/dist/app/command-navigation-actions.d.ts +19 -0
  18. package/dist/app/command-navigation-actions.js +267 -0
  19. package/dist/app/command-registry.d.ts +32 -0
  20. package/dist/app/command-registry.js +225 -0
  21. package/dist/app/command-runtime.d.ts +5 -0
  22. package/dist/app/command-runtime.js +32 -0
  23. package/dist/app/command-session-actions.d.ts +20 -0
  24. package/dist/app/command-session-actions.js +295 -0
  25. package/dist/app/constants.d.ts +52 -0
  26. package/dist/app/constants.js +103 -0
  27. package/dist/app/conversation-entry-renderer.d.ts +21 -0
  28. package/dist/app/conversation-entry-renderer.js +81 -0
  29. package/dist/app/conversation-shell-renderer.d.ts +5 -0
  30. package/dist/app/conversation-shell-renderer.js +43 -0
  31. package/dist/app/conversation-tool-renderer.d.ts +16 -0
  32. package/dist/app/conversation-tool-renderer.js +216 -0
  33. package/dist/app/conversation-viewport.d.ts +55 -0
  34. package/dist/app/conversation-viewport.js +252 -0
  35. package/dist/app/dcp-stats.d.ts +2 -0
  36. package/dist/app/dcp-stats.js +116 -0
  37. package/dist/app/editor-layout-renderer.d.ts +31 -0
  38. package/dist/app/editor-layout-renderer.js +211 -0
  39. package/dist/app/editor-panels.d.ts +4 -0
  40. package/dist/app/editor-panels.js +130 -0
  41. package/dist/app/extension-actions-controller.d.ts +22 -0
  42. package/dist/app/extension-actions-controller.js +60 -0
  43. package/dist/app/extension-event-bus.d.ts +3 -0
  44. package/dist/app/extension-event-bus.js +23 -0
  45. package/dist/app/extension-ui-controller.d.ts +57 -0
  46. package/dist/app/extension-ui-controller.js +532 -0
  47. package/dist/app/file-link-opener.d.ts +2 -0
  48. package/dist/app/file-link-opener.js +66 -0
  49. package/dist/app/file-links.d.ts +10 -0
  50. package/dist/app/file-links.js +117 -0
  51. package/dist/app/guards.d.ts +3 -0
  52. package/dist/app/guards.js +9 -0
  53. package/dist/app/icons.d.ts +37 -0
  54. package/dist/app/icons.js +97 -0
  55. package/dist/app/id.d.ts +1 -0
  56. package/dist/app/id.js +4 -0
  57. package/dist/app/image-click-targets.d.ts +5 -0
  58. package/dist/app/image-click-targets.js +32 -0
  59. package/dist/app/image-opener.d.ts +2 -0
  60. package/dist/app/image-opener.js +64 -0
  61. package/dist/app/input-action-controller.d.ts +47 -0
  62. package/dist/app/input-action-controller.js +209 -0
  63. package/dist/app/input-controller.d.ts +60 -0
  64. package/dist/app/input-controller.js +425 -0
  65. package/dist/app/input-paste-handler.d.ts +27 -0
  66. package/dist/app/input-paste-handler.js +146 -0
  67. package/dist/app/menu-items-controller.d.ts +32 -0
  68. package/dist/app/menu-items-controller.js +182 -0
  69. package/dist/app/message-content.d.ts +8 -0
  70. package/dist/app/message-content.js +115 -0
  71. package/dist/app/model-ref.d.ts +13 -0
  72. package/dist/app/model-ref.js +50 -0
  73. package/dist/app/model-usage-controller.d.ts +35 -0
  74. package/dist/app/model-usage-controller.js +99 -0
  75. package/dist/app/model-usage-status.d.ts +125 -0
  76. package/dist/app/model-usage-status.js +749 -0
  77. package/dist/app/mouse-controller.d.ts +182 -0
  78. package/dist/app/mouse-controller.js +968 -0
  79. package/dist/app/native-modifiers.d.ts +3 -0
  80. package/dist/app/native-modifiers.js +60 -0
  81. package/dist/app/nerd-font-controller.d.ts +11 -0
  82. package/dist/app/nerd-font-controller.js +90 -0
  83. package/dist/app/popup-action-controller.d.ts +44 -0
  84. package/dist/app/popup-action-controller.js +278 -0
  85. package/dist/app/popup-menu-controller.d.ts +183 -0
  86. package/dist/app/popup-menu-controller.js +1070 -0
  87. package/dist/app/prompt-enhancer-controller.d.ts +40 -0
  88. package/dist/app/prompt-enhancer-controller.js +215 -0
  89. package/dist/app/queued-message-controller.d.ts +62 -0
  90. package/dist/app/queued-message-controller.js +377 -0
  91. package/dist/app/render-controller.d.ts +41 -0
  92. package/dist/app/render-controller.js +378 -0
  93. package/dist/app/render-text.d.ts +19 -0
  94. package/dist/app/render-text.js +67 -0
  95. package/dist/app/request-history.d.ts +23 -0
  96. package/dist/app/request-history.js +131 -0
  97. package/dist/app/runtime.d.ts +39 -0
  98. package/dist/app/runtime.js +195 -0
  99. package/dist/app/screen-selection.d.ts +6 -0
  100. package/dist/app/screen-selection.js +9 -0
  101. package/dist/app/screen-styler.d.ts +34 -0
  102. package/dist/app/screen-styler.js +168 -0
  103. package/dist/app/scroll-controller.d.ts +51 -0
  104. package/dist/app/scroll-controller.js +207 -0
  105. package/dist/app/session-event-controller.d.ts +69 -0
  106. package/dist/app/session-event-controller.js +338 -0
  107. package/dist/app/session-history.d.ts +23 -0
  108. package/dist/app/session-history.js +164 -0
  109. package/dist/app/session-lifecycle-controller.d.ts +55 -0
  110. package/dist/app/session-lifecycle-controller.js +116 -0
  111. package/dist/app/session-search.d.ts +24 -0
  112. package/dist/app/session-search.js +215 -0
  113. package/dist/app/shell-command.d.ts +27 -0
  114. package/dist/app/shell-command.js +176 -0
  115. package/dist/app/shell-controller.d.ts +28 -0
  116. package/dist/app/shell-controller.js +124 -0
  117. package/dist/app/slash-commands.d.ts +6 -0
  118. package/dist/app/slash-commands.js +75 -0
  119. package/dist/app/startup-checks.d.ts +8 -0
  120. package/dist/app/startup-checks.js +59 -0
  121. package/dist/app/startup-info.d.ts +3 -0
  122. package/dist/app/startup-info.js +176 -0
  123. package/dist/app/status-controller.d.ts +35 -0
  124. package/dist/app/status-controller.js +105 -0
  125. package/dist/app/status-line-renderer.d.ts +68 -0
  126. package/dist/app/status-line-renderer.js +453 -0
  127. package/dist/app/subagents-files.d.ts +10 -0
  128. package/dist/app/subagents-files.js +193 -0
  129. package/dist/app/subagents-model.d.ts +23 -0
  130. package/dist/app/subagents-model.js +224 -0
  131. package/dist/app/subagents-widget-controller.d.ts +43 -0
  132. package/dist/app/subagents-widget-controller.js +311 -0
  133. package/dist/app/tab-line-renderer.d.ts +26 -0
  134. package/dist/app/tab-line-renderer.js +222 -0
  135. package/dist/app/tabs-controller.d.ts +100 -0
  136. package/dist/app/tabs-controller.js +885 -0
  137. package/dist/app/terminal-controller.d.ts +40 -0
  138. package/dist/app/terminal-controller.js +135 -0
  139. package/dist/app/terminal-edit-shortcuts.d.ts +23 -0
  140. package/dist/app/terminal-edit-shortcuts.js +138 -0
  141. package/dist/app/terminal-output-buffer.d.ts +20 -0
  142. package/dist/app/terminal-output-buffer.js +52 -0
  143. package/dist/app/toast-controller.d.ts +13 -0
  144. package/dist/app/toast-controller.js +40 -0
  145. package/dist/app/toast-renderer.d.ts +9 -0
  146. package/dist/app/toast-renderer.js +79 -0
  147. package/dist/app/todo-model.d.ts +22 -0
  148. package/dist/app/todo-model.js +179 -0
  149. package/dist/app/todo-widget-controller.d.ts +21 -0
  150. package/dist/app/todo-widget-controller.js +59 -0
  151. package/dist/app/tool-block-renderer.d.ts +26 -0
  152. package/dist/app/tool-block-renderer.js +439 -0
  153. package/dist/app/types.d.ts +550 -0
  154. package/dist/app/types.js +1 -0
  155. package/dist/app/update.d.ts +36 -0
  156. package/dist/app/update.js +315 -0
  157. package/dist/app/voice-controller.d.ts +52 -0
  158. package/dist/app/voice-controller.js +600 -0
  159. package/dist/app/workspace-actions-controller.d.ts +40 -0
  160. package/dist/app/workspace-actions-controller.js +215 -0
  161. package/dist/app/workspace-undo.d.ts +44 -0
  162. package/dist/app/workspace-undo.js +215 -0
  163. package/dist/config.d.ts +62 -0
  164. package/dist/config.js +527 -0
  165. package/dist/context-progress-bar.d.ts +17 -0
  166. package/dist/context-progress-bar.js +48 -0
  167. package/dist/default-pix-config.d.ts +1 -0
  168. package/dist/default-pix-config.js +375 -0
  169. package/dist/fuzzy.d.ts +23 -0
  170. package/dist/fuzzy.js +165 -0
  171. package/dist/input-editor-files.d.ts +15 -0
  172. package/dist/input-editor-files.js +62 -0
  173. package/dist/input-editor.d.ts +186 -0
  174. package/dist/input-editor.js +835 -0
  175. package/dist/main.d.ts +1 -0
  176. package/dist/main.js +12 -0
  177. package/dist/markdown-format.d.ts +22 -0
  178. package/dist/markdown-format.js +542 -0
  179. package/dist/sdk.d.ts +3 -0
  180. package/dist/sdk.js +1 -0
  181. package/dist/syntax-highlight.d.ts +22 -0
  182. package/dist/syntax-highlight.js +528 -0
  183. package/dist/terminal-width.d.ts +6 -0
  184. package/dist/terminal-width.js +245 -0
  185. package/dist/theme.d.ts +56 -0
  186. package/dist/theme.js +118 -0
  187. package/dist/tool-renderers/apply-patch.d.ts +2 -0
  188. package/dist/tool-renderers/apply-patch.js +36 -0
  189. package/dist/tool-renderers/ast.d.ts +2 -0
  190. package/dist/tool-renderers/ast.js +5 -0
  191. package/dist/tool-renderers/compress.d.ts +2 -0
  192. package/dist/tool-renderers/compress.js +126 -0
  193. package/dist/tool-renderers/index.d.ts +3 -0
  194. package/dist/tool-renderers/index.js +44 -0
  195. package/dist/tool-renderers/question.d.ts +2 -0
  196. package/dist/tool-renderers/question.js +88 -0
  197. package/dist/tool-renderers/read.d.ts +2 -0
  198. package/dist/tool-renderers/read.js +36 -0
  199. package/dist/tool-renderers/repo.d.ts +2 -0
  200. package/dist/tool-renderers/repo.js +13 -0
  201. package/dist/tool-renderers/shell.d.ts +3 -0
  202. package/dist/tool-renderers/shell.js +27 -0
  203. package/dist/tool-renderers/skill.d.ts +2 -0
  204. package/dist/tool-renderers/skill.js +132 -0
  205. package/dist/tool-renderers/subagents.d.ts +2 -0
  206. package/dist/tool-renderers/subagents.js +51 -0
  207. package/dist/tool-renderers/todo.d.ts +2 -0
  208. package/dist/tool-renderers/todo.js +9 -0
  209. package/dist/tool-renderers/types.d.ts +42 -0
  210. package/dist/tool-renderers/types.js +1 -0
  211. package/dist/tool-renderers/utils.d.ts +31 -0
  212. package/dist/tool-renderers/utils.js +230 -0
  213. package/dist/tool-renderers/web.d.ts +3 -0
  214. package/dist/tool-renderers/web.js +9 -0
  215. package/dist/tool-renderers/write.d.ts +2 -0
  216. package/dist/tool-renderers/write.js +42 -0
  217. package/dist/ui.d.ts +56 -0
  218. package/dist/ui.js +94 -0
  219. package/docs/release.md +81 -0
  220. package/extensions/question/contract.ts +100 -0
  221. package/extensions/question/index.ts +34 -0
  222. package/extensions/question/render.ts +28 -0
  223. package/extensions/question/result.ts +86 -0
  224. package/extensions/question/tool-description.ts +11 -0
  225. package/extensions/question/tui.ts +629 -0
  226. package/extensions/question/types.ts +123 -0
  227. package/extensions/session-title/config.ts +169 -0
  228. package/extensions/session-title/index.ts +459 -0
  229. package/extensions/terminal-bell/index.ts +315 -0
  230. package/external/pi-tools-suite/README.md +242 -0
  231. package/external/pi-tools-suite/index.ts +1 -0
  232. package/external/pi-tools-suite/licenses/ollama-pi-web-search.MIT +21 -0
  233. package/external/pi-tools-suite/licenses/opencode-mystatus-MIT.txt +21 -0
  234. package/external/pi-tools-suite/package.json +53 -0
  235. package/external/pi-tools-suite/src/antigravity-auth/auth-store.ts +194 -0
  236. package/external/pi-tools-suite/src/antigravity-auth/commands.ts +80 -0
  237. package/external/pi-tools-suite/src/antigravity-auth/constants.ts +26 -0
  238. package/external/pi-tools-suite/src/antigravity-auth/headers.ts +20 -0
  239. package/external/pi-tools-suite/src/antigravity-auth/index.ts +104 -0
  240. package/external/pi-tools-suite/src/antigravity-auth/models.ts +86 -0
  241. package/external/pi-tools-suite/src/antigravity-auth/oauth.ts +305 -0
  242. package/external/pi-tools-suite/src/antigravity-auth/payload.ts +423 -0
  243. package/external/pi-tools-suite/src/antigravity-auth/status.ts +78 -0
  244. package/external/pi-tools-suite/src/antigravity-auth/stream.ts +302 -0
  245. package/external/pi-tools-suite/src/antigravity-auth/types.ts +113 -0
  246. package/external/pi-tools-suite/src/ast-grep/index.ts +6 -0
  247. package/external/pi-tools-suite/src/ast-grep/render.ts +70 -0
  248. package/external/pi-tools-suite/src/ast-grep/schema.ts +109 -0
  249. package/external/pi-tools-suite/src/ast-grep/tool.ts +345 -0
  250. package/external/pi-tools-suite/src/ast-grep/types.ts +55 -0
  251. package/external/pi-tools-suite/src/ast-grep/utils.ts +65 -0
  252. package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +222 -0
  253. package/external/pi-tools-suite/src/async-subagents/commands.ts +518 -0
  254. package/external/pi-tools-suite/src/async-subagents/constants.ts +11 -0
  255. package/external/pi-tools-suite/src/async-subagents/core/agent-strategy.ts +74 -0
  256. package/external/pi-tools-suite/src/async-subagents/core/attachment-bridge.ts +133 -0
  257. package/external/pi-tools-suite/src/async-subagents/core/cleanup.ts +66 -0
  258. package/external/pi-tools-suite/src/async-subagents/core/concurrency.ts +90 -0
  259. package/external/pi-tools-suite/src/async-subagents/core/config.ts +819 -0
  260. package/external/pi-tools-suite/src/async-subagents/core/log-limits.ts +166 -0
  261. package/external/pi-tools-suite/src/async-subagents/core/model-fallback.ts +133 -0
  262. package/external/pi-tools-suite/src/async-subagents/core/paths.ts +47 -0
  263. package/external/pi-tools-suite/src/async-subagents/core/pi-invocation.ts +35 -0
  264. package/external/pi-tools-suite/src/async-subagents/core/presets.ts +67 -0
  265. package/external/pi-tools-suite/src/async-subagents/core/process.ts +15 -0
  266. package/external/pi-tools-suite/src/async-subagents/core/prompt.ts +66 -0
  267. package/external/pi-tools-suite/src/async-subagents/core/registry.ts +224 -0
  268. package/external/pi-tools-suite/src/async-subagents/core/retry.ts +191 -0
  269. package/external/pi-tools-suite/src/async-subagents/core/routing.ts +259 -0
  270. package/external/pi-tools-suite/src/async-subagents/core/sessions.ts +138 -0
  271. package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +688 -0
  272. package/external/pi-tools-suite/src/async-subagents/core/state.ts +281 -0
  273. package/external/pi-tools-suite/src/async-subagents/core/stop.ts +131 -0
  274. package/external/pi-tools-suite/src/async-subagents/core/structured-result.ts +237 -0
  275. package/external/pi-tools-suite/src/async-subagents/core/tool-guard.ts +34 -0
  276. package/external/pi-tools-suite/src/async-subagents/core/types.ts +150 -0
  277. package/external/pi-tools-suite/src/async-subagents/core/ultrawork-auto.ts +184 -0
  278. package/external/pi-tools-suite/src/async-subagents/core/utils.ts +11 -0
  279. package/external/pi-tools-suite/src/async-subagents/format.ts +41 -0
  280. package/external/pi-tools-suite/src/async-subagents/index.ts +422 -0
  281. package/external/pi-tools-suite/src/async-subagents/lib.ts +88 -0
  282. package/external/pi-tools-suite/src/async-subagents/live.ts +10 -0
  283. package/external/pi-tools-suite/src/async-subagents/polling.ts +83 -0
  284. package/external/pi-tools-suite/src/async-subagents/render.ts +230 -0
  285. package/external/pi-tools-suite/src/async-subagents/subagent-overlay.ts +77 -0
  286. package/external/pi-tools-suite/src/async-subagents/tasks.ts +120 -0
  287. package/external/pi-tools-suite/src/async-subagents/tools/cleanup.ts +99 -0
  288. package/external/pi-tools-suite/src/async-subagents/tools/result.ts +179 -0
  289. package/external/pi-tools-suite/src/async-subagents/tools/spawn.ts +372 -0
  290. package/external/pi-tools-suite/src/async-subagents/tools/status.ts +60 -0
  291. package/external/pi-tools-suite/src/async-subagents/tools/stop.ts +79 -0
  292. package/external/pi-tools-suite/src/async-subagents/tools/subagents.ts +152 -0
  293. package/external/pi-tools-suite/src/async-subagents/tools/wait.ts +67 -0
  294. package/external/pi-tools-suite/src/async-subagents/types.ts +45 -0
  295. package/external/pi-tools-suite/src/async-subagents/ui.ts +5 -0
  296. package/external/pi-tools-suite/src/compress/commands.ts +440 -0
  297. package/external/pi-tools-suite/src/compress/compress-tool.ts +368 -0
  298. package/external/pi-tools-suite/src/compress/compression-blocks.ts +524 -0
  299. package/external/pi-tools-suite/src/compress/config.ts +310 -0
  300. package/external/pi-tools-suite/src/compress/dcp-tui-filter.ts +498 -0
  301. package/external/pi-tools-suite/src/compress/index.ts +397 -0
  302. package/external/pi-tools-suite/src/compress/prompts.ts +269 -0
  303. package/external/pi-tools-suite/src/compress/pruner-candidates.ts +176 -0
  304. package/external/pi-tools-suite/src/compress/pruner-compression-blocks.ts +260 -0
  305. package/external/pi-tools-suite/src/compress/pruner-message-ids.ts +147 -0
  306. package/external/pi-tools-suite/src/compress/pruner-metadata.ts +268 -0
  307. package/external/pi-tools-suite/src/compress/pruner-nudge.ts +315 -0
  308. package/external/pi-tools-suite/src/compress/pruner-tools.ts +263 -0
  309. package/external/pi-tools-suite/src/compress/pruner-types.ts +25 -0
  310. package/external/pi-tools-suite/src/compress/pruner.ts +92 -0
  311. package/external/pi-tools-suite/src/compress/state.ts +486 -0
  312. package/external/pi-tools-suite/src/compress/ui.ts +308 -0
  313. package/external/pi-tools-suite/src/config.ts +176 -0
  314. package/external/pi-tools-suite/src/context-usage.ts +31 -0
  315. package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +355 -0
  316. package/external/pi-tools-suite/src/index.ts +46 -0
  317. package/external/pi-tools-suite/src/lib/lsp.ts +62 -0
  318. package/external/pi-tools-suite/src/lib/project.ts +42 -0
  319. package/external/pi-tools-suite/src/lib/tool-args.ts +137 -0
  320. package/external/pi-tools-suite/src/lsp/_shared/config.ts +156 -0
  321. package/external/pi-tools-suite/src/lsp/_shared/glob.ts +60 -0
  322. package/external/pi-tools-suite/src/lsp/_shared/output.ts +102 -0
  323. package/external/pi-tools-suite/src/lsp/_shared/paths.ts +138 -0
  324. package/external/pi-tools-suite/src/lsp/_shared/runner.ts +64 -0
  325. package/external/pi-tools-suite/src/lsp/_shared/template.ts +23 -0
  326. package/external/pi-tools-suite/src/lsp/_shared/trust.ts +116 -0
  327. package/external/pi-tools-suite/src/lsp/_shared/types.ts +98 -0
  328. package/external/pi-tools-suite/src/lsp/async.ts +29 -0
  329. package/external/pi-tools-suite/src/lsp/child-process.ts +81 -0
  330. package/external/pi-tools-suite/src/lsp/client.ts +340 -0
  331. package/external/pi-tools-suite/src/lsp/constants.ts +9 -0
  332. package/external/pi-tools-suite/src/lsp/diagnostics-store.ts +64 -0
  333. package/external/pi-tools-suite/src/lsp/documents.ts +24 -0
  334. package/external/pi-tools-suite/src/lsp/index.ts +31 -0
  335. package/external/pi-tools-suite/src/lsp/lsp-utils.ts +37 -0
  336. package/external/pi-tools-suite/src/lsp/manager.ts +190 -0
  337. package/external/pi-tools-suite/src/lsp/mutation-events.ts +78 -0
  338. package/external/pi-tools-suite/src/lsp/renderer.ts +1 -0
  339. package/external/pi-tools-suite/src/lsp/tool-result.ts +6 -0
  340. package/external/pi-tools-suite/src/lsp/tsserver.ts +107 -0
  341. package/external/pi-tools-suite/src/lsp/types.ts +15 -0
  342. package/external/pi-tools-suite/src/model-tools/apply-patch.ts +590 -0
  343. package/external/pi-tools-suite/src/model-tools/index.ts +430 -0
  344. package/external/pi-tools-suite/src/model-tools/path-utils.ts +6 -0
  345. package/external/pi-tools-suite/src/model-tools/tool-args.ts +11 -0
  346. package/external/pi-tools-suite/src/prompt-commands/index.ts +349 -0
  347. package/external/pi-tools-suite/src/repo-discovery/index.ts +384 -0
  348. package/external/pi-tools-suite/src/repo-discovery/project.ts +7 -0
  349. package/external/pi-tools-suite/src/startup-section.ts +13 -0
  350. package/external/pi-tools-suite/src/terminal-bell/index.ts +309 -0
  351. package/external/pi-tools-suite/src/todo/index.ts +201 -0
  352. package/external/pi-tools-suite/src/todo/state/auto-clear.ts +13 -0
  353. package/external/pi-tools-suite/src/todo/state/invariants.ts +21 -0
  354. package/external/pi-tools-suite/src/todo/state/persistence.ts +94 -0
  355. package/external/pi-tools-suite/src/todo/state/replay.ts +38 -0
  356. package/external/pi-tools-suite/src/todo/state/selectors.ts +49 -0
  357. package/external/pi-tools-suite/src/todo/state/state-reducer.ts +362 -0
  358. package/external/pi-tools-suite/src/todo/state/state.ts +18 -0
  359. package/external/pi-tools-suite/src/todo/state/store.ts +52 -0
  360. package/external/pi-tools-suite/src/todo/state/task-graph.ts +57 -0
  361. package/external/pi-tools-suite/src/todo/todo.ts +487 -0
  362. package/external/pi-tools-suite/src/todo/tool/response-envelope.ts +143 -0
  363. package/external/pi-tools-suite/src/todo/tool/types.ts +188 -0
  364. package/external/pi-tools-suite/src/todo/view/format.ts +18 -0
  365. package/external/pi-tools-suite/src/todo/view/labels.ts +13 -0
  366. package/external/pi-tools-suite/src/tool-descriptions.ts +369 -0
  367. package/external/pi-tools-suite/src/usage/index.ts +152 -0
  368. package/external/pi-tools-suite/src/usage/lib/copilot.ts +535 -0
  369. package/external/pi-tools-suite/src/usage/lib/google.ts +478 -0
  370. package/external/pi-tools-suite/src/usage/lib/openai.ts +268 -0
  371. package/external/pi-tools-suite/src/usage/lib/types.ts +114 -0
  372. package/external/pi-tools-suite/src/usage/lib/utils.ts +134 -0
  373. package/external/pi-tools-suite/src/usage/lib/zhipu.ts +228 -0
  374. package/external/pi-tools-suite/src/web-search/index.ts +397 -0
  375. package/package.json +89 -0
  376. package/skills/context7/SKILL.md +69 -0
  377. package/skills/context7/scripts/context7.sh +73 -0
  378. package/skills/handoff/SKILL.md +15 -0
  379. package/skills/pdf/LICENSE.txt +30 -0
  380. package/skills/pdf/SKILL.md +314 -0
  381. package/skills/pdf/forms.md +294 -0
  382. package/skills/pdf/reference.md +612 -0
  383. package/skills/pdf/scripts/check_bounding_boxes.py +65 -0
  384. package/skills/pdf/scripts/check_fillable_fields.py +11 -0
  385. package/skills/pdf/scripts/convert_pdf_to_images.py +33 -0
  386. package/skills/pdf/scripts/create_validation_image.py +37 -0
  387. package/skills/pdf/scripts/extract_form_field_info.py +122 -0
  388. package/skills/pdf/scripts/extract_form_structure.py +115 -0
  389. package/skills/pdf/scripts/fill_fillable_fields.py +98 -0
  390. package/skills/pdf/scripts/fill_pdf_form_with_annotations.py +107 -0
  391. package/skills/playwright-cli/SKILL.md +388 -0
  392. package/skills/playwright-cli/references/element-attributes.md +23 -0
  393. package/skills/playwright-cli/references/playwright-tests.md +39 -0
  394. package/skills/playwright-cli/references/request-mocking.md +87 -0
  395. package/skills/playwright-cli/references/running-code.md +241 -0
  396. package/skills/playwright-cli/references/session-management.md +225 -0
  397. package/skills/playwright-cli/references/spec-driven-testing.md +305 -0
  398. package/skills/playwright-cli/references/storage-state.md +275 -0
  399. package/skills/playwright-cli/references/test-generation.md +134 -0
  400. package/skills/playwright-cli/references/tracing.md +139 -0
  401. package/skills/playwright-cli/references/video-recording.md +143 -0
  402. package/skills/simplify/SKILL.md +51 -0
  403. package/skills/skill-creator/LICENSE.txt +202 -0
  404. package/skills/skill-creator/SKILL.md +485 -0
  405. package/skills/skill-creator/agents/analyzer.md +274 -0
  406. package/skills/skill-creator/agents/comparator.md +202 -0
  407. package/skills/skill-creator/agents/grader.md +223 -0
  408. package/skills/skill-creator/assets/eval_review.html +146 -0
  409. package/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  410. package/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  411. package/skills/skill-creator/references/schemas.md +430 -0
  412. package/skills/skill-creator/scripts/__init__.py +0 -0
  413. package/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  414. package/skills/skill-creator/scripts/generate_report.py +326 -0
  415. package/skills/skill-creator/scripts/improve_description.py +247 -0
  416. package/skills/skill-creator/scripts/package_skill.py +136 -0
  417. package/skills/skill-creator/scripts/quick_validate.py +103 -0
  418. package/skills/skill-creator/scripts/run_eval.py +310 -0
  419. package/skills/skill-creator/scripts/run_loop.py +328 -0
  420. package/skills/skill-creator/scripts/utils.py +47 -0
@@ -0,0 +1,885 @@
1
+ import { createHash } from "node:crypto";
2
+ import { existsSync } from "node:fs";
3
+ import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
4
+ import { dirname, join, resolve } from "node:path";
5
+ import { getAgentDir, SessionManager, } from "@earendil-works/pi-coding-agent";
6
+ import { isRecord } from "./guards.js";
7
+ import { createId } from "./id.js";
8
+ import { createStartupInfoMessage, isEmptyStartupSession } from "./startup-info.js";
9
+ import { tabPanelRows } from "./tab-line-renderer.js";
10
+ const TAB_STATE_VERSION = 2;
11
+ const MAX_RESTORED_TABS = 8;
12
+ const TAB_ATTENTION_BLINK_KEY = "tab-attention";
13
+ export class AppTabsController {
14
+ host;
15
+ tabItems = [];
16
+ runtimesByTabId = new Map();
17
+ runtimeSubscriptionsByTabId = new Map();
18
+ inputStatesByTabId = new Map();
19
+ activeTabId;
20
+ pendingActiveTabId;
21
+ historyLoadGeneration = 0;
22
+ restored = false;
23
+ constructor(host) {
24
+ this.host = host;
25
+ }
26
+ tabs() {
27
+ if (!this.pendingActiveTabId)
28
+ this.syncActiveTabFromRuntime({ save: false });
29
+ const activeTabId = this.pendingActiveTabId ?? this.activeTabId;
30
+ return this.tabItems.map((tab) => ({
31
+ ...tab,
32
+ status: tab.id === activeTabId ? "active" : "waiting",
33
+ activity: this.tabActivity(tab),
34
+ ...(tab.attention === undefined ? {} : { attention: tab.attention }),
35
+ ...(tab.attention === undefined ? {} : { attentionVisible: this.host.blinkController.visible(TAB_ATTENTION_BLINK_KEY, true) }),
36
+ }));
37
+ }
38
+ isSwitching() {
39
+ return this.pendingActiveTabId !== undefined;
40
+ }
41
+ markTerminalBellAttention(sessionPath) {
42
+ if (!sessionPath)
43
+ return;
44
+ const tab = this.findTabBySessionPath(sessionPath);
45
+ if (!tab || tab.id === this.activeTabId)
46
+ return;
47
+ tab.attention = "terminal-bell";
48
+ this.startAttentionBlink();
49
+ this.host.render();
50
+ }
51
+ tabPanelRows(terminalRows) {
52
+ if (!this.pendingActiveTabId)
53
+ this.syncActiveTabFromRuntime({ save: false });
54
+ return tabPanelRows(true, terminalRows, this.tabItems.length);
55
+ }
56
+ activeInputTabId() {
57
+ if (!this.pendingActiveTabId)
58
+ this.syncActiveTabFromRuntime({ save: false });
59
+ return this.activeTabId;
60
+ }
61
+ inputStateForTab(tabId) {
62
+ if (!tabId)
63
+ return this.host.captureInputState();
64
+ if (tabId === this.activeTabId && this.pendingActiveTabId === undefined)
65
+ return this.host.captureInputState();
66
+ return this.inputStatesByTabId.get(tabId);
67
+ }
68
+ async setInputStateForTab(tabId, state) {
69
+ const nextState = {
70
+ text: state.text,
71
+ cursor: Math.max(0, Math.min(state.text.length, Math.trunc(state.cursor))),
72
+ };
73
+ const targetTabId = tabId ?? this.activeTabId;
74
+ if (targetTabId) {
75
+ if (nextState.text.length > 0)
76
+ this.inputStatesByTabId.set(targetTabId, nextState);
77
+ else
78
+ this.inputStatesByTabId.delete(targetTabId);
79
+ }
80
+ if (!tabId || (tabId === this.activeTabId && this.pendingActiveTabId === undefined)) {
81
+ this.host.restoreInputState(nextState);
82
+ }
83
+ await this.saveTabs();
84
+ }
85
+ async disposeInactiveRuntimes(disposeRuntime = this.host.disposeRuntime) {
86
+ await this.saveInputStateForQuit();
87
+ const activeRuntime = this.host.runtime();
88
+ const disposed = new Set();
89
+ for (const runtime of this.runtimesByTabId.values()) {
90
+ if (runtime === activeRuntime || disposed.has(runtime))
91
+ continue;
92
+ disposed.add(runtime);
93
+ await disposeRuntime(runtime);
94
+ }
95
+ }
96
+ async saveInputStateForQuit() {
97
+ this.syncActiveTabFromRuntime({ save: false });
98
+ this.storeActiveInputState();
99
+ await this.saveTabs();
100
+ }
101
+ syncActiveTabFromRuntime(options = {}) {
102
+ if (this.pendingActiveTabId && options.force !== true)
103
+ return;
104
+ const session = this.host.runtime()?.session;
105
+ if (!session)
106
+ return;
107
+ const sessionPath = this.sessionPath(session);
108
+ const active = this.activeTab();
109
+ const existing = sessionPath ? this.findTabBySessionPath(sessionPath, active ? { excludeTabId: active.id } : {}) : undefined;
110
+ if (existing) {
111
+ if (active)
112
+ this.storeActiveInputState();
113
+ this.activeTabId = existing.id;
114
+ this.clearTabAttention(existing);
115
+ this.updateTabFromSession(existing, session);
116
+ if (active)
117
+ this.deleteRuntimeForTab(active.id);
118
+ this.storeActiveRuntime();
119
+ if (options.save !== false)
120
+ void this.saveTabs();
121
+ return;
122
+ }
123
+ if (!active) {
124
+ const tab = this.tabFromSession(session, { titlePlaceholder: this.restored ? "new" : "loading" });
125
+ this.tabItems.push(tab);
126
+ this.activeTabId = tab.id;
127
+ this.clearTabAttention(tab);
128
+ this.storeActiveRuntime();
129
+ if (options.save !== false)
130
+ void this.saveTabs();
131
+ return;
132
+ }
133
+ this.updateTabFromSession(active, session);
134
+ this.clearTabAttention(active);
135
+ this.storeActiveRuntime();
136
+ if (options.save !== false)
137
+ void this.saveTabs();
138
+ }
139
+ async restoreAfterStartup() {
140
+ if (this.restored)
141
+ return;
142
+ this.restored = true;
143
+ const runtime = this.host.runtime();
144
+ if (!runtime)
145
+ return;
146
+ this.syncActiveTabFromRuntime({ save: false });
147
+ if (this.host.options.noSession)
148
+ return;
149
+ const saved = await this.loadTabs();
150
+ if (!saved || saved.tabs.length === 0) {
151
+ await this.saveTabs();
152
+ return;
153
+ }
154
+ const sessionTitles = await this.loadSessionTitles();
155
+ const restoredTabs = this.restoredTabs(saved, sessionTitles);
156
+ if (restoredTabs.length === 0) {
157
+ await this.saveTabs();
158
+ return;
159
+ }
160
+ const currentPath = runtime.session.sessionFile ? resolve(runtime.session.sessionFile) : undefined;
161
+ const explicitSessionPath = this.host.options.sessionPath ? resolve(this.host.options.sessionPath) : undefined;
162
+ const savedActivePath = saved.activePath ? resolve(saved.activePath) : undefined;
163
+ const desiredPath = explicitSessionPath && currentPath
164
+ ? currentPath
165
+ : savedActivePath && restoredTabs.some((tab) => tab.sessionPath === savedActivePath)
166
+ ? savedActivePath
167
+ : restoredTabs[0]?.sessionPath;
168
+ this.replaceTabs(restoredTabs, desiredPath);
169
+ this.restorePersistedInputStates(saved);
170
+ if (explicitSessionPath && currentPath)
171
+ this.ensureCurrentSessionTab(runtime.session);
172
+ if (!desiredPath) {
173
+ await this.saveTabs();
174
+ return;
175
+ }
176
+ if (currentPath !== desiredPath) {
177
+ this.host.setStatus("restoring tabs");
178
+ this.host.render();
179
+ try {
180
+ const result = await runtime.switchSession(desiredPath);
181
+ if (result.cancelled)
182
+ throw new Error("restore cancelled");
183
+ }
184
+ catch {
185
+ this.host.showToast("Could not restore the previous active tab", "warning");
186
+ this.replaceTabs([this.tabFromSession(runtime.session), ...restoredTabs], currentPath);
187
+ this.storeActiveRuntime(runtime);
188
+ await this.saveTabs();
189
+ return;
190
+ }
191
+ }
192
+ this.syncActiveTabFromRuntime({ save: false });
193
+ this.host.resetSessionView();
194
+ this.host.loadSessionHistory();
195
+ this.host.setSessionStatus(runtime.session);
196
+ this.host.setSessionActivity(this.sessionActivity(runtime.session));
197
+ if (this.activeTabId)
198
+ this.restoreInputState(this.activeTabId);
199
+ await this.saveTabs();
200
+ }
201
+ async openNewTab() {
202
+ if (this.pendingActiveTabId) {
203
+ this.host.showToast("Wait for the tab to finish loading", "info");
204
+ return;
205
+ }
206
+ const runtime = this.runtimeForCommand("new_tab");
207
+ if (!runtime)
208
+ return;
209
+ if (!this.tabsAvailable(runtime))
210
+ return;
211
+ this.cancelHistoryLoad();
212
+ this.syncActiveTabFromRuntime();
213
+ this.storeActiveInputState();
214
+ this.host.setStatus("starting new tab");
215
+ this.host.render();
216
+ const newRuntime = await this.host.createRuntimeForNewSession();
217
+ const existingTab = this.findTabForSession(newRuntime.session);
218
+ const tab = existingTab ?? this.tabFromSession(newRuntime.session, { titlePlaceholder: "new" });
219
+ if (!existingTab)
220
+ this.tabItems.push(tab);
221
+ this.activeTabId = tab.id;
222
+ this.pendingActiveTabId = tab.id;
223
+ this.clearTabAttention(tab);
224
+ this.updateTabFromSession(tab, newRuntime.session);
225
+ this.setRuntimeForTab(tab.id, newRuntime);
226
+ this.restoreInputState(tab.id);
227
+ try {
228
+ await this.host.activateRuntime(newRuntime);
229
+ }
230
+ finally {
231
+ if (this.pendingActiveTabId === tab.id)
232
+ this.pendingActiveTabId = undefined;
233
+ }
234
+ void this.saveTabs();
235
+ this.host.resetSessionView();
236
+ if (isEmptyStartupSession(newRuntime)) {
237
+ this.host.addEntry({ id: createId("system"), kind: "system", text: createStartupInfoMessage(newRuntime) });
238
+ }
239
+ else {
240
+ this.host.addEntry({ id: createId("system"), kind: "system", text: `Opened a new tab. cwd=${newRuntime.cwd}` });
241
+ }
242
+ if (newRuntime.modelFallbackMessage)
243
+ this.host.addEntry({ id: createId("system"), kind: "system", text: newRuntime.modelFallbackMessage });
244
+ for (const diag of newRuntime.diagnostics ?? []) {
245
+ const kind = diag.type === "error" ? "error" : "system";
246
+ this.host.addEntry({ id: createId("system"), kind, text: `[${diag.type}] ${diag.message}` });
247
+ }
248
+ this.host.setSessionStatus(newRuntime.session);
249
+ this.host.setSessionActivity(this.sessionActivity(newRuntime.session));
250
+ this.host.render();
251
+ }
252
+ async openSessionInNewTab(sessionPath) {
253
+ if (this.pendingActiveTabId) {
254
+ this.host.showToast("Wait for the tab to finish loading", "info");
255
+ return false;
256
+ }
257
+ const runtime = this.runtimeForCommand("search");
258
+ if (!runtime)
259
+ return false;
260
+ if (this.host.options.noSession) {
261
+ this.host.showToast("/search is unavailable with --no-session", "warning");
262
+ return false;
263
+ }
264
+ this.syncActiveTabFromRuntime({ save: false });
265
+ const resolvedSessionPath = resolve(runtime.cwd, sessionPath);
266
+ const existingTab = this.findTabBySessionPath(resolvedSessionPath);
267
+ if (existingTab) {
268
+ await this.switchToTab(existingTab.id);
269
+ return true;
270
+ }
271
+ this.cancelHistoryLoad();
272
+ this.storeActiveInputState();
273
+ const previousTabId = this.activeTabId;
274
+ const previousRuntime = runtime;
275
+ this.host.setStatus("opening session tab");
276
+ this.host.render();
277
+ let newRuntime;
278
+ try {
279
+ newRuntime = await this.host.createRuntimeForSession(resolvedSessionPath);
280
+ }
281
+ catch {
282
+ this.host.showToast("Could not open session tab", "warning");
283
+ this.host.setSessionStatus(previousRuntime.session);
284
+ this.host.render();
285
+ return false;
286
+ }
287
+ const tab = this.tabFromSession(newRuntime.session, { titlePlaceholder: "loading" });
288
+ this.tabItems.push(tab);
289
+ this.activeTabId = tab.id;
290
+ this.pendingActiveTabId = tab.id;
291
+ this.clearTabAttention(tab);
292
+ this.updateTabFromSession(tab, newRuntime.session);
293
+ this.setRuntimeForTab(tab.id, newRuntime);
294
+ this.restoreInputState(tab.id);
295
+ try {
296
+ await this.host.activateRuntime(newRuntime);
297
+ }
298
+ catch {
299
+ this.pendingActiveTabId = undefined;
300
+ this.removeTab(tab.id);
301
+ this.activeTabId = previousTabId;
302
+ if (previousTabId)
303
+ this.restoreInputState(previousTabId);
304
+ if (this.host.runtime() !== previousRuntime) {
305
+ try {
306
+ await this.host.activateRuntime(previousRuntime);
307
+ }
308
+ catch {
309
+ // Keep the best available runtime below and surface the switch failure.
310
+ }
311
+ }
312
+ void this.host.disposeRuntime(newRuntime);
313
+ this.host.showToast("Could not open session tab", "warning");
314
+ this.host.resetSessionView();
315
+ this.host.loadSessionHistory();
316
+ this.host.setSessionStatus(this.host.runtime()?.session);
317
+ this.host.setSessionActivity(this.sessionActivity(this.host.runtime()?.session));
318
+ this.host.render();
319
+ return false;
320
+ }
321
+ if (this.pendingActiveTabId === tab.id)
322
+ this.pendingActiveTabId = undefined;
323
+ this.activeTabId = tab.id;
324
+ this.clearTabAttention(tab);
325
+ this.updateTabFromSession(tab, newRuntime.session);
326
+ this.setRuntimeForTab(tab.id, newRuntime);
327
+ this.restoreInputState(tab.id);
328
+ void this.saveTabs();
329
+ await this.loadActiveSessionHistory(newRuntime);
330
+ return true;
331
+ }
332
+ async switchToTab(tabId) {
333
+ if (this.pendingActiveTabId) {
334
+ this.host.showToast("Wait for the tab to finish loading", "info");
335
+ return;
336
+ }
337
+ if (tabId === this.activeTabId)
338
+ return;
339
+ const runtime = this.runtimeForCommand("tab switch");
340
+ if (!runtime)
341
+ return;
342
+ this.syncActiveTabFromRuntime({ save: false });
343
+ const target = this.tabItems.find((tab) => tab.id === tabId);
344
+ if (!target)
345
+ return;
346
+ if (!target.sessionPath) {
347
+ this.host.showToast("Tab has no persisted session path", "warning");
348
+ return;
349
+ }
350
+ this.cancelHistoryLoad();
351
+ const previousTabId = this.activeTabId;
352
+ const previousRuntime = runtime;
353
+ const previousTargetActivity = target.activity;
354
+ this.storeActiveRuntime(runtime);
355
+ this.storeActiveInputState();
356
+ this.activeTabId = target.id;
357
+ this.pendingActiveTabId = target.id;
358
+ target.activity = "thinking";
359
+ this.clearTabAttention(target);
360
+ this.restoreInputState(target.id);
361
+ this.host.resetSessionView();
362
+ this.host.setStatus("switching tab");
363
+ this.host.setSessionActivity("thinking");
364
+ this.host.render();
365
+ let targetRuntime;
366
+ try {
367
+ targetRuntime = await this.runtimeForTab(target);
368
+ if (!targetRuntime)
369
+ throw new Error("Could not load tab runtime");
370
+ await this.host.activateRuntime(targetRuntime);
371
+ }
372
+ catch {
373
+ this.pendingActiveTabId = undefined;
374
+ if (previousTargetActivity === undefined)
375
+ delete target.activity;
376
+ else
377
+ target.activity = previousTargetActivity;
378
+ this.activeTabId = previousTabId;
379
+ if (previousTabId)
380
+ this.restoreInputState(previousTabId);
381
+ if (this.host.runtime() !== previousRuntime) {
382
+ try {
383
+ await this.host.activateRuntime(previousRuntime);
384
+ }
385
+ catch {
386
+ // Keep the best available runtime below and surface the switch failure.
387
+ }
388
+ }
389
+ this.host.showToast("Could not switch tab", "warning");
390
+ this.host.resetSessionView();
391
+ this.host.loadSessionHistory();
392
+ const activeSession = this.host.runtime()?.session;
393
+ this.host.setSessionStatus(activeSession);
394
+ this.host.setSessionActivity(this.sessionActivity(activeSession));
395
+ this.host.render();
396
+ return;
397
+ }
398
+ this.pendingActiveTabId = undefined;
399
+ this.activeTabId = target.id;
400
+ this.clearTabAttention(target);
401
+ this.updateTabFromSession(target, targetRuntime.session);
402
+ this.setRuntimeForTab(target.id, targetRuntime);
403
+ this.restoreInputState(target.id);
404
+ void this.saveTabs();
405
+ await this.loadActiveSessionHistory(targetRuntime);
406
+ }
407
+ async closeTab(tabId) {
408
+ if (this.pendingActiveTabId) {
409
+ this.host.showToast("Wait for the tab to finish loading", "info");
410
+ return;
411
+ }
412
+ const index = this.tabItems.findIndex((tab) => tab.id === tabId);
413
+ if (index < 0)
414
+ return;
415
+ this.cancelHistoryLoad();
416
+ if (this.tabItems.length <= 1) {
417
+ await this.replaceLastTabWithNewSession(tabId);
418
+ return;
419
+ }
420
+ if (tabId !== this.activeTabId) {
421
+ const tabRuntime = this.runtimesByTabId.get(tabId);
422
+ if (tabRuntime?.session.isStreaming || tabRuntime?.session.isCompacting) {
423
+ this.host.showToast("Cannot close a running tab", "warning");
424
+ return;
425
+ }
426
+ this.tabItems.splice(index, 1);
427
+ this.deleteRuntimeForTab(tabId);
428
+ this.inputStatesByTabId.delete(tabId);
429
+ this.storeActiveInputState();
430
+ this.stopAttentionBlinkIfIdle();
431
+ if (tabRuntime)
432
+ void this.host.disposeRuntime(tabRuntime);
433
+ void this.saveTabs();
434
+ this.host.render();
435
+ return;
436
+ }
437
+ const runtime = this.idleRuntime("tab close");
438
+ if (!runtime)
439
+ return;
440
+ const nextTab = this.tabItems[index + 1] ?? this.tabItems[index - 1];
441
+ if (!nextTab?.sessionPath) {
442
+ this.host.showToast("No persisted session path for the next tab", "warning");
443
+ return;
444
+ }
445
+ this.host.setStatus("closing tab");
446
+ this.host.render();
447
+ const nextRuntime = await this.runtimeForTab(nextTab);
448
+ if (!nextRuntime)
449
+ return;
450
+ await this.host.activateRuntime(nextRuntime);
451
+ this.tabItems.splice(index, 1);
452
+ this.deleteRuntimeForTab(tabId);
453
+ this.inputStatesByTabId.delete(tabId);
454
+ this.stopAttentionBlinkIfIdle();
455
+ this.activeTabId = nextTab.id;
456
+ this.clearTabAttention(nextTab);
457
+ this.updateTabFromSession(nextTab, nextRuntime.session);
458
+ this.setRuntimeForTab(nextTab.id, nextRuntime);
459
+ this.restoreInputState(nextTab.id);
460
+ void this.host.disposeRuntime(runtime);
461
+ void this.saveTabs();
462
+ await this.loadActiveSessionHistory(nextRuntime);
463
+ }
464
+ async replaceLastTabWithNewSession(tabId) {
465
+ const tab = this.tabItems.find((item) => item.id === tabId);
466
+ if (!tab)
467
+ return;
468
+ const runtime = this.idleRuntime("new");
469
+ if (!runtime)
470
+ return;
471
+ this.activeTabId = tab.id;
472
+ this.host.setStatus("starting new session");
473
+ this.host.render();
474
+ const result = await runtime.newSession();
475
+ if (result.cancelled) {
476
+ this.host.addEntry({ id: createId("system"), kind: "system", text: "New session cancelled." });
477
+ this.host.setSessionStatus(runtime.session);
478
+ this.host.setSessionActivity(this.sessionActivity(runtime.session));
479
+ this.host.render();
480
+ return;
481
+ }
482
+ this.clearTabAttention(tab);
483
+ this.updateTabFromSession(tab, runtime.session);
484
+ this.setRuntimeForTab(tab.id, runtime);
485
+ this.inputStatesByTabId.delete(tab.id);
486
+ this.restoreInputState(tab.id);
487
+ this.stopAttentionBlinkIfIdle();
488
+ this.host.resetSessionView();
489
+ this.host.addEntry({ id: createId("system"), kind: "system", text: `Started a new session. cwd=${runtime.cwd}` });
490
+ if (runtime.modelFallbackMessage)
491
+ this.host.addEntry({ id: createId("system"), kind: "system", text: runtime.modelFallbackMessage });
492
+ for (const diag of runtime.diagnostics ?? []) {
493
+ const kind = diag.type === "error" ? "error" : "system";
494
+ this.host.addEntry({ id: createId("system"), kind, text: `[${diag.type}] ${diag.message}` });
495
+ }
496
+ this.host.setSessionStatus(runtime.session);
497
+ this.host.setSessionActivity(this.sessionActivity(runtime.session));
498
+ void this.saveTabs();
499
+ this.host.render();
500
+ }
501
+ async loadActiveSessionHistory(runtime) {
502
+ const generation = ++this.historyLoadGeneration;
503
+ const isCancelled = () => generation !== this.historyLoadGeneration || this.host.runtime() !== runtime;
504
+ this.host.resetSessionView();
505
+ this.host.setStatus("loading session history");
506
+ this.host.setSessionActivity("thinking");
507
+ this.host.render();
508
+ const completed = await this.host.loadSessionHistoryAsync({
509
+ isCancelled,
510
+ render: () => {
511
+ if (!isCancelled())
512
+ this.host.render();
513
+ },
514
+ });
515
+ if (!completed || isCancelled())
516
+ return;
517
+ this.host.setSessionStatus(runtime.session);
518
+ this.host.syncUserSessionEntryMetadata();
519
+ this.host.setSessionActivity(this.sessionActivity(runtime.session));
520
+ this.host.render();
521
+ }
522
+ cancelHistoryLoad() {
523
+ this.historyLoadGeneration += 1;
524
+ }
525
+ tabsAvailable(runtime) {
526
+ if (this.host.options.noSession) {
527
+ this.host.showToast("/new_tab is unavailable with --no-session", "warning");
528
+ return false;
529
+ }
530
+ if (!runtime.session.sessionFile) {
531
+ this.host.showToast("/new_tab requires a persisted session", "warning");
532
+ return false;
533
+ }
534
+ return true;
535
+ }
536
+ runtimeForCommand(commandName) {
537
+ const runtime = this.host.runtime();
538
+ if (!runtime) {
539
+ this.host.showToast(`/${commandName} unavailable`, "error");
540
+ this.host.addEntry({ id: createId("error"), kind: "error", text: "Runtime is not initialized" });
541
+ return undefined;
542
+ }
543
+ return runtime;
544
+ }
545
+ idleRuntime(commandName) {
546
+ const runtime = this.runtimeForCommand(commandName);
547
+ if (!runtime)
548
+ return undefined;
549
+ if (runtime.session.isStreaming || runtime.session.isCompacting) {
550
+ this.host.showToast(`/${commandName} is unavailable while the agent is running`, "warning");
551
+ return undefined;
552
+ }
553
+ return runtime;
554
+ }
555
+ activeTab() {
556
+ return this.activeTabId ? this.tabItems.find((tab) => tab.id === this.activeTabId) : undefined;
557
+ }
558
+ storeActiveRuntime(runtime = this.host.runtime()) {
559
+ if (!this.activeTabId || !runtime)
560
+ return;
561
+ this.setRuntimeForTab(this.activeTabId, runtime);
562
+ }
563
+ setRuntimeForTab(tabId, runtime) {
564
+ this.runtimesByTabId.set(tabId, runtime);
565
+ this.observeRuntimeForTab(tabId, runtime);
566
+ }
567
+ deleteRuntimeForTab(tabId) {
568
+ this.runtimesByTabId.delete(tabId);
569
+ const subscription = this.runtimeSubscriptionsByTabId.get(tabId);
570
+ subscription?.unsubscribe();
571
+ this.runtimeSubscriptionsByTabId.delete(tabId);
572
+ }
573
+ clearRuntimeSubscriptions() {
574
+ for (const subscription of this.runtimeSubscriptionsByTabId.values()) {
575
+ subscription.unsubscribe();
576
+ }
577
+ this.runtimeSubscriptionsByTabId.clear();
578
+ }
579
+ observeRuntimeForTab(tabId, runtime) {
580
+ const existing = this.runtimeSubscriptionsByTabId.get(tabId);
581
+ if (existing?.runtime === runtime && existing.session === runtime.session)
582
+ return;
583
+ existing?.unsubscribe();
584
+ const unsubscribe = runtime.session.subscribe((event) => {
585
+ if (!this.shouldSyncTabFromRuntimeEvent(event))
586
+ return;
587
+ this.syncTabFromObservedRuntime(tabId, runtime);
588
+ });
589
+ this.runtimeSubscriptionsByTabId.set(tabId, { runtime, session: runtime.session, unsubscribe });
590
+ }
591
+ shouldSyncTabFromRuntimeEvent(event) {
592
+ return event.type === "session_info_changed"
593
+ || event.type === "agent_start"
594
+ || event.type === "agent_end"
595
+ || event.type === "compaction_start"
596
+ || event.type === "compaction_end";
597
+ }
598
+ syncTabFromObservedRuntime(tabId, runtime) {
599
+ const tab = this.tabItems.find((item) => item.id === tabId);
600
+ if (!tab) {
601
+ this.deleteRuntimeForTab(tabId);
602
+ return;
603
+ }
604
+ const previousTitle = tab.title;
605
+ const previousActivity = tab.activity;
606
+ const previousSessionPath = tab.sessionPath;
607
+ this.updateTabFromSession(tab, runtime.session);
608
+ if (tab.title === previousTitle && tab.activity === previousActivity && tab.sessionPath === previousSessionPath)
609
+ return;
610
+ if (tab.title !== previousTitle || tab.sessionPath !== previousSessionPath)
611
+ void this.saveTabs();
612
+ this.host.render();
613
+ }
614
+ storeActiveInputState() {
615
+ if (!this.activeTabId)
616
+ return;
617
+ const state = this.host.captureInputState();
618
+ this.inputStatesByTabId.set(this.activeTabId, {
619
+ text: state.text,
620
+ cursor: state.cursor,
621
+ });
622
+ }
623
+ restoreInputState(tabId) {
624
+ this.host.restoreInputState(this.inputStatesByTabId.get(tabId) ?? { text: "", cursor: 0 });
625
+ }
626
+ async runtimeForTab(tab) {
627
+ const existing = this.runtimesByTabId.get(tab.id);
628
+ if (existing)
629
+ return existing;
630
+ if (!tab.sessionPath) {
631
+ this.host.showToast("Tab has no persisted session path", "warning");
632
+ return undefined;
633
+ }
634
+ const runtime = await this.host.createRuntimeForSession(tab.sessionPath);
635
+ this.setRuntimeForTab(tab.id, runtime);
636
+ return runtime;
637
+ }
638
+ findTabForSession(session, options = {}) {
639
+ const sessionPath = this.sessionPath(session);
640
+ return sessionPath ? this.findTabBySessionPath(sessionPath, options) : undefined;
641
+ }
642
+ findTabBySessionPath(sessionPath, options = {}) {
643
+ const normalizedPath = resolve(sessionPath);
644
+ return this.tabItems.find((tab) => (tab.id !== options.excludeTabId
645
+ && tab.sessionPath !== undefined
646
+ && resolve(tab.sessionPath) === normalizedPath));
647
+ }
648
+ replaceTabs(tabs, activeSessionPath) {
649
+ this.tabItems.length = 0;
650
+ this.runtimesByTabId.clear();
651
+ this.clearRuntimeSubscriptions();
652
+ this.inputStatesByTabId.clear();
653
+ const seen = new Set();
654
+ for (const tab of tabs) {
655
+ const sessionPath = tab.sessionPath ? resolve(tab.sessionPath) : undefined;
656
+ if (sessionPath) {
657
+ if (seen.has(sessionPath))
658
+ continue;
659
+ seen.add(sessionPath);
660
+ }
661
+ this.tabItems.push({
662
+ id: tab.id,
663
+ title: tab.title,
664
+ status: "waiting",
665
+ activity: tab.activity ?? "idle",
666
+ ...(sessionPath ? { sessionPath } : {}),
667
+ });
668
+ }
669
+ const activePath = activeSessionPath ? resolve(activeSessionPath) : undefined;
670
+ this.activeTabId = activePath
671
+ ? this.tabItems.find((tab) => tab.sessionPath === activePath)?.id
672
+ : this.tabItems[0]?.id;
673
+ this.activeTabId ??= this.tabItems[0]?.id;
674
+ }
675
+ removeTab(tabId) {
676
+ const index = this.tabItems.findIndex((tab) => tab.id === tabId);
677
+ if (index >= 0)
678
+ this.tabItems.splice(index, 1);
679
+ this.deleteRuntimeForTab(tabId);
680
+ this.inputStatesByTabId.delete(tabId);
681
+ }
682
+ restorePersistedInputStates(saved) {
683
+ const inputsByPath = new Map();
684
+ for (const tab of saved.tabs) {
685
+ if (!tab.input)
686
+ continue;
687
+ inputsByPath.set(resolve(tab.path), tab.input);
688
+ }
689
+ for (const tab of this.tabItems) {
690
+ if (!tab.sessionPath)
691
+ continue;
692
+ const input = inputsByPath.get(resolve(tab.sessionPath));
693
+ if (!input)
694
+ continue;
695
+ this.inputStatesByTabId.set(tab.id, input);
696
+ }
697
+ }
698
+ ensureCurrentSessionTab(session) {
699
+ const currentPath = session.sessionFile ? resolve(session.sessionFile) : undefined;
700
+ if (!currentPath)
701
+ return;
702
+ const existing = this.tabItems.find((tab) => tab.sessionPath === currentPath);
703
+ if (existing) {
704
+ this.activeTabId = existing.id;
705
+ this.clearTabAttention(existing);
706
+ this.updateTabFromSession(existing, session);
707
+ return;
708
+ }
709
+ const tab = this.tabFromSession(session);
710
+ this.tabItems.unshift(tab);
711
+ this.activeTabId = tab.id;
712
+ }
713
+ tabFromSession(session, options = {}) {
714
+ const sessionPath = this.sessionPath(session);
715
+ return {
716
+ id: createId("tab"),
717
+ title: this.sessionTitle(session),
718
+ ...(options.titlePlaceholder ? { titlePlaceholder: options.titlePlaceholder } : {}),
719
+ status: "active",
720
+ activity: this.sessionActivity(session),
721
+ ...(sessionPath ? { sessionPath } : {}),
722
+ };
723
+ }
724
+ updateTabFromSession(tab, session) {
725
+ tab.title = this.sessionTitle(session);
726
+ tab.activity = this.sessionActivity(session);
727
+ const sessionPath = this.sessionPath(session);
728
+ if (sessionPath)
729
+ tab.sessionPath = sessionPath;
730
+ }
731
+ sessionPath(session) {
732
+ return session.sessionFile ? resolve(session.sessionFile) : undefined;
733
+ }
734
+ sessionTitle(session) {
735
+ const name = session.sessionName?.trim();
736
+ return name ? name : `session ${session.sessionId.slice(0, 8)}`;
737
+ }
738
+ sessionActivity(session) {
739
+ return session?.isStreaming || session?.isCompacting ? "running" : "idle";
740
+ }
741
+ tabActivity(tab) {
742
+ if (tab.id === this.pendingActiveTabId)
743
+ return "thinking";
744
+ const runtime = this.runtimesByTabId.get(tab.id);
745
+ return runtime ? this.sessionActivity(runtime.session) : tab.activity ?? "idle";
746
+ }
747
+ clearTabAttention(tab) {
748
+ if (tab.attention === undefined && tab.attentionVisible === undefined)
749
+ return;
750
+ delete tab.attention;
751
+ delete tab.attentionVisible;
752
+ this.stopAttentionBlinkIfIdle();
753
+ }
754
+ startAttentionBlink() {
755
+ this.host.blinkController.setActive(TAB_ATTENTION_BLINK_KEY, true, {
756
+ scope: "full",
757
+ initialVisible: true,
758
+ });
759
+ }
760
+ stopAttentionBlinkIfIdle() {
761
+ if (this.tabItems.some((tab) => tab.attention !== undefined))
762
+ return;
763
+ this.host.blinkController.setActive(TAB_ATTENTION_BLINK_KEY, false, {
764
+ scope: "full",
765
+ initialVisible: true,
766
+ });
767
+ }
768
+ async loadSessionTitles() {
769
+ try {
770
+ const sessions = await SessionManager.list(this.host.options.cwd);
771
+ return new Map(sessions.map((session) => [
772
+ resolve(session.path),
773
+ (session.name?.trim() || `session ${session.id.slice(0, 8)}`),
774
+ ]));
775
+ }
776
+ catch {
777
+ return new Map();
778
+ }
779
+ }
780
+ restoredTabs(saved, titles) {
781
+ const tabs = [];
782
+ const seen = new Set();
783
+ for (const tab of saved.tabs) {
784
+ const sessionPath = resolve(tab.path);
785
+ const hasDraftInput = (tab.input?.text.length ?? 0) > 0;
786
+ if (seen.has(sessionPath) || (!existsSync(sessionPath) && !hasDraftInput))
787
+ continue;
788
+ seen.add(sessionPath);
789
+ const title = titles.get(sessionPath) ?? tab.title?.trim();
790
+ tabs.push({
791
+ id: createId("tab"),
792
+ title: title || "session",
793
+ status: "waiting",
794
+ sessionPath,
795
+ });
796
+ if (tabs.length >= MAX_RESTORED_TABS)
797
+ break;
798
+ }
799
+ return tabs;
800
+ }
801
+ async loadTabs() {
802
+ try {
803
+ const raw = await readFile(this.filePath(), "utf8");
804
+ const parsed = JSON.parse(raw);
805
+ if (!isRecord(parsed) || (parsed.version !== 1 && parsed.version !== TAB_STATE_VERSION) || !Array.isArray(parsed.tabs))
806
+ return undefined;
807
+ const tabs = [];
808
+ for (const value of parsed.tabs) {
809
+ if (!isRecord(value) || typeof value.path !== "string")
810
+ continue;
811
+ const input = this.parsePersistedInputState(value.input);
812
+ tabs.push({
813
+ path: value.path,
814
+ ...(typeof value.title === "string" ? { title: value.title } : {}),
815
+ ...(input ? { input } : {}),
816
+ });
817
+ }
818
+ return {
819
+ version: parsed.version === 1 ? 1 : TAB_STATE_VERSION,
820
+ cwd: typeof parsed.cwd === "string" ? parsed.cwd : this.host.options.cwd,
821
+ tabs,
822
+ ...(typeof parsed.activePath === "string" ? { activePath: parsed.activePath } : {}),
823
+ };
824
+ }
825
+ catch (error) {
826
+ if (isRecord(error) && error.code === "ENOENT")
827
+ return undefined;
828
+ return undefined;
829
+ }
830
+ }
831
+ parsePersistedInputState(value) {
832
+ if (!isRecord(value) || typeof value.text !== "string")
833
+ return undefined;
834
+ const cursor = typeof value.cursor === "number" && Number.isFinite(value.cursor)
835
+ ? Math.max(0, Math.min(value.text.length, Math.trunc(value.cursor)))
836
+ : value.text.length;
837
+ return { text: value.text, cursor };
838
+ }
839
+ async saveTabs() {
840
+ if (this.host.options.noSession)
841
+ return;
842
+ try {
843
+ const tabs = [];
844
+ const seen = new Set();
845
+ for (const tab of this.tabItems) {
846
+ if (!tab.sessionPath)
847
+ continue;
848
+ const sessionPath = resolve(tab.sessionPath);
849
+ if (seen.has(sessionPath))
850
+ continue;
851
+ seen.add(sessionPath);
852
+ const persistedTab = { path: sessionPath, title: tab.title };
853
+ const input = this.inputStatesByTabId.get(tab.id);
854
+ if (input?.text.length) {
855
+ persistedTab.input = {
856
+ text: input.text,
857
+ cursor: Math.max(0, Math.min(input.text.length, Math.trunc(input.cursor))),
858
+ };
859
+ }
860
+ tabs.push(persistedTab);
861
+ }
862
+ if (tabs.length === 0)
863
+ return;
864
+ const activePath = this.activeTab()?.sessionPath;
865
+ const payload = JSON.stringify({
866
+ version: TAB_STATE_VERSION,
867
+ cwd: this.host.options.cwd,
868
+ tabs,
869
+ ...(activePath ? { activePath: resolve(activePath) } : {}),
870
+ }, null, 2);
871
+ const filePath = this.filePath();
872
+ await mkdir(dirname(filePath), { recursive: true });
873
+ const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
874
+ await writeFile(tempPath, payload, "utf8");
875
+ await rename(tempPath, filePath);
876
+ }
877
+ catch {
878
+ // Tab state should never interrupt the terminal UI.
879
+ }
880
+ }
881
+ filePath() {
882
+ const key = createHash("sha256").update(resolve(this.host.options.cwd)).digest("hex").slice(0, 24);
883
+ return join(getAgentDir(), "pix", "tabs", `${key}.json`);
884
+ }
885
+ }