@jack200714/mafw 4.5.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 (631) hide show
  1. package/.opencode/mafw/.backup-v61/parametric/base-skill-manifest.yaml +2 -0
  2. package/.opencode/mafw/STATUS.md +6 -0
  3. package/.opencode/mafw/ledger.md +20 -0
  4. package/.opencode/mafw/memory/.harmonic_index.json +28 -0
  5. package/.opencode/mafw/memory/.review_queue.json +1 -0
  6. package/.opencode/mafw/memory/tier3/parametric.json +13 -0
  7. package/.opencode/mafw/memory/tier3/test-goal.json +19 -0
  8. package/.opencode/mafw/memory-index.json +6 -0
  9. package/.opencode/mafw/parametric/base-skill-manifest.yaml +2 -0
  10. package/.opencode/mafw/waves.json +4 -0
  11. package/.opencode/opencode.json +3 -0
  12. package/.opencode/plugins/mafw-plugin.ts +8 -0
  13. package/.opencode/skills/banner-design/SKILL.md +196 -0
  14. package/.opencode/skills/banner-design/references/banner-sizes-and-styles.md +118 -0
  15. package/.opencode/skills/brand/SKILL.md +97 -0
  16. package/.opencode/skills/brand/references/approval-checklist.md +169 -0
  17. package/.opencode/skills/brand/references/asset-organization.md +157 -0
  18. package/.opencode/skills/brand/references/brand-guideline-template.md +140 -0
  19. package/.opencode/skills/brand/references/color-palette-management.md +186 -0
  20. package/.opencode/skills/brand/references/consistency-checklist.md +94 -0
  21. package/.opencode/skills/brand/references/logo-usage-rules.md +185 -0
  22. package/.opencode/skills/brand/references/messaging-framework.md +85 -0
  23. package/.opencode/skills/brand/references/typography-specifications.md +214 -0
  24. package/.opencode/skills/brand/references/update.md +118 -0
  25. package/.opencode/skills/brand/references/visual-identity.md +96 -0
  26. package/.opencode/skills/brand/references/voice-framework.md +88 -0
  27. package/.opencode/skills/brand/scripts/extract-colors.cjs +341 -0
  28. package/.opencode/skills/brand/scripts/inject-brand-context.cjs +349 -0
  29. package/.opencode/skills/brand/scripts/sync-brand-to-tokens.cjs +248 -0
  30. package/.opencode/skills/brand/scripts/tests/test_sync_brand_to_tokens.py +52 -0
  31. package/.opencode/skills/brand/scripts/validate-asset.cjs +387 -0
  32. package/.opencode/skills/brand/templates/brand-guidelines-starter.md +275 -0
  33. package/.opencode/skills/design/SKILL.md +313 -0
  34. package/.opencode/skills/design/data/cip/deliverables.csv +51 -0
  35. package/.opencode/skills/design/data/cip/industries.csv +21 -0
  36. package/.opencode/skills/design/data/cip/mockup-contexts.csv +21 -0
  37. package/.opencode/skills/design/data/cip/styles.csv +21 -0
  38. package/.opencode/skills/design/data/icon/styles.csv +16 -0
  39. package/.opencode/skills/design/data/logo/colors.csv +56 -0
  40. package/.opencode/skills/design/data/logo/industries.csv +56 -0
  41. package/.opencode/skills/design/data/logo/styles.csv +56 -0
  42. package/.opencode/skills/design/references/banner-sizes-and-styles.md +118 -0
  43. package/.opencode/skills/design/references/cip-deliverable-guide.md +95 -0
  44. package/.opencode/skills/design/references/cip-design.md +121 -0
  45. package/.opencode/skills/design/references/cip-prompt-engineering.md +84 -0
  46. package/.opencode/skills/design/references/cip-style-guide.md +68 -0
  47. package/.opencode/skills/design/references/design-routing.md +207 -0
  48. package/.opencode/skills/design/references/icon-design.md +122 -0
  49. package/.opencode/skills/design/references/logo-color-psychology.md +101 -0
  50. package/.opencode/skills/design/references/logo-design.md +92 -0
  51. package/.opencode/skills/design/references/logo-prompt-engineering.md +158 -0
  52. package/.opencode/skills/design/references/logo-style-guide.md +109 -0
  53. package/.opencode/skills/design/references/slides-copywriting-formulas.md +84 -0
  54. package/.opencode/skills/design/references/slides-create.md +4 -0
  55. package/.opencode/skills/design/references/slides-html-template.md +295 -0
  56. package/.opencode/skills/design/references/slides-layout-patterns.md +137 -0
  57. package/.opencode/skills/design/references/slides-strategies.md +94 -0
  58. package/.opencode/skills/design/references/slides.md +42 -0
  59. package/.opencode/skills/design/references/social-photos-design.md +329 -0
  60. package/.opencode/skills/design/scripts/cip/core.py +215 -0
  61. package/.opencode/skills/design/scripts/cip/generate.py +484 -0
  62. package/.opencode/skills/design/scripts/cip/render-html.py +424 -0
  63. package/.opencode/skills/design/scripts/cip/search.py +127 -0
  64. package/.opencode/skills/design/scripts/icon/generate.py +487 -0
  65. package/.opencode/skills/design/scripts/logo/core.py +175 -0
  66. package/.opencode/skills/design/scripts/logo/generate.py +362 -0
  67. package/.opencode/skills/design/scripts/logo/search.py +114 -0
  68. package/.opencode/skills/design-system/SKILL.md +244 -0
  69. package/.opencode/skills/design-system/data/slide-backgrounds.csv +11 -0
  70. package/.opencode/skills/design-system/data/slide-charts.csv +26 -0
  71. package/.opencode/skills/design-system/data/slide-color-logic.csv +14 -0
  72. package/.opencode/skills/design-system/data/slide-copy.csv +26 -0
  73. package/.opencode/skills/design-system/data/slide-layout-logic.csv +16 -0
  74. package/.opencode/skills/design-system/data/slide-layouts.csv +26 -0
  75. package/.opencode/skills/design-system/data/slide-strategies.csv +16 -0
  76. package/.opencode/skills/design-system/data/slide-typography.csv +15 -0
  77. package/.opencode/skills/design-system/references/component-specs.md +236 -0
  78. package/.opencode/skills/design-system/references/component-tokens.md +214 -0
  79. package/.opencode/skills/design-system/references/primitive-tokens.md +203 -0
  80. package/.opencode/skills/design-system/references/semantic-tokens.md +215 -0
  81. package/.opencode/skills/design-system/references/states-and-variants.md +241 -0
  82. package/.opencode/skills/design-system/references/tailwind-integration.md +251 -0
  83. package/.opencode/skills/design-system/references/token-architecture.md +224 -0
  84. package/.opencode/skills/design-system/scripts/embed-tokens.cjs +99 -0
  85. package/.opencode/skills/design-system/scripts/fetch-background.py +317 -0
  86. package/.opencode/skills/design-system/scripts/generate-slide.py +770 -0
  87. package/.opencode/skills/design-system/scripts/generate-tokens.cjs +205 -0
  88. package/.opencode/skills/design-system/scripts/html-token-validator.py +327 -0
  89. package/.opencode/skills/design-system/scripts/search-slides.py +218 -0
  90. package/.opencode/skills/design-system/scripts/slide-token-validator.py +35 -0
  91. package/.opencode/skills/design-system/scripts/slide_search_core.py +453 -0
  92. package/.opencode/skills/design-system/scripts/tests/test_validate_tokens.py +48 -0
  93. package/.opencode/skills/design-system/scripts/validate-tokens.cjs +246 -0
  94. package/.opencode/skills/design-system/templates/design-tokens-starter.json +143 -0
  95. package/.opencode/skills/frontend-design/LICENSE.txt +21 -0
  96. package/.opencode/skills/frontend-design/SKILL.md +115 -0
  97. package/.opencode/skills/mafw-automation-interview/SKILL.md +43 -0
  98. package/.opencode/skills/mafw-cli-scanner/SKILL.md +37 -0
  99. package/.opencode/skills/mafw-compression-verifier/SKILL.md +52 -0
  100. package/.opencode/skills/mafw-desktop-inspector/SKILL.md +170 -0
  101. package/.opencode/skills/mafw-execute/SKILL.md +62 -0
  102. package/.opencode/skills/mafw-gateway-restart/SKILL.md +87 -0
  103. package/.opencode/skills/mafw-github-scanner/SKILL.md +38 -0
  104. package/.opencode/skills/mafw-goal/SKILL.md +76 -0
  105. package/.opencode/skills/mafw-interview/SKILL.md +68 -0
  106. package/.opencode/skills/mafw-memory-extractor/SKILL.md +65 -0
  107. package/.opencode/skills/mafw-plan/SKILL.md +58 -0
  108. package/.opencode/skills/mafw-review/SKILL.md +85 -0
  109. package/.opencode/skills/runtime-plugin-authoring/SKILL.md +791 -0
  110. package/.opencode/skills/slides/SKILL.md +40 -0
  111. package/.opencode/skills/slides/references/copywriting-formulas.md +84 -0
  112. package/.opencode/skills/slides/references/create.md +4 -0
  113. package/.opencode/skills/slides/references/html-template.md +295 -0
  114. package/.opencode/skills/slides/references/layout-patterns.md +137 -0
  115. package/.opencode/skills/slides/references/slide-strategies.md +94 -0
  116. package/.opencode/skills/ui-styling/LICENSE.txt +202 -0
  117. package/.opencode/skills/ui-styling/SKILL.md +324 -0
  118. package/.opencode/skills/ui-styling/references/canvas-design-system.md +320 -0
  119. package/.opencode/skills/ui-styling/references/shadcn-accessibility.md +471 -0
  120. package/.opencode/skills/ui-styling/references/shadcn-components.md +424 -0
  121. package/.opencode/skills/ui-styling/references/shadcn-theming.md +373 -0
  122. package/.opencode/skills/ui-styling/references/tailwind-customization.md +483 -0
  123. package/.opencode/skills/ui-styling/references/tailwind-responsive.md +382 -0
  124. package/.opencode/skills/ui-styling/references/tailwind-utilities.md +455 -0
  125. package/.opencode/skills/ui-styling/scripts/requirements.txt +17 -0
  126. package/.opencode/skills/ui-styling/scripts/shadcn_add.py +308 -0
  127. package/.opencode/skills/ui-styling/scripts/tailwind_config_gen.py +473 -0
  128. package/.opencode/skills/ui-styling/scripts/tests/coverage-ui.json +1 -0
  129. package/.opencode/skills/ui-styling/scripts/tests/requirements.txt +3 -0
  130. package/.opencode/skills/ui-styling/scripts/tests/test_shadcn_add.py +266 -0
  131. package/.opencode/skills/ui-styling/scripts/tests/test_tailwind_config_gen.py +394 -0
  132. package/.opencode/skills/ui-ux-pro-max/SKILL.md +388 -0
  133. package/.opencode/skills/ui-ux-pro-max/data/app-interface.csv +31 -0
  134. package/.opencode/skills/ui-ux-pro-max/data/charts.csv +26 -0
  135. package/.opencode/skills/ui-ux-pro-max/data/colors.csv +193 -0
  136. package/.opencode/skills/ui-ux-pro-max/data/google-fonts.csv +1924 -0
  137. package/.opencode/skills/ui-ux-pro-max/data/icons.csv +106 -0
  138. package/.opencode/skills/ui-ux-pro-max/data/landing.csv +35 -0
  139. package/.opencode/skills/ui-ux-pro-max/data/motion.csv +17 -0
  140. package/.opencode/skills/ui-ux-pro-max/data/products.csv +193 -0
  141. package/.opencode/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
  142. package/.opencode/skills/ui-ux-pro-max/data/stacks/angular.csv +51 -0
  143. package/.opencode/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
  144. package/.opencode/skills/ui-ux-pro-max/data/stacks/avalonia.csv +57 -0
  145. package/.opencode/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  146. package/.opencode/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  147. package/.opencode/skills/ui-ux-pro-max/data/stacks/javafx.csv +76 -0
  148. package/.opencode/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
  149. package/.opencode/skills/ui-ux-pro-max/data/stacks/laravel.csv +51 -0
  150. package/.opencode/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  151. package/.opencode/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +71 -0
  152. package/.opencode/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  153. package/.opencode/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  154. package/.opencode/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
  155. package/.opencode/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
  156. package/.opencode/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  157. package/.opencode/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  158. package/.opencode/skills/ui-ux-pro-max/data/stacks/threejs.csv +54 -0
  159. package/.opencode/skills/ui-ux-pro-max/data/stacks/uno.csv +60 -0
  160. package/.opencode/skills/ui-ux-pro-max/data/stacks/uwp.csv +56 -0
  161. package/.opencode/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  162. package/.opencode/skills/ui-ux-pro-max/data/stacks/winui.csv +60 -0
  163. package/.opencode/skills/ui-ux-pro-max/data/stacks/wpf.csv +57 -0
  164. package/.opencode/skills/ui-ux-pro-max/data/styles.csv +85 -0
  165. package/.opencode/skills/ui-ux-pro-max/data/typography.csv +75 -0
  166. package/.opencode/skills/ui-ux-pro-max/data/ui-reasoning.csv +162 -0
  167. package/.opencode/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  168. package/.opencode/skills/ui-ux-pro-max/scripts/core.py +464 -0
  169. package/.opencode/skills/ui-ux-pro-max/scripts/design_system.py +1479 -0
  170. package/.opencode/skills/ui-ux-pro-max/scripts/search.py +162 -0
  171. package/.opencode/skills/ui-ux-pro-max/scripts/tests/test_core.py +134 -0
  172. package/.opencode/skills/ui-ux-pro-max/scripts/tests/test_design_system_mode.py +159 -0
  173. package/.opencode/skills/ui-ux-pro-max/scripts/validate_data.py +114 -0
  174. package/LICENSE +21 -0
  175. package/README.md +199 -0
  176. package/bin/mafw.js +436 -0
  177. package/dist/hooks/bash-python-guide.d.ts +4 -0
  178. package/dist/hooks/bash-python-guide.d.ts.map +1 -0
  179. package/dist/hooks/bash-python-guide.js +67 -0
  180. package/dist/hooks/bash-python-guide.js.map +1 -0
  181. package/dist/hooks/handoff.d.ts +9 -0
  182. package/dist/hooks/handoff.d.ts.map +1 -0
  183. package/dist/hooks/handoff.js +8 -0
  184. package/dist/hooks/handoff.js.map +1 -0
  185. package/dist/hooks/hook-manager.d.ts +32 -0
  186. package/dist/hooks/hook-manager.d.ts.map +1 -0
  187. package/dist/hooks/hook-manager.js +91 -0
  188. package/dist/hooks/hook-manager.js.map +1 -0
  189. package/dist/hooks/llm-after.d.ts +8 -0
  190. package/dist/hooks/llm-after.d.ts.map +1 -0
  191. package/dist/hooks/llm-after.js +8 -0
  192. package/dist/hooks/llm-after.js.map +1 -0
  193. package/dist/hooks/media-ingest.d.ts +43 -0
  194. package/dist/hooks/media-ingest.d.ts.map +1 -0
  195. package/dist/hooks/media-ingest.js +269 -0
  196. package/dist/hooks/media-ingest.js.map +1 -0
  197. package/dist/hooks/memory-guide.d.ts +3 -0
  198. package/dist/hooks/memory-guide.d.ts.map +1 -0
  199. package/dist/hooks/memory-guide.js +47 -0
  200. package/dist/hooks/memory-guide.js.map +1 -0
  201. package/dist/hooks/observation-capture.d.ts +17 -0
  202. package/dist/hooks/observation-capture.d.ts.map +1 -0
  203. package/dist/hooks/observation-capture.js +20 -0
  204. package/dist/hooks/observation-capture.js.map +1 -0
  205. package/dist/hooks/session-compacting.d.ts +6 -0
  206. package/dist/hooks/session-compacting.d.ts.map +1 -0
  207. package/dist/hooks/session-compacting.js +6 -0
  208. package/dist/hooks/session-compacting.js.map +1 -0
  209. package/dist/hooks/session-ending.d.ts +20 -0
  210. package/dist/hooks/session-ending.d.ts.map +1 -0
  211. package/dist/hooks/session-ending.js +113 -0
  212. package/dist/hooks/session-ending.js.map +1 -0
  213. package/dist/hooks/session-recall.d.ts +2 -0
  214. package/dist/hooks/session-recall.d.ts.map +1 -0
  215. package/dist/hooks/session-recall.js +103 -0
  216. package/dist/hooks/session-recall.js.map +1 -0
  217. package/dist/hooks/session-start.d.ts +9 -0
  218. package/dist/hooks/session-start.d.ts.map +1 -0
  219. package/dist/hooks/session-start.js +42 -0
  220. package/dist/hooks/session-start.js.map +1 -0
  221. package/dist/hooks/tool-before.d.ts +10 -0
  222. package/dist/hooks/tool-before.d.ts.map +1 -0
  223. package/dist/hooks/tool-before.js +6 -0
  224. package/dist/hooks/tool-before.js.map +1 -0
  225. package/dist/hooks/tool-executed.d.ts +19 -0
  226. package/dist/hooks/tool-executed.d.ts.map +1 -0
  227. package/dist/hooks/tool-executed.js +12 -0
  228. package/dist/hooks/tool-executed.js.map +1 -0
  229. package/dist/hooks/user-profile.d.ts +2 -0
  230. package/dist/hooks/user-profile.d.ts.map +1 -0
  231. package/dist/hooks/user-profile.js +30 -0
  232. package/dist/hooks/user-profile.js.map +1 -0
  233. package/dist/hooks/user-prompt.d.ts +9 -0
  234. package/dist/hooks/user-prompt.d.ts.map +1 -0
  235. package/dist/hooks/user-prompt.js +8 -0
  236. package/dist/hooks/user-prompt.js.map +1 -0
  237. package/dist/hooks/voice-guide.d.ts +8 -0
  238. package/dist/hooks/voice-guide.d.ts.map +1 -0
  239. package/dist/hooks/voice-guide.js +101 -0
  240. package/dist/hooks/voice-guide.js.map +1 -0
  241. package/dist/plugin.d.ts +55 -0
  242. package/dist/plugin.d.ts.map +1 -0
  243. package/dist/plugin.js +342 -0
  244. package/dist/plugin.js.map +1 -0
  245. package/dist/tools/add-memory.d.ts +17 -0
  246. package/dist/tools/add-memory.d.ts.map +1 -0
  247. package/dist/tools/add-memory.js +60 -0
  248. package/dist/tools/add-memory.js.map +1 -0
  249. package/dist/tools/media-ask.d.ts +14 -0
  250. package/dist/tools/media-ask.d.ts.map +1 -0
  251. package/dist/tools/media-ask.js +224 -0
  252. package/dist/tools/media-ask.js.map +1 -0
  253. package/dist/tools/media-speak.d.ts +16 -0
  254. package/dist/tools/media-speak.d.ts.map +1 -0
  255. package/dist/tools/media-speak.js +78 -0
  256. package/dist/tools/media-speak.js.map +1 -0
  257. package/dist/tools/media-upload.d.ts +14 -0
  258. package/dist/tools/media-upload.d.ts.map +1 -0
  259. package/dist/tools/media-upload.js +155 -0
  260. package/dist/tools/media-upload.js.map +1 -0
  261. package/dist/tools/python-exec.d.ts +13 -0
  262. package/dist/tools/python-exec.d.ts.map +1 -0
  263. package/dist/tools/python-exec.js +99 -0
  264. package/dist/tools/python-exec.js.map +1 -0
  265. package/dist/tools/python-restart.d.ts +11 -0
  266. package/dist/tools/python-restart.d.ts.map +1 -0
  267. package/dist/tools/python-restart.js +33 -0
  268. package/dist/tools/python-restart.js.map +1 -0
  269. package/dist/utils/circuit-breaker.d.ts +23 -0
  270. package/dist/utils/circuit-breaker.d.ts.map +1 -0
  271. package/dist/utils/circuit-breaker.js +75 -0
  272. package/dist/utils/circuit-breaker.js.map +1 -0
  273. package/dist/utils/config-loader.d.ts +20 -0
  274. package/dist/utils/config-loader.d.ts.map +1 -0
  275. package/dist/utils/config-loader.js +194 -0
  276. package/dist/utils/config-loader.js.map +1 -0
  277. package/dist/utils/fallback.d.ts +7 -0
  278. package/dist/utils/fallback.d.ts.map +1 -0
  279. package/dist/utils/fallback.js +20 -0
  280. package/dist/utils/fallback.js.map +1 -0
  281. package/dist/utils/git.d.ts +24 -0
  282. package/dist/utils/git.d.ts.map +1 -0
  283. package/dist/utils/git.js +71 -0
  284. package/dist/utils/git.js.map +1 -0
  285. package/dist/utils/github.d.ts +37 -0
  286. package/dist/utils/github.d.ts.map +1 -0
  287. package/dist/utils/github.js +53 -0
  288. package/dist/utils/github.js.map +1 -0
  289. package/dist/utils/global-path.d.ts +2 -0
  290. package/dist/utils/global-path.d.ts.map +1 -0
  291. package/dist/utils/global-path.js +32 -0
  292. package/dist/utils/global-path.js.map +1 -0
  293. package/dist/utils/logger.d.ts +6 -0
  294. package/dist/utils/logger.d.ts.map +1 -0
  295. package/dist/utils/logger.js +69 -0
  296. package/dist/utils/logger.js.map +1 -0
  297. package/dist/utils/obs-capture.d.ts +7 -0
  298. package/dist/utils/obs-capture.d.ts.map +1 -0
  299. package/dist/utils/obs-capture.js +49 -0
  300. package/dist/utils/obs-capture.js.map +1 -0
  301. package/dist/utils/retry.d.ts +11 -0
  302. package/dist/utils/retry.d.ts.map +1 -0
  303. package/dist/utils/retry.js +45 -0
  304. package/dist/utils/retry.js.map +1 -0
  305. package/dist/utils/self-wiring.d.ts +13 -0
  306. package/dist/utils/self-wiring.d.ts.map +1 -0
  307. package/dist/utils/self-wiring.js +86 -0
  308. package/dist/utils/self-wiring.js.map +1 -0
  309. package/dist/utils/state.d.ts +106 -0
  310. package/dist/utils/state.d.ts.map +1 -0
  311. package/dist/utils/state.js +166 -0
  312. package/dist/utils/state.js.map +1 -0
  313. package/dist/utils/status.d.ts +47 -0
  314. package/dist/utils/status.d.ts.map +1 -0
  315. package/dist/utils/status.js +147 -0
  316. package/dist/utils/status.js.map +1 -0
  317. package/dist/utils/ttl-map.d.ts +17 -0
  318. package/dist/utils/ttl-map.d.ts.map +1 -0
  319. package/dist/utils/ttl-map.js +67 -0
  320. package/dist/utils/ttl-map.js.map +1 -0
  321. package/gateway/dist/automation-engine.js +729 -0
  322. package/gateway/dist/chat/chat-sessions.js +95 -0
  323. package/gateway/dist/chat/graph-runner.js +76 -0
  324. package/gateway/dist/chat/index.js +7 -0
  325. package/gateway/dist/chat/intent-classifier.js +23 -0
  326. package/gateway/dist/config.js +416 -0
  327. package/gateway/dist/core/auth.js +96 -0
  328. package/gateway/dist/core/budget-guard.js +52 -0
  329. package/gateway/dist/core/compression/bm25-index.js +176 -0
  330. package/gateway/dist/core/compression/compression-pipeline.js +157 -0
  331. package/gateway/dist/core/compression/compression-strategy-selector.js +32 -0
  332. package/gateway/dist/core/compression/compression-verifier.js +79 -0
  333. package/gateway/dist/core/compression/diff-compressor.js +69 -0
  334. package/gateway/dist/core/compression/hybrid-compressor.js +104 -0
  335. package/gateway/dist/core/compression/lesson-compactor.js +114 -0
  336. package/gateway/dist/core/compression/memory-index.js +190 -0
  337. package/gateway/dist/core/compression/observation-classifier.js +41 -0
  338. package/gateway/dist/core/compression/observation-deduplicator.js +43 -0
  339. package/gateway/dist/core/compression/rrf-fusion.js +63 -0
  340. package/gateway/dist/core/compression/session-pruner.js +137 -0
  341. package/gateway/dist/core/compression/token-budget-allocator.js +141 -0
  342. package/gateway/dist/core/compression/vector-index.js +117 -0
  343. package/gateway/dist/core/compression/zero-token-compressor.js +80 -0
  344. package/gateway/dist/core/cost/cognitive-router.js +41 -0
  345. package/gateway/dist/core/cost/cost-estimator.js +63 -0
  346. package/gateway/dist/core/cost/types.js +19 -0
  347. package/gateway/dist/core/engine/degradation.js +159 -0
  348. package/gateway/dist/core/engine/goal-worktree-manager.js +131 -0
  349. package/gateway/dist/core/engine/lesson-manager.js +222 -0
  350. package/gateway/dist/core/engine/optimistic-sync.js +100 -0
  351. package/gateway/dist/core/engine/phase-orchestrator.js +153 -0
  352. package/gateway/dist/core/engine/report-generator.js +131 -0
  353. package/gateway/dist/core/engine/state-lock.js +91 -0
  354. package/gateway/dist/core/engine/task-branch-manager.js +102 -0
  355. package/gateway/dist/core/engine/wave-dependency.js +67 -0
  356. package/gateway/dist/core/engine/wave-executor.js +112 -0
  357. package/gateway/dist/core/goal-budget.js +63 -0
  358. package/gateway/dist/core/graph/entity-extractor.js +53 -0
  359. package/gateway/dist/core/graph/graph-searcher.js +120 -0
  360. package/gateway/dist/core/graph/knowledge-graph-manager.js +138 -0
  361. package/gateway/dist/core/graph/relation-inferencer.js +67 -0
  362. package/gateway/dist/core/graph/types.js +2 -0
  363. package/gateway/dist/core/hooks/handoff.js +7 -0
  364. package/gateway/dist/core/hooks/hook-manager.js +91 -0
  365. package/gateway/dist/core/hooks/llm-after.js +7 -0
  366. package/gateway/dist/core/hooks/observation-capture.js +19 -0
  367. package/gateway/dist/core/hooks/session-compacting.js +5 -0
  368. package/gateway/dist/core/hooks/session-ending.js +123 -0
  369. package/gateway/dist/core/hooks/session-start.js +51 -0
  370. package/gateway/dist/core/hooks/tool-before.js +5 -0
  371. package/gateway/dist/core/hooks/tool-executed.js +11 -0
  372. package/gateway/dist/core/hooks/user-prompt.js +7 -0
  373. package/gateway/dist/core/index.js +119 -0
  374. package/gateway/dist/core/langchain/agent-adapter.js +39 -0
  375. package/gateway/dist/core/langchain/index.js +12 -0
  376. package/gateway/dist/core/langchain/memory.js +35 -0
  377. package/gateway/dist/core/langchain/node-runner.js +115 -0
  378. package/gateway/dist/core/langchain/retriever.js +28 -0
  379. package/gateway/dist/core/langgraph/checkpointer.js +200 -0
  380. package/gateway/dist/core/langgraph/graph.js +57 -0
  381. package/gateway/dist/core/langgraph/index.js +25 -0
  382. package/gateway/dist/core/langgraph/loop-state.js +71 -0
  383. package/gateway/dist/core/langgraph/nodes/archive.node.js +27 -0
  384. package/gateway/dist/core/langgraph/nodes/execute.node.js +5 -0
  385. package/gateway/dist/core/langgraph/nodes/plan.node.js +101 -0
  386. package/gateway/dist/core/langgraph/nodes/review.node.js +89 -0
  387. package/gateway/dist/core/langgraph/nodes/sync.node.js +70 -0
  388. package/gateway/dist/core/langgraph/review-parser.js +35 -0
  389. package/gateway/dist/core/langgraph/signature-detector.js +23 -0
  390. package/gateway/dist/core/manager/goal-snapshot.js +73 -0
  391. package/gateway/dist/core/manager/milestone-push.js +81 -0
  392. package/gateway/dist/core/manager/question-ledger.js +121 -0
  393. package/gateway/dist/core/manager/system-rule-templates.js +76 -0
  394. package/gateway/dist/core/mcp/tools.js +562 -0
  395. package/gateway/dist/core/mcp/validator.js +165 -0
  396. package/gateway/dist/core/mcp-server.js +86 -0
  397. package/gateway/dist/core/memory/abstraction-distiller.js +44 -0
  398. package/gateway/dist/core/memory/cognitive-graph.js +103 -0
  399. package/gateway/dist/core/memory/energy-system.js +69 -0
  400. package/gateway/dist/core/memory/extractor.js +164 -0
  401. package/gateway/dist/core/memory/harmonic-index.js +530 -0
  402. package/gateway/dist/core/memory/harmonic-types.js +6 -0
  403. package/gateway/dist/core/memory/injector.js +111 -0
  404. package/gateway/dist/core/memory/l5-store.js +141 -0
  405. package/gateway/dist/core/memory/matcher.js +66 -0
  406. package/gateway/dist/core/memory/merger.js +95 -0
  407. package/gateway/dist/core/memory/migrate-v6.1.js +133 -0
  408. package/gateway/dist/core/memory/minhash-merger.js +195 -0
  409. package/gateway/dist/core/memory/observation-service.js +87 -0
  410. package/gateway/dist/core/memory/privacy-filter.js +163 -0
  411. package/gateway/dist/core/memory/reranker.js +208 -0
  412. package/gateway/dist/core/memory/review-scheduler.js +100 -0
  413. package/gateway/dist/core/memory/salience-perceptor.js +24 -0
  414. package/gateway/dist/core/memory/store.js +284 -0
  415. package/gateway/dist/core/memory/t1-store.js +183 -0
  416. package/gateway/dist/core/memory/t1-to-t2-compressor.js +119 -0
  417. package/gateway/dist/core/memory/validator.js +66 -0
  418. package/gateway/dist/core/plugin.js +902 -0
  419. package/gateway/dist/core/skills/mafw-execute/entry.js +226 -0
  420. package/gateway/dist/core/skills/mafw-goal/entry.js +203 -0
  421. package/gateway/dist/core/skills/mafw-plan/entry.js +184 -0
  422. package/gateway/dist/core/skills/mafw-review/entry.js +200 -0
  423. package/gateway/dist/core/storage/file-storage.js +120 -0
  424. package/gateway/dist/core/storage/sqlite-storage.js +205 -0
  425. package/gateway/dist/core/storage/types.js +2 -0
  426. package/gateway/dist/core/tools/archive-worktree.js +208 -0
  427. package/gateway/dist/core/tools/remote-cli.js +106 -0
  428. package/gateway/dist/core/tools/run-ask-user.js +79 -0
  429. package/gateway/dist/core/tools/run-execute-wave.js +32 -0
  430. package/gateway/dist/core/tools/run-plan.js +42 -0
  431. package/gateway/dist/core/tools/run-record-feedback.js +55 -0
  432. package/gateway/dist/core/tools/run-review.js +46 -0
  433. package/gateway/dist/core/tools/write-lesson.js +97 -0
  434. package/gateway/dist/core/utils/circuit-breaker.js +74 -0
  435. package/gateway/dist/core/utils/config-loader.js +203 -0
  436. package/gateway/dist/core/utils/fallback.js +19 -0
  437. package/gateway/dist/core/utils/git.js +70 -0
  438. package/gateway/dist/core/utils/github.js +52 -0
  439. package/gateway/dist/core/utils/global-path.js +41 -0
  440. package/gateway/dist/core/utils/logger.js +71 -0
  441. package/gateway/dist/core/utils/retry.js +44 -0
  442. package/gateway/dist/core/utils/state.js +175 -0
  443. package/gateway/dist/core/utils/status.js +156 -0
  444. package/gateway/dist/cost/service.js +17 -0
  445. package/gateway/dist/dashboard/api.js +1156 -0
  446. package/gateway/dist/dashboard/public/assets/index-DOXm39dm.css +1 -0
  447. package/gateway/dist/dashboard/public/index.html +13 -0
  448. package/gateway/dist/dashboard/server.js +140 -0
  449. package/gateway/dist/dashboard/types.js +2 -0
  450. package/gateway/dist/desktop-client.js +124 -0
  451. package/gateway/dist/eval-endpoint.js +219 -0
  452. package/gateway/dist/event-bus.js +7 -0
  453. package/gateway/dist/feedback/anchor-promotion.js +99 -0
  454. package/gateway/dist/feedback/deferred-memory.js +13 -0
  455. package/gateway/dist/feedback/miss-attribution.js +20 -0
  456. package/gateway/dist/graph/anchor-graph-store.js +113 -0
  457. package/gateway/dist/graph/anchor-graph.js +48 -0
  458. package/gateway/dist/health.js +53 -0
  459. package/gateway/dist/heartbeat.js +109 -0
  460. package/gateway/dist/index.js +6377 -0
  461. package/gateway/dist/interceptors/memory-injector.js +79 -0
  462. package/gateway/dist/ledger.js +121 -0
  463. package/gateway/dist/loop-monitor.js +74 -0
  464. package/gateway/dist/mcp/handlers/add-memory.js +113 -0
  465. package/gateway/dist/mcp/handlers/ask-user.js +63 -0
  466. package/gateway/dist/mcp/handlers/commit-heuristic.js +19 -0
  467. package/gateway/dist/mcp/handlers/create-goal.js +83 -0
  468. package/gateway/dist/mcp/handlers/desktop-click.js +18 -0
  469. package/gateway/dist/mcp/handlers/desktop-get-state.js +15 -0
  470. package/gateway/dist/mcp/handlers/desktop-navigate.js +18 -0
  471. package/gateway/dist/mcp/handlers/desktop-screenshot.js +16 -0
  472. package/gateway/dist/mcp/handlers/desktop-scroll.js +19 -0
  473. package/gateway/dist/mcp/handlers/desktop-type.js +19 -0
  474. package/gateway/dist/mcp/handlers/draft-automation-rule.js +45 -0
  475. package/gateway/dist/mcp/handlers/get-automation-history.js +15 -0
  476. package/gateway/dist/mcp/handlers/get-automation-rule.js +26 -0
  477. package/gateway/dist/mcp/handlers/get-axioms.js +14 -0
  478. package/gateway/dist/mcp/handlers/get-deltas.js +77 -0
  479. package/gateway/dist/mcp/handlers/get-memory.js +67 -0
  480. package/gateway/dist/mcp/handlers/get-model-route.js +16 -0
  481. package/gateway/dist/mcp/handlers/get-triage-item.js +17 -0
  482. package/gateway/dist/mcp/handlers/list-automation-rules.js +24 -0
  483. package/gateway/dist/mcp/handlers/list-triage-items.js +14 -0
  484. package/gateway/dist/mcp/handlers/load-state.js +54 -0
  485. package/gateway/dist/mcp/handlers/manager-answer-question.js +67 -0
  486. package/gateway/dist/mcp/handlers/manager-cancel-goal.js +58 -0
  487. package/gateway/dist/mcp/handlers/manager-get-evidence.js +57 -0
  488. package/gateway/dist/mcp/handlers/manager-get-goal-status.js +58 -0
  489. package/gateway/dist/mcp/handlers/manager-list-goals.js +60 -0
  490. package/gateway/dist/mcp/handlers/manager-list-pending-questions.js +67 -0
  491. package/gateway/dist/mcp/handlers/manager-set-goal.js +78 -0
  492. package/gateway/dist/mcp/handlers/merge-memory.js +184 -0
  493. package/gateway/dist/mcp/handlers/pin-memory.js +53 -0
  494. package/gateway/dist/mcp/handlers/propose-triage-decision.js +38 -0
  495. package/gateway/dist/mcp/handlers/record-feedback.js +59 -0
  496. package/gateway/dist/mcp/handlers/resolve-merge.js +92 -0
  497. package/gateway/dist/mcp/handlers/restart-agent.js +22 -0
  498. package/gateway/dist/mcp/handlers/run-automation.js +28 -0
  499. package/gateway/dist/mcp/handlers/search-hybrid.js +181 -0
  500. package/gateway/dist/mcp/handlers/supersede-memory.js +47 -0
  501. package/gateway/dist/mcp/handlers/update-state.js +59 -0
  502. package/gateway/dist/mcp/handlers/validate-rule.js +39 -0
  503. package/gateway/dist/mcp/sse-transport.js +97 -0
  504. package/gateway/dist/mcp/streamable-endpoint.js +98 -0
  505. package/gateway/dist/mcp/tool-registry.js +630 -0
  506. package/gateway/dist/media/media-agent.js +755 -0
  507. package/gateway/dist/media/media-plugin-loader.js +254 -0
  508. package/gateway/dist/media/media-runtime-executor.js +227 -0
  509. package/gateway/dist/media/media-service.js +199 -0
  510. package/gateway/dist/media/pi-adapter.js +159 -0
  511. package/gateway/dist/media/plugin-context.js +14 -0
  512. package/gateway/dist/media/tts-service.js +173 -0
  513. package/gateway/dist/memory/abstraction-matcher.js +28 -0
  514. package/gateway/dist/memory/consolidation-service.js +209 -0
  515. package/gateway/dist/memory/derived-terms.js +50 -0
  516. package/gateway/dist/memory/embedding-provider.js +330 -0
  517. package/gateway/dist/memory/embedding-runtime.js +174 -0
  518. package/gateway/dist/memory/event-log.js +84 -0
  519. package/gateway/dist/memory/gateway-db.js +465 -0
  520. package/gateway/dist/memory/harmonic-file-store.js +338 -0
  521. package/gateway/dist/memory/llamacpp-provider.js +370 -0
  522. package/gateway/dist/memory/okf-index-renderer.js +61 -0
  523. package/gateway/dist/memory/okf-migrator.js +71 -0
  524. package/gateway/dist/memory/okf-parser.js +57 -0
  525. package/gateway/dist/memory/okf-writer.js +107 -0
  526. package/gateway/dist/memory/service.js +81 -0
  527. package/gateway/dist/memory/t1-db.js +139 -0
  528. package/gateway/dist/memory/unmerge-cleanup.js +82 -0
  529. package/gateway/dist/memory/vector-store.js +246 -0
  530. package/gateway/dist/memory/write-queue.js +31 -0
  531. package/gateway/dist/metrics/collector.js +28 -0
  532. package/gateway/dist/mobile/device-store.js +123 -0
  533. package/gateway/dist/mobile/mdns-advertiser.js +55 -0
  534. package/gateway/dist/mobile/media-proxy.js +292 -0
  535. package/gateway/dist/mobile/pairing.js +139 -0
  536. package/gateway/dist/mobile/push-gateway.js +126 -0
  537. package/gateway/dist/mobile/tts-artifact.js +43 -0
  538. package/gateway/dist/opencode-adapter.js +241 -0
  539. package/gateway/dist/orchestration/outcome-recorder.js +218 -0
  540. package/gateway/dist/orchestration/policy.js +57 -0
  541. package/gateway/dist/orchestration/registry.js +47 -0
  542. package/gateway/dist/plugins/hub.js +188 -0
  543. package/gateway/dist/poll.js +110 -0
  544. package/gateway/dist/python/kernel-service.js +762 -0
  545. package/gateway/dist/recall/constraints-migrate.js +108 -0
  546. package/gateway/dist/recall/data-dir-migrate.js +120 -0
  547. package/gateway/dist/recall/gateway-db-migrate.js +134 -0
  548. package/gateway/dist/recall/index-scan.js +437 -0
  549. package/gateway/dist/recall/inject-format.js +222 -0
  550. package/gateway/dist/recall/legacy-t1-archive.js +86 -0
  551. package/gateway/dist/recall/memory-worker.js +128 -0
  552. package/gateway/dist/recall/pipeline-rules.js +76 -0
  553. package/gateway/dist/recall/recall-context.js +297 -0
  554. package/gateway/dist/recall/reflect-cursor.js +50 -0
  555. package/gateway/dist/recall/reflection.js +318 -0
  556. package/gateway/dist/recall/session-worker-pool.js +100 -0
  557. package/gateway/dist/recall/stale-verify.js +72 -0
  558. package/gateway/dist/recall/step-inject.js +173 -0
  559. package/gateway/dist/recall/time-anchor.js +143 -0
  560. package/gateway/dist/recall/ttl-map.js +72 -0
  561. package/gateway/dist/recall/turn-completion.js +44 -0
  562. package/gateway/dist/recall/turn-pipeline.js +150 -0
  563. package/gateway/dist/recovery.js +158 -0
  564. package/gateway/dist/resources/sdk-session.js +202 -0
  565. package/gateway/dist/retrieval/guided-retriever.js +131 -0
  566. package/gateway/dist/retrieval/idf-stats.js +31 -0
  567. package/gateway/dist/routes/embedding-config.js +100 -0
  568. package/gateway/dist/routes/goal-sessions.js +45 -0
  569. package/gateway/dist/routes/manager-rotate.js +55 -0
  570. package/gateway/dist/routes/media-switch.js +78 -0
  571. package/gateway/dist/routes/model-config.js +132 -0
  572. package/gateway/dist/routes/note-board.js +51 -0
  573. package/gateway/dist/routes/permission.js +43 -0
  574. package/gateway/dist/routes/pinned-recall.js +36 -0
  575. package/gateway/dist/routes/plugins.js +77 -0
  576. package/gateway/dist/routes/restart-agent.js +28 -0
  577. package/gateway/dist/routes/runtime-switch.js +80 -0
  578. package/gateway/dist/routes/session-branch.js +63 -0
  579. package/gateway/dist/routes/session-mutations.js +58 -0
  580. package/gateway/dist/routes/session-summarize.js +49 -0
  581. package/gateway/dist/routes/usage-plugins.js +251 -0
  582. package/gateway/dist/runtime/agent-definition.js +2 -0
  583. package/gateway/dist/runtime/auth.js +80 -0
  584. package/gateway/dist/runtime/completion-http.js +97 -0
  585. package/gateway/dist/runtime/contract.js +37 -0
  586. package/gateway/dist/runtime/loader.js +240 -0
  587. package/gateway/dist/runtime/normalize.js +59 -0
  588. package/gateway/dist/runtime/opencode-runtime.js +336 -0
  589. package/gateway/dist/runtime/pi/pi-agent-config.js +294 -0
  590. package/gateway/dist/runtime/pi/pi-approval-bridge.js +44 -0
  591. package/gateway/dist/runtime/pi/pi-approval-extension.js +62 -0
  592. package/gateway/dist/runtime/pi/pi-events.js +122 -0
  593. package/gateway/dist/runtime/pi/pi-messages.js +20 -0
  594. package/gateway/dist/runtime/pi/pi-provider.js +19 -0
  595. package/gateway/dist/runtime/pi/pi-session-storage.js +44 -0
  596. package/gateway/dist/runtime/pi/pi-session.js +328 -0
  597. package/gateway/dist/runtime/plugins/pi-runtime.js +285 -0
  598. package/gateway/dist/runtime/serve-for-runtime.js +22 -0
  599. package/gateway/dist/runtime/serve-sidecar.js +164 -0
  600. package/gateway/dist/runtime/serve-supervisor.js +80 -0
  601. package/gateway/dist/self-update.js +309 -0
  602. package/gateway/dist/skills/manager-agent-config.js +37 -0
  603. package/gateway/dist/skills/manager-identity.js +11 -0
  604. package/gateway/dist/skills/memory-curator-agent.js +55 -0
  605. package/gateway/dist/trajectory/api.js +154 -0
  606. package/gateway/dist/trajectory/backfill-provider.js +41 -0
  607. package/gateway/dist/trajectory/collector.js +337 -0
  608. package/gateway/dist/trajectory/trajectory-store.js +336 -0
  609. package/gateway/dist/trajectory/types.js +17 -0
  610. package/gateway/dist/tray/index.js +131 -0
  611. package/gateway/dist/tray/tray.ps1 +96 -0
  612. package/gateway/dist/types.js +2 -0
  613. package/gateway/dist/usage/builtin-plugins/commandcode.js +71 -0
  614. package/gateway/dist/usage/builtin-plugins/deepseek.js +56 -0
  615. package/gateway/dist/usage/builtin-plugins/gateway.js +127 -0
  616. package/gateway/dist/usage/builtin-plugins/kimi-for-coding.js +93 -0
  617. package/gateway/dist/usage/builtin-plugins/kimi.js +48 -0
  618. package/gateway/dist/usage/builtin-plugins/opencode-go.js +54 -0
  619. package/gateway/dist/usage/builtin-plugins/openrouter.js +48 -0
  620. package/gateway/dist/usage/builtin-plugins/siliconflow-cn.js +49 -0
  621. package/gateway/dist/usage/builtin-plugins/zhipuai-coding-plan.js +44 -0
  622. package/gateway/dist/usage/model-prices.js +39 -0
  623. package/gateway/dist/usage/model-stats.js +21 -0
  624. package/gateway/dist/usage/plugin-context.js +79 -0
  625. package/gateway/dist/usage/plugin-loader.js +280 -0
  626. package/gateway/dist/usage/templates.js +227 -0
  627. package/gateway/dist/usage/types.js +2 -0
  628. package/gateway/dist/usage/usage-poller.js +216 -0
  629. package/gateway/package.json +37 -0
  630. package/package.json +78 -0
  631. package/packages/tui/dist/cli.js +4664 -0
@@ -0,0 +1,4664 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'module'; const require = createRequire(import.meta.url);
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
10
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
11
+ }) : x)(function(x) {
12
+ if (typeof require !== "undefined") return require.apply(this, arguments);
13
+ throw Error('Dynamic require of "' + x + '" is not supported');
14
+ });
15
+ var __esm = (fn, res) => function __init() {
16
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
17
+ };
18
+ var __commonJS = (cb, mod) => function __require2() {
19
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
20
+ };
21
+ var __export = (target, all) => {
22
+ for (var name in all)
23
+ __defProp(target, name, { get: all[name], enumerable: true });
24
+ };
25
+ var __copyProps = (to, from, except, desc) => {
26
+ if (from && typeof from === "object" || typeof from === "function") {
27
+ for (let key of __getOwnPropNames(from))
28
+ if (!__hasOwnProp.call(to, key) && key !== except)
29
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
30
+ }
31
+ return to;
32
+ };
33
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
34
+ // If the importer is in node compatibility mode or this is not an ESM
35
+ // file that has been converted to a CommonJS file using a Babel-
36
+ // compatible transform (i.e. "__esModule" has not been set), then set
37
+ // "default" to the CommonJS "module.exports" for node compatibility.
38
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
39
+ mod
40
+ ));
41
+
42
+ // ../gateway-sdk/dist/sse.js
43
+ var require_sse = __commonJS({
44
+ "../gateway-sdk/dist/sse.js"(exports) {
45
+ "use strict";
46
+ Object.defineProperty(exports, "__esModule", { value: true });
47
+ exports.SSEConnection = void 0;
48
+ var SSEConnection = class {
49
+ eventSource = null;
50
+ listeners = /* @__PURE__ */ new Map();
51
+ _connected = false;
52
+ get connected() {
53
+ return this._connected;
54
+ }
55
+ connect(baseUrl) {
56
+ this.disconnect();
57
+ this.eventSource = new EventSource(`${baseUrl}/api/events`);
58
+ this.eventSource.onopen = () => {
59
+ this._connected = true;
60
+ };
61
+ this.eventSource.onerror = () => {
62
+ this._connected = false;
63
+ };
64
+ this.setupMessageHandler();
65
+ }
66
+ connectToSession(baseUrl, sessionID) {
67
+ this.disconnect();
68
+ this.eventSource = new EventSource(`${baseUrl}/api/events?sessionID=${encodeURIComponent(sessionID)}`);
69
+ this.eventSource.onopen = () => {
70
+ this._connected = true;
71
+ };
72
+ this.eventSource.onerror = () => {
73
+ this._connected = false;
74
+ };
75
+ this.setupMessageHandler();
76
+ }
77
+ setupMessageHandler() {
78
+ if (!this.eventSource)
79
+ return;
80
+ this.eventSource.onmessage = (e) => {
81
+ try {
82
+ let data = JSON.parse(e.data);
83
+ if (data && data.type === "opencode_event" && data.data && typeof data.data === "object") {
84
+ data = data.data;
85
+ }
86
+ const type = data.type || "message";
87
+ const set = this.listeners.get(type);
88
+ if (set)
89
+ for (const cb of set)
90
+ cb(data);
91
+ const wildcard = this.listeners.get("*");
92
+ if (wildcard)
93
+ for (const cb of wildcard)
94
+ cb(data);
95
+ } catch {
96
+ }
97
+ };
98
+ }
99
+ disconnect() {
100
+ this.eventSource?.close();
101
+ this.eventSource = null;
102
+ this._connected = false;
103
+ }
104
+ on(eventType, cb) {
105
+ if (!this.listeners.has(eventType)) {
106
+ this.listeners.set(eventType, /* @__PURE__ */ new Set());
107
+ }
108
+ this.listeners.get(eventType).add(cb);
109
+ return () => {
110
+ this.listeners.get(eventType)?.delete(cb);
111
+ };
112
+ }
113
+ /** @internal For testing: emit an event without a real SSE connection */
114
+ emit(event) {
115
+ const type = event.type || "message";
116
+ const set = this.listeners.get(type);
117
+ if (set)
118
+ for (const cb of set)
119
+ cb(event);
120
+ const wildcard = this.listeners.get("*");
121
+ if (wildcard)
122
+ for (const cb of wildcard)
123
+ cb(event);
124
+ }
125
+ };
126
+ exports.SSEConnection = SSEConnection;
127
+ }
128
+ });
129
+
130
+ // ../gateway-sdk/dist/client.js
131
+ var require_client = __commonJS({
132
+ "../gateway-sdk/dist/client.js"(exports) {
133
+ "use strict";
134
+ Object.defineProperty(exports, "__esModule", { value: true });
135
+ exports.MafwClient = void 0;
136
+ var crypto_1 = __require("crypto");
137
+ var sse_1 = require_sse();
138
+ var MafwClient2 = class {
139
+ baseUrl;
140
+ _sse;
141
+ fetchImpl;
142
+ constructor(opts) {
143
+ this.baseUrl = typeof opts === "string" ? opts : opts?.baseUrl || "http://localhost:3000";
144
+ this.fetchImpl = typeof opts === "object" && opts?.fetchImpl ? opts.fetchImpl : fetch.bind(globalThis);
145
+ this._sse = new sse_1.SSEConnection();
146
+ }
147
+ async request(path, init) {
148
+ const res = await this.fetchImpl(`${this.baseUrl}${path}`, {
149
+ headers: { "Content-Type": "application/json" },
150
+ ...init
151
+ });
152
+ if (!res.ok)
153
+ throw new Error(`HTTP ${res.status}: ${res.statusText}`);
154
+ const ct = res.headers?.get?.("content-type") ?? "";
155
+ if (ct.includes("text/html")) {
156
+ throw new Error(`Received HTML instead of JSON from ${path} \u2014 gateway is older than the SDK (route missing). Update/restart the gateway.`);
157
+ }
158
+ return res.json();
159
+ }
160
+ // ── Session ──
161
+ session = {
162
+ create: async (params) => {
163
+ return this.request("/api/session", {
164
+ method: "POST",
165
+ body: JSON.stringify(params || {})
166
+ });
167
+ },
168
+ get: async (params) => {
169
+ return this.request(`/api/sessions/${params.path.id}`);
170
+ },
171
+ list: async (params) => {
172
+ const pid = params?.query?.projectID;
173
+ const query = pid ? `?projectID=${encodeURIComponent(pid)}` : "";
174
+ const data = await this.request(`/api/sessions${query}`);
175
+ if (!data || !Array.isArray(data.sessions))
176
+ return [];
177
+ return data.sessions;
178
+ },
179
+ delete: async (params) => {
180
+ await this.request(`/api/sessions/${params.path.id}`, { method: "DELETE" });
181
+ },
182
+ rename: async (params) => {
183
+ await this.request(`/api/sessions/${params.path.id}`, {
184
+ method: "PATCH",
185
+ headers: { "Content-Type": "application/json" },
186
+ body: JSON.stringify({ title: params.body.title })
187
+ });
188
+ },
189
+ messages: async (params) => {
190
+ const q = new URLSearchParams();
191
+ if (params.query?.limit)
192
+ q.set("limit", String(params.query.limit));
193
+ if (params.query?.before)
194
+ q.set("before", params.query.before);
195
+ return this.request(`/api/sessions/${params.path.id}/messages?${q}`);
196
+ },
197
+ todo: async (params) => {
198
+ return this.request(`/api/sessions/${params.path.id}/todo`);
199
+ },
200
+ children: async (params) => {
201
+ const data = await this.request(`/api/sessions/${params.path.id}/children`);
202
+ return data.items || [];
203
+ },
204
+ abort: async (params) => {
205
+ await this.request(`/api/session/${params.path.id}/abort`, { method: "POST" });
206
+ },
207
+ fork: async (params) => {
208
+ return this.request(`/api/sessions/${params.path.id}/fork`, {
209
+ method: "POST",
210
+ headers: { "Content-Type": "application/json" },
211
+ body: JSON.stringify(params.body ?? {})
212
+ });
213
+ },
214
+ revert: async (params) => {
215
+ await this.request(`/api/sessions/${params.path.id}/revert`, {
216
+ method: "POST",
217
+ headers: { "Content-Type": "application/json" },
218
+ body: JSON.stringify(params.body)
219
+ });
220
+ },
221
+ unrevert: async (params) => {
222
+ await this.request(`/api/sessions/${params.path.id}/unrevert`, { method: "POST" });
223
+ },
224
+ summarize: async (params) => {
225
+ await this.request(`/api/session/${params.path.id}/summarize`, {
226
+ method: "POST",
227
+ body: JSON.stringify(params.body ?? {})
228
+ });
229
+ },
230
+ prompt: async (params) => {
231
+ return this.request(`/api/session/${params.path.id}/prompt`, {
232
+ method: "POST",
233
+ body: JSON.stringify(params.body)
234
+ });
235
+ },
236
+ promptAsync: async (params) => {
237
+ const res = await fetch(`${this.baseUrl}/api/session/${params.path.id}/promptAsync`, {
238
+ method: "POST",
239
+ headers: { "Content-Type": "application/json" },
240
+ body: JSON.stringify({
241
+ message: params.body.message,
242
+ parts: params.body.parts,
243
+ agent: params.body.agent,
244
+ model: params.body.model
245
+ })
246
+ });
247
+ if (!res.ok)
248
+ throw new Error(`HTTP ${res.status}: ${res.statusText}`);
249
+ },
250
+ trajectory: async (params) => {
251
+ const q = new URLSearchParams();
252
+ if (params.query?.limit)
253
+ q.set("limit", String(params.query.limit));
254
+ if (params.query?.before_turn)
255
+ q.set("before_turn", String(params.query.before_turn));
256
+ if (params.query?.rebuild)
257
+ q.set("rebuild", "1");
258
+ return this.request(`/api/sessions/${params.path.id}/trajectory?${q}`);
259
+ },
260
+ tokenSummary: async (params) => {
261
+ return this.request(`/api/sessions/${params.path.id}/token-summary`);
262
+ },
263
+ usageSummary: async (params) => {
264
+ const q = new URLSearchParams();
265
+ if (params.query?.sessionID)
266
+ q.set("sessionID", params.query.sessionID);
267
+ if (params.query?.projectID)
268
+ q.set("projectID", params.query.projectID);
269
+ return this.request(`/api/usage/summary?${q}`);
270
+ },
271
+ usage: async (params) => {
272
+ const q = new URLSearchParams();
273
+ if (params?.sessionID)
274
+ q.set("sessionID", params.sessionID);
275
+ if (params?.projectID)
276
+ q.set("projectID", params.projectID);
277
+ const qs = q.toString();
278
+ return this.request(`/api/usage${qs ? "?" + qs : ""}`);
279
+ },
280
+ usagePlugins: async () => {
281
+ return this.request("/api/usage/plugins");
282
+ },
283
+ usagePluginsReload: async () => {
284
+ return this.request("/api/usage/plugins/reload", { method: "POST" });
285
+ },
286
+ usagePluginsCreate: async (body) => {
287
+ return this.request("/api/usage/plugins/create", { method: "POST", body: JSON.stringify(body) });
288
+ },
289
+ usagePluginSource: async (name) => {
290
+ return this.request(`/api/usage/plugins/${encodeURIComponent(name)}/source`);
291
+ },
292
+ usagePluginSourceSave: async (name, source) => {
293
+ return this.request(`/api/usage/plugins/${encodeURIComponent(name)}/source`, { method: "PUT", body: JSON.stringify({ source }) });
294
+ },
295
+ usagePluginDelete: async (name) => {
296
+ return this.request(`/api/usage/plugins/${encodeURIComponent(name)}`, { method: "DELETE" });
297
+ },
298
+ usagePluginTest: async (name) => {
299
+ return this.request(`/api/usage/plugins/${encodeURIComponent(name)}/test`, { method: "POST" });
300
+ },
301
+ events: async (params) => {
302
+ const sse = new sse_1.SSEConnection();
303
+ sse.connectToSession(this.baseUrl, params.path.id);
304
+ return {
305
+ on: (event, cb) => {
306
+ if (event === "data") {
307
+ sse.on("*", cb);
308
+ } else {
309
+ sse.on(event, cb);
310
+ }
311
+ }
312
+ };
313
+ },
314
+ command: async (params) => {
315
+ const res = await fetch(`${this.baseUrl}/api/session/${params.path.id}/command`, {
316
+ method: "POST",
317
+ headers: { "Content-Type": "application/json" },
318
+ body: JSON.stringify(params.body)
319
+ });
320
+ if (!res.ok)
321
+ throw new Error(`HTTP ${res.status}: ${res.statusText}`);
322
+ }
323
+ };
324
+ // ── Commands & skills (opencode serve, proxied by the gateway) ──
325
+ command = {
326
+ list: async (directory) => {
327
+ const query = directory ? `?directory=${encodeURIComponent(directory)}` : "";
328
+ const data = await this.request(`/command${query}`);
329
+ if (Array.isArray(data))
330
+ return data;
331
+ return data.items || data.commands || [];
332
+ }
333
+ };
334
+ skill = {
335
+ list: async (directory) => {
336
+ const query = directory ? `?directory=${encodeURIComponent(directory)}` : "";
337
+ const data = await this.request(`/skill${query}`);
338
+ if (Array.isArray(data))
339
+ return data;
340
+ return data.items || data.skills || [];
341
+ }
342
+ };
343
+ // ── MAFW native commands (desktop slash panel) ──
344
+ mafwCommands = {
345
+ run: async (params) => {
346
+ return this.request("/api/mafw-commands/run", {
347
+ method: "POST",
348
+ body: JSON.stringify(params)
349
+ });
350
+ }
351
+ };
352
+ // ── Manager session (authoritative per-project manager, from gateway DB) ──
353
+ manager = {
354
+ session: async (projectDir) => {
355
+ const q = projectDir ? `?projectDir=${encodeURIComponent(projectDir)}` : "";
356
+ try {
357
+ return await this.request(`/api/manager/session${q}`);
358
+ } catch {
359
+ return null;
360
+ }
361
+ },
362
+ rotate: async (projectDir, reason) => {
363
+ return await this.request("/api/manager/session/rotate", {
364
+ method: "POST",
365
+ headers: { "Content-Type": "application/json" },
366
+ body: JSON.stringify({ projectDir, reason })
367
+ });
368
+ }
369
+ };
370
+ // ── Project ──
371
+ project = {
372
+ list: async () => {
373
+ const data = await this.request("/api/projects");
374
+ return data.projects || [];
375
+ },
376
+ current: async () => {
377
+ const data = await this.request("/api/projects/current");
378
+ if (data.project)
379
+ return data.project;
380
+ return { id: data.projectDir || ".", worktree: data.projectDir || "." };
381
+ },
382
+ setCurrent: async (path) => {
383
+ await this.request("/api/projects/register", {
384
+ method: "POST",
385
+ body: JSON.stringify({ projectDir: path, mafwDir: path + "/.mafw" })
386
+ });
387
+ }
388
+ };
389
+ // ── Event ──
390
+ event = {
391
+ subscribe: async () => {
392
+ this._sse.connect(this.baseUrl);
393
+ return {
394
+ on: (event, cb) => {
395
+ if (event === "data") {
396
+ this._sse.on("*", cb);
397
+ } else {
398
+ this._sse.on(event, cb);
399
+ }
400
+ }
401
+ };
402
+ },
403
+ subscribeToSession: async (sessionID) => {
404
+ this._sse.connectToSession(this.baseUrl, sessionID);
405
+ return {
406
+ on: (event, cb) => {
407
+ if (event === "data") {
408
+ this._sse.on("*", cb);
409
+ } else {
410
+ this._sse.on(event, cb);
411
+ }
412
+ }
413
+ };
414
+ },
415
+ /** SSE 连接状态(onopen/onerror 维护;供监督器健康轮询)。 */
416
+ connected: () => this._sse.connected
417
+ };
418
+ // ── Runtime ──
419
+ runtime = {
420
+ /** Get active runtime identity, capabilities, and plugin scan state. */
421
+ get: async () => {
422
+ return this.request("/api/runtime");
423
+ },
424
+ /**
425
+ * Switch active runtime plugin.
426
+ * @param plugin - Plugin name (empty string switches to builtin opencode).
427
+ * @returns The new active runtime state.
428
+ */
429
+ switch: async (plugin) => {
430
+ const res = await fetch(`${this.baseUrl}/api/runtime/switch`, {
431
+ method: "POST",
432
+ headers: { "Content-Type": "application/json" },
433
+ body: JSON.stringify({ plugin })
434
+ });
435
+ if (!res.ok) {
436
+ const body = await res.json().catch(() => ({}));
437
+ throw new Error(body.error || `Runtime switch failed: ${res.status}`);
438
+ }
439
+ return res.json();
440
+ },
441
+ /** Restart the agent runtime (opencode serve). */
442
+ restartAgent: async () => {
443
+ const res = await fetch(`${this.baseUrl}/api/runtime/restart-agent`, {
444
+ method: "POST",
445
+ headers: { "Content-Type": "application/json" }
446
+ });
447
+ if (!res.ok) {
448
+ const body = await res.json().catch(() => ({}));
449
+ throw new Error(body.error || `Agent restart failed: ${res.status}`);
450
+ }
451
+ return res.json();
452
+ }
453
+ };
454
+ // ── Plugins Hub ──
455
+ plugins = {
456
+ list: async () => this.request("/api/plugins"),
457
+ install: async (input) => {
458
+ const res = await this.fetchImpl(`${this.baseUrl}/api/plugins/install`, {
459
+ method: "POST",
460
+ headers: { "Content-Type": "application/json" },
461
+ body: JSON.stringify({ ...input, overwrite: !!input.overwrite })
462
+ });
463
+ if (!res.ok) {
464
+ const body = await res.json().catch(() => ({}));
465
+ throw new Error(body.error || `Plugin install failed: ${res.status}`);
466
+ }
467
+ return res.json();
468
+ },
469
+ enable: async (type, filename) => {
470
+ const res = await this.fetchImpl(`${this.baseUrl}/api/plugins/enable`, {
471
+ method: "POST",
472
+ headers: { "Content-Type": "application/json" },
473
+ body: JSON.stringify({ type, filename })
474
+ });
475
+ if (!res.ok) {
476
+ const body = await res.json().catch(() => ({}));
477
+ throw new Error(body.error || `Plugin enable failed: ${res.status}`);
478
+ }
479
+ return res.json();
480
+ },
481
+ disable: async (type, filename) => {
482
+ const res = await this.fetchImpl(`${this.baseUrl}/api/plugins/disable`, {
483
+ method: "POST",
484
+ headers: { "Content-Type": "application/json" },
485
+ body: JSON.stringify({ type, filename })
486
+ });
487
+ if (!res.ok) {
488
+ const body = await res.json().catch(() => ({}));
489
+ throw new Error(body.error || `Plugin disable failed: ${res.status}`);
490
+ }
491
+ return res.json();
492
+ },
493
+ delete: async (type, filename) => {
494
+ const res = await this.fetchImpl(`${this.baseUrl}/api/plugins/delete`, {
495
+ method: "POST",
496
+ headers: { "Content-Type": "application/json" },
497
+ body: JSON.stringify({ type, filename })
498
+ });
499
+ if (!res.ok) {
500
+ const body = await res.json().catch(() => ({}));
501
+ throw new Error(body.error || `Plugin delete failed: ${res.status}`);
502
+ }
503
+ return res.json();
504
+ }
505
+ };
506
+ // ── Config ──
507
+ config = {
508
+ get: async (key) => {
509
+ const data = await this.request("/api/config");
510
+ return key ? data[key] : data;
511
+ },
512
+ set: async (key, value) => {
513
+ const current = await this.request("/api/config");
514
+ current[key] = value;
515
+ await fetch(`${this.baseUrl}/api/config`, {
516
+ method: "PUT",
517
+ headers: { "Content-Type": "application/json" },
518
+ body: JSON.stringify(current)
519
+ });
520
+ }
521
+ };
522
+ // ── OpenCode config (native opencode /config) ──
523
+ opencodeConfig = {
524
+ get: async () => {
525
+ const data = await this.request("/api/opencode-config");
526
+ return data.config;
527
+ },
528
+ update: async (config) => {
529
+ const res = await fetch(`${this.baseUrl}/api/opencode-config`, {
530
+ method: "PATCH",
531
+ headers: { "Content-Type": "application/json" },
532
+ body: JSON.stringify(config)
533
+ });
534
+ if (!res.ok)
535
+ throw new Error(`OpenCodeConfig update failed: ${res.status}`);
536
+ return res.json();
537
+ }
538
+ };
539
+ // ── Goals ──
540
+ goals = {
541
+ list: async () => {
542
+ const data = await this.request("/api/goals");
543
+ return data.goals || [];
544
+ },
545
+ get: async (id) => {
546
+ try {
547
+ return await this.request(`/api/goals/${id}`);
548
+ } catch (e) {
549
+ if (e.message?.includes("HTTP 404"))
550
+ return null;
551
+ throw e;
552
+ }
553
+ },
554
+ sessions: async (goalId) => {
555
+ const data = await this.request(`/api/goals/${encodeURIComponent(goalId)}/sessions`);
556
+ return data?.sessions ?? [];
557
+ },
558
+ validate: async (input) => {
559
+ return this.request(`/api/work/${input.goalId}/validate`, {
560
+ method: "POST",
561
+ body: JSON.stringify(input)
562
+ });
563
+ },
564
+ control: async (action) => {
565
+ await this.request("/api/goals/control", {
566
+ method: "POST",
567
+ body: JSON.stringify(action)
568
+ });
569
+ }
570
+ };
571
+ // ── Memory ──
572
+ memory = {
573
+ search: async (opts) => {
574
+ const params = new URLSearchParams({ query: opts.query });
575
+ if (opts.topK)
576
+ params.set("topK", String(opts.topK));
577
+ if (opts.goalId)
578
+ params.set("goalId", opts.goalId);
579
+ if (opts.retriever)
580
+ params.set("retriever", opts.retriever);
581
+ const data = await this.request(`/api/memory/search?${params}`);
582
+ return data.results || [];
583
+ },
584
+ mergedSearch: async (opts) => {
585
+ const params = new URLSearchParams({ query: opts.query });
586
+ if (opts.maxFacts)
587
+ params.set("maxFacts", String(opts.maxFacts));
588
+ const data = await this.request(`/api/memory/merged-search?${params}`);
589
+ return data.results || [];
590
+ },
591
+ getEnergyDistribution: async () => {
592
+ return this.request("/api/memory/energy-distribution");
593
+ },
594
+ getL5Axioms: async (topK) => {
595
+ const params = topK ? `?topK=${topK}` : "";
596
+ const data = await this.request(`/api/l5/axioms${params}`);
597
+ return { axioms: data.axioms || [], heuristics: data.heuristics || [] };
598
+ },
599
+ delete: async (id) => {
600
+ await this.request(`/api/memory/${id}`, { method: "DELETE" });
601
+ },
602
+ listSticky: async () => {
603
+ const data = await this.request("/api/memory/sticky");
604
+ return { entries: data.entries || [], budget: data.budget || { max: 10, maxChars: 800, used: 0 } };
605
+ },
606
+ setSticky: async (id, sticky, stickyDays) => {
607
+ await this.request("/api/memory/pin", {
608
+ method: "POST",
609
+ headers: { "Content-Type": "application/json" },
610
+ body: JSON.stringify({ id, sticky, stickyDays })
611
+ });
612
+ }
613
+ };
614
+ // ── Approvals ──
615
+ approvals = {
616
+ list: async () => {
617
+ const data = await this.request("/api/approvals");
618
+ return data.approvals || [];
619
+ },
620
+ respond: async (id, decision) => {
621
+ await this.request(`/api/approvals/${id}/respond`, {
622
+ method: "POST",
623
+ body: JSON.stringify({ decision })
624
+ });
625
+ }
626
+ };
627
+ // ── Triage ──
628
+ triage = {
629
+ list: async () => {
630
+ const data = await this.request("/api/triage");
631
+ return data.items || [];
632
+ },
633
+ dismiss: async (id) => {
634
+ await this.request(`/api/triage/${id}/dismiss`, { method: "POST" });
635
+ },
636
+ confirm: async (id) => {
637
+ await this.request(`/api/triage/${id}/confirm`, { method: "POST" });
638
+ },
639
+ reject: async (id) => {
640
+ await this.request(`/api/triage/${id}/reject`, { method: "POST" });
641
+ },
642
+ propose: async (id, suggestion, reason, priority = "medium") => {
643
+ return this.request(`/api/triage/${id}/propose`, {
644
+ method: "POST",
645
+ body: JSON.stringify({ suggestion, reason, priority })
646
+ });
647
+ }
648
+ };
649
+ // ── Questions (AskCard — proxies the native opencode Question API) ──
650
+ questions = {
651
+ list: async () => {
652
+ const data = await this.request("/api/questions");
653
+ return data.items || [];
654
+ },
655
+ reply: async (id, answers) => {
656
+ await this.request(`/api/questions/${id}/reply`, {
657
+ method: "POST",
658
+ body: JSON.stringify({ answers })
659
+ });
660
+ },
661
+ reject: async (id) => {
662
+ await this.request(`/api/questions/${id}/reject`, { method: "POST" });
663
+ }
664
+ };
665
+ // ── Permissions (PermissionCard — proxies the native opencode Permission API) ──
666
+ permissions = {
667
+ list: async () => {
668
+ const data = await this.request("/api/permissions");
669
+ return data.items || [];
670
+ },
671
+ reply: async (id, reply, message) => {
672
+ await this.request(`/api/permissions/${id}/reply`, {
673
+ method: "POST",
674
+ body: JSON.stringify({ reply, message })
675
+ });
676
+ }
677
+ };
678
+ // ── Chat ──
679
+ chat = {
680
+ send: async (message, sessionID) => {
681
+ const res = await fetch(`${this.baseUrl}/api/chat`, {
682
+ method: "POST",
683
+ headers: { "Content-Type": "application/json" },
684
+ body: JSON.stringify({ message, sessionID })
685
+ });
686
+ if (!res.ok)
687
+ throw new Error(`Chat send failed: ${res.status}`);
688
+ return res.json();
689
+ },
690
+ sendEnriched: async (opts) => {
691
+ const res = await fetch(`${this.baseUrl}/api/chat/enriched`, {
692
+ method: "POST",
693
+ headers: { "Content-Type": "application/json" },
694
+ body: JSON.stringify(opts)
695
+ });
696
+ if (!res.ok)
697
+ throw new Error(`Chat sendEnriched failed: ${res.status}`);
698
+ return res.json();
699
+ }
700
+ };
701
+ // ── Media (A2A Media Agent) ──
702
+ media = {
703
+ /** List media engine plugins (status + modalities). */
704
+ plugins: async () => {
705
+ return this.request("/api/media/plugins");
706
+ },
707
+ /**
708
+ * Switch media engine per modality.
709
+ * @param opts - Engine overrides (top-level engine and/or per-modality).
710
+ * @returns The new media engine configuration.
711
+ */
712
+ switch: async (opts) => {
713
+ const res = await fetch(`${this.baseUrl}/api/media/switch`, {
714
+ method: "POST",
715
+ headers: { "Content-Type": "application/json" },
716
+ body: JSON.stringify(opts)
717
+ });
718
+ if (!res.ok) {
719
+ const body = await res.json().catch(() => ({}));
720
+ throw new Error(body.error || `Media switch failed: ${res.status}`);
721
+ }
722
+ return res.json();
723
+ },
724
+ createTask: async (opts) => {
725
+ const parts = opts.artifactId ? [{ url: `/a2a/artifacts/${opts.artifactId}`, mediaType: opts.mediaType || "application/octet-stream", filename: "upload.bin" }] : [{ raw: opts.dataUrl?.split(",")[1] || "", mediaType: opts.mediaType || "image/png", filename: "paste.bin" }];
726
+ if (opts.question)
727
+ parts.push({ text: opts.question });
728
+ const res = await fetch(`${this.baseUrl}/a2a`, {
729
+ method: "POST",
730
+ headers: { "Content-Type": "application/json", "A2A-Version": "1.0" },
731
+ body: JSON.stringify({
732
+ jsonrpc: "2.0",
733
+ id: 1,
734
+ method: "SendMessage",
735
+ params: {
736
+ message: {
737
+ messageId: `fe-${(0, crypto_1.randomUUID)()}`,
738
+ role: 1,
739
+ // ROLE_USER
740
+ parts
741
+ }
742
+ }
743
+ })
744
+ });
745
+ if (!res.ok)
746
+ throw new Error(`Vision createTask failed: HTTP ${res.status}`);
747
+ const parsed = await res.json();
748
+ if (parsed?.error)
749
+ throw new Error(`Vision createTask failed: ${parsed.error?.message || JSON.stringify(parsed.error)}`);
750
+ const task = parsed?.result?.task;
751
+ if (!task?.id)
752
+ throw new Error("Vision createTask failed: no task returned");
753
+ return { id: task.id, contextId: task.contextId, state: task.status?.state || "" };
754
+ }
755
+ };
756
+ // ── TTS (MiMo-V2.5-TTS speech synthesis) ──
757
+ tts = {
758
+ speak: async (opts) => {
759
+ const res = await fetch(`${this.baseUrl}/api/tts`, {
760
+ method: "POST",
761
+ headers: { "Content-Type": "application/json" },
762
+ body: JSON.stringify(opts)
763
+ });
764
+ if (!res.ok) {
765
+ let detail = "";
766
+ try {
767
+ const j = await res.json();
768
+ detail = j?.error || "";
769
+ } catch {
770
+ }
771
+ throw new Error(`TTS failed: HTTP ${res.status}${detail ? ` (${detail})` : ""}`);
772
+ }
773
+ return res.json();
774
+ },
775
+ voices: async () => {
776
+ const res = await fetch(`${this.baseUrl}/api/tts/voices`);
777
+ if (!res.ok)
778
+ throw new Error(`TTS voices failed: HTTP ${res.status}`);
779
+ return res.json();
780
+ },
781
+ /** 流式 TTS:返回 async iterable of base64 PCM16 chunks(24kHz mono)。 */
782
+ speakStream: (opts) => {
783
+ const base = this.baseUrl;
784
+ return (async function* () {
785
+ const res = await fetch(`${base}/api/tts/stream`, {
786
+ method: "POST",
787
+ headers: { "Content-Type": "application/json" },
788
+ body: JSON.stringify(opts)
789
+ });
790
+ if (!res.ok || !res.body)
791
+ throw new Error(`TTS stream failed: HTTP ${res.status}`);
792
+ const reader = res.body.getReader();
793
+ const decoder = new TextDecoder();
794
+ let buf = "";
795
+ try {
796
+ while (true) {
797
+ const { done, value } = await reader.read();
798
+ if (done)
799
+ break;
800
+ buf += decoder.decode(value, { stream: true });
801
+ const lines = buf.split("\n");
802
+ buf = lines.pop() || "";
803
+ for (const line of lines) {
804
+ const t = line.trim();
805
+ if (!t.startsWith("data:"))
806
+ continue;
807
+ try {
808
+ const j = JSON.parse(t.slice(5).trim());
809
+ if (j.data)
810
+ yield { data: j.data, voice: j.voice || "" };
811
+ if (j.done)
812
+ return;
813
+ } catch {
814
+ }
815
+ }
816
+ }
817
+ } finally {
818
+ reader.releaseLock();
819
+ }
820
+ })();
821
+ }
822
+ };
823
+ // ── Providers & Agents (composer model pill / @agent mention) ──
824
+ providers = {
825
+ list: async () => {
826
+ const data = await this.request("/api/provider");
827
+ return data.items;
828
+ }
829
+ };
830
+ agents = {
831
+ list: async () => {
832
+ const data = await this.request("/api/agents");
833
+ return data.items || [];
834
+ }
835
+ };
836
+ // ── Model config (recall worker + media models) ──
837
+ models = {
838
+ get: async () => {
839
+ return this.request("/api/model-config");
840
+ },
841
+ update: async (opts) => {
842
+ const res = await fetch(`${this.baseUrl}/api/model-config`, {
843
+ method: "POST",
844
+ headers: { "Content-Type": "application/json" },
845
+ body: JSON.stringify(opts)
846
+ });
847
+ if (!res.ok) {
848
+ const body = await res.json().catch(() => ({}));
849
+ throw new Error(body.error || `Model config update failed: ${res.status}`);
850
+ }
851
+ return res.json();
852
+ }
853
+ };
854
+ // ── Memory embedding config (hot-swap engine, no restart) ──
855
+ embedding = {
856
+ get: async () => {
857
+ return this.request("/api/memory/embedding-config");
858
+ },
859
+ update: async (opts) => {
860
+ const res = await fetch(`${this.baseUrl}/api/memory/embedding-config`, {
861
+ method: "POST",
862
+ headers: { "Content-Type": "application/json" },
863
+ body: JSON.stringify(opts)
864
+ });
865
+ if (!res.ok) {
866
+ const body = await res.json().catch(() => ({}));
867
+ throw new Error(body.error || `Embedding config update failed: ${res.status}`);
868
+ }
869
+ return res.json();
870
+ }
871
+ };
872
+ // ── Automations ──
873
+ automations = {
874
+ list: async () => {
875
+ const data = await this.request("/api/automations");
876
+ return data.rules || [];
877
+ },
878
+ toggle: async (id, enabled) => {
879
+ await this.request(`/api/automations/${id}`, {
880
+ method: "PUT",
881
+ body: JSON.stringify({ enabled })
882
+ });
883
+ },
884
+ /** 起草规则(enabled=false 落盘,需手动启用)。返回校验结果。 */
885
+ draft: async (input) => {
886
+ return this.request("/api/automations/draft", {
887
+ method: "POST",
888
+ body: JSON.stringify(input)
889
+ });
890
+ }
891
+ };
892
+ };
893
+ exports.MafwClient = MafwClient2;
894
+ }
895
+ });
896
+
897
+ // ../gateway-sdk/dist/types.js
898
+ var require_types = __commonJS({
899
+ "../gateway-sdk/dist/types.js"(exports) {
900
+ "use strict";
901
+ Object.defineProperty(exports, "__esModule", { value: true });
902
+ exports.MethodNotSupportedError = void 0;
903
+ var MethodNotSupportedError = class extends Error {
904
+ constructor(method) {
905
+ super(`Method not supported by Gateway: ${method}`);
906
+ this.name = "MethodNotSupportedError";
907
+ }
908
+ };
909
+ exports.MethodNotSupportedError = MethodNotSupportedError;
910
+ }
911
+ });
912
+
913
+ // ../gateway-sdk/dist/index.js
914
+ var require_dist = __commonJS({
915
+ "../gateway-sdk/dist/index.js"(exports) {
916
+ "use strict";
917
+ var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
918
+ if (k2 === void 0) k2 = k;
919
+ var desc = Object.getOwnPropertyDescriptor(m, k);
920
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
921
+ desc = { enumerable: true, get: function() {
922
+ return m[k];
923
+ } };
924
+ }
925
+ Object.defineProperty(o, k2, desc);
926
+ }) : (function(o, m, k, k2) {
927
+ if (k2 === void 0) k2 = k;
928
+ o[k2] = m[k];
929
+ }));
930
+ var __exportStar = exports && exports.__exportStar || function(m, exports2) {
931
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports2, p)) __createBinding(exports2, m, p);
932
+ };
933
+ Object.defineProperty(exports, "__esModule", { value: true });
934
+ exports.SSEConnection = exports.MafwClient = void 0;
935
+ var client_1 = require_client();
936
+ Object.defineProperty(exports, "MafwClient", { enumerable: true, get: function() {
937
+ return client_1.MafwClient;
938
+ } });
939
+ var sse_1 = require_sse();
940
+ Object.defineProperty(exports, "SSEConnection", { enumerable: true, get: function() {
941
+ return sse_1.SSEConnection;
942
+ } });
943
+ __exportStar(require_types(), exports);
944
+ }
945
+ });
946
+
947
+ // src/theme.ts
948
+ import chalk from "chalk";
949
+ var theme;
950
+ var init_theme = __esm({
951
+ "src/theme.ts"() {
952
+ "use strict";
953
+ theme = {
954
+ accent: (s) => chalk.cyan(s),
955
+ dim: (s) => chalk.gray(s),
956
+ active: (s) => chalk.bgCyan.black(` ${s} `),
957
+ inactive: (s) => chalk.gray(` ${s} `),
958
+ ok: (s) => chalk.green(s),
959
+ warn: (s) => chalk.yellow(s),
960
+ err: (s) => chalk.red(s),
961
+ border: (s) => chalk.gray(s)
962
+ };
963
+ }
964
+ });
965
+
966
+ // src/ui/tab-strip.ts
967
+ import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
968
+ var TABS, TabStrip;
969
+ var init_tab_strip = __esm({
970
+ "src/ui/tab-strip.ts"() {
971
+ "use strict";
972
+ init_theme();
973
+ TABS = [
974
+ { id: "chat", label: "Chat" },
975
+ { id: "goals", label: "Goals" },
976
+ { id: "memory", label: "Memory" },
977
+ { id: "triage", label: "Triage" }
978
+ ];
979
+ TabStrip = class {
980
+ active = "chat";
981
+ connected = false;
982
+ /** 点击 tab 切换(app 注入;与键盘 1-4 同路径)。 */
983
+ onTabClick;
984
+ setActive(id) {
985
+ this.active = id;
986
+ }
987
+ setConnected(ok) {
988
+ this.connected = ok;
989
+ }
990
+ invalidate() {
991
+ }
992
+ segment(i) {
993
+ const t = TABS[i];
994
+ const label = `${i + 1}:${t.label}`;
995
+ return t.id === this.active ? theme.active(label) : theme.inactive(label);
996
+ }
997
+ /** 列位置 → tab(分隔符与 gateway 指示区不可点)。 */
998
+ clickAt(col) {
999
+ let x = 0;
1000
+ for (let i = 0; i < TABS.length; i++) {
1001
+ const w = visibleWidth(this.segment(i));
1002
+ if (col >= x && col < x + w) return TABS[i].id;
1003
+ x += w + 1;
1004
+ }
1005
+ return null;
1006
+ }
1007
+ handleMouseClick(col, _row) {
1008
+ const id = this.clickAt(col);
1009
+ if (!id) return false;
1010
+ this.onTabClick?.(id);
1011
+ return true;
1012
+ }
1013
+ render(width) {
1014
+ const tabs = TABS.map((_, i) => this.segment(i)).join(theme.dim("\u2502"));
1015
+ const conn = this.connected ? theme.ok("\u25CF") : theme.err("\u25CF");
1016
+ return [truncateToWidth(`${tabs} ${theme.dim("gateway:")}${conn}`, width)];
1017
+ }
1018
+ };
1019
+ }
1020
+ });
1021
+
1022
+ // src/ui/status-bar.ts
1023
+ import { truncateToWidth as truncateToWidth2 } from "@earendil-works/pi-tui";
1024
+ function formatTokens(n) {
1025
+ if (n < 1e3) return String(n);
1026
+ if (n < 1e6) return `${(n / 1e3).toFixed(1).replace(/\.0$/, "")}K`;
1027
+ return `${(n / 1e6).toFixed(1).replace(/\.0$/, "")}M`;
1028
+ }
1029
+ function formatDuration(ms) {
1030
+ const totalSec = Math.floor(ms / 1e3);
1031
+ if (totalSec < 60) return `${totalSec}s`;
1032
+ const m = Math.floor(totalSec / 60);
1033
+ const s = totalSec % 60;
1034
+ return s > 0 ? `${m}m ${s}s` : `${m}m`;
1035
+ }
1036
+ var StatusBar;
1037
+ var init_status_bar = __esm({
1038
+ "src/ui/status-bar.ts"() {
1039
+ "use strict";
1040
+ init_theme();
1041
+ StatusBar = class {
1042
+ state = { conn: "down" };
1043
+ setState(s) {
1044
+ this.state = s;
1045
+ }
1046
+ invalidate() {
1047
+ }
1048
+ render(width) {
1049
+ const s = this.state;
1050
+ const conn = s.conn === "ok" ? theme.ok("connected") : s.conn === "reconnecting" ? theme.warn("reconnecting") : theme.err("disconnected");
1051
+ const parts = [s.project ?? "-", s.session ? s.session.slice(0, 12) : "-", conn];
1052
+ if (s.busy) parts.push(theme.warn("busy"));
1053
+ if (typeof s.queued === "number" && s.queued > 0) parts.push(theme.warn(`queued ${s.queued}`));
1054
+ if (typeof s.stashed === "number" && s.stashed > 0) parts.push(theme.dim(`\u{1F4CC}${s.stashed}`));
1055
+ if (s.focus) parts.push(theme.accent("\u25C9 focus"));
1056
+ const u = s.usage;
1057
+ if (u) {
1058
+ const usageBits = [];
1059
+ if (u.model) usageBits.push(u.model);
1060
+ if (typeof u.tokens === "number") usageBits.push(`${formatTokens(u.tokens)} tok`);
1061
+ if (typeof u.costUsd === "number" && u.costUsd > 0) usageBits.push(`$${u.costUsd < 0.01 ? u.costUsd.toFixed(3) : u.costUsd.toFixed(2)}`);
1062
+ if (typeof u.promptMs === "number") usageBits.push(theme.accent(`\u23F1 ${formatDuration(u.promptMs)}`));
1063
+ if (typeof u.durationMs === "number") usageBits.push(formatDuration(u.durationMs));
1064
+ if (usageBits.length > 0) parts.push(usageBits.join(" "));
1065
+ }
1066
+ parts.push(s.hint ?? "1-4:tab q:quit ?:help");
1067
+ return [truncateToWidth2(theme.dim(parts.join(" \xB7 ")), width)];
1068
+ }
1069
+ };
1070
+ }
1071
+ });
1072
+
1073
+ // src/ui/app-model.ts
1074
+ var AppModel;
1075
+ var init_app_model = __esm({
1076
+ "src/ui/app-model.ts"() {
1077
+ "use strict";
1078
+ AppModel = class {
1079
+ active = "chat";
1080
+ helpVisible = false;
1081
+ /** 输入焦点在 Editor/Input 时,字符键(q/?/数字)归编辑器;由 app 按 tab/焦点状态注入。 */
1082
+ editing = false;
1083
+ switchTab(id) {
1084
+ this.active = id;
1085
+ }
1086
+ toggleHelp() {
1087
+ this.helpVisible = !this.helpVisible;
1088
+ }
1089
+ };
1090
+ }
1091
+ });
1092
+
1093
+ // src/store/connection.ts
1094
+ var BACKOFF, HEALTH_TICK_MS, KNOWN_EVENTS, ConnectionStore;
1095
+ var init_connection = __esm({
1096
+ "src/store/connection.ts"() {
1097
+ "use strict";
1098
+ BACKOFF = [1e3, 2e3, 5e3, 15e3];
1099
+ HEALTH_TICK_MS = 1e3;
1100
+ KNOWN_EVENTS = [
1101
+ "message.part.updated",
1102
+ "session.idle",
1103
+ "message.complete",
1104
+ "message.error",
1105
+ "session.error",
1106
+ "permission.asked",
1107
+ "question.asked"
1108
+ ];
1109
+ ConnectionStore = class {
1110
+ backoffIdx = 0;
1111
+ stopped = false;
1112
+ unsubs = [];
1113
+ schedule;
1114
+ deps;
1115
+ constructor(deps) {
1116
+ this.deps = deps;
1117
+ this.schedule = deps.schedule ?? ((fn, ms) => {
1118
+ setTimeout(fn, ms);
1119
+ });
1120
+ }
1121
+ start() {
1122
+ this.stopped = false;
1123
+ void this.subscribe();
1124
+ this.schedule(() => this.healthTick(), HEALTH_TICK_MS);
1125
+ }
1126
+ /** 切换订阅目标会话(/sessions 切换后重订事件流)。 */
1127
+ setSession(sessionID) {
1128
+ if (this.stopped || sessionID === this.deps.sessionID) return;
1129
+ this.deps.sessionID = sessionID;
1130
+ void this.resubscribe();
1131
+ }
1132
+ stop() {
1133
+ this.stopped = true;
1134
+ for (const u of this.unsubs) u();
1135
+ this.unsubs = [];
1136
+ }
1137
+ async subscribe() {
1138
+ if (this.stopped) return;
1139
+ try {
1140
+ const sub = await this.deps.subscribe(this.deps.sessionID);
1141
+ const unsubs = [];
1142
+ const track = (u) => {
1143
+ if (typeof u === "function") unsubs.push(u);
1144
+ };
1145
+ for (const e of KNOWN_EVENTS) track(sub.on(e, (d) => this.deps.onEvent(e, d)));
1146
+ track(sub.on("*", (d) => {
1147
+ if (d?.type && !KNOWN_EVENTS.includes(d.type)) this.deps.onEvent(d.type, d);
1148
+ }));
1149
+ this.unsubs = unsubs;
1150
+ } catch {
1151
+ this.deps.onState("down");
1152
+ }
1153
+ }
1154
+ healthTick() {
1155
+ if (this.stopped) return;
1156
+ if (this.deps.isConnected()) {
1157
+ this.backoffIdx = 0;
1158
+ this.deps.onState("ok");
1159
+ this.schedule(() => this.healthTick(), HEALTH_TICK_MS);
1160
+ return;
1161
+ }
1162
+ this.deps.onState("reconnecting");
1163
+ const delay = BACKOFF[Math.min(this.backoffIdx, BACKOFF.length - 1)];
1164
+ this.backoffIdx++;
1165
+ this.schedule(() => {
1166
+ void this.resubscribe();
1167
+ }, delay);
1168
+ }
1169
+ async resubscribe() {
1170
+ if (this.stopped) return;
1171
+ for (const u of this.unsubs) u();
1172
+ this.unsubs = [];
1173
+ await this.subscribe();
1174
+ this.schedule(() => this.healthTick(), HEALTH_TICK_MS);
1175
+ }
1176
+ };
1177
+ }
1178
+ });
1179
+
1180
+ // src/store/chat-store.ts
1181
+ function historyItemsToTurns(items) {
1182
+ const turns = [];
1183
+ const byId = /* @__PURE__ */ new Map();
1184
+ for (const item of items ?? []) {
1185
+ const info = item.info ?? item;
1186
+ const mid = info.id ?? `t-${turns.length}`;
1187
+ let t = byId.get(mid);
1188
+ if (!t) {
1189
+ t = { messageID: mid, role: info.role === "user" ? "user" : "assistant", parts: [], done: true };
1190
+ byId.set(mid, t);
1191
+ turns.push(t);
1192
+ }
1193
+ for (const p of item.parts ?? []) {
1194
+ const hp = historyPart(p);
1195
+ if (hp) t.parts.push(hp);
1196
+ }
1197
+ }
1198
+ return turns;
1199
+ }
1200
+ function isInjectionBlock(text) {
1201
+ const t = text.trimStart();
1202
+ return INJECTION_TAGS.some((tag) => t.startsWith(tag));
1203
+ }
1204
+ function historyPart(p) {
1205
+ if (p.type === "tool") {
1206
+ const st = p.state?.status;
1207
+ const cmd = String(p.state?.input?.command ?? p.state?.input?.description ?? p.tool ?? "");
1208
+ const out = st === "running" ? "" : String(p.state?.output ?? "");
1209
+ return {
1210
+ id: p.id,
1211
+ type: "tool",
1212
+ toolName: p.tool,
1213
+ state: st === "completed" ? "completed" : st === "error" ? "error" : st === "running" ? "running" : "completed",
1214
+ text: out ? `${cmd}
1215
+ ${out}` : cmd
1216
+ };
1217
+ }
1218
+ const text = String(p.text ?? "");
1219
+ if (isInjectionBlock(text)) return null;
1220
+ return { id: p.id, type: p.type ?? "text", text, state: "completed" };
1221
+ }
1222
+ function livePart(part) {
1223
+ if (part.type === "tool") {
1224
+ const st = part.state?.status;
1225
+ const cmd = String(part.state?.input?.command ?? part.state?.input?.description ?? part.tool ?? "");
1226
+ const out = st === "completed" || st === "error" ? String(part.state?.output ?? "") : "";
1227
+ return {
1228
+ id: part.id,
1229
+ type: "tool",
1230
+ toolName: part.tool,
1231
+ state: st === "completed" ? "completed" : st === "error" ? "error" : "running",
1232
+ text: out ? `${cmd}
1233
+ ${out}` : cmd
1234
+ };
1235
+ }
1236
+ const text = String(part.text ?? "");
1237
+ if (isInjectionBlock(text)) return null;
1238
+ return { id: part.id, type: part.type ?? "text", text };
1239
+ }
1240
+ var ChatStore, INJECTION_TAGS;
1241
+ var init_chat_store = __esm({
1242
+ "src/store/chat-store.ts"() {
1243
+ "use strict";
1244
+ ChatStore = class {
1245
+ turns = [];
1246
+ streaming = false;
1247
+ _sessionID;
1248
+ cursor = null;
1249
+ byMessage = /* @__PURE__ */ new Map();
1250
+ queuedTurns = [];
1251
+ deps;
1252
+ constructor(deps) {
1253
+ this.deps = deps;
1254
+ this._sessionID = deps.sessionID;
1255
+ }
1256
+ get sessionID() {
1257
+ return this._sessionID;
1258
+ }
1259
+ constructorDeps() {
1260
+ return this.deps;
1261
+ }
1262
+ async loadHistory(limit = 50) {
1263
+ try {
1264
+ const res = await this.deps.session.messages({ path: { id: this._sessionID }, query: { limit } });
1265
+ this.cursor = res.nextCursor ?? null;
1266
+ this.turns.length = 0;
1267
+ this.byMessage.clear();
1268
+ this.queuedTurns = [];
1269
+ for (const item of res.data ?? []) this.appendHistoryItem(item);
1270
+ this.deps.onChange();
1271
+ } catch (err) {
1272
+ this.deps.onError?.(err?.message ?? String(err));
1273
+ }
1274
+ }
1275
+ /** 切换会话:清空本地状态并按新 sessionID 重载历史。 */
1276
+ async switchSession(sessionID) {
1277
+ this._sessionID = sessionID;
1278
+ this.cursor = null;
1279
+ this.streaming = false;
1280
+ this.turns.length = 0;
1281
+ this.byMessage.clear();
1282
+ this.queuedTurns = [];
1283
+ this.deps.onChange();
1284
+ await this.loadHistory();
1285
+ }
1286
+ /** 返回新加载的 turn 数,0 = 没有更早历史。 */
1287
+ async loadOlder() {
1288
+ if (!this.cursor) return 0;
1289
+ try {
1290
+ const res = await this.deps.session.messages({
1291
+ path: { id: this._sessionID },
1292
+ query: { limit: 50, before: this.cursor }
1293
+ });
1294
+ this.cursor = res.nextCursor ?? null;
1295
+ const older = [];
1296
+ const map = /* @__PURE__ */ new Map();
1297
+ for (const item of res.data ?? []) {
1298
+ const info = item.info ?? item;
1299
+ const mid = info.id ?? `older-${older.length}`;
1300
+ let t = map.get(mid);
1301
+ if (!t) {
1302
+ t = { messageID: mid, role: info.role === "user" ? "user" : "assistant", parts: [], done: true };
1303
+ map.set(mid, t);
1304
+ older.push(t);
1305
+ }
1306
+ for (const p of item.parts ?? []) {
1307
+ const hp = historyPart(p);
1308
+ if (hp) t.parts.push(hp);
1309
+ }
1310
+ }
1311
+ this.turns.unshift(...older);
1312
+ for (const t of older) this.byMessage.set(t.messageID, t);
1313
+ this.deps.onChange();
1314
+ return older.length;
1315
+ } catch (err) {
1316
+ this.deps.onError?.(err?.message ?? String(err));
1317
+ return 0;
1318
+ }
1319
+ }
1320
+ async send(text) {
1321
+ const trimmed = text.trim();
1322
+ if (!trimmed) return;
1323
+ if (this.streaming) {
1324
+ const turn = {
1325
+ messageID: `queued-${Date.now()}-${this.queuedTurns.length}`,
1326
+ role: "user",
1327
+ parts: [{ id: `q-${Date.now()}`, type: "text", text: trimmed }],
1328
+ done: true,
1329
+ queued: true
1330
+ };
1331
+ this.queuedTurns.push(turn);
1332
+ this.turns.push(turn);
1333
+ this.deps.onChange();
1334
+ return;
1335
+ }
1336
+ await this.deliver(trimmed);
1337
+ }
1338
+ async deliver(text, reuseTurn) {
1339
+ const mid = reuseTurn?.messageID ?? `local-${Date.now()}`;
1340
+ let turn = reuseTurn;
1341
+ if (turn) {
1342
+ turn.queued = void 0;
1343
+ } else {
1344
+ turn = {
1345
+ messageID: mid,
1346
+ role: "user",
1347
+ parts: [{ id: `${mid}-p`, type: "text", text }],
1348
+ done: true
1349
+ };
1350
+ this.turns.push(turn);
1351
+ this.byMessage.set(mid, turn);
1352
+ }
1353
+ this.streaming = true;
1354
+ this.deps.onChange();
1355
+ try {
1356
+ const model = this.deps.getModel?.();
1357
+ await this.deps.session.promptAsync({
1358
+ path: { id: this._sessionID },
1359
+ body: { parts: [{ type: "text", text }], ...model ? { model } : {} }
1360
+ });
1361
+ } catch (err) {
1362
+ this.streaming = false;
1363
+ this.deps.onError?.(err?.message ?? String(err));
1364
+ this.deps.onChange();
1365
+ }
1366
+ }
1367
+ /** idle 后自动续发排队消息(每次一条,下一条等下一个 idle)。 */
1368
+ flushQueue() {
1369
+ if (this.streaming || this.queuedTurns.length === 0) return;
1370
+ const next = this.queuedTurns.shift();
1371
+ void this.deliver(next.parts[0]?.text ?? "", next);
1372
+ }
1373
+ /** 排队中的消息数。 */
1374
+ get queuedCount() {
1375
+ return this.queuedTurns.length;
1376
+ }
1377
+ /** 排队消息文本(/queue overlay 渲染用)。 */
1378
+ get queuedTexts() {
1379
+ return this.queuedTurns.map((t) => t.parts[0]?.text ?? "");
1380
+ }
1381
+ /** 收回全部排队消息(Claude Up-take-back 语义):返回文本数组并从 turns 一并移除。 */
1382
+ takeBackAll() {
1383
+ const texts = this.queuedTurns.map((t) => t.parts[0]?.text ?? "");
1384
+ for (const t of this.queuedTurns) {
1385
+ const idx = this.turns.indexOf(t);
1386
+ if (idx >= 0) this.turns.splice(idx, 1);
1387
+ }
1388
+ this.queuedTurns = [];
1389
+ this.deps.onChange();
1390
+ return texts;
1391
+ }
1392
+ /** 丢弃第 i 条排队消息(0 = 最早);返回其文本,越界返回 null。 */
1393
+ dropQueuedAt(i) {
1394
+ const t = this.queuedTurns[i];
1395
+ if (!t) return null;
1396
+ this.queuedTurns.splice(i, 1);
1397
+ const idx = this.turns.indexOf(t);
1398
+ if (idx >= 0) this.turns.splice(idx, 1);
1399
+ this.deps.onChange();
1400
+ return t.parts[0]?.text ?? "";
1401
+ }
1402
+ async abort() {
1403
+ try {
1404
+ await this.deps.session.abort({ path: { id: this._sessionID } });
1405
+ } catch (err) {
1406
+ this.deps.onError?.(err?.message ?? String(err));
1407
+ }
1408
+ this.streaming = false;
1409
+ this.deps.onChange();
1410
+ }
1411
+ /** /undo:回退到最后一条已持久化的 user 消息(连带其后的回复)。 */
1412
+ async undo() {
1413
+ const lastUser = [...this.turns].reverse().find((t) => t.role === "user" && !t.queued && !t.messageID.startsWith("local-") && !t.messageID.startsWith("queued-"));
1414
+ if (!lastUser) return "\u6CA1\u6709\u53EF\u56DE\u9000\u7684\u6D88\u606F\uFF08\u672C\u5730\u672A\u6301\u4E45\u5316\u7684\u6D88\u606F\u4E0D\u7B97\uFF09";
1415
+ if (!this.deps.session.revert) return "\u5F53\u524D runtime \u4E0D\u652F\u6301 revert";
1416
+ try {
1417
+ await this.deps.session.revert({ path: { id: this._sessionID }, body: { messageID: lastUser.messageID } });
1418
+ await this.loadHistory();
1419
+ return null;
1420
+ } catch (err) {
1421
+ return `undo \u5931\u8D25: ${err?.message ?? String(err)}`;
1422
+ }
1423
+ }
1424
+ /** /redo:恢复上一次 revert(opencode-only;pi runtime 无 unrevert)。 */
1425
+ async redo() {
1426
+ if (!this.deps.session.unrevert) return "\u5F53\u524D runtime \u4E0D\u652F\u6301 unrevert";
1427
+ try {
1428
+ await this.deps.session.unrevert({ path: { id: this._sessionID } });
1429
+ await this.loadHistory();
1430
+ return null;
1431
+ } catch (err) {
1432
+ return `redo \u5931\u8D25: ${err?.message ?? String(err)}`;
1433
+ }
1434
+ }
1435
+ applyEvent(type, data) {
1436
+ if (type === "message.part.updated") {
1437
+ const part = data?.properties?.part;
1438
+ if (!part) return;
1439
+ if (part.sessionID && part.sessionID !== this._sessionID) return;
1440
+ this.applyPart(part);
1441
+ this.deps.onChange();
1442
+ } else if (type === "session.idle") {
1443
+ this.finalizeTurn();
1444
+ } else if (type === "message.complete") {
1445
+ const sid = data?.sessionID;
1446
+ if (!sid || sid === this._sessionID) this.finalizeTurn();
1447
+ }
1448
+ }
1449
+ finalizeTurn() {
1450
+ this.streaming = false;
1451
+ const last = this.turns.at(-1);
1452
+ if (last && last.role === "assistant") last.done = true;
1453
+ this.deps.onChange();
1454
+ this.flushQueue();
1455
+ }
1456
+ applyPart(part) {
1457
+ const cp = livePart(part);
1458
+ if (!cp) return;
1459
+ let turn = this.byMessage.get(part.messageID);
1460
+ if (!turn) {
1461
+ turn = { messageID: part.messageID, role: "assistant", parts: [], done: false };
1462
+ this.byMessage.set(part.messageID, turn);
1463
+ this.turns.push(turn);
1464
+ }
1465
+ const idx = turn.parts.findIndex((p) => p.id === cp.id);
1466
+ if (idx >= 0) turn.parts[idx] = cp;
1467
+ else turn.parts.push(cp);
1468
+ }
1469
+ appendHistoryItem(item) {
1470
+ const info = item.info ?? item;
1471
+ const mid = info.id ?? `hist-${this.turns.length}`;
1472
+ const parts = (item.parts ?? []).map(historyPart).filter((p) => p !== null);
1473
+ const turn = {
1474
+ messageID: mid,
1475
+ role: info.role === "user" ? "user" : "assistant",
1476
+ parts,
1477
+ done: true
1478
+ };
1479
+ this.turns.push(turn);
1480
+ this.byMessage.set(mid, turn);
1481
+ }
1482
+ };
1483
+ INJECTION_TAGS = ["<recall>", "<note-board>", "<goal-snapshot>", "<user-profile>", "<memory-guide>"];
1484
+ }
1485
+ });
1486
+
1487
+ // src/ui/message-blocks.ts
1488
+ import { truncateToWidth as truncateToWidth3, wrapTextWithAnsi, Markdown } from "@earendil-works/pi-tui";
1489
+ import chalk2 from "chalk";
1490
+ function colorDiffLine(l) {
1491
+ if (l.startsWith("+++") || l.startsWith("---")) return theme.dim(l);
1492
+ if (l.startsWith("+")) return theme.ok(l);
1493
+ if (l.startsWith("-")) return theme.err(l);
1494
+ if (l.startsWith("@@")) return theme.accent(l);
1495
+ return theme.dim(l);
1496
+ }
1497
+ function turnToLines(turn, width) {
1498
+ const lines = [];
1499
+ const queued = turn.role === "user" && turn.queued;
1500
+ for (const part of turn.parts) {
1501
+ if (part.type === "tool") {
1502
+ const icon = part.state === "completed" ? theme.ok("\u2713") : part.state === "error" ? theme.err("\u2717") : theme.warn("\u2026");
1503
+ const firstLine = stripAnsi(part.text).split("\n")[0] ?? "";
1504
+ lines.push(truncateToWidth3(
1505
+ `${theme.dim("\u25B8 ")}${theme.accent(part.toolName ?? "tool")} ${icon} ${theme.dim(firstLine)}`,
1506
+ width
1507
+ ));
1508
+ if (part.state !== "running") {
1509
+ for (const l of stripAnsi(part.text).split("\n").slice(1)) {
1510
+ if (!l) continue;
1511
+ for (const w of wrapTextWithAnsi(l, Math.max(4, width - 4))) {
1512
+ lines.push(truncateToWidth3(` ${colorDiffLine(w)}`, width));
1513
+ }
1514
+ }
1515
+ }
1516
+ } else if (part.type === "reasoning") {
1517
+ for (const raw of stripAnsi(part.text).split("\n")) {
1518
+ for (const w of wrapTextWithAnsi(raw, Math.max(4, width - 4))) {
1519
+ lines.push(truncateToWidth3(theme.dim(` \u283F ${w}`), width));
1520
+ }
1521
+ }
1522
+ } else if (turn.role === "assistant") {
1523
+ const md = new Markdown(part.text, 1, 0, markdownTheme);
1524
+ for (const l of md.render(Math.max(8, width - 2))) {
1525
+ lines.push(truncateToWidth3(` ${l}`, width));
1526
+ }
1527
+ } else {
1528
+ const isUser = turn.role === "user";
1529
+ const body = stripAnsi(part.text).split("\n").flatMap((l) => wrapTextWithAnsi(l, Math.max(4, width - 4)));
1530
+ for (let i = 0; i < body.length; i++) {
1531
+ if (queued) {
1532
+ lines.push(truncateToWidth3(theme.dim(`${i === 0 ? "\u23F3 " : " "}${body[i]}`), width));
1533
+ continue;
1534
+ }
1535
+ const prefix = isUser ? i === 0 ? `${theme.accent(">")} ` : " " : " ";
1536
+ lines.push(truncateToWidth3(`${prefix}${body[i]}`, width));
1537
+ }
1538
+ }
1539
+ }
1540
+ if (turn.role === "assistant" && !turn.done) lines.push(theme.dim(" \u2026"));
1541
+ return lines;
1542
+ }
1543
+ var stripAnsi, markdownTheme;
1544
+ var init_message_blocks = __esm({
1545
+ "src/ui/message-blocks.ts"() {
1546
+ "use strict";
1547
+ init_theme();
1548
+ stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, "");
1549
+ markdownTheme = {
1550
+ heading: (t) => chalk2.bold.cyan(t),
1551
+ link: (t) => chalk2.cyan.underline(t),
1552
+ linkUrl: (t) => chalk2.gray(t),
1553
+ code: (t) => chalk2.yellow(t),
1554
+ codeBlock: (t) => chalk2.white(t),
1555
+ codeBlockBorder: (t) => chalk2.gray(t),
1556
+ quote: (t) => chalk2.gray(t),
1557
+ quoteBorder: (t) => chalk2.gray(t),
1558
+ hr: (t) => chalk2.gray(t),
1559
+ listBullet: (t) => chalk2.cyan(t),
1560
+ bold: (t) => chalk2.bold(t),
1561
+ italic: (t) => chalk2.italic(t),
1562
+ underline: (t) => chalk2.underline(t),
1563
+ strikethrough: (t) => chalk2.strikethrough(t)
1564
+ };
1565
+ }
1566
+ });
1567
+
1568
+ // src/ui/blocks.ts
1569
+ import { Markdown as Markdown2, truncateToWidth as truncateToWidth4, wrapTextWithAnsi as wrapTextWithAnsi2 } from "@earendil-works/pi-tui";
1570
+ function buildBlock(part, turn, settings, requestRender) {
1571
+ if (part.type === "tool") return new ToolBlock(part, settings, requestRender);
1572
+ if (part.type === "reasoning") return new ReasoningBlock(part);
1573
+ if (turn.role === "assistant") return new AssistantMarkdownBlock(part);
1574
+ return new UserTextBlock(part, turn.queued === true);
1575
+ }
1576
+ var stripAnsi2, UserTextBlock, AssistantMarkdownBlock, ReasoningBlock, ToolBlock, PendingBlock;
1577
+ var init_blocks = __esm({
1578
+ "src/ui/blocks.ts"() {
1579
+ "use strict";
1580
+ init_message_blocks();
1581
+ init_theme();
1582
+ stripAnsi2 = (s) => s.replace(/\x1b\[[0-9;]*m/g, "");
1583
+ UserTextBlock = class {
1584
+ part;
1585
+ queued;
1586
+ cachedWidth;
1587
+ cached;
1588
+ constructor(part, queued = false) {
1589
+ this.part = part;
1590
+ this.queued = queued;
1591
+ }
1592
+ update(part) {
1593
+ this.part = part;
1594
+ this.cachedWidth = void 0;
1595
+ }
1596
+ invalidate() {
1597
+ this.cachedWidth = void 0;
1598
+ }
1599
+ render(width) {
1600
+ if (this.cached && this.cachedWidth === width) return this.cached;
1601
+ const lines = [];
1602
+ const body = stripAnsi2(this.part.text).split("\n").flatMap((l) => wrapTextWithAnsi2(l, Math.max(4, width - 4)));
1603
+ for (let i = 0; i < body.length; i++) {
1604
+ if (this.queued) {
1605
+ lines.push(truncateToWidth4(theme.dim(`${i === 0 ? "\u23F3 " : " "}${body[i]}`), width));
1606
+ continue;
1607
+ }
1608
+ const prefix = i === 0 ? `${theme.accent(">")} ` : " ";
1609
+ lines.push(truncateToWidth4(`${prefix}${body[i]}`, width));
1610
+ }
1611
+ this.cached = lines;
1612
+ this.cachedWidth = width;
1613
+ return lines;
1614
+ }
1615
+ };
1616
+ AssistantMarkdownBlock = class {
1617
+ part;
1618
+ cachedWidth;
1619
+ cached;
1620
+ constructor(part) {
1621
+ this.part = part;
1622
+ }
1623
+ update(part) {
1624
+ this.part = part;
1625
+ this.cachedWidth = void 0;
1626
+ }
1627
+ invalidate() {
1628
+ this.cachedWidth = void 0;
1629
+ }
1630
+ render(width) {
1631
+ if (this.cached && this.cachedWidth === width) return this.cached;
1632
+ const md = new Markdown2(this.part.text, 1, 0, markdownTheme);
1633
+ const lines = md.render(Math.max(8, width - 2)).map((l) => truncateToWidth4(` ${l}`, width));
1634
+ this.cached = lines;
1635
+ this.cachedWidth = width;
1636
+ return lines;
1637
+ }
1638
+ };
1639
+ ReasoningBlock = class {
1640
+ part;
1641
+ cachedWidth;
1642
+ cached;
1643
+ constructor(part) {
1644
+ this.part = part;
1645
+ }
1646
+ update(part) {
1647
+ this.part = part;
1648
+ this.cachedWidth = void 0;
1649
+ }
1650
+ invalidate() {
1651
+ this.cachedWidth = void 0;
1652
+ }
1653
+ render(width) {
1654
+ if (this.cached && this.cachedWidth === width) return this.cached;
1655
+ const lines = [];
1656
+ for (const raw of stripAnsi2(this.part.text).split("\n")) {
1657
+ for (const w of wrapTextWithAnsi2(raw, Math.max(4, width - 4))) {
1658
+ lines.push(truncateToWidth4(theme.dim(` \u283F ${w}`), width));
1659
+ }
1660
+ }
1661
+ this.cached = lines;
1662
+ this.cachedWidth = width;
1663
+ return lines;
1664
+ }
1665
+ };
1666
+ ToolBlock = class {
1667
+ part;
1668
+ settings;
1669
+ requestRender;
1670
+ userExpanded = null;
1671
+ // null = 跟随全局
1672
+ cachedWidth;
1673
+ cachedKey;
1674
+ cached;
1675
+ constructor(part, settings, requestRender) {
1676
+ this.part = part;
1677
+ this.settings = settings;
1678
+ this.requestRender = requestRender;
1679
+ }
1680
+ update(part) {
1681
+ this.part = part;
1682
+ this.cachedKey = void 0;
1683
+ }
1684
+ invalidate() {
1685
+ this.cachedKey = void 0;
1686
+ }
1687
+ handleMouseClick(_col, _row) {
1688
+ this.userExpanded = !this.expanded;
1689
+ this.cachedKey = void 0;
1690
+ this.requestRender();
1691
+ return true;
1692
+ }
1693
+ get expanded() {
1694
+ return this.userExpanded ?? this.settings.toolVerbosity === "all";
1695
+ }
1696
+ render(width) {
1697
+ const key = `${width}|${this.expanded}|${this.settings.focus}|${this.part.state}|${this.part.text}`;
1698
+ if (this.cached && this.cachedKey === key) return this.cached;
1699
+ const lines = this.renderInner(width);
1700
+ this.cached = lines;
1701
+ this.cachedKey = key;
1702
+ return lines;
1703
+ }
1704
+ renderInner(width) {
1705
+ if (this.settings.focus) {
1706
+ return [truncateToWidth4(theme.dim(` \u22EF ${this.part.toolName ?? "tool"} \u8F93\u51FA\u9690\u85CF\uFF08/focus off \u663E\u793A\uFF09`), width)];
1707
+ }
1708
+ const icon = this.part.state === "completed" ? theme.ok("\u2713") : this.part.state === "error" ? theme.err("\u2717") : theme.warn("\u2026");
1709
+ const firstLine = stripAnsi2(this.part.text).split("\n")[0] ?? "";
1710
+ const summary = truncateToWidth4(
1711
+ `${theme.dim("\u25B8 ")}${theme.accent(this.part.toolName ?? "tool")} ${icon} ${theme.dim(firstLine)}`,
1712
+ width
1713
+ );
1714
+ if (!this.expanded || this.part.state === "running") return [summary];
1715
+ const lines = [summary];
1716
+ for (const l of stripAnsi2(this.part.text).split("\n").slice(1)) {
1717
+ if (!l) continue;
1718
+ for (const w of wrapTextWithAnsi2(l, Math.max(4, width - 4))) {
1719
+ lines.push(truncateToWidth4(` ${colorDiffLine(w)}`, width));
1720
+ }
1721
+ }
1722
+ return lines;
1723
+ }
1724
+ };
1725
+ PendingBlock = class {
1726
+ invalidate() {
1727
+ }
1728
+ render(_width) {
1729
+ return [theme.dim(" \u2026")];
1730
+ }
1731
+ };
1732
+ }
1733
+ });
1734
+
1735
+ // src/shell-mode.ts
1736
+ import { exec } from "node:child_process";
1737
+ import { truncateToWidth as truncateToWidth5, wrapTextWithAnsi as wrapTextWithAnsi3 } from "@earendil-works/pi-tui";
1738
+ function isShellCommand(text) {
1739
+ return text.startsWith("!") && text.slice(1).trim().length > 0;
1740
+ }
1741
+ function parseShellCommand(text) {
1742
+ return text.slice(1).trim();
1743
+ }
1744
+ function runShell(cmd, opts) {
1745
+ const execFn = opts?.execFn ?? ((c, o, cb) => exec(c, o, cb));
1746
+ return new Promise((resolve) => {
1747
+ execFn(
1748
+ cmd,
1749
+ { cwd: opts?.cwd, timeout: opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS, maxBuffer: MAX_BUFFER },
1750
+ (err, stdout, stderr) => {
1751
+ resolve({
1752
+ command: cmd,
1753
+ exitCode: err?.code ?? (err ? null : 0),
1754
+ stdout: String(stdout ?? ""),
1755
+ stderr: String(stderr ?? ""),
1756
+ timedOut: !!err?.killed
1757
+ });
1758
+ }
1759
+ );
1760
+ });
1761
+ }
1762
+ function shellResultToLines(r, width) {
1763
+ const lines = [];
1764
+ const status = r.timedOut ? theme.err("\u8D85\u65F6") : r.exitCode === 0 ? theme.ok(`exit 0`) : r.exitCode === null ? theme.warn("exit ?") : theme.err(`exit ${r.exitCode}`);
1765
+ lines.push(truncateToWidth5(`${theme.accent("!")} ${theme.dim(r.command)} ${status}`, width));
1766
+ const outLines = (r.stdout + (r.stderr ? `
1767
+ ${r.stderr}` : "")).split("\n").filter((l) => l.length > 0);
1768
+ const capped = outLines.slice(0, MAX_OUTPUT_LINES);
1769
+ for (const l of capped) {
1770
+ const isErr = r.stderr.split("\n").includes(l);
1771
+ for (const w of wrapTextWithAnsi3(l, Math.max(4, width - 4))) {
1772
+ lines.push(truncateToWidth5(` ${isErr ? theme.err(w) : theme.dim(w)}`, width));
1773
+ }
1774
+ }
1775
+ if (outLines.length > MAX_OUTPUT_LINES) lines.push(theme.dim(` \u2026 \u8F93\u51FA\u622A\u65AD\uFF08${outLines.length} \u884C\uFF09`));
1776
+ if (outLines.length === 0) lines.push(theme.dim(" \uFF08\u65E0\u8F93\u51FA\uFF09"));
1777
+ return lines;
1778
+ }
1779
+ var DEFAULT_TIMEOUT_MS, MAX_BUFFER, MAX_OUTPUT_LINES;
1780
+ var init_shell_mode = __esm({
1781
+ "src/shell-mode.ts"() {
1782
+ "use strict";
1783
+ init_theme();
1784
+ DEFAULT_TIMEOUT_MS = 3e4;
1785
+ MAX_BUFFER = 128 * 1024;
1786
+ MAX_OUTPUT_LINES = 50;
1787
+ }
1788
+ });
1789
+
1790
+ // src/ui/command-registry.ts
1791
+ function resolveCommand(name) {
1792
+ for (const c of COMMAND_REGISTRY) {
1793
+ if (c.name === name) return c.name;
1794
+ if (c.aliases?.includes(name)) return c.name;
1795
+ }
1796
+ return null;
1797
+ }
1798
+ function autocompleteItems() {
1799
+ return COMMAND_REGISTRY.map((c) => ({ name: c.name, description: c.description }));
1800
+ }
1801
+ function helpLines() {
1802
+ const lines = [];
1803
+ for (const cat of COMMAND_CATEGORIES) {
1804
+ const cmds = COMMAND_REGISTRY.filter((c) => c.category === cat);
1805
+ if (cmds.length === 0) continue;
1806
+ lines.push(theme.accent(cat));
1807
+ for (const c of cmds) {
1808
+ const mark = c.destructive ? theme.warn(" \u26A0") : "";
1809
+ lines.push(` /${c.name.padEnd(10)}${c.description}${mark}`);
1810
+ }
1811
+ lines.push("");
1812
+ }
1813
+ return lines;
1814
+ }
1815
+ var COMMAND_CATEGORIES, COMMAND_REGISTRY;
1816
+ var init_command_registry = __esm({
1817
+ "src/ui/command-registry.ts"() {
1818
+ "use strict";
1819
+ init_theme();
1820
+ COMMAND_CATEGORIES = ["\u4F1A\u8BDD", "\u4E0A\u4E0B\u6587", "\u6A21\u578B", "\u8F93\u5165", "\u5E2E\u52A9"];
1821
+ COMMAND_REGISTRY = [
1822
+ { name: "new", description: "\u65B0\u8BDD\u9898\uFF08rotate manager session\uFF09", category: "\u4F1A\u8BDD", aliases: ["clear"], destructive: true },
1823
+ { name: "sessions", description: "\u4F1A\u8BDD\u5217\u8868/\u5207\u6362\uFF08/resume /switch\uFF09", category: "\u4F1A\u8BDD", aliases: ["resume", "switch"], immediate: true },
1824
+ { name: "rename", description: "\u4F1A\u8BDD\u547D\u540D\uFF1A/rename <\u6807\u9898>", category: "\u4F1A\u8BDD", immediate: true },
1825
+ { name: "fork", description: "\u5206\u53C9\u5F53\u524D\u4F1A\u8BDD\uFF08\u5386\u53F2\u526F\u672C\uFF09", category: "\u4F1A\u8BDD", immediate: true },
1826
+ { name: "status", description: "\u4F1A\u8BDD\u72B6\u6001\u56DE\u987E\uFF08\u672C\u5730\u8BA1\u7B97\uFF09", category: "\u4F1A\u8BDD", immediate: true },
1827
+ { name: "queue", description: "\u6392\u961F\u6D88\u606F\u7BA1\u7406\uFF08\u6536\u56DE/\u4E22\u5F03\uFF09", category: "\u4F1A\u8BDD", aliases: ["q"], immediate: true },
1828
+ { name: "btw", description: "\u652F\u7EBF\u95EE\u7B54\uFF1A/btw <\u95EE\u9898>", category: "\u4F1A\u8BDD" },
1829
+ { name: "compact", description: "\u538B\u7F29\u5F53\u524D\u4F1A\u8BDD\u4E0A\u4E0B\u6587", category: "\u4E0A\u4E0B\u6587", destructive: true, immediate: true },
1830
+ { name: "undo", description: "\u56DE\u9000\u6700\u540E\u4E00\u8F6E\u5BF9\u8BDD", category: "\u4E0A\u4E0B\u6587", destructive: true },
1831
+ { name: "redo", description: "\u6062\u590D\u4E0A\u4E00\u6B21\u56DE\u9000", category: "\u4E0A\u4E0B\u6587", destructive: true },
1832
+ { name: "older", description: "\u52A0\u8F7D\u66F4\u65E9\u5386\u53F2", category: "\u4E0A\u4E0B\u6587", immediate: true },
1833
+ { name: "verbose", description: "\u5DE5\u5177\u8F93\u51FA\u5C55\u5F00/\u6298\u53E0\u5FAA\u73AF", category: "\u4E0A\u4E0B\u6587", immediate: true },
1834
+ { name: "focus", description: "\u9759\u89C6\u56FE\uFF08\u9690\u85CF\u5DE5\u5177\u8F93\u51FA\uFF09", category: "\u4E0A\u4E0B\u6587", immediate: true },
1835
+ { name: "diff", description: "\u67E5\u770B git \u53D8\u66F4\uFF08/diff staged|all\uFF09", category: "\u4E0A\u4E0B\u6587", immediate: true },
1836
+ { name: "model", description: "\u9009\u62E9\u6A21\u578B\uFF08\u4F5C\u7528\u4E8E\u540E\u7EED\u6D88\u606F\uFF09", category: "\u6A21\u578B", immediate: true },
1837
+ { name: "editor", description: "\u5916\u90E8\u7F16\u8F91\u5668\u7F16\u8F91\u8F93\u5165\uFF08\u540C Ctrl+G\uFF09", category: "\u8F93\u5165", immediate: true },
1838
+ { name: "help", description: "\u5FEB\u6377\u952E\u5E2E\u52A9", category: "\u5E2E\u52A9", immediate: true }
1839
+ ];
1840
+ }
1841
+ });
1842
+
1843
+ // src/ui/prompt-stash.ts
1844
+ var PromptStash;
1845
+ var init_prompt_stash = __esm({
1846
+ "src/ui/prompt-stash.ts"() {
1847
+ "use strict";
1848
+ PromptStash = class {
1849
+ stack = [];
1850
+ get size() {
1851
+ return this.stack.length;
1852
+ }
1853
+ push(text) {
1854
+ if (text.trim().length === 0) return;
1855
+ this.stack.push(text);
1856
+ }
1857
+ pop() {
1858
+ return this.stack.pop() ?? null;
1859
+ }
1860
+ };
1861
+ }
1862
+ });
1863
+
1864
+ // src/ui/chat-tab.ts
1865
+ import {
1866
+ VStack,
1867
+ Container,
1868
+ Editor,
1869
+ CombinedAutocompleteProvider,
1870
+ ScrollView
1871
+ } from "@earendil-works/pi-tui";
1872
+ function parseSlash(text) {
1873
+ const m = text.match(/^\/(\w+)(?:\s+([\s\S]*))?$/);
1874
+ return m ? { cmd: m[1], args: m[2] ?? "" } : null;
1875
+ }
1876
+ var selectListTheme, editorTheme, RawLines, ChatTab;
1877
+ var init_chat_tab = __esm({
1878
+ "src/ui/chat-tab.ts"() {
1879
+ "use strict";
1880
+ init_blocks();
1881
+ init_shell_mode();
1882
+ init_command_registry();
1883
+ init_prompt_stash();
1884
+ init_theme();
1885
+ selectListTheme = {
1886
+ selectedPrefix: (t) => theme.accent(t),
1887
+ selectedText: (t) => theme.accent(t),
1888
+ description: (t) => theme.dim(t),
1889
+ scrollInfo: (t) => theme.dim(t),
1890
+ noMatch: (t) => theme.dim(t)
1891
+ };
1892
+ editorTheme = {
1893
+ borderColor: (s) => theme.border(s),
1894
+ selectList: selectListTheme
1895
+ };
1896
+ RawLines = class {
1897
+ lines;
1898
+ constructor(lines) {
1899
+ this.lines = lines;
1900
+ }
1901
+ invalidate() {
1902
+ }
1903
+ render(_width) {
1904
+ return this.lines;
1905
+ }
1906
+ };
1907
+ ChatTab = class extends VStack {
1908
+ editor;
1909
+ transcript = new Container();
1910
+ scrollView;
1911
+ rendered = [];
1912
+ localBlocks = [];
1913
+ stash = new PromptStash();
1914
+ /** 阅读模式设置(/focus /verbose 驱动,块渲染时读取)。 */
1915
+ display = { focus: false, toolVerbosity: "all" };
1916
+ savedVerbosity = "all";
1917
+ _focused = false;
1918
+ deps;
1919
+ get focused() {
1920
+ return this._focused;
1921
+ }
1922
+ set focused(v) {
1923
+ this._focused = v;
1924
+ this.editor.focused = v;
1925
+ }
1926
+ // IME 传播
1927
+ /** 焦点组件输入路由:TUI 只调 focusedComponent.handleInput,VStack 无此方法——
1928
+ * 必须显式转发给 Editor,否则按键被静默丢弃("tui 没法输入"根因之二)。
1929
+ * 转发前拦截三个 app 级输入交互:Up 收回排队(Claude)、Ctrl+S 草稿栈(Hermes)。 */
1930
+ handleInput(data) {
1931
+ if (data === "\x1B[A") {
1932
+ if (this.deps.store.queuedCount > 0 && this.getEditorText().trim() === "") {
1933
+ const texts = this.deps.store.takeBackAll();
1934
+ if (texts.length > 0) {
1935
+ this.setEditorText(texts.join("\n"));
1936
+ return;
1937
+ }
1938
+ }
1939
+ }
1940
+ if (data === "") {
1941
+ const text = this.getEditorText();
1942
+ if (text.trim() !== "") {
1943
+ this.stash.push(text);
1944
+ this.setEditorText("");
1945
+ this.deps.tui.requestRender();
1946
+ return;
1947
+ }
1948
+ const restored = this.stash.pop();
1949
+ if (restored !== null) this.setEditorText(restored);
1950
+ return;
1951
+ }
1952
+ this.editor.handleInput(data);
1953
+ }
1954
+ /** stash 深度(状态栏 📌N 徽标用)。 */
1955
+ get stashCount() {
1956
+ return this.stash.size;
1957
+ }
1958
+ /** 鼠标点击:未聚焦时点击即聚焦;已聚焦时放行(保留 pi-tui 拖选复制)。 */
1959
+ handleMouseClick(_col, _row) {
1960
+ if (!this._focused) {
1961
+ this.deps.tui.setFocus(this);
1962
+ return true;
1963
+ }
1964
+ return false;
1965
+ }
1966
+ constructor(deps) {
1967
+ super([]);
1968
+ this.deps = deps;
1969
+ this.scrollView = new ScrollView(this.transcript, { follow: "end", primary: true });
1970
+ this.editor = new Editor(deps.tui, editorTheme);
1971
+ this.editor.setAutocompleteProvider(new CombinedAutocompleteProvider(
1972
+ autocompleteItems(),
1973
+ process.cwd()
1974
+ ));
1975
+ this.editor.onSubmit = (text) => {
1976
+ void this.submit(text);
1977
+ };
1978
+ this.addChild(this.scrollView, { basis: 0, grow: 1, minSize: 1 });
1979
+ this.addChild(this.editor, { basis: "auto", shrink: 1, minSize: 1 });
1980
+ deps.store.loadHistory().then(() => this.rebuild());
1981
+ }
1982
+ /** 顶部(用户上翻到头)→ 自动 loadOlder。由 app 轮询调用。 */
1983
+ get atTop() {
1984
+ return !this.scrollView.isFollowingEnd && this.scrollView.scrollTop === 0;
1985
+ }
1986
+ async loadOlder() {
1987
+ const n = await this.deps.store.loadOlder();
1988
+ if (n > 0) {
1989
+ this.rebuild();
1990
+ }
1991
+ }
1992
+ /** 编辑器文本透传(Ctrl+G 外部编辑器用)。 */
1993
+ getEditorText() {
1994
+ return this.editor.getText();
1995
+ }
1996
+ setEditorText(text) {
1997
+ this.editor.setText(text);
1998
+ this.deps.tui.requestRender();
1999
+ }
2000
+ /** 会话切换后全量重建 transcript(本地块如 shell 结果跨重建保留)。 */
2001
+ rebuild() {
2002
+ this.transcript.clear();
2003
+ this.rendered = [];
2004
+ this.refreshTranscript();
2005
+ for (const b of this.localBlocks) this.transcript.addChild(b);
2006
+ this.deps.tui.requestRender();
2007
+ }
2008
+ async submit(text) {
2009
+ const trimmed = text.trim();
2010
+ if (!trimmed) return;
2011
+ this.editor.addToHistory(trimmed);
2012
+ if (trimmed === "!") {
2013
+ this.addLocalLines([theme.dim("\u7528\u6CD5: ! <command>\uFF08\u672C\u5730\u6267\u884C\uFF0C\u4E0D\u8FDB\u5BF9\u8BDD\uFF09")]);
2014
+ return;
2015
+ }
2016
+ if (isShellCommand(trimmed)) {
2017
+ const cmd = parseShellCommand(trimmed);
2018
+ this.addLocalLines([`${theme.accent("!")} ${theme.dim(cmd)} ${theme.warn("running\u2026")}`]);
2019
+ const width = this.deps.tui.terminal?.columns ?? 80;
2020
+ const result = await runShell(cmd);
2021
+ this.addLocalLines(shellResultToLines(result, width));
2022
+ return;
2023
+ }
2024
+ const slash = parseSlash(trimmed);
2025
+ if (slash) {
2026
+ const reply = await this.deps.onSlash(slash.cmd, slash.args);
2027
+ if (reply) {
2028
+ const turn = {
2029
+ messageID: `slash-${Date.now()}`,
2030
+ role: "assistant",
2031
+ done: true,
2032
+ parts: [{ id: `${Date.now()}`, type: "text", text: reply }]
2033
+ };
2034
+ for (const p of turn.parts) this.transcript.addChild(buildBlock(p, turn, this.display, () => this.deps.tui.requestRender()));
2035
+ this.deps.tui.requestRender();
2036
+ }
2037
+ return;
2038
+ }
2039
+ await this.deps.store.send(trimmed);
2040
+ this.refreshTranscript();
2041
+ }
2042
+ addLocalLines(lines) {
2043
+ const block = new RawLines(lines);
2044
+ this.localBlocks.push(block);
2045
+ this.transcript.addChild(block);
2046
+ this.deps.tui.requestRender();
2047
+ }
2048
+ /** store.onChange 回调:增量挂新 turn/新 part;流式快照替换;pending 移除。 */
2049
+ refreshTranscript() {
2050
+ const turns = this.deps.store.turns;
2051
+ for (let i = 0; i < this.rendered.length && i < turns.length; i++) {
2052
+ const r = this.rendered[i];
2053
+ if (r.queued && !turns[i].queued) {
2054
+ this.rebuild();
2055
+ return;
2056
+ }
2057
+ }
2058
+ while (this.rendered.length < turns.length) {
2059
+ const t = turns[this.rendered.length];
2060
+ const entry2 = { messageID: t.messageID, blocks: [], queued: t.queued };
2061
+ for (const p of t.parts) entry2.blocks.push(this.mkBlock(p, t));
2062
+ if (t.role === "assistant" && !t.done) entry2.pending = new PendingBlock();
2063
+ for (const b of entry2.blocks) this.transcript.addChild(b);
2064
+ if (entry2.pending) this.transcript.addChild(entry2.pending);
2065
+ this.rendered.push(entry2);
2066
+ }
2067
+ const last = turns.at(-1);
2068
+ const entry = this.rendered.at(-1);
2069
+ if (!last || !entry || entry.messageID !== last.messageID) {
2070
+ this.deps.tui.requestRender();
2071
+ return;
2072
+ }
2073
+ while (entry.blocks.length < last.parts.length) {
2074
+ const p = last.parts[entry.blocks.length];
2075
+ const b = this.mkBlock(p, last);
2076
+ entry.blocks.push(b);
2077
+ if (entry.pending) this.transcript.removeChild(entry.pending);
2078
+ this.transcript.addChild(b);
2079
+ if (entry.pending) this.transcript.addChild(entry.pending);
2080
+ }
2081
+ for (let j = 0; j < entry.blocks.length; j++) entry.blocks[j].update(last.parts[j]);
2082
+ if (entry.pending && last.done) {
2083
+ this.transcript.removeChild(entry.pending);
2084
+ entry.pending = void 0;
2085
+ }
2086
+ this.deps.tui.requestRender();
2087
+ }
2088
+ /** 滚动 transcript 使目标 turn 位于视口顶部(搜索跳转用)。 */
2089
+ scrollToTurn(messageID) {
2090
+ const entry = this.rendered.find((e) => e.messageID === messageID);
2091
+ if (!entry || entry.blocks.length === 0) return false;
2092
+ const first = entry.blocks[0];
2093
+ const idx = this.transcript.children.indexOf(first);
2094
+ if (idx < 0) return false;
2095
+ const width = this.deps.tui.terminal?.columns ?? 80;
2096
+ let lines = 0;
2097
+ for (let i = 0; i < idx; i++) lines += this.transcript.children[i].render(width).length;
2098
+ this.scrollView.scrollTo(Math.max(0, lines - 1));
2099
+ return true;
2100
+ }
2101
+ mkBlock(p, t) {
2102
+ return buildBlock(p, t, this.display, () => this.deps.tui.requestRender());
2103
+ }
2104
+ /** /verbose 循环:all(默认展开)↔ off(默认折叠,单击仍可展开)。 */
2105
+ cycleVerbosity() {
2106
+ this.display.toolVerbosity = this.display.toolVerbosity === "all" ? "off" : "all";
2107
+ if (!this.display.focus) this.savedVerbosity = this.display.toolVerbosity;
2108
+ this.invalidateAll();
2109
+ return this.display.toolVerbosity;
2110
+ }
2111
+ /** /focus 静视图(Hermes 语义:开时收起 tool 输出并记住,关时恢复)。 */
2112
+ toggleFocus() {
2113
+ if (this.display.focus) {
2114
+ this.display.focus = false;
2115
+ this.display.toolVerbosity = this.savedVerbosity;
2116
+ } else {
2117
+ this.display.focus = true;
2118
+ this.savedVerbosity = this.display.toolVerbosity;
2119
+ this.display.toolVerbosity = "off";
2120
+ }
2121
+ this.invalidateAll();
2122
+ return this.display.focus;
2123
+ }
2124
+ invalidateAll() {
2125
+ for (const e of this.rendered) for (const b of e.blocks) b.invalidate();
2126
+ this.deps.tui.requestRender();
2127
+ }
2128
+ };
2129
+ }
2130
+ });
2131
+
2132
+ // src/ui/slash-commands.ts
2133
+ function createSlashHandler(deps) {
2134
+ return async (rawCmd, args) => {
2135
+ const cmd = resolveCommand(rawCmd) ?? rawCmd;
2136
+ switch (cmd) {
2137
+ case "help":
2138
+ deps.toggleHelp();
2139
+ return null;
2140
+ case "older":
2141
+ await deps.loadOlder();
2142
+ return null;
2143
+ case "verbose":
2144
+ return `\u5DE5\u5177\u8F93\u51FA: ${deps.cycleVerbosity() === "all" ? "\u5C55\u5F00" : "\u6298\u53E0"}\uFF08\u5355\u51FB tool \u5757\u53EF\u5355\u72EC\u5207\u6362\uFF09`;
2145
+ case "focus":
2146
+ return deps.toggleFocus() ? "\u9759\u89C6\u56FE\u5DF2\u5F00\u542F\uFF08/focus off \u6062\u590D\uFF09" : "\u9759\u89C6\u56FE\u5DF2\u5173\u95ED";
2147
+ case "diff":
2148
+ return deps.showDiff(args.trim());
2149
+ case "new":
2150
+ return deps.rotateTopic();
2151
+ case "btw":
2152
+ return deps.btw(args);
2153
+ case "sessions":
2154
+ await deps.showSessionPicker();
2155
+ return null;
2156
+ case "rename":
2157
+ return deps.rename(args);
2158
+ case "fork":
2159
+ return deps.fork();
2160
+ case "status":
2161
+ deps.showStatusRecap();
2162
+ return null;
2163
+ case "queue":
2164
+ await deps.showQueueManager();
2165
+ return null;
2166
+ case "model":
2167
+ await deps.showModelPicker();
2168
+ return null;
2169
+ case "compact":
2170
+ return deps.compact();
2171
+ case "undo":
2172
+ return deps.undo();
2173
+ case "redo":
2174
+ return deps.redo();
2175
+ case "editor":
2176
+ await deps.openExternalEditor();
2177
+ return null;
2178
+ default:
2179
+ return `\u672A\u77E5\u547D\u4EE4 /${rawCmd}\uFF08\u53EF\u7528: ${COMMAND_REGISTRY.map((c) => `/${c.name}`).join(" ")}\uFF09`;
2180
+ }
2181
+ };
2182
+ }
2183
+ var init_slash_commands = __esm({
2184
+ "src/ui/slash-commands.ts"() {
2185
+ "use strict";
2186
+ init_command_registry();
2187
+ }
2188
+ });
2189
+
2190
+ // src/external-editor.ts
2191
+ import { spawn } from "node:child_process";
2192
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2193
+ import { tmpdir } from "node:os";
2194
+ import { join } from "node:path";
2195
+ function defaultEditorCommand(env = process.env, platform = process.platform) {
2196
+ return env.VISUAL || env.EDITOR || (platform === "win32" ? "notepad" : "nano");
2197
+ }
2198
+ async function editInExternalEditor(opts) {
2199
+ const directory = mkdtempSync(join(tmpdir(), "mafw-tui-editor-"));
2200
+ const filePath = join(directory, "prompt.md");
2201
+ try {
2202
+ writeFileSync(filePath, opts.content, "utf-8");
2203
+ const [editor, ...editorArgs] = opts.command.split(" ").filter(Boolean);
2204
+ const spawnFn = opts.spawnFn ?? ((ed, args, o) => spawn(ed, args, o));
2205
+ const exitCode = await new Promise((resolve) => {
2206
+ const child = spawnFn(editor, [...editorArgs, filePath], {
2207
+ stdio: "inherit",
2208
+ shell: process.platform === "win32"
2209
+ });
2210
+ child.on("error", () => resolve(null));
2211
+ child.on("close", (code) => resolve(code));
2212
+ });
2213
+ if (exitCode !== 0) return { status: "failed" };
2214
+ return { status: "complete", content: readFileSync(filePath, "utf-8").replace(/\n$/, "") };
2215
+ } finally {
2216
+ try {
2217
+ rmSync(directory, { recursive: true, force: true });
2218
+ } catch {
2219
+ }
2220
+ }
2221
+ }
2222
+ var init_external_editor = __esm({
2223
+ "src/external-editor.ts"() {
2224
+ "use strict";
2225
+ }
2226
+ });
2227
+
2228
+ // src/ui/clickable-tui.ts
2229
+ function enableClickDispatch(tui) {
2230
+ const self = tui;
2231
+ if (typeof self.handleSelectionMouseEvent !== "function") return;
2232
+ const original = self.handleSelectionMouseEvent.bind(tui);
2233
+ self.handleSelectionMouseEvent = (event) => {
2234
+ if (!event.release && (event.button & 3) === 0 && (event.button & 32) === 0) {
2235
+ if (dispatchClick(tui, event.x, event.y)) return;
2236
+ }
2237
+ original(event);
2238
+ };
2239
+ }
2240
+ function dispatchClick(tui, x, y) {
2241
+ try {
2242
+ const self = tui;
2243
+ const top = typeof self.getTopmostVisibleOverlay === "function" ? self.getTopmostVisibleOverlay() : void 0;
2244
+ if (top) return dispatchOverlayClick(tui, top, x, y);
2245
+ return dispatchLayoutClick(tui, x, y);
2246
+ } catch {
2247
+ return false;
2248
+ }
2249
+ }
2250
+ function dispatchOverlayClick(tui, entry, x, y) {
2251
+ const self = tui;
2252
+ const resolve = self.resolveOverlayLayout;
2253
+ if (typeof resolve !== "function") return false;
2254
+ const termWidth = tui.terminal.columns;
2255
+ const termHeight = tui.terminal.rows;
2256
+ const { width, maxHeight } = resolve.call(tui, entry.options, 0, termWidth, termHeight);
2257
+ let lines = entry.component?.render(width) ?? [];
2258
+ if (maxHeight !== void 0 && lines.length > maxHeight) lines = lines.slice(0, maxHeight);
2259
+ const { row, col } = resolve.call(tui, entry.options, lines.length, termWidth, termHeight);
2260
+ if (x >= col && x < col + width && y >= row && y < row + lines.length) {
2261
+ const comp = entry.component;
2262
+ if (comp && typeof comp.handleMouseClick === "function") {
2263
+ return comp.handleMouseClick(x - col, y - row) === true;
2264
+ }
2265
+ }
2266
+ return true;
2267
+ }
2268
+ function dispatchLayoutClick(tui, x, y) {
2269
+ const root = tui.currentLayout?.root;
2270
+ if (!root) return false;
2271
+ const hits = [];
2272
+ const visit = (box, depth) => {
2273
+ const r = box?.rect;
2274
+ if (r && x >= r.x && x < r.x + r.width && y >= r.y && y < r.y + r.height && box.component) {
2275
+ hits.push({ comp: box.component, rect: r, depth });
2276
+ }
2277
+ for (const child of box?.children ?? []) visit(child, depth + 1);
2278
+ };
2279
+ visit(root, 0);
2280
+ hits.sort((a, b) => b.depth - a.depth);
2281
+ for (const hit of hits) {
2282
+ const comp = hit.comp;
2283
+ if (typeof comp.handleMouseClick === "function") {
2284
+ if (comp.handleMouseClick(x - hit.rect.x, y - hit.rect.y) === true) return true;
2285
+ }
2286
+ }
2287
+ return false;
2288
+ }
2289
+ var init_clickable_tui = __esm({
2290
+ "src/ui/clickable-tui.ts"() {
2291
+ "use strict";
2292
+ }
2293
+ });
2294
+
2295
+ // src/ui/clickable-select-list.ts
2296
+ var ClickableSelectList, HeaderSelectOverlay;
2297
+ var init_clickable_select_list = __esm({
2298
+ "src/ui/clickable-select-list.ts"() {
2299
+ "use strict";
2300
+ ClickableSelectList = class {
2301
+ /** 被包装的 SelectList(公开只读:调用方挂 onSelect/onCancel 用)。 */
2302
+ list;
2303
+ constructor(list) {
2304
+ this.list = list;
2305
+ }
2306
+ render(width) {
2307
+ return this.list.render(width);
2308
+ }
2309
+ invalidate() {
2310
+ this.list.invalidate();
2311
+ }
2312
+ handleInput(data) {
2313
+ this.list.handleInput(data);
2314
+ }
2315
+ handleMouseClick(_col, row) {
2316
+ const items = this.list.filteredItems;
2317
+ if (!Array.isArray(items) || items.length === 0) return false;
2318
+ const selectedIndex = this.list.selectedIndex;
2319
+ const maxVisible = this.list.maxVisible;
2320
+ const start = Math.max(0, Math.min(selectedIndex - Math.floor(maxVisible / 2), items.length - maxVisible));
2321
+ const visible = Math.min(maxVisible, items.length - start);
2322
+ if (row < 0 || row >= visible) return false;
2323
+ const item = items[start + row];
2324
+ if (!item) return false;
2325
+ this.list.setSelectedIndex(start + row);
2326
+ this.list.onSelect?.(item);
2327
+ return true;
2328
+ }
2329
+ };
2330
+ HeaderSelectOverlay = class {
2331
+ header;
2332
+ wrapper;
2333
+ headerLines = 1;
2334
+ constructor(header, list) {
2335
+ this.header = header;
2336
+ this.wrapper = new ClickableSelectList(list);
2337
+ }
2338
+ render(width) {
2339
+ const head = this.header.render(width);
2340
+ this.headerLines = head.length;
2341
+ return [...head, ...this.wrapper.render(width)];
2342
+ }
2343
+ invalidate() {
2344
+ this.header.invalidate();
2345
+ this.wrapper.invalidate();
2346
+ }
2347
+ handleInput(data) {
2348
+ this.wrapper.handleInput(data);
2349
+ }
2350
+ handleMouseClick(col, row) {
2351
+ return this.wrapper.handleMouseClick(col, row - this.headerLines);
2352
+ }
2353
+ };
2354
+ }
2355
+ });
2356
+
2357
+ // src/ui/keymap.ts
2358
+ import { matchesKey, Key } from "@earendil-works/pi-tui";
2359
+ function dispatchKey(data, ctx) {
2360
+ ctx.interaction.update({
2361
+ overlayOpen: ctx.queries.overlayOpen(),
2362
+ streaming: ctx.queries.streaming()
2363
+ });
2364
+ for (const b of BINDINGS) {
2365
+ if (b.match(data) && b.when(ctx)) {
2366
+ b.invoke(ctx);
2367
+ return true;
2368
+ }
2369
+ }
2370
+ return false;
2371
+ }
2372
+ var TAB_IDS, ALT_DIGITS, BINDINGS;
2373
+ var init_keymap = __esm({
2374
+ "src/ui/keymap.ts"() {
2375
+ "use strict";
2376
+ TAB_IDS = ["chat", "goals", "memory", "triage"];
2377
+ ALT_DIGITS = [Key.alt("1"), Key.alt("2"), Key.alt("3"), Key.alt("4")];
2378
+ BINDINGS = [
2379
+ {
2380
+ invoke: (ctx) => ctx.actions.quit(),
2381
+ match: (d) => matchesKey(d, Key.ctrl("c")),
2382
+ when: () => true
2383
+ },
2384
+ {
2385
+ invoke: (ctx) => ctx.actions.abortTurn(),
2386
+ match: (d) => matchesKey(d, Key.escape),
2387
+ when: (ctx) => ctx.interaction.is("busy")
2388
+ },
2389
+ {
2390
+ invoke: (ctx) => ctx.actions.blurMemorySearch(),
2391
+ match: (d) => matchesKey(d, Key.escape),
2392
+ when: (ctx) => ctx.queries.activeTab() === "memory" && ctx.queries.memoryInputFocused()
2393
+ },
2394
+ {
2395
+ invoke: (ctx) => ctx.actions.openExternalEditor(),
2396
+ match: (d) => matchesKey(d, Key.ctrl("g")),
2397
+ when: (ctx) => ctx.queries.activeTab() === "chat" && ctx.queries.editing() && !ctx.queries.overlayOpen()
2398
+ },
2399
+ ...TAB_IDS.map((id, i) => ({
2400
+ invoke: (ctx) => ctx.actions.switchTab(id),
2401
+ match: (d) => d === String(i + 1),
2402
+ when: (ctx) => !ctx.queries.editing()
2403
+ })),
2404
+ ...TAB_IDS.map((id, i) => ({
2405
+ invoke: (ctx) => ctx.actions.switchTab(id),
2406
+ match: (d) => matchesKey(d, ALT_DIGITS[i]),
2407
+ when: () => true
2408
+ })),
2409
+ {
2410
+ invoke: (ctx) => ctx.actions.openTranscriptSearch(),
2411
+ match: (d) => matchesKey(d, Key.ctrl("o")),
2412
+ when: (ctx) => ctx.queries.activeTab() === "chat" && !ctx.queries.overlayOpen()
2413
+ },
2414
+ {
2415
+ invoke: (ctx) => ctx.actions.quit(),
2416
+ match: (d) => d === "q",
2417
+ when: (ctx) => !ctx.queries.editing()
2418
+ },
2419
+ {
2420
+ invoke: (ctx) => ctx.actions.toggleHelp(),
2421
+ match: (d) => d === "?",
2422
+ when: (ctx) => !ctx.queries.editing()
2423
+ }
2424
+ ];
2425
+ }
2426
+ });
2427
+
2428
+ // src/ui/interaction-state.ts
2429
+ var InteractionStateMachine;
2430
+ var init_interaction_state = __esm({
2431
+ "src/ui/interaction-state.ts"() {
2432
+ "use strict";
2433
+ InteractionStateMachine = class {
2434
+ mode = "prompt";
2435
+ onChange;
2436
+ /** 由原始输入派生模式(transcript 态由显式 enter/exit 管理,update 不覆盖)。 */
2437
+ update(inputs) {
2438
+ if (this.mode === "transcript") return this.mode;
2439
+ const next = inputs.overlayOpen ? "overlay" : inputs.streaming ? "busy" : "prompt";
2440
+ this.setMode(next);
2441
+ return this.mode;
2442
+ }
2443
+ enterTranscript() {
2444
+ this.setMode("transcript");
2445
+ }
2446
+ exitTranscript() {
2447
+ this.setMode("prompt");
2448
+ }
2449
+ is(m) {
2450
+ return this.mode === m;
2451
+ }
2452
+ setMode(next) {
2453
+ if (this.mode === next) return;
2454
+ this.mode = next;
2455
+ this.onChange?.(next);
2456
+ }
2457
+ };
2458
+ }
2459
+ });
2460
+
2461
+ // src/ui/queue-overlay.ts
2462
+ import { truncateToWidth as truncateToWidth6 } from "@earendil-works/pi-tui";
2463
+ var QueueOverlay;
2464
+ var init_queue_overlay = __esm({
2465
+ "src/ui/queue-overlay.ts"() {
2466
+ "use strict";
2467
+ init_theme();
2468
+ QueueOverlay = class {
2469
+ selected = 0;
2470
+ deps;
2471
+ constructor(deps) {
2472
+ this.deps = deps;
2473
+ }
2474
+ invalidate() {
2475
+ }
2476
+ render(width) {
2477
+ const items = this.deps.getItems();
2478
+ const lines = [theme.accent("\u6392\u961F\u6D88\u606F"), ""];
2479
+ if (items.length === 0) {
2480
+ lines.push(theme.dim(" \uFF08\u961F\u5217\u4E3A\u7A7A\u2014\u2014busy \u671F\u95F4\u53D1\u9001\u7684\u6D88\u606F\u4F1A\u6392\u961F\uFF09"));
2481
+ lines.push("");
2482
+ lines.push(theme.dim(" Esc \u5173\u95ED"));
2483
+ return lines;
2484
+ }
2485
+ items.forEach((text, i) => {
2486
+ const mark = i === this.selected ? theme.accent("\u203A ") : " ";
2487
+ const preview = text.length > 60 ? `${text.slice(0, 57)}...` : text;
2488
+ lines.push(truncateToWidth6(`${mark}${i + 1}. ${preview}`, width));
2489
+ });
2490
+ lines.push("");
2491
+ lines.push(theme.dim(" Enter \u6536\u56DE\u7F16\u8F91 \xB7 d \u4E22\u5F03 \xB7 Esc \u5173\u95ED"));
2492
+ return lines;
2493
+ }
2494
+ handleInput(data) {
2495
+ const items = this.deps.getItems();
2496
+ if (items.length === 0) {
2497
+ this.deps.onClose();
2498
+ return;
2499
+ }
2500
+ if (data === "j" || data === "\x1B[B") {
2501
+ this.selected = Math.min(items.length - 1, this.selected + 1);
2502
+ this.deps.requestRender();
2503
+ return;
2504
+ }
2505
+ if (data === "k" || data === "\x1B[A") {
2506
+ this.selected = Math.max(0, this.selected - 1);
2507
+ this.deps.requestRender();
2508
+ return;
2509
+ }
2510
+ if (data === "\r" || data === "\n") {
2511
+ this.deps.onTakeBack(this.selected);
2512
+ this.deps.onClose();
2513
+ return;
2514
+ }
2515
+ if (data === "d") {
2516
+ this.deps.onDrop(this.selected);
2517
+ this.selected = Math.min(this.selected, Math.max(0, this.deps.getItems().length - 1));
2518
+ return;
2519
+ }
2520
+ if (data === "\x1B" || data === "") {
2521
+ this.deps.onClose();
2522
+ return;
2523
+ }
2524
+ }
2525
+ };
2526
+ }
2527
+ });
2528
+
2529
+ // src/ui/transcript-search.ts
2530
+ import { truncateToWidth as truncateToWidth7 } from "@earendil-works/pi-tui";
2531
+ var stripAnsi3, MAX_RESULTS, SNIPPET_WIDTH, TranscriptSearchOverlay;
2532
+ var init_transcript_search = __esm({
2533
+ "src/ui/transcript-search.ts"() {
2534
+ "use strict";
2535
+ init_theme();
2536
+ stripAnsi3 = (s) => s.replace(/\x1b\[[0-9;]*m/g, "");
2537
+ MAX_RESULTS = 8;
2538
+ SNIPPET_WIDTH = 56;
2539
+ TranscriptSearchOverlay = class {
2540
+ query = "";
2541
+ selected = 0;
2542
+ deps;
2543
+ constructor(deps) {
2544
+ this.deps = deps;
2545
+ }
2546
+ invalidate() {
2547
+ }
2548
+ matches() {
2549
+ const q = this.query.trim().toLowerCase();
2550
+ if (!q) return [];
2551
+ const out = [];
2552
+ const turns = this.deps.getTurns();
2553
+ for (let i = 0; i < turns.length && out.length < MAX_RESULTS; i++) {
2554
+ const t = turns[i];
2555
+ for (const p of t.parts) {
2556
+ if (p.type === "tool") continue;
2557
+ const text = stripAnsi3(p.text);
2558
+ const at = text.toLowerCase().indexOf(q);
2559
+ if (at < 0) continue;
2560
+ const start = Math.max(0, at - 12);
2561
+ const snippet = `${start > 0 ? "\u2026" : ""}${text.slice(start, start + SNIPPET_WIDTH).replace(/\n/g, " ")}`;
2562
+ out.push({ turnIndex: i, role: t.role, snippet });
2563
+ break;
2564
+ }
2565
+ }
2566
+ return out;
2567
+ }
2568
+ render(width) {
2569
+ const lines = [];
2570
+ lines.push(truncateToWidth7(`${theme.accent("\u641C\u7D22: ")}${this.query}${theme.dim("\u258F")}`, width));
2571
+ const ms = this.matches();
2572
+ if (this.query.trim() === "") {
2573
+ lines.push(theme.dim(" \u8F93\u5165\u5173\u952E\u8BCD\u8FC7\u6EE4\u4F1A\u8BDD\u5185\u5BB9"));
2574
+ } else if (ms.length === 0) {
2575
+ lines.push(theme.dim(" \u65E0\u5339\u914D"));
2576
+ } else {
2577
+ ms.forEach((m, i) => {
2578
+ const mark = i === this.selected ? theme.accent("\u203A ") : " ";
2579
+ const role = m.role === "user" ? theme.accent("\u4F60") : theme.dim("AI");
2580
+ lines.push(truncateToWidth7(`${mark}${role} ${theme.dim(m.snippet)}`, width));
2581
+ });
2582
+ }
2583
+ lines.push(theme.dim(" \u2191/\u2193 \u9009\u62E9 \xB7 Enter \u8DF3\u8F6C \xB7 Esc \u5173\u95ED"));
2584
+ return lines;
2585
+ }
2586
+ handleInput(data) {
2587
+ if (data === "\x1B" || data === "") {
2588
+ this.deps.onClose();
2589
+ return;
2590
+ }
2591
+ if (data === "\r" || data === "\n") {
2592
+ const ms = this.matches();
2593
+ const m = ms[this.selected];
2594
+ if (m) this.deps.onJump(m.turnIndex);
2595
+ this.deps.onClose();
2596
+ return;
2597
+ }
2598
+ if (data === "\x1B[A" || data === "k") {
2599
+ this.selected = Math.max(0, this.selected - 1);
2600
+ this.deps.requestRender();
2601
+ return;
2602
+ }
2603
+ if (data === "\x1B[B" || data === "j") {
2604
+ this.selected = Math.min(this.matches().length - 1, this.selected + 1);
2605
+ this.deps.requestRender();
2606
+ return;
2607
+ }
2608
+ if (data === "\x7F" || data === "\b") {
2609
+ this.query = this.query.slice(0, -1);
2610
+ this.selected = 0;
2611
+ this.deps.requestRender();
2612
+ return;
2613
+ }
2614
+ if (data.length === 1 && data >= " ") {
2615
+ this.query += data;
2616
+ this.selected = 0;
2617
+ this.deps.requestRender();
2618
+ return;
2619
+ }
2620
+ }
2621
+ };
2622
+ }
2623
+ });
2624
+
2625
+ // src/ui/confirm-overlay.ts
2626
+ import { truncateToWidth as truncateToWidth8 } from "@earendil-works/pi-tui";
2627
+ var ConfirmOverlay;
2628
+ var init_confirm_overlay = __esm({
2629
+ "src/ui/confirm-overlay.ts"() {
2630
+ "use strict";
2631
+ init_theme();
2632
+ ConfirmOverlay = class {
2633
+ selected = 0;
2634
+ deps;
2635
+ constructor(deps) {
2636
+ this.deps = deps;
2637
+ }
2638
+ invalidate() {
2639
+ }
2640
+ options() {
2641
+ return [
2642
+ { key: "once", label: "\u672C\u6B21\u6267\u884C" },
2643
+ { key: "always", label: "\u672C\u4F1A\u8BDD\u603B\u662F\u6267\u884C" },
2644
+ { key: "cancel", label: "\u53D6\u6D88" }
2645
+ ];
2646
+ }
2647
+ render(width) {
2648
+ const lines = [truncateToWidth8(theme.warn(this.deps.title), width)];
2649
+ if (this.deps.description) lines.push(truncateToWidth8(theme.dim(this.deps.description), width));
2650
+ lines.push("");
2651
+ this.options().forEach((o, i) => {
2652
+ const mark = i === this.selected ? theme.accent("\u203A ") : " ";
2653
+ lines.push(truncateToWidth8(`${mark}${o.label}`, width));
2654
+ });
2655
+ lines.push("");
2656
+ lines.push(theme.dim(" Enter \u6267\u884C \xB7 l \u603B\u662F \xB7 d \u53D6\u6D88 \xB7 j/k \u9009\u62E9"));
2657
+ return lines;
2658
+ }
2659
+ handleInput(data) {
2660
+ const opts = this.options();
2661
+ if (data === "\r" || data === "\n") {
2662
+ this.deps.onAnswer(opts[this.selected].key);
2663
+ return;
2664
+ }
2665
+ if (data === "l") {
2666
+ this.deps.onAnswer("always");
2667
+ return;
2668
+ }
2669
+ if (data === "d" || data === "c" || data === "\x1B" || data === "") {
2670
+ this.deps.onAnswer("cancel");
2671
+ return;
2672
+ }
2673
+ if (data === "j" || data === "\x1B[B") {
2674
+ this.selected = Math.min(opts.length - 1, this.selected + 1);
2675
+ this.deps.requestRender();
2676
+ return;
2677
+ }
2678
+ if (data === "k" || data === "\x1B[A") {
2679
+ this.selected = Math.max(0, this.selected - 1);
2680
+ this.deps.requestRender();
2681
+ }
2682
+ }
2683
+ };
2684
+ }
2685
+ });
2686
+
2687
+ // src/ui/session-recap.ts
2688
+ import { truncateToWidth as truncateToWidth9 } from "@earendil-works/pi-tui";
2689
+ function computeRecap(turns) {
2690
+ let userTurns = 0;
2691
+ let assistantTurns = 0;
2692
+ let toolCalls = 0;
2693
+ const toolCounts = /* @__PURE__ */ new Map();
2694
+ let lastUserPrompt = null;
2695
+ let lastAssistantText = null;
2696
+ for (const t of turns) {
2697
+ if (t.queued) continue;
2698
+ if (t.role === "user") {
2699
+ userTurns++;
2700
+ const text = t.parts.find((p) => p.type === "text")?.text;
2701
+ if (text) lastUserPrompt = stripAnsi4(text);
2702
+ } else {
2703
+ assistantTurns++;
2704
+ const text = t.parts.filter((p) => p.type === "text").map((p) => p.text).join("\n").trim();
2705
+ if (text) lastAssistantText = stripAnsi4(text);
2706
+ for (const p of t.parts) {
2707
+ if (p.type !== "tool") continue;
2708
+ toolCalls++;
2709
+ const name = p.toolName ?? "tool";
2710
+ toolCounts.set(name, (toolCounts.get(name) ?? 0) + 1);
2711
+ }
2712
+ }
2713
+ }
2714
+ const topTools = [...toolCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
2715
+ return { userTurns, assistantTurns, toolCalls, topTools, lastUserPrompt, lastAssistantText };
2716
+ }
2717
+ function recapLines(recap, sessionID, project) {
2718
+ const lines = [];
2719
+ lines.push(theme.accent("\u4F1A\u8BDD\u72B6\u6001"));
2720
+ lines.push(theme.dim(` ${project} \xB7 ${sessionID.slice(0, 16)}`));
2721
+ lines.push("");
2722
+ lines.push("\u8F6E\u6B21");
2723
+ lines.push(theme.dim(` user ${recap.userTurns} \xB7 assistant ${recap.assistantTurns} \xB7 \u5DE5\u5177\u8C03\u7528 ${recap.toolCalls}`));
2724
+ if (recap.topTools.length > 0) {
2725
+ lines.push(theme.dim(` top: ${recap.topTools.map(([n, c]) => `${n}\xD7${c}`).join(" ")}`));
2726
+ }
2727
+ lines.push("");
2728
+ lines.push("\u6700\u8FD1\u4EA4\u4E92");
2729
+ lines.push(truncateToWidth9(theme.dim(` \u4F60: ${recap.lastUserPrompt ?? "\uFF08\u65E0\uFF09"}`), 78));
2730
+ lines.push(truncateToWidth9(theme.dim(` AI: ${recap.lastAssistantText ? trunc(recap.lastAssistantText, 100) : "\uFF08\u65E0\uFF09"}`), 78));
2731
+ return lines;
2732
+ }
2733
+ var stripAnsi4, trunc;
2734
+ var init_session_recap = __esm({
2735
+ "src/ui/session-recap.ts"() {
2736
+ "use strict";
2737
+ init_theme();
2738
+ stripAnsi4 = (s) => s.replace(/\x1b\[[0-9;]*m/g, "");
2739
+ trunc = (s, n) => s.length > n ? `${s.slice(0, n - 1)}\u2026` : s;
2740
+ }
2741
+ });
2742
+
2743
+ // src/ui/overlays.ts
2744
+ import { Text, SelectList } from "@earendil-works/pi-tui";
2745
+ function permissionSummary(req) {
2746
+ const patterns = req.patterns.length > 0 ? req.patterns.join(", ") : "(\u65E0 patterns)";
2747
+ return `${req.permission}: ${patterns}`;
2748
+ }
2749
+ function permissionToItems() {
2750
+ return [
2751
+ { value: "once", label: "\u5141\u8BB8\u4E00\u6B21", description: "\u4EC5\u672C\u6B21" },
2752
+ { value: "always", label: "\u603B\u662F\u5141\u8BB8", description: "\u672C\u4F1A\u8BDD\u5185" },
2753
+ { value: "reject", label: "\u62D2\u7EDD", description: "\u62D2\u7EDD\u6B64\u8BF7\u6C42" }
2754
+ ];
2755
+ }
2756
+ function showPermissionOverlay(tui, req, reply) {
2757
+ const list = new SelectList(permissionToItems(), 3, selectListTheme);
2758
+ const overlay = new HeaderSelectOverlay(
2759
+ new Text(theme.warn("\u6743\u9650\u8BF7\u6C42") + " " + permissionSummary(req), 1, 1),
2760
+ list
2761
+ );
2762
+ const handle = tui.showOverlay(overlay, { width: "70%", maxHeight: 10, anchor: "center" });
2763
+ const done = () => handle.hide();
2764
+ list.onSelect = (item) => {
2765
+ void reply(item.value).catch(() => {
2766
+ }).finally(done);
2767
+ };
2768
+ list.onCancel = done;
2769
+ return handle;
2770
+ }
2771
+ var init_overlays = __esm({
2772
+ "src/ui/overlays.ts"() {
2773
+ "use strict";
2774
+ init_theme();
2775
+ init_chat_tab();
2776
+ init_clickable_select_list();
2777
+ }
2778
+ });
2779
+
2780
+ // src/store/goals-store.ts
2781
+ var TERMINAL_PHASES, GoalsStore;
2782
+ var init_goals_store = __esm({
2783
+ "src/store/goals-store.ts"() {
2784
+ "use strict";
2785
+ TERMINAL_PHASES = ["COMPLETED", "FAILED", "ARCHIVED", "CANCELLED"];
2786
+ GoalsStore = class _GoalsStore {
2787
+ rows = [];
2788
+ questions = [];
2789
+ stopped = false;
2790
+ cleanup;
2791
+ schedule;
2792
+ deps;
2793
+ constructor(deps) {
2794
+ this.deps = deps;
2795
+ this.schedule = deps.schedule ?? ((fn, ms) => {
2796
+ const t = setInterval(fn, ms);
2797
+ return () => clearInterval(t);
2798
+ });
2799
+ }
2800
+ static isActive(g) {
2801
+ return !TERMINAL_PHASES.includes(g.phase);
2802
+ }
2803
+ start() {
2804
+ this.stopped = false;
2805
+ void this.poll();
2806
+ this.cleanup = this.schedule(() => {
2807
+ if (!this.stopped) void this.poll();
2808
+ }, 1e4);
2809
+ }
2810
+ stop() {
2811
+ this.stopped = true;
2812
+ this.cleanup?.();
2813
+ }
2814
+ async poll() {
2815
+ try {
2816
+ const [goals, questions] = await Promise.all([
2817
+ this.deps.goals.list(),
2818
+ this.deps.questions.list().catch(() => [])
2819
+ ]);
2820
+ this.rows.length = 0;
2821
+ const rows = [...goals];
2822
+ rows.sort((a, b) => Number(_GoalsStore.isActive(b)) - Number(_GoalsStore.isActive(a)));
2823
+ this.rows.push(...rows);
2824
+ this.questions.length = 0;
2825
+ this.questions.push(...questions);
2826
+ this.deps.onChange();
2827
+ } catch {
2828
+ }
2829
+ }
2830
+ };
2831
+ }
2832
+ });
2833
+
2834
+ // src/ui/goals-tab.ts
2835
+ import {
2836
+ Text as Text2,
2837
+ SelectList as SelectList2,
2838
+ matchesKey as matchesKey2,
2839
+ Key as Key2,
2840
+ truncateToWidth as truncateToWidth10
2841
+ } from "@earendil-works/pi-tui";
2842
+ function formatGoalSessions(sessions) {
2843
+ if (sessions.length === 0) return [theme.dim(" \uFF08\u65E0 session \u8BB0\u5F55\uFF09")];
2844
+ return sessions.map((s) => `${theme.accent("\u203A")} ${s.phase} \xB7 loop ${s.loop}${s.title ? theme.dim(` \xB7 ${s.title}`) : ""} ${theme.dim(s.sessionID)}`);
2845
+ }
2846
+ function escCloser(tui, close) {
2847
+ const off = tui.addInputListener((data) => {
2848
+ if (matchesKey2(data, Key2.escape)) {
2849
+ off();
2850
+ close();
2851
+ return { consume: true };
2852
+ }
2853
+ return void 0;
2854
+ });
2855
+ return off;
2856
+ }
2857
+ var GoalsTab;
2858
+ var init_goals_tab = __esm({
2859
+ "src/ui/goals-tab.ts"() {
2860
+ "use strict";
2861
+ init_goals_store();
2862
+ init_chat_store();
2863
+ init_message_blocks();
2864
+ init_theme();
2865
+ init_chat_tab();
2866
+ init_clickable_select_list();
2867
+ GoalsTab = class {
2868
+ selected = 0;
2869
+ deps;
2870
+ constructor(deps) {
2871
+ this.deps = deps;
2872
+ }
2873
+ invalidate() {
2874
+ }
2875
+ get rowCount() {
2876
+ return this.deps.store.rows.length;
2877
+ }
2878
+ get questionCount() {
2879
+ return this.deps.store.questions.length;
2880
+ }
2881
+ get totalSelectable() {
2882
+ return this.rowCount + this.questionCount;
2883
+ }
2884
+ render(width) {
2885
+ const { store } = this.deps;
2886
+ const lines = [];
2887
+ const active = store.rows.filter(GoalsStore.isActive).length;
2888
+ lines.push(truncateToWidth10(
2889
+ theme.accent(`Goals `) + theme.dim(`${active} active / ${store.rows.length} total \xB7 Enter \u8BE6\u60C5 \xB7 x \u53D6\u6D88`),
2890
+ width
2891
+ ));
2892
+ if (store.rows.length === 0) lines.push(theme.dim(" \uFF08\u65E0 goal\uFF09"));
2893
+ store.rows.forEach((g, i) => {
2894
+ const mark = GoalsStore.isActive(g) ? theme.ok("\u25CF") : theme.dim("\u25CB");
2895
+ const qMark = i === this.selected ? theme.accent("\u203A ") : " ";
2896
+ const line = `${qMark}${mark} ${g.goalId} ${theme.dim(`\xB7 ${g.phase} \xB7 loop ${g.loop} \xB7 wave ${g.currentWave}/${g.totalWaves}`)}`;
2897
+ lines.push(truncateToWidth10(line, width));
2898
+ });
2899
+ if (store.questions.length > 0) {
2900
+ lines.push("");
2901
+ lines.push(truncateToWidth10(theme.warn(`? \u5F85\u51B3\u95EE\u7B54 (${store.questions.length}) \xB7 Enter \u4F5C\u7B54`), width));
2902
+ store.questions.forEach((q, i) => {
2903
+ const idx = this.rowCount + i;
2904
+ const qMark = idx === this.selected ? theme.accent("\u203A ") : " ";
2905
+ const text = q.questions[0]?.question ?? q.id;
2906
+ lines.push(truncateToWidth10(`${qMark}${theme.warn("?")} ${text}`, width));
2907
+ });
2908
+ }
2909
+ return lines;
2910
+ }
2911
+ /** 鼠标点击行选中(Enter 仍负责激活;行映射与 render 同构:header/空行/header 不可点)。 */
2912
+ handleMouseClick(_col, row) {
2913
+ const goalsRows = this.rowCount;
2914
+ if (goalsRows > 0 && row >= 1 && row <= goalsRows) {
2915
+ this.selected = row - 1;
2916
+ this.deps.tui.requestRender();
2917
+ return true;
2918
+ }
2919
+ if (this.questionCount > 0) {
2920
+ const qStart = 1 + Math.max(goalsRows, 1) + 2;
2921
+ if (row >= qStart && row < qStart + this.questionCount) {
2922
+ this.selected = goalsRows + (row - qStart);
2923
+ this.deps.tui.requestRender();
2924
+ return true;
2925
+ }
2926
+ }
2927
+ return false;
2928
+ }
2929
+ /** 返回 true 表示按键已消费(app 层据此不再转发)。 */
2930
+ handleTabKey(data) {
2931
+ if (matchesKey2(data, Key2.up)) {
2932
+ this.selected = Math.max(0, this.selected - 1);
2933
+ this.deps.tui.requestRender();
2934
+ return true;
2935
+ }
2936
+ if (matchesKey2(data, Key2.down)) {
2937
+ this.selected = Math.min(this.totalSelectable - 1, this.selected + 1);
2938
+ this.deps.tui.requestRender();
2939
+ return true;
2940
+ }
2941
+ if (matchesKey2(data, Key2.enter)) {
2942
+ if (this.selected < this.rowCount) this.openGoalDetail(this.deps.store.rows[this.selected].goalId);
2943
+ else this.openQuestionOverlay(this.deps.store.questions[this.selected - this.rowCount]);
2944
+ return true;
2945
+ }
2946
+ if (data === "x" && this.selected < this.rowCount) {
2947
+ this.confirmAbort(this.deps.store.rows[this.selected].goalId);
2948
+ return true;
2949
+ }
2950
+ return false;
2951
+ }
2952
+ openGoalDetail(goalId) {
2953
+ void this.deps.client.goals.get(goalId).then((g) => {
2954
+ if (!g) return;
2955
+ const body = [
2956
+ `goalId: ${g.goalId}`,
2957
+ `phase: ${g.phase}`,
2958
+ `loop: ${g.loop}`,
2959
+ `wave: ${g.currentWave}/${g.totalWaves}`,
2960
+ g.nextAction ? `next: ${g.nextAction}` : "",
2961
+ g.updatedAt ? `\u66F4\u65B0: ${g.updatedAt}` : "",
2962
+ "",
2963
+ theme.dim("s: \u67E5\u770B session \u5217\u8868 \xB7 Esc: \u5173\u95ED")
2964
+ ].filter(Boolean).join("\n");
2965
+ const overlay = this.deps.tui.showOverlay(new Text2(body, 1, 1), { width: 64, maxHeight: 16, anchor: "center" });
2966
+ const close = () => {
2967
+ offKey();
2968
+ offEsc();
2969
+ overlay.hide();
2970
+ };
2971
+ const offEsc = escCloser(this.deps.tui, close);
2972
+ const offKey = this.deps.tui.addInputListener((data) => {
2973
+ if (data === "s") {
2974
+ close();
2975
+ void this.openGoalSessions(goalId);
2976
+ return { consume: true };
2977
+ }
2978
+ return void 0;
2979
+ });
2980
+ });
2981
+ }
2982
+ /** goal → sessions 下钻(编排可视化:plan/execute/review 会话列表)。 */
2983
+ async openGoalSessions(goalId) {
2984
+ let sessions;
2985
+ try {
2986
+ sessions = await this.deps.client.goals.sessions(goalId);
2987
+ } catch (e) {
2988
+ this.deps.setStatus({ hint: theme.err(`sessions \u83B7\u53D6\u5931\u8D25: ${String(e?.message ?? e).slice(0, 60)}`) });
2989
+ return;
2990
+ }
2991
+ if (sessions.length === 0) {
2992
+ const overlay = this.deps.tui.showOverlay(
2993
+ new Text2(`goal ${goalId}
2994
+ ${formatGoalSessions(sessions).join("\n")}`, 1, 1),
2995
+ { width: 56, maxHeight: 6, anchor: "center" }
2996
+ );
2997
+ const close2 = () => {
2998
+ off2();
2999
+ overlay.hide();
3000
+ };
3001
+ const off2 = escCloser(this.deps.tui, close2);
3002
+ return;
3003
+ }
3004
+ const list = new SelectList2(
3005
+ sessions.map((s) => ({
3006
+ value: s.sessionID,
3007
+ label: `${s.phase} \xB7 loop ${s.loop}`,
3008
+ description: s.title ?? s.sessionID
3009
+ })),
3010
+ Math.min(sessions.length, 8),
3011
+ selectListTheme
3012
+ );
3013
+ const handle = this.deps.tui.showOverlay(new ClickableSelectList(list), { width: "70%", maxHeight: 12, anchor: "center" });
3014
+ const close = () => {
3015
+ off();
3016
+ handle.hide();
3017
+ };
3018
+ const off = escCloser(this.deps.tui, close);
3019
+ list.onSelect = (item) => {
3020
+ close();
3021
+ void this.openTranscript(String(item.value));
3022
+ };
3023
+ list.onCancel = close;
3024
+ }
3025
+ /** 只读 transcript overlay(最近 50 条消息)。 */
3026
+ async openTranscript(sessionID) {
3027
+ let messages;
3028
+ try {
3029
+ const res = await this.deps.client.session.messages({ path: { id: sessionID }, query: { limit: 50 } });
3030
+ messages = res.data ?? [];
3031
+ } catch (e) {
3032
+ this.deps.setStatus({ hint: theme.err(`transcript \u83B7\u53D6\u5931\u8D25: ${String(e?.message ?? e).slice(0, 60)}`) });
3033
+ return;
3034
+ }
3035
+ const turns = historyItemsToTurns(messages);
3036
+ const lines = turns.flatMap((t) => turnToLines(t, 76));
3037
+ const body = lines.length > 0 ? lines.join("\n") : theme.dim("\uFF08\u7A7A\u4F1A\u8BDD\uFF09");
3038
+ const overlay = this.deps.tui.showOverlay(
3039
+ new Text2(`${theme.accent("transcript")} ${theme.dim(sessionID)}
3040
+
3041
+ ${body}`, 1, 1),
3042
+ { width: "80%", maxHeight: "70%", anchor: "center" }
3043
+ );
3044
+ const close = () => {
3045
+ off();
3046
+ overlay.hide();
3047
+ };
3048
+ const off = escCloser(this.deps.tui, close);
3049
+ }
3050
+ confirmAbort(goalId) {
3051
+ const list = new SelectList2(
3052
+ [{ value: "yes", label: "\u786E\u8BA4\u53D6\u6D88", description: goalId }, { value: "no", label: "\u8FD4\u56DE" }],
3053
+ 2,
3054
+ selectListTheme
3055
+ );
3056
+ const handle = this.deps.tui.showOverlay(new ClickableSelectList(list), { width: 44, maxHeight: 6, anchor: "center" });
3057
+ const close = () => {
3058
+ off();
3059
+ handle.hide();
3060
+ };
3061
+ const off = escCloser(this.deps.tui, close);
3062
+ list.onSelect = (item) => {
3063
+ close();
3064
+ if (item.value === "yes") {
3065
+ void this.deps.client.goals.control({ goalId, action: "ABORT" }).then(() => this.deps.setStatus({ hint: theme.ok(`\u5DF2\u53D6\u6D88 ${goalId}`) })).catch((e) => this.deps.setStatus({ hint: theme.err(`\u53D6\u6D88\u5931\u8D25: ${e.message}`) }));
3066
+ }
3067
+ };
3068
+ list.onCancel = close;
3069
+ }
3070
+ openQuestionOverlay(q) {
3071
+ const first = q.questions[0];
3072
+ if (!first) return;
3073
+ if (q.questions.length > 1) {
3074
+ const handle = this.deps.tui.showOverlay(
3075
+ new Text2(theme.warn("\u591A\u95EE\u9898\u8BF7\u6C42") + "\uFF08v1 \u8BF7\u5728\u684C\u9762\u7AEF\u5904\u7406\uFF09\nEsc \u5173\u95ED", 1, 1),
3076
+ { width: 52, maxHeight: 6, anchor: "center" }
3077
+ );
3078
+ const close = () => {
3079
+ off();
3080
+ handle.hide();
3081
+ };
3082
+ const off = escCloser(this.deps.tui, close);
3083
+ return;
3084
+ }
3085
+ const finish = (answer) => {
3086
+ void this.deps.client.questions.reply(q.id, answer).then(() => this.deps.setStatus({ hint: theme.ok("\u5DF2\u56DE\u7B54") })).catch((e) => this.deps.setStatus({ hint: theme.err(`\u56DE\u7B54\u5931\u8D25: ${e.message}`) }));
3087
+ };
3088
+ if (first.options.length > 0) {
3089
+ const list = new SelectList2(
3090
+ first.options.map((o) => ({ value: o.label, label: o.label, description: o.description })),
3091
+ Math.min(first.options.length, 6),
3092
+ selectListTheme
3093
+ );
3094
+ const handle = this.deps.tui.showOverlay(new ClickableSelectList(list), { width: "70%", maxHeight: 12, anchor: "center" });
3095
+ const close = () => {
3096
+ off();
3097
+ handle.hide();
3098
+ };
3099
+ const off = escCloser(this.deps.tui, close);
3100
+ list.onSelect = (item) => {
3101
+ close();
3102
+ finish([[item.value]]);
3103
+ };
3104
+ list.onCancel = close;
3105
+ } else {
3106
+ const overlay = this.deps.tui.showOverlay(
3107
+ new Text2(`${first.question}
3108
+
3109
+ \uFF08\u5F00\u653E\u95EE\u9898\u8BF7\u5728\u684C\u9762\u7AEF\u56DE\u7B54\uFF1Br \u62D2\u7EDD\uFF09`, 1, 1),
3110
+ { width: 60, maxHeight: 8, anchor: "center" }
3111
+ );
3112
+ const off = this.deps.tui.addInputListener((data) => {
3113
+ if (data === "r") {
3114
+ off();
3115
+ overlay.hide();
3116
+ void this.deps.client.questions.reject(q.id).catch(() => {
3117
+ });
3118
+ return { consume: true };
3119
+ }
3120
+ if (matchesKey2(data, Key2.escape)) {
3121
+ off();
3122
+ overlay.hide();
3123
+ return { consume: true };
3124
+ }
3125
+ return void 0;
3126
+ });
3127
+ }
3128
+ }
3129
+ };
3130
+ }
3131
+ });
3132
+
3133
+ // src/store/memory-store.ts
3134
+ var MemoryStore;
3135
+ var init_memory_store = __esm({
3136
+ "src/store/memory-store.ts"() {
3137
+ "use strict";
3138
+ MemoryStore = class {
3139
+ results = [];
3140
+ sticky = [];
3141
+ budget;
3142
+ deps;
3143
+ retriever;
3144
+ constructor(deps, retriever = "bm25") {
3145
+ this.deps = deps;
3146
+ this.retriever = retriever;
3147
+ }
3148
+ async search(query) {
3149
+ try {
3150
+ const r = await this.deps.memory.search({ query, topK: 20, retriever: this.retriever });
3151
+ this.results.length = 0;
3152
+ this.results.push(...r);
3153
+ this.deps.onChange();
3154
+ } catch (err) {
3155
+ this.deps.onError?.(err?.message ?? String(err));
3156
+ }
3157
+ }
3158
+ async refreshSticky() {
3159
+ try {
3160
+ const s = await this.deps.memory.listSticky();
3161
+ this.sticky.length = 0;
3162
+ this.sticky.push(...s.entries);
3163
+ this.budget = s.budget;
3164
+ this.deps.onChange();
3165
+ } catch (err) {
3166
+ this.deps.onError?.(err?.message ?? String(err));
3167
+ }
3168
+ }
3169
+ async unstick(id) {
3170
+ try {
3171
+ await this.deps.memory.setSticky(id, false);
3172
+ } catch (err) {
3173
+ this.deps.onError?.(err?.message ?? String(err));
3174
+ }
3175
+ await this.refreshSticky();
3176
+ }
3177
+ };
3178
+ }
3179
+ });
3180
+
3181
+ // src/ui/memory-tab.ts
3182
+ import {
3183
+ Container as Container3,
3184
+ Input,
3185
+ Text as Text3,
3186
+ matchesKey as matchesKey3,
3187
+ Key as Key3
3188
+ } from "@earendil-works/pi-tui";
3189
+ function memoryLine(item, selected) {
3190
+ const mark = selected ? "\u203A " : " ";
3191
+ return `${mark}[${item.type}] ${item.primary_abstraction} ${theme.dim(`E:${item.energy.toFixed(1)}`)} ${theme.dim(item.id.slice(-6))}`;
3192
+ }
3193
+ var MemoryTab;
3194
+ var init_memory_tab = __esm({
3195
+ "src/ui/memory-tab.ts"() {
3196
+ "use strict";
3197
+ init_theme();
3198
+ MemoryTab = class extends Container3 {
3199
+ searchInput = new Input();
3200
+ list = new Container3();
3201
+ selected = 0;
3202
+ deps;
3203
+ _focused = false;
3204
+ get focused() {
3205
+ return this._focused;
3206
+ }
3207
+ set focused(v) {
3208
+ this._focused = v;
3209
+ this.searchInput.focused = v;
3210
+ }
3211
+ // IME 传播
3212
+ get inputFocused() {
3213
+ return this._focused;
3214
+ }
3215
+ constructor(deps) {
3216
+ super();
3217
+ this.deps = deps;
3218
+ this.searchInput.onSubmit = (text) => {
3219
+ const q = text.trim();
3220
+ if (q) void this.deps.store.search(q);
3221
+ };
3222
+ this.addChild(this.searchInput);
3223
+ this.addChild(this.list);
3224
+ void this.deps.store.refreshSticky();
3225
+ this.refresh();
3226
+ }
3227
+ invalidate() {
3228
+ this.refresh();
3229
+ }
3230
+ get resultCount() {
3231
+ return this.deps.store.results.length;
3232
+ }
3233
+ get stickyCount() {
3234
+ return this.deps.store.sticky.length;
3235
+ }
3236
+ get totalSelectable() {
3237
+ return this.resultCount + this.stickyCount;
3238
+ }
3239
+ /** 由 store.onChange / invalidate 触发:重建列表文本块。 */
3240
+ refresh() {
3241
+ const { store } = this.deps;
3242
+ const lines = [];
3243
+ const budget = store.budget ? `\u{1F4DD} ${store.sticky.length}/${store.budget.max}` : "";
3244
+ lines.push(theme.accent("\u641C\u7D22: ") + (this._focused ? theme.dim("\u8F93\u5165\u540E Enter \u68C0\u7D22 \xB7 Esc \u5230\u5217\u8868") : theme.dim("i \u805A\u7126\u8F93\u5165\u6846")) + theme.dim(` \xB7 ${budget}`));
3245
+ lines.push(theme.accent(`\u68C0\u7D22\u7ED3\u679C (${store.results.length})`));
3246
+ if (store.results.length === 0) lines.push(theme.dim(" \uFF08\u7A7A\u2014\u2014\u5728\u4E0A\u65B9\u8F93\u5165\u5173\u952E\u8BCD\uFF09"));
3247
+ store.results.forEach((u, i) => lines.push(memoryLine(u, i === this.selected)));
3248
+ if (store.sticky.length > 0) {
3249
+ lines.push("");
3250
+ lines.push(theme.warn(`\u{1F4DD} \u4FBF\u7B7E\u677F (${store.sticky.length}) \xB7 u \u4E0B\u67B6`));
3251
+ store.sticky.forEach((n, i) => {
3252
+ const idx = this.resultCount + i;
3253
+ lines.push(memoryLine(n, idx === this.selected));
3254
+ });
3255
+ }
3256
+ this.list.clear();
3257
+ this.list.addChild(new Text3(lines.join("\n"), 0, 0));
3258
+ this.deps.tui.requestRender();
3259
+ }
3260
+ /** 鼠标点击行选中(Enter 仍负责看全文 / u 下架;行映射与 refresh() 同构)。 */
3261
+ handleMouseClick(_col, row) {
3262
+ const rc = this.resultCount;
3263
+ if (rc > 0 && row >= 2 && row < 2 + rc) {
3264
+ this.selected = row - 2;
3265
+ this.refresh();
3266
+ return true;
3267
+ }
3268
+ const sc = this.stickyCount;
3269
+ if (sc > 0) {
3270
+ const stickyStart = 2 + Math.max(rc, 1) + 2;
3271
+ if (row >= stickyStart && row < stickyStart + sc) {
3272
+ this.selected = rc + (row - stickyStart);
3273
+ this.refresh();
3274
+ return true;
3275
+ }
3276
+ }
3277
+ return false;
3278
+ }
3279
+ /** 列表态按键(Input 聚焦时返回 false 让输入框处理)。 */
3280
+ handleTabKey(data) {
3281
+ if (this._focused) return false;
3282
+ if (matchesKey3(data, Key3.up)) {
3283
+ this.selected = Math.max(0, this.selected - 1);
3284
+ this.refresh();
3285
+ return true;
3286
+ }
3287
+ if (matchesKey3(data, Key3.down)) {
3288
+ this.selected = Math.min(this.totalSelectable - 1, this.selected + 1);
3289
+ this.refresh();
3290
+ return true;
3291
+ }
3292
+ if (matchesKey3(data, Key3.enter)) {
3293
+ const r = this.selected;
3294
+ const s = this.deps.store;
3295
+ if (r < s.results.length) this.showFull(s.results[r]);
3296
+ else if (r - s.results.length < s.sticky.length) this.showFull(s.sticky[r - s.results.length]);
3297
+ return true;
3298
+ }
3299
+ if (data === "u" && this.selected >= this.resultCount && this.selected - this.resultCount < this.stickyCount) {
3300
+ const note = this.deps.store.sticky[this.selected - this.resultCount];
3301
+ if (note) {
3302
+ void this.deps.store.unstick(note.id).then(() => this.deps.setStatus({ hint: theme.ok("\u5DF2\u4E0B\u67B6\u4FBF\u7B7E") }));
3303
+ return true;
3304
+ }
3305
+ }
3306
+ if (data === "i" || data === "/") {
3307
+ this.setSearchFocus(true);
3308
+ return true;
3309
+ }
3310
+ return false;
3311
+ }
3312
+ /** 聚焦/失焦搜索框。 */
3313
+ setSearchFocus(on) {
3314
+ this._focused = on;
3315
+ this.searchInput.focused = on;
3316
+ this.deps.setEditing(on);
3317
+ this.deps.tui.setFocus(on ? this.searchInput : null);
3318
+ this.refresh();
3319
+ }
3320
+ blurSearch() {
3321
+ if (this._focused) this.setSearchFocus(false);
3322
+ }
3323
+ showFull(item) {
3324
+ const body = [
3325
+ `${theme.dim("id:")} ${item.id} ${theme.dim("type:")} ${item.type} ${theme.dim("E:")} ${item.energy.toFixed(2)}`,
3326
+ "",
3327
+ item.primary_abstraction,
3328
+ "",
3329
+ item.memory_value || theme.dim("(\u7A7A)")
3330
+ ].join("\n");
3331
+ const overlay = this.deps.tui.showOverlay(new Text3(body, 1, 1), { width: "80%", maxHeight: "70%", anchor: "center" });
3332
+ const close = () => {
3333
+ off();
3334
+ overlay.hide();
3335
+ };
3336
+ const off = this.deps.tui.addInputListener((data) => {
3337
+ if (matchesKey3(data, Key3.escape) || matchesKey3(data, Key3.enter)) {
3338
+ close();
3339
+ return { consume: true };
3340
+ }
3341
+ return void 0;
3342
+ });
3343
+ }
3344
+ };
3345
+ }
3346
+ });
3347
+
3348
+ // src/store/triage-store.ts
3349
+ var TriageStore;
3350
+ var init_triage_store = __esm({
3351
+ "src/store/triage-store.ts"() {
3352
+ "use strict";
3353
+ TriageStore = class {
3354
+ approvals = [];
3355
+ items = [];
3356
+ stopped = false;
3357
+ cleanup;
3358
+ schedule;
3359
+ deps;
3360
+ constructor(deps) {
3361
+ this.deps = deps;
3362
+ this.schedule = deps.schedule ?? ((fn, ms) => {
3363
+ const t = setInterval(fn, ms);
3364
+ return () => clearInterval(t);
3365
+ });
3366
+ }
3367
+ start() {
3368
+ this.stopped = false;
3369
+ void this.poll();
3370
+ this.cleanup = this.schedule(() => {
3371
+ if (!this.stopped) void this.poll();
3372
+ }, 1e4);
3373
+ }
3374
+ stop() {
3375
+ this.stopped = true;
3376
+ this.cleanup?.();
3377
+ }
3378
+ async poll() {
3379
+ try {
3380
+ const [a, t] = await Promise.all([this.deps.approvals.list(), this.deps.triage.list()]);
3381
+ this.approvals.length = 0;
3382
+ this.approvals.push(...a);
3383
+ this.items.length = 0;
3384
+ this.items.push(...t);
3385
+ this.deps.onChange();
3386
+ } catch {
3387
+ }
3388
+ }
3389
+ async act(kind, id, action) {
3390
+ if (kind === "approval") {
3391
+ await this.deps.approvals.respond(id, action === "approve" ? "approve" : "reject");
3392
+ } else if (action === "confirm") {
3393
+ await this.deps.triage.confirm(id);
3394
+ } else if (action === "dismiss") {
3395
+ await this.deps.triage.dismiss(id);
3396
+ } else {
3397
+ await this.deps.triage.reject(id);
3398
+ }
3399
+ await this.poll();
3400
+ }
3401
+ };
3402
+ }
3403
+ });
3404
+
3405
+ // src/ui/triage-tab.ts
3406
+ import {
3407
+ Text as Text4,
3408
+ matchesKey as matchesKey4,
3409
+ Key as Key4,
3410
+ truncateToWidth as truncateToWidth11
3411
+ } from "@earendil-works/pi-tui";
3412
+ var TriageTab;
3413
+ var init_triage_tab = __esm({
3414
+ "src/ui/triage-tab.ts"() {
3415
+ "use strict";
3416
+ init_theme();
3417
+ init_chat_tab();
3418
+ TriageTab = class {
3419
+ selected = 0;
3420
+ deps;
3421
+ constructor(deps) {
3422
+ this.deps = deps;
3423
+ }
3424
+ invalidate() {
3425
+ }
3426
+ get approvalCount() {
3427
+ return this.deps.store.approvals.length;
3428
+ }
3429
+ get itemCount() {
3430
+ return this.deps.store.items.length;
3431
+ }
3432
+ get totalSelectable() {
3433
+ return this.approvalCount + this.itemCount;
3434
+ }
3435
+ render(width) {
3436
+ const { store } = this.deps;
3437
+ const lines = [];
3438
+ lines.push(truncateToWidth11(
3439
+ theme.accent("Approvals ") + theme.dim(`${store.approvals.length} pending \xB7 a \u6279\u51C6 \xB7 r \u62D2\u7EDD`),
3440
+ width
3441
+ ));
3442
+ if (store.approvals.length === 0) lines.push(theme.dim(" \uFF08\u65E0\u5F85\u5BA1\u6279\uFF09"));
3443
+ store.approvals.forEach((a, i) => {
3444
+ const mark = i === this.selected ? theme.accent("\u203A ") : " ";
3445
+ lines.push(truncateToWidth11(`${mark}[A] ${a.question || a.id} ${theme.dim(a.goalId)}`, width));
3446
+ });
3447
+ lines.push("");
3448
+ lines.push(truncateToWidth11(
3449
+ theme.accent("Triage ") + theme.dim(`${store.items.length} items \xB7 c \u786E\u8BA4 \xB7 r \u62D2\u7EDD \xB7 d \u5FFD\u7565`),
3450
+ width
3451
+ ));
3452
+ if (store.items.length === 0) lines.push(theme.dim(" \uFF08\u65E0 triage \u9879\uFF09"));
3453
+ store.items.forEach((t, i) => {
3454
+ const idx = this.approvalCount + i;
3455
+ const mark = idx === this.selected ? theme.accent("\u203A ") : " ";
3456
+ const sev = t.severity === "high" ? theme.err(t.severity) : t.severity === "medium" ? theme.warn(t.severity) : theme.dim(t.severity);
3457
+ lines.push(truncateToWidth11(`${mark}[${sev}] ${t.reason} ${theme.dim(t.goalId)}`, width));
3458
+ });
3459
+ return lines;
3460
+ }
3461
+ /** 鼠标点击行选中(a/r/c/d/Enter 仍负责动作;行映射与 render 同构、占位行不可点)。 */
3462
+ handleMouseClick(_col, row) {
3463
+ if (this.approvalCount > 0 && row >= 1 && row <= this.approvalCount) {
3464
+ this.selected = row - 1;
3465
+ this.deps.tui.requestRender();
3466
+ return true;
3467
+ }
3468
+ const itemCount = this.itemCount;
3469
+ if (itemCount > 0) {
3470
+ const itemsStart = 1 + Math.max(this.approvalCount, 1) + 2;
3471
+ if (row >= itemsStart && row < itemsStart + itemCount) {
3472
+ this.selected = this.approvalCount + (row - itemsStart);
3473
+ this.deps.tui.requestRender();
3474
+ return true;
3475
+ }
3476
+ }
3477
+ return false;
3478
+ }
3479
+ handleTabKey(data) {
3480
+ if (matchesKey4(data, Key4.up)) {
3481
+ this.selected = Math.max(0, this.selected - 1);
3482
+ this.deps.tui.requestRender();
3483
+ return true;
3484
+ }
3485
+ if (matchesKey4(data, Key4.down)) {
3486
+ this.selected = Math.min(this.totalSelectable - 1, this.selected + 1);
3487
+ this.deps.tui.requestRender();
3488
+ return true;
3489
+ }
3490
+ const isApproval = this.selected < this.approvalCount;
3491
+ const kind = isApproval ? "approval" : "triage";
3492
+ const id = isApproval ? this.deps.store.approvals[this.selected]?.id : this.deps.store.items[this.selected - this.approvalCount]?.id;
3493
+ if (!id) return false;
3494
+ let action = null;
3495
+ if (isApproval) {
3496
+ if (data === "a") action = "approve";
3497
+ if (data === "r") action = "reject";
3498
+ } else {
3499
+ if (data === "c") action = "confirm";
3500
+ if (data === "d") action = "dismiss";
3501
+ if (data === "r") action = "reject";
3502
+ }
3503
+ if (action) {
3504
+ this.apply(kind, id, action);
3505
+ return true;
3506
+ }
3507
+ if (matchesKey4(data, Key4.enter)) {
3508
+ this.showDetail(kind, id);
3509
+ return true;
3510
+ }
3511
+ return false;
3512
+ }
3513
+ apply(kind, id, action) {
3514
+ void this.deps.store.act(kind, id, action).then(() => this.deps.setStatus({ hint: theme.ok(`${kind} ${action} \u2713`) })).catch((e) => this.deps.setStatus({ hint: theme.err(`${action} \u5931\u8D25: ${e.message}`) }));
3515
+ }
3516
+ showDetail(kind, id) {
3517
+ const isApproval = kind === "approval";
3518
+ const item = isApproval ? this.deps.store.approvals.find((a) => a.id === id) : this.deps.store.items.find((t) => t.id === id);
3519
+ if (!item) return;
3520
+ const body = isApproval ? `\u7C7B\u578B: approval
3521
+ question: ${item.question}
3522
+ goalId: ${item.goalId}
3523
+
3524
+ a \u6279\u51C6 \xB7 r \u62D2\u7EDD` : `\u7C7B\u578B: triage
3525
+ severity: ${item.severity}
3526
+ reason: ${item.reason}
3527
+ goalId: ${item.goalId}
3528
+
3529
+ c \u786E\u8BA4 \xB7 r \u62D2\u7EDD \xB7 d \u5FFD\u7565`;
3530
+ const overlay = this.deps.tui.showOverlay(new Text4(body, 1, 1), { width: 64, maxHeight: 12, anchor: "center" });
3531
+ const close = () => {
3532
+ off();
3533
+ overlay.hide();
3534
+ };
3535
+ const off = this.deps.tui.addInputListener((data) => {
3536
+ if (matchesKey4(data, Key4.escape)) {
3537
+ close();
3538
+ return { consume: true };
3539
+ }
3540
+ if (isApproval && (data === "a" || data === "r")) {
3541
+ close();
3542
+ this.apply("approval", id, data === "a" ? "approve" : "reject");
3543
+ return { consume: true };
3544
+ }
3545
+ if (!isApproval && (data === "c" || data === "r" || data === "d")) {
3546
+ close();
3547
+ this.apply("triage", id, data === "c" ? "confirm" : data === "d" ? "dismiss" : "reject");
3548
+ return { consume: true };
3549
+ }
3550
+ return void 0;
3551
+ });
3552
+ void selectListTheme;
3553
+ }
3554
+ };
3555
+ }
3556
+ });
3557
+
3558
+ // src/ui/app.ts
3559
+ var app_exports = {};
3560
+ __export(app_exports, {
3561
+ runApp: () => runApp
3562
+ });
3563
+ import {
3564
+ ProcessTerminal,
3565
+ TuiAltScreen,
3566
+ VStack as VStack2,
3567
+ Text as Text5,
3568
+ SelectList as SelectList4,
3569
+ matchesKey as matchesKey5,
3570
+ Key as Key5
3571
+ } from "@earendil-works/pi-tui";
3572
+ import { basename } from "node:path";
3573
+ async function runApp(opts) {
3574
+ const client = new import_sdk.MafwClient(opts.baseUrl);
3575
+ const model = new AppModel();
3576
+ const terminal = new ProcessTerminal();
3577
+ const tui = new TuiAltScreen(terminal);
3578
+ enableClickDispatch(tui);
3579
+ const tabStrip = new TabStrip();
3580
+ const statusBar = new StatusBar();
3581
+ const appStart = Date.now();
3582
+ const project = await client.project.current().catch(() => null);
3583
+ const projectDir = project?.worktree ?? null;
3584
+ const projectID = project?.id ?? null;
3585
+ const mgr = !opts.sessionID && projectDir ? await client.manager.session(projectDir).catch(() => null) : null;
3586
+ const managerSessionID = opts.sessionID ?? mgr?.sessionId ?? null;
3587
+ const status = {
3588
+ project: projectDir ? basename(projectDir) : void 0,
3589
+ session: managerSessionID ?? void 0,
3590
+ conn: "ok"
3591
+ };
3592
+ const setStatus = (patch) => {
3593
+ Object.assign(status, patch);
3594
+ statusBar.setState(status);
3595
+ tui.requestRender();
3596
+ };
3597
+ statusBar.setState(status);
3598
+ tabStrip.setConnected(true);
3599
+ let modelSelection = null;
3600
+ let defaultModelLabel = "";
3601
+ void client.providers.list().then((p) => {
3602
+ const d = p?.default;
3603
+ if (d?.modelID) {
3604
+ defaultModelLabel = String(d.modelID);
3605
+ setStatus({ usage: { ...status.usage ?? {}, model: defaultModelLabel } });
3606
+ }
3607
+ }).catch(() => {
3608
+ });
3609
+ const interaction = new InteractionStateMachine();
3610
+ const chatStore = new ChatStore({
3611
+ session: client.session,
3612
+ sessionID: managerSessionID ?? "none",
3613
+ onChange: () => {
3614
+ tui.requestRender();
3615
+ syncInteraction();
3616
+ },
3617
+ onError: (message) => setStatus({ hint: theme.err(`\u26A0 ${message.slice(0, 60)}`) }),
3618
+ getModel: () => modelSelection
3619
+ });
3620
+ const chatTab = new ChatTab({
3621
+ tui,
3622
+ store: chatStore,
3623
+ onSlash: (cmd, args) => handleSlashWithConfirm(cmd, args),
3624
+ onError: (m) => setStatus({ hint: theme.err(`\u26A0 ${m.slice(0, 60)}`) })
3625
+ });
3626
+ const goalsStore = new GoalsStore({
3627
+ goals: client.goals,
3628
+ questions: client.questions,
3629
+ onChange: () => tui.requestRender()
3630
+ });
3631
+ const goalsTab = new GoalsTab({ tui, store: goalsStore, client, setStatus });
3632
+ goalsStore.start();
3633
+ const memoryStore = new MemoryStore({
3634
+ memory: client.memory,
3635
+ onChange: () => memoryTab.refresh(),
3636
+ onError: (m) => setStatus({ hint: theme.err(`\u26A0 ${m.slice(0, 60)}`) })
3637
+ }, opts.retriever);
3638
+ const memoryTab = new MemoryTab({ tui, store: memoryStore, client, setStatus, setEditing: (v) => {
3639
+ model.editing = v;
3640
+ } });
3641
+ const triageStore = new TriageStore({
3642
+ approvals: client.approvals,
3643
+ triage: client.triage,
3644
+ onChange: () => tui.requestRender()
3645
+ });
3646
+ const triageTab = new TriageTab({ tui, store: triageStore, client, setStatus });
3647
+ triageStore.start();
3648
+ let helpHandle = null;
3649
+ function toggleHelp() {
3650
+ if (helpHandle) {
3651
+ helpHandle.hide();
3652
+ helpHandle = null;
3653
+ model.helpVisible = false;
3654
+ applyTab();
3655
+ return;
3656
+ }
3657
+ const body = [
3658
+ "\u5FEB\u6377\u952E",
3659
+ "",
3660
+ " 1-4 / Alt+1-4 \u5207\u6362 Chat / Goals / Memory / Triage",
3661
+ " q / Ctrl+C \u9000\u51FA\uFF08\u8F93\u5165\u6001\u5F52\u7F16\u8F91\u5668\uFF09",
3662
+ " Esc busy \u65F6\u4E2D\u6B62\u56DE\u5408 / \u5173\u95ED overlay",
3663
+ " Ctrl+G \u5916\u90E8\u7F16\u8F91\u5668\u7F16\u8F91\u8F93\u5165\uFF08$EDITOR\uFF09",
3664
+ " \u2191 / \u2193 \u8F93\u5165\u5386\u53F2\uFF08\u63D0\u4EA4\u8FC7\u624D\u6709\uFF09",
3665
+ "",
3666
+ "Chat \u8F93\u5165",
3667
+ " ! <command> \u672C\u5730 shell\uFF08\u96F6\u6210\u672C\uFF0C\u4E0D\u8FDB\u5BF9\u8BDD\uFF09",
3668
+ " @\u8DEF\u5F84 / /\u547D\u4EE4 \u7F16\u8F91\u5668\u8865\u5168",
3669
+ "",
3670
+ ...helpLines(),
3671
+ theme.dim("\u26A0 = \u7834\u574F\u6027\u64CD\u4F5C")
3672
+ ].join("\n");
3673
+ model.helpVisible = true;
3674
+ applyTab();
3675
+ helpHandle = tui.showOverlay(new Text5(body, 1, 1), { width: 62, maxHeight: 32, anchor: "center" });
3676
+ }
3677
+ let conn = null;
3678
+ function ensureConn(_sessionID) {
3679
+ if (conn) {
3680
+ return conn;
3681
+ }
3682
+ conn = new ConnectionStore({
3683
+ sessionID: "global",
3684
+ subscribe: () => client.event.subscribe(),
3685
+ isConnected: () => client.event.connected(),
3686
+ onEvent,
3687
+ onState: (s) => {
3688
+ setStatus({ conn: s });
3689
+ tabStrip.setConnected(s === "ok");
3690
+ }
3691
+ });
3692
+ conn.start();
3693
+ return conn;
3694
+ }
3695
+ async function switchToSession(sessionID) {
3696
+ await chatStore.switchSession(sessionID);
3697
+ chatTab.rebuild();
3698
+ ensureConn(sessionID);
3699
+ setStatus({ session: sessionID });
3700
+ void refreshUsage();
3701
+ }
3702
+ async function showSessionPicker() {
3703
+ let sessions;
3704
+ try {
3705
+ sessions = await client.session.list(projectID ? { query: { projectID } } : void 0);
3706
+ } catch (e) {
3707
+ setStatus({ hint: theme.err(`\u4F1A\u8BDD\u5217\u8868\u5931\u8D25: ${String(e?.message ?? e).slice(0, 50)}`) });
3708
+ return;
3709
+ }
3710
+ if (!sessions || sessions.length === 0) {
3711
+ setStatus({ hint: theme.dim("\uFF08\u5F53\u524D\u9879\u76EE\u65E0\u4F1A\u8BDD\uFF09") });
3712
+ return;
3713
+ }
3714
+ const items = sessions.map((s) => ({
3715
+ value: s.id,
3716
+ label: `${s.title || String(s.id).slice(0, 20)}${s.id === managerSessionID ? theme.ok(" \u2605manager") : ""}`,
3717
+ description: s.time?.updated ? new Date(s.time.updated).toLocaleString() : ""
3718
+ }));
3719
+ const list = new SelectList4(items, Math.min(items.length, 10), selectListTheme);
3720
+ const handle = tui.showOverlay(new ClickableSelectList(list), { width: "70%", maxHeight: 16, anchor: "center" });
3721
+ const close = () => {
3722
+ off();
3723
+ handle.hide();
3724
+ };
3725
+ const off = tui.addInputListener((data) => {
3726
+ if (matchesKey5(data, Key5.escape)) {
3727
+ close();
3728
+ return { consume: true };
3729
+ }
3730
+ return void 0;
3731
+ });
3732
+ list.onSelect = (item) => {
3733
+ close();
3734
+ void switchToSession(String(item.value));
3735
+ };
3736
+ list.onCancel = close;
3737
+ }
3738
+ async function showModelPicker() {
3739
+ const p = await client.providers.list().catch(() => null);
3740
+ const all = p?.all ?? [];
3741
+ const connected = new Set(p?.connected ?? []);
3742
+ const items = [];
3743
+ for (const prov of all) {
3744
+ if (connected.size > 0 && !connected.has(prov.id)) continue;
3745
+ for (const [mid, m] of Object.entries(prov.models ?? {})) {
3746
+ items.push({ value: `${prov.id}/${mid}`, label: m?.name || mid, description: prov.id });
3747
+ }
3748
+ }
3749
+ if (items.length === 0) {
3750
+ setStatus({ hint: theme.dim("\uFF08\u65E0\u53EF\u7528\u6A21\u578B\u2014\u2014\u68C0\u67E5 provider \u8FDE\u63A5\uFF09") });
3751
+ return;
3752
+ }
3753
+ const list = new SelectList4(items, 10, selectListTheme);
3754
+ const handle = tui.showOverlay(new ClickableSelectList(list), { width: "60%", maxHeight: 16, anchor: "center" });
3755
+ const close = () => {
3756
+ off();
3757
+ handle.hide();
3758
+ };
3759
+ const off = tui.addInputListener((data) => {
3760
+ if (matchesKey5(data, Key5.escape)) {
3761
+ close();
3762
+ return { consume: true };
3763
+ }
3764
+ return void 0;
3765
+ });
3766
+ list.onSelect = (item) => {
3767
+ close();
3768
+ const slash = String(item.value).indexOf("/");
3769
+ if (slash <= 0) return;
3770
+ modelSelection = {
3771
+ providerID: String(item.value).slice(0, slash),
3772
+ modelID: String(item.value).slice(slash + 1)
3773
+ };
3774
+ setStatus({ usage: { ...status.usage ?? {}, model: modelSelection.modelID } });
3775
+ setStatus({ hint: theme.ok(`\u6A21\u578B: ${modelSelection.providerID}/${modelSelection.modelID}`) });
3776
+ };
3777
+ list.onCancel = close;
3778
+ }
3779
+ let promptStart = null;
3780
+ let lastPromptMs = 0;
3781
+ async function refreshUsage() {
3782
+ const sid = chatStore.sessionID;
3783
+ if (!sid || sid === "none") return;
3784
+ try {
3785
+ const t = await client.session.tokenSummary({ path: { id: sid } });
3786
+ const total = t.totalTokens.input + t.totalTokens.output + t.totalTokens.reasoning;
3787
+ setStatus({
3788
+ usage: {
3789
+ model: modelSelection?.modelID ?? defaultModelLabel,
3790
+ tokens: total,
3791
+ costUsd: t.totalCost ?? null,
3792
+ promptMs: promptStart !== null ? Date.now() - promptStart : lastPromptMs,
3793
+ durationMs: Date.now() - appStart
3794
+ }
3795
+ });
3796
+ } catch {
3797
+ }
3798
+ }
3799
+ setInterval(() => {
3800
+ if (model.active === "chat") void refreshUsage();
3801
+ }, 15e3);
3802
+ void refreshUsage();
3803
+ async function openInExternalEditor() {
3804
+ const command = defaultEditorCommand();
3805
+ tui.stop();
3806
+ try {
3807
+ const result = await editInExternalEditor({ command, content: chatTab.getEditorText() });
3808
+ if (result.status === "complete" && result.content !== void 0) chatTab.setEditorText(result.content);
3809
+ } finally {
3810
+ tui.start();
3811
+ tui.requestRender(true);
3812
+ }
3813
+ }
3814
+ function showQueueManager() {
3815
+ const overlay = new QueueOverlay({
3816
+ getItems: () => chatStore.queuedTexts,
3817
+ onTakeBack: (i) => {
3818
+ const text = chatStore.dropQueuedAt(i);
3819
+ if (text !== null) chatTab.setEditorText(text);
3820
+ },
3821
+ onDrop: (i) => {
3822
+ chatStore.dropQueuedAt(i);
3823
+ },
3824
+ onClose: () => {
3825
+ handle.hide();
3826
+ syncInteraction();
3827
+ },
3828
+ requestRender: () => tui.requestRender()
3829
+ });
3830
+ const handle = tui.showOverlay(overlay, { width: "60%", maxHeight: 14, anchor: "center" });
3831
+ syncInteraction();
3832
+ return Promise.resolve();
3833
+ }
3834
+ function openTranscriptSearch() {
3835
+ const overlay = new TranscriptSearchOverlay({
3836
+ getTurns: () => chatStore.turns,
3837
+ onJump: (turnIndex) => {
3838
+ const t = chatStore.turns[turnIndex];
3839
+ if (t) chatTab.scrollToTurn(t.messageID);
3840
+ },
3841
+ onClose: () => handle.hide(),
3842
+ requestRender: () => tui.requestRender()
3843
+ });
3844
+ const handle = tui.showOverlay(overlay, { width: "70%", maxHeight: 14, anchor: "center" });
3845
+ }
3846
+ async function showDiff(scope) {
3847
+ const arg = scope === "staged" ? " --staged" : scope === "all" ? " HEAD" : "";
3848
+ const r = await runShell(`git diff${arg}`, { timeoutMs: 15e3 });
3849
+ if (r.exitCode !== 0 && r.stderr) return `git diff \u5931\u8D25: ${r.stderr.split("\n")[0].slice(0, 80)}`;
3850
+ const raw = (r.stdout || "").split("\n");
3851
+ if (raw.length <= 1 && !r.stdout) {
3852
+ showDiffOverlay(["\uFF08\u65E0\u672A\u63D0\u4EA4\u53D8\u66F4\uFF09"], scope);
3853
+ return null;
3854
+ }
3855
+ const MAX_DIFF_LINES = 150;
3856
+ const lines = raw.slice(0, MAX_DIFF_LINES).map((l) => colorDiffLine(l));
3857
+ if (raw.length > MAX_DIFF_LINES) lines.push(theme.dim(`\u2026 \u622A\u65AD\uFF08\u5171 ${raw.length} \u884C\uFF09`));
3858
+ showDiffOverlay(lines, scope);
3859
+ return null;
3860
+ }
3861
+ function showDiffOverlay(lines, scope) {
3862
+ const title = theme.accent(`git diff${scope ? ` (${scope})` : ""}`) + theme.dim(" \xB7 Esc \u5173\u95ED");
3863
+ const overlay = tui.showOverlay(new Text5([title, "", ...lines].join("\n"), 1, 1), { width: "90%", maxHeight: "70%", anchor: "center" });
3864
+ const close = () => {
3865
+ off();
3866
+ overlay.hide();
3867
+ };
3868
+ const off = tui.addInputListener((data) => {
3869
+ if (matchesKey5(data, Key5.escape)) {
3870
+ close();
3871
+ return { consume: true };
3872
+ }
3873
+ return void 0;
3874
+ });
3875
+ }
3876
+ const sessionApproved = /* @__PURE__ */ new Set();
3877
+ function showConfirm(title, description) {
3878
+ return new Promise((resolve) => {
3879
+ const overlay = new ConfirmOverlay({
3880
+ title,
3881
+ description,
3882
+ onAnswer: (mode) => {
3883
+ handle.hide();
3884
+ resolve(mode);
3885
+ },
3886
+ requestRender: () => tui.requestRender()
3887
+ });
3888
+ const handle = tui.showOverlay(overlay, { width: 52, maxHeight: 12, anchor: "center" });
3889
+ });
3890
+ }
3891
+ async function handleSlashWithConfirm(rawCmd, args) {
3892
+ const name = resolveCommand(rawCmd);
3893
+ const def = COMMAND_REGISTRY.find((c) => c.name === name);
3894
+ const inlineSkip = /^(now|--yes|-y)(\s|$)/i.test(args.trim());
3895
+ if (def?.destructive && !inlineSkip && !sessionApproved.has(def.name)) {
3896
+ const answer = await showConfirm(`\u786E\u8BA4\u6267\u884C /${def.name}\uFF1F`, def.description);
3897
+ if (answer === "cancel") return "\u5DF2\u53D6\u6D88";
3898
+ if (answer === "always") sessionApproved.add(def.name);
3899
+ }
3900
+ return slashHandler(rawCmd, inlineSkip ? args.trim().replace(/^(now|--yes|-y)\s+/i, "") : args);
3901
+ }
3902
+ const slashHandler = createSlashHandler({
3903
+ loadOlder: () => chatTab.loadOlder(),
3904
+ toggleHelp,
3905
+ rotateTopic: async () => {
3906
+ if (!projectDir) return "\u5F53\u524D\u65E0\u9879\u76EE\u4E0A\u4E0B\u6587\uFF0C\u65E0\u6CD5 rotate";
3907
+ const r = await client.manager.rotate(projectDir, "tui /new").catch((e) => ({ error: e.message }));
3908
+ if ("error" in r) return `rotate \u5931\u8D25: ${r.error}`;
3909
+ const next = await client.manager.session(projectDir).catch(() => null);
3910
+ if (next?.sessionId) await switchToSession(next.sessionId);
3911
+ return "\u5DF2\u5F00\u65B0\u8BDD\u9898\uFF08manager session \u5DF2\u8F6E\u6362\u5E76\u5207\u6362\uFF09";
3912
+ },
3913
+ btw: async (args) => {
3914
+ if (!args.trim()) return "\u7528\u6CD5: /btw <\u95EE\u9898>";
3915
+ const r = await client.mafwCommands.run({ command: "btw", args }).catch((e) => ({ error: e.message }));
3916
+ if ("error" in r) return `btw \u5931\u8D25: ${r.error}`;
3917
+ return null;
3918
+ },
3919
+ showSessionPicker,
3920
+ showQueueManager,
3921
+ showModelPicker,
3922
+ cycleVerbosity: () => {
3923
+ const v = chatTab.cycleVerbosity();
3924
+ setStatus({ focus: chatTab.display.focus });
3925
+ return v;
3926
+ },
3927
+ toggleFocus: () => {
3928
+ const f = chatTab.toggleFocus();
3929
+ setStatus({ focus: f });
3930
+ return f;
3931
+ },
3932
+ showDiff,
3933
+ rename: async (args) => {
3934
+ const title = args.trim();
3935
+ if (!title) return "\u7528\u6CD5: /rename <\u6807\u9898>";
3936
+ try {
3937
+ await client.session.rename({ path: { id: chatStore.sessionID }, body: { title } });
3938
+ return `\u5DF2\u547D\u540D: ${title}`;
3939
+ } catch (e) {
3940
+ return `rename \u5931\u8D25: ${String(e?.message ?? e).slice(0, 80)}`;
3941
+ }
3942
+ },
3943
+ fork: async () => {
3944
+ try {
3945
+ const r = await client.session.fork({ path: { id: chatStore.sessionID } });
3946
+ const newId = r?.session?.id;
3947
+ if (!newId) return "fork \u5931\u8D25: \u65E0\u65B0\u4F1A\u8BDD\u8FD4\u56DE";
3948
+ await switchToSession(String(newId));
3949
+ return `\u5DF2\u5206\u53C9 \u2192 ${String(newId).slice(0, 16)}`;
3950
+ } catch (e) {
3951
+ return `fork \u5931\u8D25: ${String(e?.message ?? e).slice(0, 80)}`;
3952
+ }
3953
+ },
3954
+ showStatusRecap: () => {
3955
+ const body = recapLines(
3956
+ computeRecap(chatStore.turns),
3957
+ chatStore.sessionID,
3958
+ status.project ?? "-"
3959
+ ).join("\n");
3960
+ const overlay = tui.showOverlay(new Text5(body, 1, 1), { width: "70%", maxHeight: 20, anchor: "center" });
3961
+ const close = () => {
3962
+ off();
3963
+ overlay.hide();
3964
+ };
3965
+ const off = tui.addInputListener((data) => {
3966
+ if (matchesKey5(data, Key5.escape) || matchesKey5(data, Key5.enter)) {
3967
+ close();
3968
+ return { consume: true };
3969
+ }
3970
+ return void 0;
3971
+ });
3972
+ },
3973
+ compact: async () => {
3974
+ const sid = chatStore.sessionID;
3975
+ if (!sid || sid === "none") return "\u5F53\u524D\u65E0\u4F1A\u8BDD";
3976
+ try {
3977
+ await client.session.summarize({ path: { id: sid } });
3978
+ await chatStore.loadHistory();
3979
+ chatTab.rebuild();
3980
+ return null;
3981
+ } catch (e) {
3982
+ return `compact \u5931\u8D25: ${String(e?.message ?? e).slice(0, 80)}`;
3983
+ }
3984
+ },
3985
+ undo: () => chatStore.undo(),
3986
+ redo: () => chatStore.redo(),
3987
+ openExternalEditor: openInExternalEditor
3988
+ });
3989
+ const bodies = /* @__PURE__ */ new Map([
3990
+ ["chat", chatTab],
3991
+ ["goals", goalsTab],
3992
+ ["memory", memoryTab],
3993
+ ["triage", triageTab]
3994
+ ]);
3995
+ let currentBody = bodies.get(model.active);
3996
+ const contentHost = new VStack2([]);
3997
+ contentHost.addChild(currentBody, { basis: 0, grow: 1, minSize: 1 });
3998
+ tui.setLayoutRoot(new VStack2([
3999
+ { component: tabStrip, basis: "auto", minSize: 1 },
4000
+ { component: contentHost, basis: 0, grow: 1, minSize: 1 },
4001
+ { component: statusBar, basis: "auto", minSize: 1 }
4002
+ ]));
4003
+ const onEvent = (type, data) => {
4004
+ if (type === "permission.asked") {
4005
+ const req = data?.properties ?? data;
4006
+ if (req?.id) {
4007
+ showPermissionOverlay(tui, req, (r) => client.permissions.reply(req.id, r));
4008
+ }
4009
+ } else if (type === "session.idle") {
4010
+ void refreshUsage();
4011
+ }
4012
+ };
4013
+ if (managerSessionID) ensureConn(managerSessionID);
4014
+ function applyTab() {
4015
+ contentHost.removeChild(currentBody);
4016
+ currentBody = bodies.get(model.active);
4017
+ contentHost.addChild(currentBody, { basis: 0, grow: 1, minSize: 1 });
4018
+ model.editing = model.active === "chat" && !model.helpVisible;
4019
+ if (model.active === "chat") {
4020
+ tui.setFocus(chatTab);
4021
+ memoryTab.blurSearch();
4022
+ } else if (model.active === "memory") {
4023
+ if (!memoryTab.inputFocused) tui.setFocus(null);
4024
+ } else {
4025
+ tui.setFocus(null);
4026
+ memoryTab.blurSearch();
4027
+ }
4028
+ tabStrip.setActive(model.active);
4029
+ tui.requestRender();
4030
+ }
4031
+ tabStrip.onTabClick = (id) => {
4032
+ model.switchTab(id);
4033
+ applyTab();
4034
+ };
4035
+ const topTimer = setInterval(() => {
4036
+ syncInteraction();
4037
+ if (model.active === "chat" && chatTab.atTop) void chatTab.loadOlder();
4038
+ }, 1e3);
4039
+ function syncInteraction() {
4040
+ const wasStreaming = chatStore.streaming;
4041
+ interaction.update({ overlayOpen: tui.hasOverlay(), streaming: chatStore.streaming });
4042
+ setStatus({ busy: interaction.is("busy"), queued: chatStore.queuedCount, stashed: chatTab.stashCount });
4043
+ if (chatStore.streaming && promptStart === null) promptStart = Date.now();
4044
+ if (!chatStore.streaming && promptStart !== null) {
4045
+ lastPromptMs = Date.now() - promptStart;
4046
+ promptStart = null;
4047
+ if (process.env.MAFW_TUI_BELL !== "0") tui.terminal.write("\x07");
4048
+ }
4049
+ void wasStreaming;
4050
+ }
4051
+ function quitApp() {
4052
+ clearInterval(topTimer);
4053
+ goalsStore.stop();
4054
+ triageStore.stop();
4055
+ tui.stop();
4056
+ process.exit(0);
4057
+ }
4058
+ const keyCtx = {
4059
+ model,
4060
+ interaction,
4061
+ actions: {
4062
+ quit: quitApp,
4063
+ switchTab: (id) => {
4064
+ model.switchTab(id);
4065
+ applyTab();
4066
+ },
4067
+ toggleHelp: () => toggleHelp(),
4068
+ abortTurn: () => {
4069
+ void chatStore.abort();
4070
+ },
4071
+ openExternalEditor: () => {
4072
+ void openInExternalEditor();
4073
+ },
4074
+ blurMemorySearch: () => memoryTab.blurSearch(),
4075
+ openTranscriptSearch: () => openTranscriptSearch()
4076
+ },
4077
+ queries: {
4078
+ activeTab: () => model.active,
4079
+ editing: () => model.editing,
4080
+ overlayOpen: () => tui.hasOverlay(),
4081
+ streaming: () => chatStore.streaming,
4082
+ memoryInputFocused: () => memoryTab.inputFocused
4083
+ }
4084
+ };
4085
+ tui.addInputListener((data) => {
4086
+ if (dispatchKey(data, keyCtx)) return { consume: true };
4087
+ if (!model.editing && !tui.hasOverlay() && model.active !== "chat") {
4088
+ const tab = currentBody;
4089
+ if (typeof tab.handleTabKey === "function" && tab.handleTabKey(data)) return { consume: true };
4090
+ }
4091
+ return void 0;
4092
+ });
4093
+ applyTab();
4094
+ tui.start();
4095
+ }
4096
+ var import_sdk;
4097
+ var init_app = __esm({
4098
+ "src/ui/app.ts"() {
4099
+ "use strict";
4100
+ import_sdk = __toESM(require_dist(), 1);
4101
+ init_tab_strip();
4102
+ init_status_bar();
4103
+ init_app_model();
4104
+ init_connection();
4105
+ init_chat_store();
4106
+ init_chat_tab();
4107
+ init_slash_commands();
4108
+ init_external_editor();
4109
+ init_clickable_tui();
4110
+ init_clickable_select_list();
4111
+ init_keymap();
4112
+ init_interaction_state();
4113
+ init_command_registry();
4114
+ init_command_registry();
4115
+ init_queue_overlay();
4116
+ init_transcript_search();
4117
+ init_confirm_overlay();
4118
+ init_session_recap();
4119
+ init_message_blocks();
4120
+ init_shell_mode();
4121
+ init_overlays();
4122
+ init_goals_store();
4123
+ init_goals_tab();
4124
+ init_memory_store();
4125
+ init_memory_tab();
4126
+ init_triage_store();
4127
+ init_triage_tab();
4128
+ init_theme();
4129
+ }
4130
+ });
4131
+
4132
+ // ../../node_modules/eventsource-parser/dist/index.js
4133
+ var ParseError = class extends Error {
4134
+ constructor(message, options) {
4135
+ super(message), this.name = "ParseError", this.type = options.type, this.field = options.field, this.value = options.value, this.line = options.line;
4136
+ }
4137
+ };
4138
+ var LF = 10;
4139
+ var CR = 13;
4140
+ var SPACE = 32;
4141
+ function noop(_arg) {
4142
+ }
4143
+ function createParser(config) {
4144
+ if (typeof config == "function")
4145
+ throw new TypeError(
4146
+ "`config` must be an object, got a function instead. Did you mean `createParser({onEvent: fn})`?"
4147
+ );
4148
+ const { onEvent = noop, onError = noop, onRetry = noop, onComment, maxBufferSize } = config, pendingFragments = [];
4149
+ let pendingFragmentsLength = 0, isFirstChunk = true, id, data = "", dataLines = 0, eventType, terminated = false;
4150
+ function feed(chunk) {
4151
+ if (terminated)
4152
+ throw new Error(
4153
+ "Cannot feed parser: it was terminated after exceeding the configured max buffer size. Call `reset()` to resume parsing."
4154
+ );
4155
+ if (isFirstChunk && (isFirstChunk = false, chunk.charCodeAt(0) === 239 && chunk.charCodeAt(1) === 187 && chunk.charCodeAt(2) === 191 && (chunk = chunk.slice(3))), pendingFragments.length === 0) {
4156
+ const trailing2 = processLines(chunk);
4157
+ trailing2 !== "" && (pendingFragments.push(trailing2), pendingFragmentsLength = trailing2.length), checkBufferSize();
4158
+ return;
4159
+ }
4160
+ if (chunk.indexOf(`
4161
+ `) === -1 && chunk.indexOf("\r") === -1) {
4162
+ pendingFragments.push(chunk), pendingFragmentsLength += chunk.length, checkBufferSize();
4163
+ return;
4164
+ }
4165
+ pendingFragments.push(chunk);
4166
+ const input = pendingFragments.join("");
4167
+ pendingFragments.length = 0, pendingFragmentsLength = 0;
4168
+ const trailing = processLines(input);
4169
+ trailing !== "" && (pendingFragments.push(trailing), pendingFragmentsLength = trailing.length), checkBufferSize();
4170
+ }
4171
+ function checkBufferSize() {
4172
+ maxBufferSize !== void 0 && (pendingFragmentsLength + data.length <= maxBufferSize || (terminated = true, pendingFragments.length = 0, pendingFragmentsLength = 0, id = void 0, data = "", dataLines = 0, eventType = void 0, onError(
4173
+ new ParseError(`Buffered data exceeded max buffer size of ${maxBufferSize} characters`, {
4174
+ type: "max-buffer-size-exceeded"
4175
+ })
4176
+ )));
4177
+ }
4178
+ function processLines(chunk) {
4179
+ let searchIndex = 0;
4180
+ if (chunk.indexOf("\r") === -1) {
4181
+ let lfIndex = chunk.indexOf(`
4182
+ `, searchIndex);
4183
+ for (; lfIndex !== -1; ) {
4184
+ if (searchIndex === lfIndex) {
4185
+ dataLines > 0 && onEvent({ id, event: eventType, data }), id = void 0, data = "", dataLines = 0, eventType = void 0, searchIndex = lfIndex + 1, lfIndex = chunk.indexOf(`
4186
+ `, searchIndex);
4187
+ continue;
4188
+ }
4189
+ const firstCharCode = chunk.charCodeAt(searchIndex);
4190
+ if (isDataPrefix(chunk, searchIndex, firstCharCode)) {
4191
+ const valueStart = chunk.charCodeAt(searchIndex + 5) === SPACE ? searchIndex + 6 : searchIndex + 5, value = chunk.slice(valueStart, lfIndex);
4192
+ if (dataLines === 0 && chunk.charCodeAt(lfIndex + 1) === LF) {
4193
+ onEvent({ id, event: eventType, data: value }), id = void 0, data = "", eventType = void 0, searchIndex = lfIndex + 2, lfIndex = chunk.indexOf(`
4194
+ `, searchIndex);
4195
+ continue;
4196
+ }
4197
+ data = dataLines === 0 ? value : `${data}
4198
+ ${value}`, dataLines++;
4199
+ } else isEventPrefix(chunk, searchIndex, firstCharCode) ? eventType = chunk.slice(
4200
+ chunk.charCodeAt(searchIndex + 6) === SPACE ? searchIndex + 7 : searchIndex + 6,
4201
+ lfIndex
4202
+ ) || void 0 : parseLine(chunk, searchIndex, lfIndex);
4203
+ searchIndex = lfIndex + 1, lfIndex = chunk.indexOf(`
4204
+ `, searchIndex);
4205
+ }
4206
+ return chunk.slice(searchIndex);
4207
+ }
4208
+ for (; searchIndex < chunk.length; ) {
4209
+ const crIndex = chunk.indexOf("\r", searchIndex), lfIndex = chunk.indexOf(`
4210
+ `, searchIndex);
4211
+ let lineEnd = -1;
4212
+ if (crIndex !== -1 && lfIndex !== -1 ? lineEnd = crIndex < lfIndex ? crIndex : lfIndex : crIndex !== -1 ? crIndex === chunk.length - 1 ? lineEnd = -1 : lineEnd = crIndex : lfIndex !== -1 && (lineEnd = lfIndex), lineEnd === -1)
4213
+ break;
4214
+ parseLine(chunk, searchIndex, lineEnd), searchIndex = lineEnd + 1, chunk.charCodeAt(searchIndex - 1) === CR && chunk.charCodeAt(searchIndex) === LF && searchIndex++;
4215
+ }
4216
+ return chunk.slice(searchIndex);
4217
+ }
4218
+ function parseLine(chunk, start, end) {
4219
+ if (start === end) {
4220
+ dispatchEvent();
4221
+ return;
4222
+ }
4223
+ const firstCharCode = chunk.charCodeAt(start);
4224
+ if (isDataPrefix(chunk, start, firstCharCode)) {
4225
+ const valueStart = chunk.charCodeAt(start + 5) === SPACE ? start + 6 : start + 5, value2 = chunk.slice(valueStart, end);
4226
+ data = dataLines === 0 ? value2 : `${data}
4227
+ ${value2}`, dataLines++;
4228
+ return;
4229
+ }
4230
+ if (isEventPrefix(chunk, start, firstCharCode)) {
4231
+ eventType = chunk.slice(chunk.charCodeAt(start + 6) === SPACE ? start + 7 : start + 6, end) || void 0;
4232
+ return;
4233
+ }
4234
+ if (firstCharCode === 105 && chunk.charCodeAt(start + 1) === 100 && chunk.charCodeAt(start + 2) === 58) {
4235
+ const value2 = chunk.slice(chunk.charCodeAt(start + 3) === SPACE ? start + 4 : start + 3, end);
4236
+ id = value2.includes("\0") ? void 0 : value2;
4237
+ return;
4238
+ }
4239
+ if (firstCharCode === 58) {
4240
+ if (onComment) {
4241
+ const line2 = chunk.slice(start, end);
4242
+ onComment(line2.slice(chunk.charCodeAt(start + 1) === SPACE ? 2 : 1));
4243
+ }
4244
+ return;
4245
+ }
4246
+ const line = chunk.slice(start, end), fieldSeparatorIndex = line.indexOf(":");
4247
+ if (fieldSeparatorIndex === -1) {
4248
+ processField(line, "", line);
4249
+ return;
4250
+ }
4251
+ const field = line.slice(0, fieldSeparatorIndex), offset = line.charCodeAt(fieldSeparatorIndex + 1) === SPACE ? 2 : 1, value = line.slice(fieldSeparatorIndex + offset);
4252
+ processField(field, value, line);
4253
+ }
4254
+ function processField(field, value, line) {
4255
+ switch (field) {
4256
+ case "event":
4257
+ eventType = value || void 0;
4258
+ break;
4259
+ case "data":
4260
+ data = dataLines === 0 ? value : `${data}
4261
+ ${value}`, dataLines++;
4262
+ break;
4263
+ case "id":
4264
+ id = value.includes("\0") ? void 0 : value;
4265
+ break;
4266
+ case "retry":
4267
+ /^\d+$/.test(value) ? onRetry(parseInt(value, 10)) : onError(
4268
+ new ParseError(`Invalid \`retry\` value: "${value}"`, {
4269
+ type: "invalid-retry",
4270
+ value,
4271
+ line
4272
+ })
4273
+ );
4274
+ break;
4275
+ default:
4276
+ onError(
4277
+ new ParseError(
4278
+ `Unknown field "${field.length > 20 ? `${field.slice(0, 20)}\u2026` : field}"`,
4279
+ { type: "unknown-field", field, value, line }
4280
+ )
4281
+ );
4282
+ break;
4283
+ }
4284
+ }
4285
+ function dispatchEvent() {
4286
+ dataLines > 0 && onEvent({
4287
+ id,
4288
+ event: eventType,
4289
+ data
4290
+ }), id = void 0, data = "", dataLines = 0, eventType = void 0;
4291
+ }
4292
+ function reset(options = {}) {
4293
+ if (options.consume && pendingFragments.length > 0) {
4294
+ const incompleteLine = pendingFragments.join("");
4295
+ parseLine(incompleteLine, 0, incompleteLine.length);
4296
+ }
4297
+ isFirstChunk = true, id = void 0, data = "", dataLines = 0, eventType = void 0, pendingFragments.length = 0, pendingFragmentsLength = 0, terminated = false;
4298
+ }
4299
+ return { feed, reset };
4300
+ }
4301
+ function isDataPrefix(chunk, i, firstCharCode) {
4302
+ return firstCharCode === 100 && chunk.charCodeAt(i + 1) === 97 && chunk.charCodeAt(i + 2) === 116 && chunk.charCodeAt(i + 3) === 97 && chunk.charCodeAt(i + 4) === 58;
4303
+ }
4304
+ function isEventPrefix(chunk, i, firstCharCode) {
4305
+ return firstCharCode === 101 && chunk.charCodeAt(i + 1) === 118 && chunk.charCodeAt(i + 2) === 101 && chunk.charCodeAt(i + 3) === 110 && chunk.charCodeAt(i + 4) === 116 && chunk.charCodeAt(i + 5) === 58;
4306
+ }
4307
+
4308
+ // ../../node_modules/eventsource/dist/index.js
4309
+ var ErrorEvent = class extends Event {
4310
+ /**
4311
+ * Constructs a new `ErrorEvent` instance. This is typically not called directly,
4312
+ * but rather emitted by the `EventSource` object when an error occurs.
4313
+ *
4314
+ * @param type - The type of the event (should be "error")
4315
+ * @param errorEventInitDict - Optional properties to include in the error event
4316
+ */
4317
+ constructor(type, errorEventInitDict) {
4318
+ var _a, _b;
4319
+ super(type), this.code = (_a = errorEventInitDict == null ? void 0 : errorEventInitDict.code) != null ? _a : void 0, this.message = (_b = errorEventInitDict == null ? void 0 : errorEventInitDict.message) != null ? _b : void 0;
4320
+ }
4321
+ /**
4322
+ * Node.js "hides" the `message` and `code` properties of the `ErrorEvent` instance,
4323
+ * when it is `console.log`'ed. This makes it harder to debug errors. To ease debugging,
4324
+ * we explicitly include the properties in the `inspect` method.
4325
+ *
4326
+ * This is automatically called by Node.js when you `console.log` an instance of this class.
4327
+ *
4328
+ * @param _depth - The current depth
4329
+ * @param options - The options passed to `util.inspect`
4330
+ * @param inspect - The inspect function to use (prevents having to import it from `util`)
4331
+ * @returns A string representation of the error
4332
+ */
4333
+ [Symbol.for("nodejs.util.inspect.custom")](_depth, options, inspect) {
4334
+ return inspect(inspectableError(this), options);
4335
+ }
4336
+ /**
4337
+ * Deno "hides" the `message` and `code` properties of the `ErrorEvent` instance,
4338
+ * when it is `console.log`'ed. This makes it harder to debug errors. To ease debugging,
4339
+ * we explicitly include the properties in the `inspect` method.
4340
+ *
4341
+ * This is automatically called by Deno when you `console.log` an instance of this class.
4342
+ *
4343
+ * @param inspect - The inspect function to use (prevents having to import it from `util`)
4344
+ * @param options - The options passed to `Deno.inspect`
4345
+ * @returns A string representation of the error
4346
+ */
4347
+ [Symbol.for("Deno.customInspect")](inspect, options) {
4348
+ return inspect(inspectableError(this), options);
4349
+ }
4350
+ };
4351
+ function syntaxError(message) {
4352
+ const DomException = globalThis.DOMException;
4353
+ return typeof DomException == "function" ? new DomException(message, "SyntaxError") : new SyntaxError(message);
4354
+ }
4355
+ function flattenError(err) {
4356
+ return err instanceof Error ? "errors" in err && Array.isArray(err.errors) ? err.errors.map(flattenError).join(", ") : "cause" in err && err.cause instanceof Error ? `${err}: ${flattenError(err.cause)}` : err.message : `${err}`;
4357
+ }
4358
+ function inspectableError(err) {
4359
+ return {
4360
+ type: err.type,
4361
+ message: err.message,
4362
+ code: err.code,
4363
+ defaultPrevented: err.defaultPrevented,
4364
+ cancelable: err.cancelable,
4365
+ timeStamp: err.timeStamp
4366
+ };
4367
+ }
4368
+ var __typeError = (msg) => {
4369
+ throw TypeError(msg);
4370
+ };
4371
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
4372
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
4373
+ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
4374
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value);
4375
+ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
4376
+ var _readyState;
4377
+ var _url;
4378
+ var _redirectUrl;
4379
+ var _withCredentials;
4380
+ var _fetch;
4381
+ var _reconnectInterval;
4382
+ var _reconnectTimer;
4383
+ var _lastEventId;
4384
+ var _controller;
4385
+ var _parser;
4386
+ var _onError;
4387
+ var _onMessage;
4388
+ var _onOpen;
4389
+ var _EventSource_instances;
4390
+ var connect_fn;
4391
+ var _onFetchResponse;
4392
+ var _onFetchError;
4393
+ var getRequestOptions_fn;
4394
+ var _onEvent;
4395
+ var _onRetryChange;
4396
+ var failConnection_fn;
4397
+ var scheduleReconnect_fn;
4398
+ var _reconnect;
4399
+ var EventSource2 = class extends EventTarget {
4400
+ constructor(url, eventSourceInitDict) {
4401
+ var _a, _b;
4402
+ super(), __privateAdd(this, _EventSource_instances), this.CONNECTING = 0, this.OPEN = 1, this.CLOSED = 2, __privateAdd(this, _readyState), __privateAdd(this, _url), __privateAdd(this, _redirectUrl), __privateAdd(this, _withCredentials), __privateAdd(this, _fetch), __privateAdd(this, _reconnectInterval), __privateAdd(this, _reconnectTimer), __privateAdd(this, _lastEventId, null), __privateAdd(this, _controller), __privateAdd(this, _parser), __privateAdd(this, _onError, null), __privateAdd(this, _onMessage, null), __privateAdd(this, _onOpen, null), __privateAdd(this, _onFetchResponse, async (response) => {
4403
+ var _a2;
4404
+ __privateGet(this, _parser).reset();
4405
+ const { body, redirected, status, headers } = response;
4406
+ if (status === 204) {
4407
+ __privateMethod(this, _EventSource_instances, failConnection_fn).call(this, "Server sent HTTP 204, not reconnecting", 204), this.close();
4408
+ return;
4409
+ }
4410
+ if (redirected ? __privateSet(this, _redirectUrl, new URL(response.url)) : __privateSet(this, _redirectUrl, void 0), status !== 200) {
4411
+ __privateMethod(this, _EventSource_instances, failConnection_fn).call(this, `Non-200 status code (${status})`, status);
4412
+ return;
4413
+ }
4414
+ if (!(headers.get("content-type") || "").startsWith("text/event-stream")) {
4415
+ __privateMethod(this, _EventSource_instances, failConnection_fn).call(this, 'Invalid content type, expected "text/event-stream"', status);
4416
+ return;
4417
+ }
4418
+ if (__privateGet(this, _readyState) === this.CLOSED)
4419
+ return;
4420
+ __privateSet(this, _readyState, this.OPEN);
4421
+ const openEvent = new Event("open");
4422
+ if ((_a2 = __privateGet(this, _onOpen)) == null || _a2.call(this, openEvent), this.dispatchEvent(openEvent), typeof body != "object" || !body || !("getReader" in body)) {
4423
+ __privateMethod(this, _EventSource_instances, failConnection_fn).call(this, "Invalid response body, expected a web ReadableStream", status), this.close();
4424
+ return;
4425
+ }
4426
+ const decoder = new TextDecoder(), reader = body.getReader();
4427
+ let open = true;
4428
+ do {
4429
+ const { done, value } = await reader.read();
4430
+ value && __privateGet(this, _parser).feed(decoder.decode(value, { stream: !done })), done && (open = false, __privateGet(this, _parser).reset(), __privateMethod(this, _EventSource_instances, scheduleReconnect_fn).call(this));
4431
+ } while (open);
4432
+ }), __privateAdd(this, _onFetchError, (err) => {
4433
+ __privateSet(this, _controller, void 0), !(err.name === "AbortError" || err.type === "aborted") && __privateMethod(this, _EventSource_instances, scheduleReconnect_fn).call(this, flattenError(err));
4434
+ }), __privateAdd(this, _onEvent, (event) => {
4435
+ typeof event.id == "string" && __privateSet(this, _lastEventId, event.id);
4436
+ const messageEvent = new MessageEvent(event.event || "message", {
4437
+ data: event.data,
4438
+ origin: __privateGet(this, _redirectUrl) ? __privateGet(this, _redirectUrl).origin : __privateGet(this, _url).origin,
4439
+ lastEventId: event.id || ""
4440
+ });
4441
+ __privateGet(this, _onMessage) && (!event.event || event.event === "message") && __privateGet(this, _onMessage).call(this, messageEvent), this.dispatchEvent(messageEvent);
4442
+ }), __privateAdd(this, _onRetryChange, (value) => {
4443
+ __privateSet(this, _reconnectInterval, value);
4444
+ }), __privateAdd(this, _reconnect, () => {
4445
+ __privateSet(this, _reconnectTimer, void 0), __privateGet(this, _readyState) === this.CONNECTING && __privateMethod(this, _EventSource_instances, connect_fn).call(this);
4446
+ });
4447
+ try {
4448
+ if (url instanceof URL)
4449
+ __privateSet(this, _url, url);
4450
+ else if (typeof url == "string")
4451
+ __privateSet(this, _url, new URL(url, getBaseURL()));
4452
+ else
4453
+ throw new Error("Invalid URL");
4454
+ } catch {
4455
+ throw syntaxError("An invalid or illegal string was specified");
4456
+ }
4457
+ __privateSet(this, _parser, createParser({
4458
+ onEvent: __privateGet(this, _onEvent),
4459
+ onRetry: __privateGet(this, _onRetryChange)
4460
+ })), __privateSet(this, _readyState, this.CONNECTING), __privateSet(this, _reconnectInterval, 3e3), __privateSet(this, _fetch, (_a = eventSourceInitDict == null ? void 0 : eventSourceInitDict.fetch) != null ? _a : globalThis.fetch), __privateSet(this, _withCredentials, (_b = eventSourceInitDict == null ? void 0 : eventSourceInitDict.withCredentials) != null ? _b : false), __privateMethod(this, _EventSource_instances, connect_fn).call(this);
4461
+ }
4462
+ /**
4463
+ * Returns the state of this EventSource object's connection. It can have the values described below.
4464
+ *
4465
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/readyState)
4466
+ *
4467
+ * Note: typed as `number` instead of `0 | 1 | 2` for compatibility with the `EventSource` interface,
4468
+ * defined in the TypeScript `dom` library.
4469
+ *
4470
+ * @public
4471
+ */
4472
+ get readyState() {
4473
+ return __privateGet(this, _readyState);
4474
+ }
4475
+ /**
4476
+ * Returns the URL providing the event stream.
4477
+ *
4478
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/url)
4479
+ *
4480
+ * @public
4481
+ */
4482
+ get url() {
4483
+ return __privateGet(this, _url).href;
4484
+ }
4485
+ /**
4486
+ * Returns true if the credentials mode for connection requests to the URL providing the event stream is set to "include", and false otherwise.
4487
+ *
4488
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/withCredentials)
4489
+ */
4490
+ get withCredentials() {
4491
+ return __privateGet(this, _withCredentials);
4492
+ }
4493
+ /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/error_event) */
4494
+ get onerror() {
4495
+ return __privateGet(this, _onError);
4496
+ }
4497
+ set onerror(value) {
4498
+ __privateSet(this, _onError, value);
4499
+ }
4500
+ /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/message_event) */
4501
+ get onmessage() {
4502
+ return __privateGet(this, _onMessage);
4503
+ }
4504
+ set onmessage(value) {
4505
+ __privateSet(this, _onMessage, value);
4506
+ }
4507
+ /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/open_event) */
4508
+ get onopen() {
4509
+ return __privateGet(this, _onOpen);
4510
+ }
4511
+ set onopen(value) {
4512
+ __privateSet(this, _onOpen, value);
4513
+ }
4514
+ addEventListener(type, listener, options) {
4515
+ const listen = listener;
4516
+ super.addEventListener(type, listen, options);
4517
+ }
4518
+ removeEventListener(type, listener, options) {
4519
+ const listen = listener;
4520
+ super.removeEventListener(type, listen, options);
4521
+ }
4522
+ /**
4523
+ * Aborts any instances of the fetch algorithm started for this EventSource object, and sets the readyState attribute to CLOSED.
4524
+ *
4525
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/close)
4526
+ *
4527
+ * @public
4528
+ */
4529
+ close() {
4530
+ __privateGet(this, _reconnectTimer) && clearTimeout(__privateGet(this, _reconnectTimer)), __privateGet(this, _readyState) !== this.CLOSED && (__privateGet(this, _controller) && __privateGet(this, _controller).abort(), __privateSet(this, _readyState, this.CLOSED), __privateSet(this, _controller, void 0));
4531
+ }
4532
+ };
4533
+ _readyState = /* @__PURE__ */ new WeakMap(), _url = /* @__PURE__ */ new WeakMap(), _redirectUrl = /* @__PURE__ */ new WeakMap(), _withCredentials = /* @__PURE__ */ new WeakMap(), _fetch = /* @__PURE__ */ new WeakMap(), _reconnectInterval = /* @__PURE__ */ new WeakMap(), _reconnectTimer = /* @__PURE__ */ new WeakMap(), _lastEventId = /* @__PURE__ */ new WeakMap(), _controller = /* @__PURE__ */ new WeakMap(), _parser = /* @__PURE__ */ new WeakMap(), _onError = /* @__PURE__ */ new WeakMap(), _onMessage = /* @__PURE__ */ new WeakMap(), _onOpen = /* @__PURE__ */ new WeakMap(), _EventSource_instances = /* @__PURE__ */ new WeakSet(), /**
4534
+ * Connect to the given URL and start receiving events
4535
+ *
4536
+ * @internal
4537
+ */
4538
+ connect_fn = function() {
4539
+ __privateSet(this, _readyState, this.CONNECTING), __privateSet(this, _controller, new AbortController()), __privateGet(this, _fetch)(__privateGet(this, _url), __privateMethod(this, _EventSource_instances, getRequestOptions_fn).call(this)).then(__privateGet(this, _onFetchResponse)).catch(__privateGet(this, _onFetchError));
4540
+ }, _onFetchResponse = /* @__PURE__ */ new WeakMap(), _onFetchError = /* @__PURE__ */ new WeakMap(), /**
4541
+ * Get request options for the `fetch()` request
4542
+ *
4543
+ * @returns The request options
4544
+ * @internal
4545
+ */
4546
+ getRequestOptions_fn = function() {
4547
+ var _a;
4548
+ const init = {
4549
+ // [spec] Let `corsAttributeState` be `Anonymous`…
4550
+ // [spec] …will have their mode set to "cors"…
4551
+ mode: "cors",
4552
+ redirect: "follow",
4553
+ headers: { Accept: "text/event-stream", ...__privateGet(this, _lastEventId) ? { "Last-Event-ID": __privateGet(this, _lastEventId) } : void 0 },
4554
+ cache: "no-store",
4555
+ signal: (_a = __privateGet(this, _controller)) == null ? void 0 : _a.signal
4556
+ };
4557
+ return "window" in globalThis && (init.credentials = this.withCredentials ? "include" : "same-origin"), init;
4558
+ }, _onEvent = /* @__PURE__ */ new WeakMap(), _onRetryChange = /* @__PURE__ */ new WeakMap(), /**
4559
+ * Handles the process referred to in the EventSource specification as "failing a connection".
4560
+ *
4561
+ * @param error - The error causing the connection to fail
4562
+ * @param code - The HTTP status code, if available
4563
+ * @internal
4564
+ */
4565
+ failConnection_fn = function(message, code) {
4566
+ var _a;
4567
+ __privateGet(this, _readyState) !== this.CLOSED && __privateSet(this, _readyState, this.CLOSED);
4568
+ const errorEvent = new ErrorEvent("error", { code, message });
4569
+ (_a = __privateGet(this, _onError)) == null || _a.call(this, errorEvent), this.dispatchEvent(errorEvent);
4570
+ }, /**
4571
+ * Schedules a reconnection attempt against the EventSource endpoint.
4572
+ *
4573
+ * @param message - The error causing the connection to fail
4574
+ * @param code - The HTTP status code, if available
4575
+ * @internal
4576
+ */
4577
+ scheduleReconnect_fn = function(message, code) {
4578
+ var _a;
4579
+ if (__privateGet(this, _readyState) === this.CLOSED)
4580
+ return;
4581
+ __privateSet(this, _readyState, this.CONNECTING);
4582
+ const errorEvent = new ErrorEvent("error", { code, message });
4583
+ (_a = __privateGet(this, _onError)) == null || _a.call(this, errorEvent), this.dispatchEvent(errorEvent), __privateSet(this, _reconnectTimer, setTimeout(__privateGet(this, _reconnect), __privateGet(this, _reconnectInterval)));
4584
+ }, _reconnect = /* @__PURE__ */ new WeakMap(), /**
4585
+ * ReadyState representing an EventSource currently trying to connect
4586
+ *
4587
+ * @public
4588
+ */
4589
+ EventSource2.CONNECTING = 0, /**
4590
+ * ReadyState representing an EventSource connection that is open (eg connected)
4591
+ *
4592
+ * @public
4593
+ */
4594
+ EventSource2.OPEN = 1, /**
4595
+ * ReadyState representing an EventSource connection that is closed (eg disconnected)
4596
+ *
4597
+ * @public
4598
+ */
4599
+ EventSource2.CLOSED = 2;
4600
+ function getBaseURL() {
4601
+ const doc = "document" in globalThis ? globalThis.document : void 0;
4602
+ return doc && typeof doc == "object" && "baseURI" in doc && typeof doc.baseURI == "string" ? doc.baseURI : void 0;
4603
+ }
4604
+
4605
+ // src/cli.ts
4606
+ import { pathToFileURL } from "node:url";
4607
+
4608
+ // src/probe.ts
4609
+ async function probeGateway(fetchFn = fetch, candidates = defaultCandidates(), timeoutMs = 2e3) {
4610
+ for (const base of candidates) {
4611
+ try {
4612
+ const res = await fetchFn(`${base}/health`, { signal: AbortSignal.timeout(timeoutMs) });
4613
+ if (res.ok) return base;
4614
+ } catch {
4615
+ }
4616
+ }
4617
+ return null;
4618
+ }
4619
+ function defaultCandidates() {
4620
+ const override = process.env.MAFW_TUI_PORTS;
4621
+ if (override) return override.split(",").map((p) => `http://localhost:${p.trim()}`).filter(Boolean);
4622
+ const port = process.env.MAFW_SERVER_API_PORT || process.env.MAFW_GATEWAY_PORT;
4623
+ const urls = [];
4624
+ if (port) urls.push(`http://localhost:${port}`);
4625
+ urls.push("http://localhost:3000");
4626
+ return [...new Set(urls)];
4627
+ }
4628
+
4629
+ // src/cli.ts
4630
+ if (typeof globalThis.EventSource === "undefined") {
4631
+ ;
4632
+ globalThis.EventSource = EventSource2;
4633
+ }
4634
+ function parseArgs(argv) {
4635
+ const args = { retriever: "bm25" };
4636
+ for (let i = 0; i < argv.length; i++) {
4637
+ if (argv[i] === "--hybrid") args.retriever = "hybrid";
4638
+ else if (argv[i] === "--session" && argv[i + 1]) {
4639
+ args.sessionID = argv[i + 1];
4640
+ i++;
4641
+ }
4642
+ }
4643
+ return args;
4644
+ }
4645
+ async function main() {
4646
+ const baseUrl = await probeGateway();
4647
+ if (!baseUrl) {
4648
+ console.error("[mafw tui] gateway \u672A\u8FD0\u884C\uFF08/health \u63A2\u6D4B\u5931\u8D25\uFF09\u3002\u8BF7\u5148\u542F\u52A8\uFF1Amafw daemon");
4649
+ process.exit(1);
4650
+ }
4651
+ const args = parseArgs(process.argv.slice(2));
4652
+ const { runApp: runApp2 } = await Promise.resolve().then(() => (init_app(), app_exports));
4653
+ await runApp2({ baseUrl, retriever: args.retriever, ...args.sessionID ? { sessionID: args.sessionID } : {} });
4654
+ }
4655
+ var isEntry = !!process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
4656
+ if (isEntry) {
4657
+ main().catch((err) => {
4658
+ console.error(`[mafw tui] ${err instanceof Error ? err.message : err}`);
4659
+ process.exit(1);
4660
+ });
4661
+ }
4662
+ export {
4663
+ parseArgs
4664
+ };