@undefineds.co/linx 0.3.26 → 0.3.28

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 (362) hide show
  1. package/README.md +4 -3
  2. package/dist/index.js +3 -832
  3. package/dist/index.js.map +1 -1
  4. package/dist/lib/agent-stream-bridge-contract.js +2 -0
  5. package/dist/lib/agent-stream-bridge-contract.js.map +1 -0
  6. package/dist/lib/auto-mode/auth.js +100 -2
  7. package/dist/lib/auto-mode/auth.js.map +1 -1
  8. package/dist/lib/auto-mode/backend-kind.js +13 -0
  9. package/dist/lib/auto-mode/backend-kind.js.map +1 -0
  10. package/dist/lib/auto-mode/display.js +1 -1
  11. package/dist/lib/auto-mode/display.js.map +1 -1
  12. package/dist/lib/auto-mode/format.js +1 -1
  13. package/dist/lib/auto-mode/format.js.map +1 -1
  14. package/dist/lib/auto-mode/hooks/{index.js → registry.js} +1 -1
  15. package/dist/lib/auto-mode/hooks/registry.js.map +1 -0
  16. package/dist/lib/auto-mode/pod-ai.js +1 -14
  17. package/dist/lib/auto-mode/pod-ai.js.map +1 -1
  18. package/dist/lib/auto-mode/pod-approval-store.js +596 -0
  19. package/dist/lib/auto-mode/pod-approval-store.js.map +1 -0
  20. package/dist/lib/auto-mode/pod-approval.js +4 -607
  21. package/dist/lib/auto-mode/pod-approval.js.map +1 -1
  22. package/dist/lib/auto-mode/pod-persistence-builders.js +266 -0
  23. package/dist/lib/auto-mode/pod-persistence-builders.js.map +1 -0
  24. package/dist/lib/auto-mode/pod-persistence.js +1 -281
  25. package/dist/lib/auto-mode/pod-persistence.js.map +1 -1
  26. package/dist/lib/auto-mode/runner.js +41 -378
  27. package/dist/lib/auto-mode/runner.js.map +1 -1
  28. package/dist/lib/auto-mode/runtime.js +25 -0
  29. package/dist/lib/auto-mode/runtime.js.map +1 -0
  30. package/dist/lib/auto-mode/secretary-reaction-window.js +12 -0
  31. package/dist/lib/auto-mode/secretary-reaction-window.js.map +1 -0
  32. package/dist/lib/auto-mode/secretary-runtime-config.js +66 -0
  33. package/dist/lib/auto-mode/secretary-runtime-config.js.map +1 -0
  34. package/dist/lib/auto-mode/secretary.js +2 -64
  35. package/dist/lib/auto-mode/secretary.js.map +1 -1
  36. package/dist/lib/auto-mode/session-log.js +25 -0
  37. package/dist/lib/auto-mode/session-log.js.map +1 -0
  38. package/dist/lib/auto-mode/shell-command.js +172 -0
  39. package/dist/lib/auto-mode/shell-command.js.map +1 -0
  40. package/dist/lib/auto-mode-command.js +2 -1
  41. package/dist/lib/auto-mode-command.js.map +1 -1
  42. package/dist/lib/backend-command-router-contract.js +2 -0
  43. package/dist/lib/backend-command-router-contract.js.map +1 -0
  44. package/dist/lib/{pi-adapter/backend-credentials.js → backend-credentials.js} +8 -8
  45. package/dist/lib/backend-credentials.js.map +1 -0
  46. package/dist/lib/linx-ai-connect-command.js +201 -0
  47. package/dist/lib/linx-ai-connect-command.js.map +1 -0
  48. package/dist/lib/linx-assistant-message-rendering.js +33 -0
  49. package/dist/lib/linx-assistant-message-rendering.js.map +1 -0
  50. package/dist/lib/linx-auto-command-routing.js +53 -0
  51. package/dist/lib/linx-auto-command-routing.js.map +1 -0
  52. package/dist/lib/linx-auto-editor-indicator-host.js +15 -0
  53. package/dist/lib/linx-auto-editor-indicator-host.js.map +1 -0
  54. package/dist/lib/linx-auto-editor-indicator.js +56 -0
  55. package/dist/lib/linx-auto-editor-indicator.js.map +1 -0
  56. package/dist/lib/linx-auto-mode-cli-admission.js +16 -0
  57. package/dist/lib/linx-auto-mode-cli-admission.js.map +1 -0
  58. package/dist/lib/linx-backend-command-router.js +64 -0
  59. package/dist/lib/linx-backend-command-router.js.map +1 -0
  60. package/dist/lib/linx-backend-event-source.js +32 -0
  61. package/dist/lib/linx-backend-event-source.js.map +1 -0
  62. package/dist/lib/linx-chat-completion-projection.js +276 -0
  63. package/dist/lib/linx-chat-completion-projection.js.map +1 -0
  64. package/dist/lib/linx-cloud-errors.js +1 -0
  65. package/dist/lib/linx-cloud-errors.js.map +1 -1
  66. package/dist/lib/linx-cloud-models.js +75 -0
  67. package/dist/lib/linx-cloud-models.js.map +1 -0
  68. package/dist/lib/linx-cloud-runtime-auth.js +146 -0
  69. package/dist/lib/linx-cloud-runtime-auth.js.map +1 -0
  70. package/dist/lib/linx-cloud-runtime-coordinator.js +102 -0
  71. package/dist/lib/linx-cloud-runtime-coordinator.js.map +1 -0
  72. package/dist/lib/linx-codex-plugin-command.js +56 -0
  73. package/dist/lib/linx-codex-plugin-command.js.map +1 -0
  74. package/dist/lib/linx-command-autocomplete.js +123 -0
  75. package/dist/lib/linx-command-autocomplete.js.map +1 -0
  76. package/dist/lib/linx-completion-backend.js +2 -0
  77. package/dist/lib/linx-completion-backend.js.map +1 -0
  78. package/dist/lib/linx-config-command.js +12 -0
  79. package/dist/lib/linx-config-command.js.map +1 -0
  80. package/dist/lib/linx-editor-component-router.js +83 -0
  81. package/dist/lib/linx-editor-component-router.js.map +1 -0
  82. package/dist/lib/linx-extension-ui-context-router.js +43 -0
  83. package/dist/lib/linx-extension-ui-context-router.js.map +1 -0
  84. package/dist/lib/linx-external-open-host.js +8 -0
  85. package/dist/lib/linx-external-open-host.js.map +1 -0
  86. package/dist/lib/linx-external-url.js +16 -0
  87. package/dist/lib/linx-external-url.js.map +1 -0
  88. package/dist/lib/linx-footer-patch.js +52 -0
  89. package/dist/lib/linx-footer-patch.js.map +1 -0
  90. package/dist/lib/linx-input-command-routing.js +65 -0
  91. package/dist/lib/linx-input-command-routing.js.map +1 -0
  92. package/dist/lib/linx-interactive-auth-state-host.js +50 -0
  93. package/dist/lib/linx-interactive-auth-state-host.js.map +1 -0
  94. package/dist/lib/linx-interactive-autocomplete-host.js +32 -0
  95. package/dist/lib/linx-interactive-autocomplete-host.js.map +1 -0
  96. package/dist/lib/linx-interactive-bootstrap.js +66 -0
  97. package/dist/lib/linx-interactive-bootstrap.js.map +1 -0
  98. package/dist/lib/linx-interactive-branding.js +24 -0
  99. package/dist/lib/linx-interactive-branding.js.map +1 -0
  100. package/dist/lib/linx-interactive-chat-text-host.js +16 -0
  101. package/dist/lib/linx-interactive-chat-text-host.js.map +1 -0
  102. package/dist/lib/linx-interactive-command-routing-host.js +31 -0
  103. package/dist/lib/linx-interactive-command-routing-host.js.map +1 -0
  104. package/dist/lib/linx-interactive-command-routing.js +75 -0
  105. package/dist/lib/linx-interactive-command-routing.js.map +1 -0
  106. package/dist/lib/linx-interactive-command-surface.js +16 -0
  107. package/dist/lib/linx-interactive-command-surface.js.map +1 -0
  108. package/dist/lib/linx-interactive-editor-text-host.js +16 -0
  109. package/dist/lib/linx-interactive-editor-text-host.js.map +1 -0
  110. package/dist/lib/linx-interactive-error-display.js +8 -0
  111. package/dist/lib/linx-interactive-error-display.js.map +1 -0
  112. package/dist/lib/linx-interactive-event-router.js +91 -0
  113. package/dist/lib/linx-interactive-event-router.js.map +1 -0
  114. package/dist/lib/linx-interactive-extension-input-host.js +7 -0
  115. package/dist/lib/linx-interactive-extension-input-host.js.map +1 -0
  116. package/dist/lib/linx-interactive-extension-selector-host.js +12 -0
  117. package/dist/lib/linx-interactive-extension-selector-host.js.map +1 -0
  118. package/dist/lib/linx-interactive-header-host.js +14 -0
  119. package/dist/lib/linx-interactive-header-host.js.map +1 -0
  120. package/dist/lib/linx-interactive-lifecycle-host.js +4 -0
  121. package/dist/lib/linx-interactive-lifecycle-host.js.map +1 -0
  122. package/dist/lib/linx-interactive-login-ui-router.js +81 -0
  123. package/dist/lib/linx-interactive-login-ui-router.js.map +1 -0
  124. package/dist/lib/linx-interactive-model-registry-host.js +14 -0
  125. package/dist/lib/linx-interactive-model-registry-host.js.map +1 -0
  126. package/dist/lib/linx-interactive-post-init.js +60 -0
  127. package/dist/lib/linx-interactive-post-init.js.map +1 -0
  128. package/dist/lib/linx-interactive-provider-count-host.js +4 -0
  129. package/dist/lib/linx-interactive-provider-count-host.js.map +1 -0
  130. package/dist/lib/linx-interactive-run-router.js +43 -0
  131. package/dist/lib/linx-interactive-run-router.js.map +1 -0
  132. package/dist/lib/linx-interactive-runtime-host.js +113 -0
  133. package/dist/lib/linx-interactive-runtime-host.js.map +1 -0
  134. package/dist/lib/linx-interactive-selector-host.js +12 -0
  135. package/dist/lib/linx-interactive-selector-host.js.map +1 -0
  136. package/dist/lib/linx-interactive-shell-state.js +215 -0
  137. package/dist/lib/linx-interactive-shell-state.js.map +1 -0
  138. package/dist/lib/linx-interactive-status-display.js +9 -0
  139. package/dist/lib/linx-interactive-status-display.js.map +1 -0
  140. package/dist/lib/linx-interactive-stop-router.js +85 -0
  141. package/dist/lib/linx-interactive-stop-router.js.map +1 -0
  142. package/dist/lib/linx-interactive-streaming-message-host.js +11 -0
  143. package/dist/lib/linx-interactive-streaming-message-host.js.map +1 -0
  144. package/dist/lib/linx-interactive-submit-router.js +63 -0
  145. package/dist/lib/linx-interactive-submit-router.js.map +1 -0
  146. package/dist/lib/linx-interactive-update-router.js +78 -0
  147. package/dist/lib/linx-interactive-update-router.js.map +1 -0
  148. package/dist/lib/linx-interactive-update-state-host.js +43 -0
  149. package/dist/lib/linx-interactive-update-state-host.js.map +1 -0
  150. package/dist/lib/linx-interactive-warning-display.js +4 -0
  151. package/dist/lib/linx-interactive-warning-display.js.map +1 -0
  152. package/dist/lib/linx-interrupt-control-host.js +22 -0
  153. package/dist/lib/linx-interrupt-control-host.js.map +1 -0
  154. package/dist/lib/linx-interrupt-control.js +116 -0
  155. package/dist/lib/linx-interrupt-control.js.map +1 -0
  156. package/dist/lib/linx-login-flow.js +566 -0
  157. package/dist/lib/linx-login-flow.js.map +1 -0
  158. package/dist/lib/linx-models-command.js +71 -0
  159. package/dist/lib/linx-models-command.js.map +1 -0
  160. package/dist/lib/linx-package-command.js +117 -0
  161. package/dist/lib/linx-package-command.js.map +1 -0
  162. package/dist/lib/linx-peer-command-routing.js +20 -0
  163. package/dist/lib/linx-peer-command-routing.js.map +1 -0
  164. package/dist/lib/linx-pi-cli-command.js +121 -0
  165. package/dist/lib/linx-pi-cli-command.js.map +1 -0
  166. package/dist/lib/linx-pi-completion-events.js +101 -0
  167. package/dist/lib/linx-pi-completion-events.js.map +1 -0
  168. package/dist/lib/linx-pi-normalized-event-stream.js +41 -0
  169. package/dist/lib/linx-pi-normalized-event-stream.js.map +1 -0
  170. package/dist/lib/linx-pi-resume-cli-admission.js +21 -0
  171. package/dist/lib/linx-pi-resume-cli-admission.js.map +1 -0
  172. package/dist/lib/linx-pi-runtime-execution.js +67 -0
  173. package/dist/lib/linx-pi-runtime-execution.js.map +1 -0
  174. package/dist/lib/linx-pi-startup-control.js +49 -0
  175. package/dist/lib/linx-pi-startup-control.js.map +1 -0
  176. package/dist/lib/linx-pi-startup-plan.js +103 -0
  177. package/dist/lib/linx-pi-startup-plan.js.map +1 -0
  178. package/dist/lib/linx-pi-stream-errors.js +10 -0
  179. package/dist/lib/linx-pi-stream-errors.js.map +1 -0
  180. package/dist/lib/linx-pod-backed-extension-ui.js +29 -0
  181. package/dist/lib/linx-pod-backed-extension-ui.js.map +1 -0
  182. package/dist/lib/linx-pod-data-session-factory.js +19 -0
  183. package/dist/lib/linx-pod-data-session-factory.js.map +1 -0
  184. package/dist/lib/linx-pod-mirror-runtime-host.js +50 -0
  185. package/dist/lib/linx-pod-mirror-runtime-host.js.map +1 -0
  186. package/dist/lib/linx-pod-mirror-sync-cli-admission.js +18 -0
  187. package/dist/lib/linx-pod-mirror-sync-cli-admission.js.map +1 -0
  188. package/dist/lib/linx-pod-mirror-sync-command.js +30 -0
  189. package/dist/lib/linx-pod-mirror-sync-command.js.map +1 -0
  190. package/dist/lib/{pi-adapter/sync-recovery.js → linx-pod-mirror-sync-recovery.js} +13 -13
  191. package/dist/lib/linx-pod-mirror-sync-recovery.js.map +1 -0
  192. package/dist/lib/{pi-adapter/pod-mirror.js → linx-pod-mirror.js} +63 -40
  193. package/dist/lib/linx-pod-mirror.js.map +1 -0
  194. package/dist/lib/linx-restored-auto-startup.js +21 -0
  195. package/dist/lib/linx-restored-auto-startup.js.map +1 -0
  196. package/dist/lib/linx-resume-output.js +209 -0
  197. package/dist/lib/linx-resume-output.js.map +1 -0
  198. package/dist/lib/linx-retired-command.js +22 -0
  199. package/dist/lib/linx-retired-command.js.map +1 -0
  200. package/dist/lib/linx-rewind-command.js +209 -0
  201. package/dist/lib/linx-rewind-command.js.map +1 -0
  202. package/dist/lib/linx-runtime-adapter-contract.js +2 -0
  203. package/dist/lib/linx-runtime-adapter-contract.js.map +1 -0
  204. package/dist/lib/linx-runtime-adapter-defaults.js +17 -0
  205. package/dist/lib/linx-runtime-adapter-defaults.js.map +1 -0
  206. package/dist/lib/linx-runtime-adapter-dependencies.js +2 -0
  207. package/dist/lib/linx-runtime-adapter-dependencies.js.map +1 -0
  208. package/dist/lib/linx-runtime-agent-session.js +130 -0
  209. package/dist/lib/linx-runtime-agent-session.js.map +1 -0
  210. package/dist/lib/linx-runtime-auth.js +5 -0
  211. package/dist/lib/linx-runtime-auth.js.map +1 -0
  212. package/dist/lib/linx-runtime-backend-composition.js +73 -0
  213. package/dist/lib/linx-runtime-backend-composition.js.map +1 -0
  214. package/dist/lib/linx-runtime-coding-tools.js +58 -0
  215. package/dist/lib/linx-runtime-coding-tools.js.map +1 -0
  216. package/dist/lib/linx-runtime-completion-backend.js +27 -0
  217. package/dist/lib/linx-runtime-completion-backend.js.map +1 -0
  218. package/dist/lib/linx-runtime-oauth-provider.js +57 -0
  219. package/dist/lib/linx-runtime-oauth-provider.js.map +1 -0
  220. package/dist/lib/linx-runtime-provider-registration.js +33 -0
  221. package/dist/lib/linx-runtime-provider-registration.js.map +1 -0
  222. package/dist/lib/linx-runtime-resources.js +167 -0
  223. package/dist/lib/linx-runtime-resources.js.map +1 -0
  224. package/dist/lib/linx-runtime-system-prompt.js +27 -0
  225. package/dist/lib/linx-runtime-system-prompt.js.map +1 -0
  226. package/dist/lib/linx-runtime-thinking.js +21 -0
  227. package/dist/lib/linx-runtime-thinking.js.map +1 -0
  228. package/dist/lib/linx-selector-choice.js +38 -0
  229. package/dist/lib/linx-selector-choice.js.map +1 -0
  230. package/dist/lib/linx-self-update.js +113 -0
  231. package/dist/lib/linx-self-update.js.map +1 -0
  232. package/dist/lib/linx-session-command-routing-host.js +24 -0
  233. package/dist/lib/linx-session-command-routing-host.js.map +1 -0
  234. package/dist/lib/linx-session-command-routing.js +68 -0
  235. package/dist/lib/linx-session-command-routing.js.map +1 -0
  236. package/dist/lib/linx-session-cwd-router.js +15 -0
  237. package/dist/lib/linx-session-cwd-router.js.map +1 -0
  238. package/dist/lib/linx-session-history.js +388 -0
  239. package/dist/lib/linx-session-history.js.map +1 -0
  240. package/dist/lib/{pi-adapter/session.js → linx-session-manager.js} +94 -46
  241. package/dist/lib/linx-session-manager.js.map +1 -0
  242. package/dist/lib/linx-session-metadata.js +55 -0
  243. package/dist/lib/linx-session-metadata.js.map +1 -0
  244. package/dist/lib/linx-session-selector-ui.js +32 -0
  245. package/dist/lib/linx-session-selector-ui.js.map +1 -0
  246. package/dist/lib/linx-session-thinking-capability-router.js +55 -0
  247. package/dist/lib/linx-session-thinking-capability-router.js.map +1 -0
  248. package/dist/lib/linx-session-work-control.js +187 -0
  249. package/dist/lib/linx-session-work-control.js.map +1 -0
  250. package/dist/lib/linx-shell-command-executor.js +41 -0
  251. package/dist/lib/linx-shell-command-executor.js.map +1 -0
  252. package/dist/lib/linx-shell-command-router.js +151 -0
  253. package/dist/lib/linx-shell-command-router.js.map +1 -0
  254. package/dist/lib/{pi-adapter/control-state.js → linx-startup-control-state.js} +6 -6
  255. package/dist/lib/linx-startup-control-state.js.map +1 -0
  256. package/dist/lib/linx-startup-login-policy.js +48 -0
  257. package/dist/lib/linx-startup-login-policy.js.map +1 -0
  258. package/dist/lib/linx-status-line-command.js +382 -0
  259. package/dist/lib/linx-status-line-command.js.map +1 -0
  260. package/dist/lib/{status-line-command.js → linx-status-line-config-command.js} +2 -12
  261. package/dist/lib/linx-status-line-config-command.js.map +1 -0
  262. package/dist/lib/linx-status-line.js +12 -4
  263. package/dist/lib/linx-status-line.js.map +1 -1
  264. package/dist/lib/linx-stream-abort.js +12 -0
  265. package/dist/lib/linx-stream-abort.js.map +1 -0
  266. package/dist/lib/linx-stream-error-formatting.js +54 -0
  267. package/dist/lib/linx-stream-error-formatting.js.map +1 -0
  268. package/dist/lib/linx-submitted-user-message-recording.js +16 -0
  269. package/dist/lib/linx-submitted-user-message-recording.js.map +1 -0
  270. package/dist/lib/linx-symphony-interactive-command.js +493 -0
  271. package/dist/lib/linx-symphony-interactive-command.js.map +1 -0
  272. package/dist/lib/linx-terminal-title-router.js +49 -0
  273. package/dist/lib/linx-terminal-title-router.js.map +1 -0
  274. package/dist/lib/{pi-adapter/theme.js → linx-theme.js} +2 -2
  275. package/dist/lib/linx-theme.js.map +1 -0
  276. package/dist/lib/linx-top-level-command-admission.js +27 -0
  277. package/dist/lib/linx-top-level-command-admission.js.map +1 -0
  278. package/dist/lib/linx-update-notification.js +209 -0
  279. package/dist/lib/linx-update-notification.js.map +1 -0
  280. package/dist/lib/linx-welcome-header.js +148 -0
  281. package/dist/lib/linx-welcome-header.js.map +1 -0
  282. package/dist/lib/linx-workspace-command.js +45 -0
  283. package/dist/lib/linx-workspace-command.js.map +1 -0
  284. package/dist/lib/native-backend-command-router.js +25 -0
  285. package/dist/lib/native-backend-command-router.js.map +1 -0
  286. package/dist/lib/native-backend-proxy.js +2 -0
  287. package/dist/lib/native-backend-proxy.js.map +1 -0
  288. package/dist/lib/native-backend-stream-backend.js +15 -0
  289. package/dist/lib/native-backend-stream-backend.js.map +1 -0
  290. package/dist/lib/oidc-auth.js +2 -4
  291. package/dist/lib/oidc-auth.js.map +1 -1
  292. package/dist/lib/pi-adapter/runtime.js +32 -841
  293. package/dist/lib/pi-adapter/runtime.js.map +1 -1
  294. package/dist/lib/pi-adapter/stream.js +17 -516
  295. package/dist/lib/pi-adapter/stream.js.map +1 -1
  296. package/dist/lib/{pi-adapter/pod-approval.js → pod-backed-extension-ui-context.js} +2 -2
  297. package/dist/lib/pod-backed-extension-ui-context.js.map +1 -0
  298. package/dist/lib/pod-chat-store-runtime.js +38 -0
  299. package/dist/lib/pod-chat-store-runtime.js.map +1 -0
  300. package/dist/lib/pod-chat-store.js +15 -65
  301. package/dist/lib/pod-chat-store.js.map +1 -1
  302. package/dist/lib/{pi-adapter/pod-mirror-mapping.js → pod-mirror-mapping.js} +8 -18
  303. package/dist/lib/pod-mirror-mapping.js.map +1 -0
  304. package/dist/lib/{pi-adapter/pod-native.js → pod-native.js} +1 -1
  305. package/dist/lib/pod-native.js.map +1 -0
  306. package/dist/lib/pod-status-output.js.map +1 -0
  307. package/dist/lib/{pi-adapter/auto-input-controller.js → secretary-auto-input-controller.js} +62 -77
  308. package/dist/lib/secretary-auto-input-controller.js.map +1 -0
  309. package/dist/lib/{pi-adapter/session-control.js → session-control.js} +80 -29
  310. package/dist/lib/session-control.js.map +1 -0
  311. package/dist/lib/shell-lifecycle.js +199 -0
  312. package/dist/lib/shell-lifecycle.js.map +1 -0
  313. package/dist/lib/solid-local-store.js +3 -3
  314. package/dist/lib/solid-local-store.js.map +1 -1
  315. package/dist/lib/symphony/pod-projection.js +1 -31
  316. package/dist/lib/symphony/pod-projection.js.map +1 -1
  317. package/dist/lib/{symphony-command.js → symphony/run.js} +11 -16
  318. package/dist/lib/symphony/run.js.map +1 -0
  319. package/dist/lib/symphony/runtime.js +30 -0
  320. package/dist/lib/symphony/runtime.js.map +1 -0
  321. package/dist/lib/symphony/status.js +242 -0
  322. package/dist/lib/symphony/status.js.map +1 -0
  323. package/dist/linx-cli-app.js +70 -0
  324. package/dist/linx-cli-app.js.map +1 -0
  325. package/dist/linx-cli-runtime-adapter-factory.js +12 -0
  326. package/dist/linx-cli-runtime-adapter-factory.js.map +1 -0
  327. package/dist/plugins/linx-symphony-codex/.codex-plugin/plugin.json +1 -1
  328. package/package.json +1 -1
  329. package/dist/lib/auto-mode/hooks/index.js.map +0 -1
  330. package/dist/lib/auto-mode/index.js +0 -10
  331. package/dist/lib/auto-mode/index.js.map +0 -1
  332. package/dist/lib/codex-plugin/index.js +0 -5
  333. package/dist/lib/codex-plugin/index.js.map +0 -1
  334. package/dist/lib/pi-adapter/auth.js +0 -68
  335. package/dist/lib/pi-adapter/auth.js.map +0 -1
  336. package/dist/lib/pi-adapter/auto-input-controller.js.map +0 -1
  337. package/dist/lib/pi-adapter/backend-command.js +0 -2
  338. package/dist/lib/pi-adapter/backend-command.js.map +0 -1
  339. package/dist/lib/pi-adapter/backend-credentials.js.map +0 -1
  340. package/dist/lib/pi-adapter/branding.js +0 -1188
  341. package/dist/lib/pi-adapter/branding.js.map +0 -1
  342. package/dist/lib/pi-adapter/control-state.js.map +0 -1
  343. package/dist/lib/pi-adapter/index.js +0 -4
  344. package/dist/lib/pi-adapter/index.js.map +0 -1
  345. package/dist/lib/pi-adapter/interactive.js +0 -3191
  346. package/dist/lib/pi-adapter/interactive.js.map +0 -1
  347. package/dist/lib/pi-adapter/pod-approval.js.map +0 -1
  348. package/dist/lib/pi-adapter/pod-mirror-mapping.js.map +0 -1
  349. package/dist/lib/pi-adapter/pod-mirror.js.map +0 -1
  350. package/dist/lib/pi-adapter/pod-native.js.map +0 -1
  351. package/dist/lib/pi-adapter/pod-status-output.js.map +0 -1
  352. package/dist/lib/pi-adapter/pod-tools.js +0 -140
  353. package/dist/lib/pi-adapter/pod-tools.js.map +0 -1
  354. package/dist/lib/pi-adapter/session-control.js.map +0 -1
  355. package/dist/lib/pi-adapter/session.js.map +0 -1
  356. package/dist/lib/pi-adapter/sync-recovery.js.map +0 -1
  357. package/dist/lib/pi-adapter/theme.js.map +0 -1
  358. package/dist/lib/pi-adapter/web-fetch.js +0 -170
  359. package/dist/lib/pi-adapter/web-fetch.js.map +0 -1
  360. package/dist/lib/status-line-command.js.map +0 -1
  361. package/dist/lib/symphony-command.js.map +0 -1
  362. /package/dist/lib/{pi-adapter/pod-status-output.js → pod-status-output.js} +0 -0
@@ -1,3191 +0,0 @@
1
- import { InteractiveMode } from '@earendil-works/pi-coding-agent';
2
- import { AssistantMessageComponent, FooterComponent, LoginDialogComponent } from '@earendil-works/pi-coding-agent';
3
- import { Container, getKeybindings, Spacer, Text, truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';
4
- import { existsSync, statSync } from 'node:fs';
5
- import { resolve } from 'node:path';
6
- import { connectAiProviderCredential } from '../ai-command.js';
7
- import { listArchivedAutoModeSessions, runAutoMode } from '../auto-mode/runner.js';
8
- import { resolveAutoModeCommandRoute, } from '../../../vendor/agent-runtime/dist/auto-mode.js';
9
- import { getAIConfigProviderCatalog, getAIConfigProviderMetadata } from '../models.js';
10
- import { runSymphony } from '../symphony-command.js';
11
- import { applyLinxInteractiveBranding, checkAndShowLinxUpdate, requestLinxCloudLogin } from './branding.js';
12
- import { installPodStatusOutputFilter } from './pod-status-output.js';
13
- import { createPodBackedExtensionUiContext } from './pod-approval.js';
14
- import { DEFAULT_SECRETARY_CHAT_ID, secretaryChatUri, secretaryThreadUri } from './pod-mirror-mapping.js';
15
- import { getSecretaryAutoInputController } from './auto-input-controller.js';
16
- import { createSymphonyIdeaRecord, listSymphonyIssues, listSymphonySessions, } from '../symphony/archive.js';
17
- import { listOpenSymphonyIssuesFromPod, listRecentSymphonyReportsFromPod, listRunningSymphonyWorkersFromPod, mirrorSymphonyProjectionJsonLdFromPod, persistSymphonyIdeaToPod, persistSymphonyControlStateToPod, } from '../symphony/pod-projection.js';
18
- import { getSessionControlManager, installSessionControlRuntimeEventBridge, } from './session-control.js';
19
- import { buildLinxFooterStatusLine, calculateSessionUsage, DEFAULT_STATUS_LINE_TOKENS, formatTokenCount, LINX_STATUS_LINE_TOKEN_NAMES, parseLinxStatusLineColorArg, parseLinxStatusLineTokenArgs, readLinxStatusLineConfig, resetLinxStatusLineConfig, writeLinxStatusLineConfigPatch, } from '../linx-status-line.js';
20
- let footerPatched = false;
21
- let assistantMessagePatched = false;
22
- let linxResumeOutputStyleRestore = null;
23
- const BACKEND_OWNED_SLASH_COMMANDS = new Set([
24
- 'commands',
25
- 'models',
26
- 'rollback',
27
- 'status',
28
- ]);
29
- const SYMPHONY_STATUS_POD_TIMEOUT_MS = 1_200;
30
- const DEFAULT_SYMPHONY_WORKER_SUPERVISOR_INTERVAL_MS = 10 * 60 * 1000;
31
- const CODEX_STYLE_STATUS_LINE_TOKENS = [
32
- 'model-with-reasoning',
33
- 'git-branch',
34
- 'context-remaining',
35
- 'total-input-tokens',
36
- 'total-output-tokens',
37
- 'current-dir',
38
- ];
39
- const COMPACT_STATUS_LINE_TOKENS = [
40
- 'model-with-reasoning',
41
- 'context-remaining',
42
- 'current-dir',
43
- ];
44
- const STATUS_LINE_CODEX_PRESET_OPTION = 'Preset: Codex-style';
45
- const STATUS_LINE_COMPACT_PRESET_OPTION = 'Preset: Compact';
46
- const STATUS_LINE_TOGGLE_COLORS_OPTION = 'Toggle colors';
47
- const STATUS_LINE_RESET_OPTION = 'Reset to default';
48
- const STATUS_LINE_DONE_OPTION = 'Done';
49
- /** Module-level reference to interactive for footer mode state (set during bootstrap). */
50
- let _linxFooterInteractive = null;
51
- export function bootstrapLinxInteractiveMode(runtime, options = {}) {
52
- installLinxResumeOutputStyle();
53
- patchPiFooter();
54
- patchPiAssistantMessageRendering();
55
- const sessionCwd = runtime?.cwd || process.cwd();
56
- ensureInteractiveRuntimeHost(runtime);
57
- const interactive = new InteractiveMode(runtime, options);
58
- interactive.runtime = runtime;
59
- interactive.__autoEnabled = runtime?.autoEnabled === true;
60
- interactive.__linxSymphonyModeEnabled = runtime?.symphonyEnabled === true;
61
- _linxFooterInteractive = interactive;
62
- if (options.onSymphonyControlChange) {
63
- ;
64
- interactive.__linxOnSymphonyControlChange = options.onSymphonyControlChange;
65
- }
66
- const sessionControlManager = getSessionControlManager(interactive, runtime, sessionCwd);
67
- runtime?.backendCommandRouter?.setSessionControl?.(sessionControlManager);
68
- const restorePodStatusOutputFilter = installPodStatusOutputFilter();
69
- applyLinxInteractiveBranding(interactive);
70
- patchInteractiveExitMessage(interactive);
71
- patchInteractivePodStatusFilterCleanup(interactive, restorePodStatusOutputFilter);
72
- installPodBackedExtensionUi(interactive, runtime, sessionControlManager);
73
- installSymphonyAutocomplete(interactive);
74
- // Register /cd slash command; workspace follows terminal while session stays.
75
- installLinxGlobalCommands(interactive, runtime, sessionCwd, options);
76
- installSymphonyCommand(interactive);
77
- installBackendCommandRouter(interactive, runtime?.backendCommandRouter);
78
- installSessionControlRuntimeEventBridge(interactive, runtime, sessionCwd);
79
- installLinxSessionCommandRouter(interactive, runtime);
80
- installLinxSessionCommandRouterAfterRebind(interactive, runtime);
81
- if (options.restoredAuto === true && runtime?.autoEnabled === true) {
82
- installLinxRestoredAutoStartup(interactive, runtime, sessionControlManager);
83
- }
84
- installLinxInteractivePostInitHooks(interactive, runtime);
85
- const bootstrap = {
86
- async init() {
87
- await interactive.init();
88
- },
89
- async run() {
90
- await bootstrap.init();
91
- await withLinxResumeOutputStyle(() => interactive.run());
92
- },
93
- requestLogin(reason = 'manual') {
94
- requestLinxCloudLogin(interactive, reason);
95
- },
96
- async requestBackendCredential(details) {
97
- return promptForBackendCredential(interactive, details);
98
- },
99
- __unsafeInteractiveForTests: interactive,
100
- stop() {
101
- interactive.stop();
102
- },
103
- };
104
- return bootstrap;
105
- }
106
- function installLinxInteractivePostInitHooks(interactive, runtime) {
107
- if (!interactive || interactive.__linxInteractivePostInitHooksInstalled) {
108
- return;
109
- }
110
- const originalInit = interactive.init?.bind(interactive);
111
- if (typeof originalInit !== 'function') {
112
- return;
113
- }
114
- interactive.init = async function patchedLinxInteractivePostInit(...args) {
115
- if (this.__linxInteractiveInitCompleted === true) {
116
- installLinxSessionCommandRouter(this, runtime);
117
- installLinxInputCommandRouter(this, runtime);
118
- installLinxFinalSubmitCommandRouter(this, runtime);
119
- installLinxEscapeInterrupt(this);
120
- return undefined;
121
- }
122
- const result = await originalInit(...args);
123
- this.__linxInteractiveInitCompleted = true;
124
- installLinxSessionCommandRouter(this, runtime);
125
- installLinxInputCommandRouter(this, runtime);
126
- installLinxFinalSubmitCommandRouter(this, runtime);
127
- installLinxEscapeInterrupt(this);
128
- return result;
129
- };
130
- interactive.__linxInteractivePostInitHooksInstalled = true;
131
- }
132
- /** @deprecated Use bootstrapLinxInteractiveMode. */
133
- export const bootstrapPiInteractiveMode = bootstrapLinxInteractiveMode;
134
- export function installLinxRestoredAutoStartup(interactive, runtime, sessionControl = getSessionControlManager(interactive, runtime)) {
135
- if (!interactive || interactive.__linxRestoredAutoStartupInstalled) {
136
- return;
137
- }
138
- const originalInit = interactive.init?.bind(interactive);
139
- if (typeof originalInit !== 'function') {
140
- return;
141
- }
142
- interactive.init = async function patchedLinxRestoredAutoInit(...args) {
143
- const result = await originalInit(...args);
144
- if (this.__autoEnabled === true && runtime?.autoEnabled === true) {
145
- const controller = getSecretaryAutoInputController(this, runtime, sessionControl);
146
- controller.start({ scheduleImmediately: true });
147
- interactive.showStatus?.([
148
- 'Auto restored from the previous session.',
149
- 'auto · Ctrl+C or /auto off to hand control back',
150
- ].join('\n'));
151
- interactive.ui?.requestRender?.();
152
- }
153
- return result;
154
- };
155
- interactive.__linxRestoredAutoStartupInstalled = true;
156
- }
157
- function ensureInteractiveRuntimeHost(runtime) {
158
- if (!runtime || typeof runtime !== 'object') {
159
- return;
160
- }
161
- if (typeof runtime.setBeforeSessionInvalidate !== 'function') {
162
- runtime.setBeforeSessionInvalidate = (callback) => {
163
- runtime.__linxBeforeSessionInvalidate = callback;
164
- };
165
- }
166
- if (typeof runtime.setRebindSession !== 'function') {
167
- runtime.setRebindSession = (callback) => {
168
- runtime.__linxRebindSession = callback;
169
- };
170
- }
171
- }
172
- function patchInteractivePodStatusFilterCleanup(interactive, restore) {
173
- const originalStop = interactive.stop?.bind(interactive);
174
- if (typeof originalStop !== 'function') {
175
- return;
176
- }
177
- interactive.stop = function patchedStopWithPodStatusCleanup(...args) {
178
- try {
179
- originalStop(...args);
180
- }
181
- finally {
182
- restore();
183
- }
184
- };
185
- }
186
- export function installPodBackedExtensionUi(interactive, runtime, sessionControl = getSessionControlManager(interactive, runtime)) {
187
- if (interactive.__linxPodBackedExtensionUiInstalled) {
188
- return;
189
- }
190
- const originalCreate = interactive.createExtensionUIContext?.bind(interactive);
191
- if (typeof originalCreate !== 'function') {
192
- return;
193
- }
194
- interactive.createExtensionUIContext = function patchedCreateExtensionUIContext(...args) {
195
- const baseUi = originalCreate(...args);
196
- if (!baseUi || typeof baseUi !== 'object') {
197
- return baseUi;
198
- }
199
- return createPodBackedExtensionUiContext(baseUi, {
200
- cwd: interactive?.session?.cwd ?? runtime?.cwd ?? process.cwd(),
201
- sessionId: () => interactive?.sessionManager?.getSessionId?.()
202
- ?? interactive?.session?.sessionManager?.getSessionId?.()
203
- ?? interactive?.session?.sessionId,
204
- sessionControl,
205
- onWarning(error) {
206
- const message = error instanceof Error ? error.message : String(error);
207
- interactive.showWarning?.(`Pod approval sync unavailable: ${message}`);
208
- },
209
- });
210
- };
211
- interactive.__linxPodBackedExtensionUiInstalled = true;
212
- }
213
- export function installBackendCommandRouter(interactive, router) {
214
- if (!router) {
215
- return;
216
- }
217
- interactive.__linxHandleProjectedBackendCommand = async (text) => {
218
- const command = text.trim();
219
- if (!shouldRouteToBackendCommand(command)) {
220
- return false;
221
- }
222
- let routed;
223
- try {
224
- routed = await router.execute(command);
225
- }
226
- catch (error) {
227
- const message = error instanceof Error ? error.message : String(error);
228
- interactive.showError?.(`${router.backend} command failed: ${message}`);
229
- return true;
230
- }
231
- if (!routed.handled) {
232
- return false;
233
- }
234
- if (routed.message) {
235
- interactive.showStatus?.(routed.message);
236
- }
237
- interactive.ui?.requestRender?.();
238
- return true;
239
- };
240
- installProjectedCommandRouter(interactive);
241
- const originalSetup = interactive.setupEditorSubmitHandler?.bind(interactive);
242
- if (typeof originalSetup !== 'function') {
243
- return;
244
- }
245
- interactive.setupEditorSubmitHandler = function patchedBackendCommandSetupEditorSubmitHandler(...args) {
246
- const result = originalSetup(...args);
247
- const originalSubmit = this.defaultEditor?.onSubmit?.bind(this.defaultEditor);
248
- if (typeof originalSubmit !== 'function') {
249
- return result;
250
- }
251
- this.defaultEditor.onSubmit = async (text) => {
252
- const command = text.trim();
253
- if (!shouldRouteToBackendCommand(command)) {
254
- await originalSubmit(text);
255
- return;
256
- }
257
- let routed;
258
- try {
259
- routed = await router.execute(command);
260
- }
261
- catch (error) {
262
- const message = error instanceof Error ? error.message : String(error);
263
- this.showError?.(`${router.backend} command failed: ${message}`);
264
- return;
265
- }
266
- if (!routed.handled) {
267
- await originalSubmit(text);
268
- return;
269
- }
270
- if (routed.clearInput !== false) {
271
- this.editor?.setText?.('');
272
- }
273
- if (routed.message) {
274
- this.showStatus?.(routed.message);
275
- }
276
- this.ui?.requestRender?.();
277
- };
278
- return result;
279
- };
280
- }
281
- function shouldRouteToBackendCommand(command) {
282
- if (!command.startsWith('/')) {
283
- return false;
284
- }
285
- const name = command.slice(1).split(/\s+/, 1)[0]?.toLowerCase();
286
- if (!name) {
287
- return false;
288
- }
289
- return BACKEND_OWNED_SLASH_COMMANDS.has(name);
290
- }
291
- export function installLinxGlobalCommands(interactive, runtime, sessionCwd, options = {}) {
292
- installLinxCwdStartupNotice(interactive, sessionCwd);
293
- installLinxAutoEditorIndicator(interactive);
294
- if (options.onAutoControlChange) {
295
- interactive.__linxOnAutoControlChange = options.onAutoControlChange;
296
- }
297
- installLinxGlobalCommandHandler(interactive, runtime);
298
- }
299
- export function installLinxAutoEditorIndicator(interactive) {
300
- if (!interactive || interactive.__linxAutoEditorIndicatorInstalled) {
301
- return;
302
- }
303
- decorateLinxAutoEditorRender(interactive.defaultEditor, interactive);
304
- if (interactive.editor && interactive.editor !== interactive.defaultEditor) {
305
- decorateLinxAutoEditorRender(interactive.editor, interactive);
306
- }
307
- const originalSetCustomEditorComponent = interactive.setCustomEditorComponent?.bind(interactive);
308
- if (typeof originalSetCustomEditorComponent === 'function') {
309
- interactive.setCustomEditorComponent = function patchedSetCustomEditorComponent(...args) {
310
- const result = originalSetCustomEditorComponent(...args);
311
- decorateLinxAutoEditorRender(this.defaultEditor, this);
312
- if (this.editor && this.editor !== this.defaultEditor) {
313
- decorateLinxAutoEditorRender(this.editor, this);
314
- }
315
- return result;
316
- };
317
- }
318
- interactive.__linxAutoEditorIndicatorInstalled = true;
319
- }
320
- function decorateLinxAutoEditorRender(editor, interactive) {
321
- if (!editor || editor.__linxAutoEditorIndicatorRenderInstalled || typeof editor.render !== 'function') {
322
- return;
323
- }
324
- const originalRender = editor.render.bind(editor);
325
- editor.render = function linxAutoEditorIndicatorRender(width) {
326
- const lines = originalRender(width);
327
- if (interactive.__autoEnabled !== true) {
328
- return lines;
329
- }
330
- return decorateLinxAutoEditorLines(lines, width);
331
- };
332
- editor.__linxAutoEditorIndicatorRenderInstalled = true;
333
- }
334
- function decorateLinxAutoEditorLines(lines, width) {
335
- const rendered = Array.isArray(lines) ? [...lines] : [];
336
- const indicator = buildLinxAutoEditorIndicatorLine(width);
337
- if (rendered.length === 0) {
338
- return [indicator];
339
- }
340
- rendered[0] = indicator;
341
- return rendered;
342
- }
343
- export function buildLinxAutoEditorIndicatorLine(width) {
344
- if (width <= 0) {
345
- return '';
346
- }
347
- const label = ' 托管中 · Secretary 自动输入 · Ctrl+C 接管 · /auto off ';
348
- const fitted = truncateToWidth(label, width);
349
- const padded = fitted + ' '.repeat(Math.max(0, width - visibleWidth(fitted)));
350
- return `\x1b[1m\x1b[38;5;230m\x1b[48;5;58m${padded}\x1b[0m`;
351
- }
352
- function installLinxGlobalCommandHandler(interactive, runtime) {
353
- if (interactive.__linxGlobalCommandHandlerInstalled) {
354
- return;
355
- }
356
- const originalSetup = interactive.setupEditorSubmitHandler?.bind(interactive);
357
- if (typeof originalSetup !== 'function') {
358
- return;
359
- }
360
- interactive.setupEditorSubmitHandler = function patchedLinxGlobalSetupEditorSubmitHandler(...args) {
361
- const result = originalSetup(...args);
362
- const originalSubmit = this.defaultEditor?.onSubmit?.bind(this.defaultEditor);
363
- if (typeof originalSubmit !== 'function') {
364
- return result;
365
- }
366
- this.defaultEditor.onSubmit = async (text) => {
367
- const command = parseLinxGlobalCommand(text.trim());
368
- if (!command) {
369
- recordSubmittedUserMessage(this, runtime, text);
370
- await originalSubmit(text);
371
- return;
372
- }
373
- this.editor?.setText?.('');
374
- await handleLinxGlobalCommand(this, runtime, command);
375
- };
376
- return result;
377
- };
378
- interactive.__linxGlobalCommandHandlerInstalled = true;
379
- interactive.__linxHandleProjectedGlobalCommand = async (text) => {
380
- const command = parseLinxGlobalCommand(text.trim());
381
- if (!command) {
382
- return false;
383
- }
384
- await handleLinxGlobalCommand(interactive, runtime, command);
385
- if (command.action === 'peer-command') {
386
- return 'peer-command';
387
- }
388
- return true;
389
- };
390
- installProjectedCommandRouter(interactive);
391
- }
392
- export function installLinxInputCommandRouter(interactive, runtime) {
393
- if (!interactive || interactive.__linxInputCommandRouterInstalled) {
394
- return;
395
- }
396
- const originalGetUserInput = interactive.getUserInput?.bind(interactive);
397
- if (typeof originalGetUserInput !== 'function') {
398
- return;
399
- }
400
- interactive.getUserInput = async function patchedLinxGetUserInput(...args) {
401
- while (true) {
402
- const input = await originalGetUserInput(...args);
403
- if (typeof input !== 'string') {
404
- return input;
405
- }
406
- const command = parseLinxGlobalCommand(input.trim());
407
- if (!command) {
408
- return input;
409
- }
410
- this.editor?.setText?.('');
411
- await handleLinxGlobalCommand(this, runtime, command);
412
- }
413
- };
414
- interactive.__linxInputCommandRouterInstalled = true;
415
- }
416
- export function installLinxFinalSubmitCommandRouter(interactive, runtime) {
417
- if (!interactive) {
418
- return;
419
- }
420
- const wrapEditor = (editor) => {
421
- if (!editor || typeof editor.onSubmit !== 'function') {
422
- return;
423
- }
424
- if (editor.onSubmit.__linxFinalSubmitCommandRouterWrapped === true) {
425
- return;
426
- }
427
- const originalSubmit = editor.onSubmit.bind(editor);
428
- const wrappedSubmit = async (text) => {
429
- const command = parseLinxGlobalCommand(String(text ?? '').trim());
430
- if (!command) {
431
- await originalSubmit(text);
432
- return;
433
- }
434
- interactive.editor?.setText?.('');
435
- await handleLinxGlobalCommand(interactive, runtime, command);
436
- };
437
- wrappedSubmit.__linxFinalSubmitCommandRouterWrapped = true;
438
- editor.onSubmit = wrappedSubmit;
439
- };
440
- wrapEditor(interactive.defaultEditor);
441
- if (interactive.editor !== interactive.defaultEditor) {
442
- wrapEditor(interactive.editor);
443
- }
444
- const originalSetCustomEditorComponent = interactive.setCustomEditorComponent?.bind(interactive);
445
- if (typeof originalSetCustomEditorComponent === 'function'
446
- && interactive.__linxFinalSubmitSetCustomEditorComponentPatched !== true) {
447
- interactive.setCustomEditorComponent = function patchedLinxFinalSubmitSetCustomEditorComponent(...args) {
448
- const result = originalSetCustomEditorComponent(...args);
449
- wrapEditor(this.defaultEditor);
450
- if (this.editor !== this.defaultEditor) {
451
- wrapEditor(this.editor);
452
- }
453
- return result;
454
- };
455
- interactive.__linxFinalSubmitSetCustomEditorComponentPatched = true;
456
- }
457
- interactive.__linxFinalSubmitCommandRouterInstalled = true;
458
- }
459
- export function installLinxSessionCommandRouter(interactive, runtime) {
460
- const session = interactive?.session ?? runtime?.session;
461
- if (!session || typeof session !== 'object' || session.__linxSessionCommandRouterInstalled === true) {
462
- return;
463
- }
464
- const originalPrompt = typeof session.prompt === 'function'
465
- ? session.prompt.bind(session)
466
- : undefined;
467
- const originalSendUserMessage = typeof session.sendUserMessage === 'function'
468
- ? session.sendUserMessage.bind(session)
469
- : undefined;
470
- if (!originalPrompt && !originalSendUserMessage) {
471
- return;
472
- }
473
- if (originalPrompt) {
474
- session.__linxPromptWithoutCommandRouting = originalPrompt;
475
- session.prompt = async (text, ...args) => {
476
- if (await maybeHandleLinxSessionCommand(interactive, runtime, text)) {
477
- return undefined;
478
- }
479
- return originalPrompt(text, ...args);
480
- };
481
- }
482
- if (originalSendUserMessage) {
483
- session.__linxSendUserMessageWithoutCommandRouting = originalSendUserMessage;
484
- session.sendUserMessage = async (text, ...args) => {
485
- if (await maybeHandleLinxSessionCommand(interactive, runtime, text)) {
486
- return undefined;
487
- }
488
- return originalSendUserMessage(text, ...args);
489
- };
490
- }
491
- session.__linxSessionCommandRouterInstalled = true;
492
- }
493
- function installLinxSessionCommandRouterAfterRebind(interactive, runtime) {
494
- if (!interactive || interactive.__linxSessionCommandRouterAfterRebindInstalled === true) {
495
- return;
496
- }
497
- const originalRebind = interactive.rebindCurrentSession?.bind(interactive);
498
- if (typeof originalRebind !== 'function') {
499
- return;
500
- }
501
- interactive.rebindCurrentSession = async function patchedLinxRebindCurrentSession(...args) {
502
- const result = await originalRebind(...args);
503
- installLinxSessionCommandRouter(this, runtime);
504
- return result;
505
- };
506
- interactive.__linxSessionCommandRouterAfterRebindInstalled = true;
507
- }
508
- async function maybeHandleLinxSessionCommand(interactive, runtime, text) {
509
- if (typeof text !== 'string') {
510
- return false;
511
- }
512
- const command = parseLinxGlobalCommand(text.trim());
513
- if (!command) {
514
- return false;
515
- }
516
- interactive.editor?.setText?.('');
517
- await handleLinxGlobalCommand(interactive, runtime, command);
518
- return true;
519
- }
520
- function recordSubmittedUserMessage(interactive, runtime, text) {
521
- const input = text.trim();
522
- if (!input || input.startsWith('/')) {
523
- return;
524
- }
525
- try {
526
- getSessionControlManager(interactive, runtime).recordUserMessage({ text: input });
527
- }
528
- catch (error) {
529
- const message = error instanceof Error ? error.message : String(error);
530
- interactive.showWarning?.(`Thread reconciliation unavailable: ${message}`);
531
- }
532
- }
533
- function parseLinxGlobalCommand(input) {
534
- const autoModeRoute = resolveAutoModeCommandRoute(input);
535
- if (autoModeRoute?.kind === 'control-command') {
536
- return { action: 'auto', route: autoModeRoute };
537
- }
538
- if (autoModeRoute?.kind === 'peer-command') {
539
- return { action: 'peer-command', route: autoModeRoute };
540
- }
541
- if (input === '/cd') {
542
- return { action: 'cd' };
543
- }
544
- if (input.startsWith('/cd ')) {
545
- return { action: 'cd', target: input.slice('/cd'.length).trim() };
546
- }
547
- if (input === '/ai connect') {
548
- return { action: 'ai-connect' };
549
- }
550
- if (input.startsWith('/ai connect ')) {
551
- return { action: 'ai-connect', ...parseInteractiveAiConnectArgs(input.slice('/ai connect'.length).trim()) };
552
- }
553
- if (input === '/statusline' || input === '/status-line') {
554
- return { action: 'statusline', args: [] };
555
- }
556
- if (input.startsWith('/statusline ') || input.startsWith('/status-line ')) {
557
- const body = input.startsWith('/statusline ')
558
- ? input.slice('/statusline'.length).trim()
559
- : input.slice('/status-line'.length).trim();
560
- return { action: 'statusline', args: splitInteractiveCommandArgs(body) };
561
- }
562
- if (input === '/update' || input === '/upgrade') {
563
- return { action: 'update' };
564
- }
565
- if (input === '/rewind') {
566
- return { action: 'rewind-select' };
567
- }
568
- if (input.startsWith('/rewind ')) {
569
- const turns = parseRewindTurnCount(input.slice('/rewind'.length).trim());
570
- return { action: 'rewind-turns', turns: turns ?? 0 };
571
- }
572
- return null;
573
- }
574
- function parseRewindTurnCount(input) {
575
- if (!/^\d+$/.test(input)) {
576
- return null;
577
- }
578
- const value = Number.parseInt(input, 10);
579
- return Number.isSafeInteger(value) && value > 0 ? value : null;
580
- }
581
- function parseInteractiveAiConnectArgs(input) {
582
- const tokens = splitInteractiveCommandArgs(input);
583
- let provider;
584
- let baseUrl;
585
- let model;
586
- for (let index = 0; index < tokens.length; index += 1) {
587
- const token = tokens[index];
588
- if (!token) {
589
- continue;
590
- }
591
- if (token === '--base-url') {
592
- baseUrl = tokens[index + 1];
593
- index += 1;
594
- continue;
595
- }
596
- if (token.startsWith('--base-url=')) {
597
- baseUrl = token.slice('--base-url='.length);
598
- continue;
599
- }
600
- if (token === '--model') {
601
- model = tokens[index + 1];
602
- index += 1;
603
- continue;
604
- }
605
- if (token.startsWith('--model=')) {
606
- model = token.slice('--model='.length);
607
- continue;
608
- }
609
- if (!token.startsWith('-') && !provider) {
610
- provider = token;
611
- }
612
- }
613
- return {
614
- ...(provider?.trim() ? { provider: provider.trim() } : {}),
615
- ...(baseUrl?.trim() ? { baseUrl: baseUrl.trim() } : {}),
616
- ...(model?.trim() ? { model: model.trim() } : {}),
617
- };
618
- }
619
- function splitInteractiveCommandArgs(input) {
620
- const tokens = [];
621
- let current = '';
622
- let quote = null;
623
- let escaping = false;
624
- for (const char of input) {
625
- if (escaping) {
626
- current += char;
627
- escaping = false;
628
- continue;
629
- }
630
- if (char === '\\') {
631
- escaping = true;
632
- continue;
633
- }
634
- if (quote) {
635
- if (char === quote) {
636
- quote = null;
637
- }
638
- else {
639
- current += char;
640
- }
641
- continue;
642
- }
643
- if (char === '"' || char === "'") {
644
- quote = char;
645
- continue;
646
- }
647
- if (/\s/.test(char)) {
648
- if (current) {
649
- tokens.push(current);
650
- current = '';
651
- }
652
- continue;
653
- }
654
- current += char;
655
- }
656
- if (escaping) {
657
- current += '\\';
658
- }
659
- if (current) {
660
- tokens.push(current);
661
- }
662
- return tokens;
663
- }
664
- async function handleLinxGlobalCommand(interactive, runtime, command) {
665
- if (command.action === 'auto') {
666
- const auto = command.route.auto;
667
- const enabled = auto?.action === 'set' ? auto.enabled : undefined;
668
- const initialInput = auto?.action === 'set' ? auto.initialInput : undefined;
669
- await handleInteractiveAutoCommand(interactive, runtime, enabled, {
670
- scheduleImmediately: initialInput === undefined,
671
- });
672
- if (initialInput) {
673
- const controller = getSecretaryAutoInputController(interactive, runtime, getSessionControlManager(interactive, runtime));
674
- await controller.submit(initialInput, { reason: 'auto-on' });
675
- }
676
- return;
677
- }
678
- if (command.action === 'peer-command') {
679
- await handleInteractivePeerCommand(interactive, runtime, command.route);
680
- return;
681
- }
682
- if (command.action === 'ai-connect') {
683
- await handleInteractiveAiConnectCommand(interactive, runtime, command);
684
- return;
685
- }
686
- if (command.action === 'statusline') {
687
- await handleInteractiveStatusLineCommand(interactive, command.args);
688
- return;
689
- }
690
- if (command.action === 'update') {
691
- await checkAndShowLinxUpdate(interactive, { manual: true });
692
- return;
693
- }
694
- if (command.action === 'rewind-select') {
695
- await handleInteractiveRewindSelector(interactive, runtime);
696
- return;
697
- }
698
- if (command.action === 'rewind-turns') {
699
- await handleInteractiveRewindTurnsCommand(interactive, runtime, command.turns);
700
- return;
701
- }
702
- await changeInteractiveCwd(interactive, runtime, command.target);
703
- }
704
- async function handleInteractiveStatusLineCommand(interactive, args) {
705
- if (args.length > 0) {
706
- handleInteractiveStatusLineArgs(interactive, args);
707
- return;
708
- }
709
- const summary = formatInteractiveStatusLineSummary();
710
- if (typeof interactive.showSelector === 'function') {
711
- await showInteractiveStatusLineMultiSelect(interactive);
712
- return;
713
- }
714
- if (typeof interactive.showExtensionSelector === 'function') {
715
- await showInteractiveStatusLineFallbackSelector(interactive);
716
- return;
717
- }
718
- {
719
- interactive.showStatus?.(`${summary} · Use /statusline set <tokens...>, /statusline tokens, /statusline colors <on|off>, or /statusline reset.`);
720
- interactive.ui?.requestRender?.();
721
- return;
722
- }
723
- }
724
- async function showInteractiveStatusLineFallbackSelector(interactive) {
725
- while (true) {
726
- const currentSummary = formatInteractiveStatusLineSummary();
727
- const config = readLinxStatusLineConfig();
728
- const options = buildInteractiveStatusLineOptions(config);
729
- const choice = await interactive.showExtensionSelector(`Status line\n${currentSummary}`, options);
730
- if (!choice || choice === STATUS_LINE_DONE_OPTION) {
731
- return;
732
- }
733
- const token = parseInteractiveStatusLineTokenChoice(choice);
734
- if (token) {
735
- toggleInteractiveStatusLineToken(interactive, token);
736
- continue;
737
- }
738
- if (choice === STATUS_LINE_CODEX_PRESET_OPTION) {
739
- writeInteractiveStatusLineConfig(interactive, {
740
- statusLine: CODEX_STYLE_STATUS_LINE_TOKENS,
741
- message: 'Status line set to Codex-style preset.',
742
- });
743
- continue;
744
- }
745
- if (choice === STATUS_LINE_COMPACT_PRESET_OPTION) {
746
- writeInteractiveStatusLineConfig(interactive, {
747
- statusLine: COMPACT_STATUS_LINE_TOKENS,
748
- message: 'Status line set to compact preset.',
749
- });
750
- continue;
751
- }
752
- if (choice === STATUS_LINE_TOGGLE_COLORS_OPTION) {
753
- const current = readLinxStatusLineConfig();
754
- writeInteractiveStatusLineConfig(interactive, {
755
- statusLineUseColors: !current.useColors,
756
- message: `Status line colors ${current.useColors ? 'disabled' : 'enabled'}.`,
757
- });
758
- continue;
759
- }
760
- if (choice === STATUS_LINE_RESET_OPTION) {
761
- resetLinxStatusLineConfig();
762
- finishInteractiveStatusLineUpdate(interactive, `Status line reset to default: ${DEFAULT_STATUS_LINE_TOKENS.join(', ')}`);
763
- }
764
- }
765
- }
766
- async function showInteractiveStatusLineMultiSelect(interactive) {
767
- await new Promise((resolvePromise, rejectPromise) => {
768
- let resolved = false;
769
- const resolveOnce = () => {
770
- if (!resolved) {
771
- resolved = true;
772
- resolvePromise();
773
- }
774
- };
775
- try {
776
- interactive.showSelector((done) => {
777
- const close = () => {
778
- done();
779
- resolveOnce();
780
- };
781
- const selector = new LinxStatusLineSelectorComponent(readLinxStatusLineConfig(), ({ tokens, useColors }) => {
782
- writeInteractiveStatusLineConfig(interactive, {
783
- statusLine: tokens,
784
- statusLineUseColors: useColors,
785
- message: `Status line updated: ${tokens.join(', ')}`,
786
- });
787
- close();
788
- }, () => {
789
- close();
790
- interactive.ui?.requestRender?.();
791
- });
792
- return { component: selector, focus: selector.getList() };
793
- });
794
- }
795
- catch (error) {
796
- rejectPromise(error);
797
- }
798
- });
799
- }
800
- function buildInteractiveStatusLineOptions(config = readLinxStatusLineConfig()) {
801
- const enabled = new Set(config.tokens);
802
- return [
803
- ...LINX_STATUS_LINE_TOKEN_NAMES.map((token) => `${enabled.has(token) ? '✓' : '○'} ${token}`),
804
- STATUS_LINE_CODEX_PRESET_OPTION,
805
- STATUS_LINE_COMPACT_PRESET_OPTION,
806
- STATUS_LINE_TOGGLE_COLORS_OPTION,
807
- STATUS_LINE_RESET_OPTION,
808
- STATUS_LINE_DONE_OPTION,
809
- ];
810
- }
811
- function parseInteractiveStatusLineTokenChoice(choice) {
812
- if (typeof choice !== 'string') {
813
- return null;
814
- }
815
- const token = choice.replace(/^[✓○]\s*/u, '').trim();
816
- if (!token) {
817
- return null;
818
- }
819
- return LINX_STATUS_LINE_TOKEN_NAMES.includes(token)
820
- ? token
821
- : null;
822
- }
823
- function toggleInteractiveStatusLineToken(interactive, token) {
824
- const current = readLinxStatusLineConfig().tokens;
825
- const exists = current.includes(token);
826
- if (exists && current.length <= 1) {
827
- interactive.showError?.('Status line needs at least one item.');
828
- return;
829
- }
830
- const next = exists
831
- ? current.filter((item) => item !== token)
832
- : [...current, token];
833
- writeInteractiveStatusLineConfig(interactive, {
834
- statusLine: next,
835
- message: `Status line ${exists ? 'removed' : 'added'}: ${token}`,
836
- });
837
- }
838
- function handleInteractiveStatusLineArgs(interactive, args) {
839
- const action = args[0]?.toLowerCase();
840
- if (action === 'tokens' || action === 'list') {
841
- showInteractiveStatusLineTokens(interactive);
842
- return;
843
- }
844
- if (action === 'reset') {
845
- resetLinxStatusLineConfig();
846
- finishInteractiveStatusLineUpdate(interactive, `Status line reset to default: ${DEFAULT_STATUS_LINE_TOKENS.join(', ')}`);
847
- return;
848
- }
849
- if (action === 'colors' || action === 'color') {
850
- const value = parseLinxStatusLineColorArg(args[1]);
851
- if (value === undefined) {
852
- interactive.showError?.('Usage: /statusline colors <on|off>');
853
- return;
854
- }
855
- writeInteractiveStatusLineConfig(interactive, {
856
- statusLineUseColors: value,
857
- message: `Status line colors ${value ? 'enabled' : 'disabled'}.`,
858
- });
859
- return;
860
- }
861
- const tokenArgs = action === 'set' ? args.slice(1) : args;
862
- try {
863
- const tokens = parseLinxStatusLineTokenArgs(tokenArgs);
864
- writeInteractiveStatusLineConfig(interactive, {
865
- statusLine: tokens,
866
- message: `Status line updated: ${tokens.join(', ')}`,
867
- });
868
- }
869
- catch (error) {
870
- const message = error instanceof Error ? error.message : String(error);
871
- interactive.showError?.(`${message}. Use /statusline tokens to list valid tokens.`);
872
- }
873
- }
874
- function writeInteractiveStatusLineConfig(interactive, patch) {
875
- writeLinxStatusLineConfigPatch({
876
- ...(patch.statusLine ? { statusLine: patch.statusLine } : {}),
877
- ...(patch.statusLineUseColors !== undefined ? { statusLineUseColors: patch.statusLineUseColors } : {}),
878
- });
879
- finishInteractiveStatusLineUpdate(interactive, patch.message);
880
- }
881
- function finishInteractiveStatusLineUpdate(interactive, message) {
882
- interactive.footer?.invalidate?.();
883
- interactive.showStatus?.(message);
884
- interactive.ui?.requestRender?.();
885
- }
886
- function showInteractiveStatusLineTokens(interactive) {
887
- interactive.showStatus?.(`Status line tokens: ${LINX_STATUS_LINE_TOKEN_NAMES.join(', ')}`);
888
- interactive.ui?.requestRender?.();
889
- }
890
- function formatInteractiveStatusLineSummary() {
891
- const config = readLinxStatusLineConfig();
892
- return `Current: ${config.tokens.join(', ')} · colors ${config.useColors ? 'on' : 'off'} · source ${config.tokenSource}`;
893
- }
894
- async function handleInteractiveRewindSelector(interactive, runtime) {
895
- const session = resolveInteractiveSession(interactive, runtime);
896
- const sessionManager = resolveInteractiveSessionManager(interactive, runtime);
897
- if (!sessionManager) {
898
- interactive.showError?.('Cannot rewind: no active LinX session history.');
899
- interactive.ui?.requestRender?.();
900
- return;
901
- }
902
- if (typeof interactive.showSelector !== 'function') {
903
- await handleInteractiveRewindTurnsCommand(interactive, runtime, 1);
904
- return;
905
- }
906
- const userMessages = collectRewindUserMessages(session, sessionManager);
907
- if (userMessages.length === 0) {
908
- interactive.showStatus?.('Nothing to rewind: no user turns in the active branch.');
909
- interactive.ui?.requestRender?.();
910
- return;
911
- }
912
- const initialSelectedId = userMessages[userMessages.length - 1]?.id;
913
- interactive.showSelector((done) => {
914
- const selector = new LinxRewindMessageSelectorComponent(userMessages, async (entryId) => {
915
- try {
916
- await rewindSessionManagerBeforeUserEntry(interactive, runtime, session, sessionManager, entryId);
917
- done();
918
- }
919
- catch (error) {
920
- done();
921
- interactive.showError?.(error instanceof Error ? error.message : String(error));
922
- }
923
- }, () => {
924
- done();
925
- interactive.ui?.requestRender?.();
926
- }, initialSelectedId);
927
- return { component: selector, focus: selector.getMessageList() };
928
- });
929
- }
930
- async function handleInteractiveRewindTurnsCommand(interactive, runtime, turns) {
931
- if (!Number.isSafeInteger(turns) || turns <= 0) {
932
- interactive.showStatus?.('Usage: /rewind [turns] where turns is a positive integer.');
933
- interactive.ui?.requestRender?.();
934
- return;
935
- }
936
- const session = resolveInteractiveSession(interactive, runtime);
937
- const sessionManager = resolveInteractiveSessionManager(interactive, runtime);
938
- if (!sessionManager) {
939
- interactive.showError?.('Cannot rewind: no active LinX session history.');
940
- interactive.ui?.requestRender?.();
941
- return;
942
- }
943
- await stopActiveSessionWorkForRewind(session);
944
- resetPendingAutoInputForRewind(interactive, runtime);
945
- const previousState = captureRewindSessionState(sessionManager);
946
- const previousBranch = getActiveSessionBranch(sessionManager);
947
- const result = rewindSessionManagerByTurns(sessionManager, turns);
948
- if (result.rewound === 0) {
949
- interactive.showStatus?.('Nothing to rewind: no user turns in the active branch.');
950
- interactive.ui?.requestRender?.();
951
- return;
952
- }
953
- const cleanResult = materializeCleanRewindSession(sessionManager, result.targetLeafId, previousState);
954
- syncAgentStateFromSessionManager(session, sessionManager);
955
- refreshInteractiveTranscriptFromSessionManager(interactive);
956
- await syncRewindProjection(interactive, runtime, {
957
- previousState,
958
- cleanResult,
959
- abandonedEntries: collectAbandonedRewindEntries(previousBranch, result.targetLeafId),
960
- });
961
- const remainingMessages = Array.isArray(session?.agent?.state?.messages)
962
- ? session.agent.state.messages.length
963
- : undefined;
964
- const target = describeRewindTarget(result.targetLeafId, cleanResult);
965
- const suffix = remainingMessages === undefined ? '' : ` Active context now has ${remainingMessages} message${remainingMessages === 1 ? '' : 's'}.`;
966
- interactive.showStatus?.(`Rewound ${result.rewound} turn${result.rewound === 1 ? '' : 's'} to ${target}.${suffix}`);
967
- interactive.ui?.requestRender?.();
968
- }
969
- async function rewindSessionManagerBeforeUserEntry(interactive, runtime, session, sessionManager, entryId) {
970
- const entry = typeof sessionManager?.getEntry === 'function'
971
- ? sessionManager.getEntry(entryId)
972
- : getActiveSessionBranch(sessionManager).find((candidate) => candidate?.id === entryId);
973
- if (!entry || entry.type !== 'message' || entry.message?.role !== 'user') {
974
- throw new Error('Cannot rewind: selected message is not a user turn in the active branch.');
975
- }
976
- const previousState = captureRewindSessionState(sessionManager);
977
- const previousBranch = getActiveSessionBranch(sessionManager);
978
- await stopActiveSessionWorkForRewind(session);
979
- resetPendingAutoInputForRewind(interactive, runtime);
980
- const targetLeafId = typeof entry.parentId === 'string' && entry.parentId ? entry.parentId : null;
981
- moveSessionManagerLeaf(sessionManager, targetLeafId);
982
- const cleanResult = materializeCleanRewindSession(sessionManager, targetLeafId, previousState);
983
- syncAgentStateFromSessionManager(session, sessionManager);
984
- refreshInteractiveTranscriptFromSessionManager(interactive);
985
- await syncRewindProjection(interactive, runtime, {
986
- previousState,
987
- cleanResult,
988
- abandonedEntries: collectAbandonedRewindEntries(previousBranch, targetLeafId),
989
- });
990
- const remainingMessages = Array.isArray(session?.agent?.state?.messages)
991
- ? session.agent.state.messages.length
992
- : undefined;
993
- const target = describeRewindTarget(targetLeafId, cleanResult);
994
- const suffix = remainingMessages === undefined ? '' : ` Active context now has ${remainingMessages} message${remainingMessages === 1 ? '' : 's'}.`;
995
- interactive.showStatus?.(`Rewound to before selected message at ${target}.${suffix}`);
996
- interactive.ui?.requestRender?.();
997
- }
998
- function resolveInteractiveSession(interactive, runtime) {
999
- return interactive?.session ?? runtime?.session;
1000
- }
1001
- function resolveInteractiveSessionManager(interactive, runtime) {
1002
- return interactive?.session?.sessionManager
1003
- ?? interactive?.sessionManager
1004
- ?? runtime?.session?.sessionManager
1005
- ?? runtime?.sessionManager;
1006
- }
1007
- async function stopActiveSessionWorkForRewind(session) {
1008
- if (!session) {
1009
- return;
1010
- }
1011
- const shouldWait = session.isStreaming === true || session.isBashRunning === true;
1012
- try {
1013
- if (session.isBashRunning === true && typeof session.abortBash === 'function') {
1014
- session.abortBash();
1015
- }
1016
- if (session.isStreaming === true && typeof session.abort === 'function') {
1017
- session.abort();
1018
- }
1019
- }
1020
- catch {
1021
- // Rewind should still repair the active branch even if abort reporting fails.
1022
- }
1023
- if (!shouldWait || typeof session.agent?.waitForIdle !== 'function') {
1024
- return;
1025
- }
1026
- await Promise.race([
1027
- Promise.resolve(session.agent.waitForIdle()).catch(() => undefined),
1028
- new Promise((resolve) => setTimeout(resolve, 1_500)),
1029
- ]);
1030
- }
1031
- function resetPendingAutoInputForRewind(interactive, runtime) {
1032
- if (interactive?.__autoEnabled !== true || !interactive?.__linxAutoInputController) {
1033
- return;
1034
- }
1035
- try {
1036
- interactive.__linxAutoInputController.stop();
1037
- interactive.__linxAutoInputController.start({ scheduleImmediately: false });
1038
- }
1039
- catch (error) {
1040
- const message = error instanceof Error ? error.message : String(error);
1041
- interactive.showWarning?.(`Auto input reset after rewind failed: ${message}`);
1042
- }
1043
- if (runtime && typeof runtime === 'object') {
1044
- runtime.autoEnabled = true;
1045
- }
1046
- }
1047
- function rewindSessionManagerByTurns(sessionManager, turns) {
1048
- let rewound = 0;
1049
- let targetLeafId = resolveSessionManagerLeafId(sessionManager);
1050
- for (; rewound < turns; rewound += 1) {
1051
- const branch = getActiveSessionBranch(sessionManager);
1052
- const latestUserEntry = findLatestUserMessageEntry(branch);
1053
- if (!latestUserEntry) {
1054
- break;
1055
- }
1056
- targetLeafId = typeof latestUserEntry.parentId === 'string' && latestUserEntry.parentId
1057
- ? latestUserEntry.parentId
1058
- : null;
1059
- moveSessionManagerLeaf(sessionManager, targetLeafId);
1060
- }
1061
- return { rewound, targetLeafId };
1062
- }
1063
- function resolveSessionManagerLeafId(sessionManager) {
1064
- return typeof sessionManager?.getLeafId === 'function'
1065
- ? sessionManager.getLeafId()
1066
- : null;
1067
- }
1068
- function getActiveSessionBranch(sessionManager) {
1069
- const hasLeafApi = typeof sessionManager?.getLeafId === 'function';
1070
- const leafId = hasLeafApi ? sessionManager.getLeafId() : undefined;
1071
- if (hasLeafApi && leafId === null) {
1072
- return [];
1073
- }
1074
- if (typeof sessionManager?.getBranch === 'function') {
1075
- const branch = hasLeafApi ? sessionManager.getBranch(leafId) : sessionManager.getBranch();
1076
- return Array.isArray(branch) ? branch : [];
1077
- }
1078
- const entries = typeof sessionManager?.getEntries === 'function' ? sessionManager.getEntries() : [];
1079
- return Array.isArray(entries) ? entries : [];
1080
- }
1081
- function findLatestUserMessageEntry(branch) {
1082
- for (let index = branch.length - 1; index >= 0; index -= 1) {
1083
- const entry = branch[index];
1084
- if (entry?.type === 'message' && entry.message?.role === 'user') {
1085
- return entry;
1086
- }
1087
- }
1088
- return null;
1089
- }
1090
- function moveSessionManagerLeaf(sessionManager, leafId) {
1091
- if (leafId) {
1092
- sessionManager.branch?.(leafId);
1093
- return;
1094
- }
1095
- sessionManager.resetLeaf?.();
1096
- }
1097
- function captureRewindSessionState(sessionManager) {
1098
- return {
1099
- id: normalizeRewindString(sessionManager?.getSessionId?.()),
1100
- file: normalizeRewindString(sessionManager?.getSessionFile?.()),
1101
- createdAt: resolveRewindSessionCreatedAt(sessionManager),
1102
- };
1103
- }
1104
- function materializeCleanRewindSession(sessionManager, targetLeafId, previousState) {
1105
- const beforeId = previousState.id;
1106
- let materialized = false;
1107
- let warning;
1108
- try {
1109
- if (targetLeafId && typeof sessionManager?.createBranchedSession === 'function') {
1110
- sessionManager.createBranchedSession(targetLeafId);
1111
- materialized = true;
1112
- }
1113
- else if (!targetLeafId && typeof sessionManager?.newSession === 'function') {
1114
- sessionManager.newSession(previousState.file ? { parentSession: previousState.file } : undefined);
1115
- materialized = true;
1116
- }
1117
- }
1118
- catch (error) {
1119
- warning = error instanceof Error ? error.message : String(error);
1120
- }
1121
- const id = normalizeRewindString(sessionManager?.getSessionId?.());
1122
- const file = normalizeRewindString(sessionManager?.getSessionFile?.());
1123
- return {
1124
- materialized,
1125
- sessionChanged: Boolean(beforeId && id && beforeId !== id),
1126
- id,
1127
- file,
1128
- ...(warning ? { warning } : {}),
1129
- };
1130
- }
1131
- function describeRewindTarget(targetLeafId, cleanResult) {
1132
- const target = targetLeafId ? `leaf ${targetLeafId}` : 'session root';
1133
- if (!cleanResult.materialized) {
1134
- return target;
1135
- }
1136
- if (cleanResult.sessionChanged && cleanResult.id) {
1137
- return `${target} in clean session ${cleanResult.id}`;
1138
- }
1139
- return `${target} in clean session`;
1140
- }
1141
- function collectAbandonedRewindEntries(previousBranch, targetLeafId) {
1142
- if (!Array.isArray(previousBranch) || previousBranch.length === 0) {
1143
- return [];
1144
- }
1145
- if (!targetLeafId) {
1146
- return previousBranch;
1147
- }
1148
- const targetIndex = previousBranch.findIndex((entry) => entry?.id === targetLeafId);
1149
- return targetIndex >= 0 ? previousBranch.slice(targetIndex + 1) : previousBranch;
1150
- }
1151
- async function syncRewindProjection(interactive, runtime, input) {
1152
- if (input.cleanResult.warning) {
1153
- interactive.showWarning?.(`Clean rewind history materialization skipped: ${input.cleanResult.warning}`);
1154
- }
1155
- const mirror = runtime?.__linxPodMirror ?? interactive?.__linxPodMirror;
1156
- if (!mirror || typeof mirror.syncRewindProjection !== 'function') {
1157
- return;
1158
- }
1159
- try {
1160
- await mirror.syncRewindProjection({
1161
- previousSessionId: input.previousState.id,
1162
- previousSessionFile: input.previousState.file,
1163
- previousCreatedAt: input.previousState.createdAt,
1164
- cleanSessionId: input.cleanResult.id,
1165
- cleanSessionFile: input.cleanResult.file,
1166
- abandonedEntries: input.abandonedEntries,
1167
- });
1168
- }
1169
- catch (error) {
1170
- const message = error instanceof Error ? error.message : String(error);
1171
- interactive.showWarning?.(`Pod rewind projection unavailable: ${message}`);
1172
- }
1173
- }
1174
- function resolveRewindSessionCreatedAt(sessionManager) {
1175
- const headerTimestamp = normalizeRewindString(sessionManager?.getHeader?.()?.timestamp);
1176
- const headerDate = toValidRewindDate(headerTimestamp);
1177
- if (headerDate) {
1178
- return headerDate;
1179
- }
1180
- const entries = Array.isArray(sessionManager?.getEntries?.()) ? sessionManager.getEntries() : [];
1181
- for (const entry of entries) {
1182
- const timestamp = normalizeRewindString(entry?.timestamp);
1183
- const date = toValidRewindDate(timestamp);
1184
- if (date) {
1185
- return date;
1186
- }
1187
- }
1188
- const sessionId = normalizeRewindString(sessionManager?.getSessionId?.());
1189
- return sessionId ? parseRewindDateFromSessionId(sessionId) ?? undefined : undefined;
1190
- }
1191
- function parseRewindDateFromSessionId(sessionId) {
1192
- const prefix = sessionId.replace(/-/g, '').slice(0, 12);
1193
- if (!/^[\da-f]{12}$/i.test(prefix)) {
1194
- return null;
1195
- }
1196
- const millis = Number.parseInt(prefix, 16);
1197
- if (!Number.isFinite(millis) || millis <= 0) {
1198
- return null;
1199
- }
1200
- return toValidRewindDate(millis);
1201
- }
1202
- function toValidRewindDate(value) {
1203
- if (value instanceof Date) {
1204
- return Number.isNaN(value.getTime()) ? null : value;
1205
- }
1206
- if (typeof value === 'number' || typeof value === 'string') {
1207
- const date = new Date(value);
1208
- return Number.isNaN(date.getTime()) ? null : date;
1209
- }
1210
- return null;
1211
- }
1212
- function normalizeRewindString(value) {
1213
- return typeof value === 'string' && value.trim() ? value.trim() : undefined;
1214
- }
1215
- function syncAgentStateFromSessionManager(session, sessionManager) {
1216
- const context = sessionManager.buildSessionContext?.();
1217
- if (!context || !session?.agent?.state) {
1218
- return;
1219
- }
1220
- if (Array.isArray(context.messages)) {
1221
- session.agent.state.messages = context.messages;
1222
- }
1223
- }
1224
- function refreshInteractiveTranscriptFromSessionManager(interactive) {
1225
- try {
1226
- if (typeof interactive?.rebuildChatFromMessages === 'function') {
1227
- interactive.rebuildChatFromMessages();
1228
- return;
1229
- }
1230
- if (typeof interactive?.renderInitialMessages === 'function') {
1231
- interactive.chatContainer?.clear?.();
1232
- interactive.renderInitialMessages();
1233
- }
1234
- }
1235
- catch (error) {
1236
- const message = error instanceof Error ? error.message : String(error);
1237
- interactive?.showWarning?.(`Rewind transcript refresh failed: ${message}`);
1238
- }
1239
- }
1240
- class LinxMultiSelectList {
1241
- getRows;
1242
- onToggle;
1243
- onAction;
1244
- onCancel;
1245
- selectedIndex = 0;
1246
- constructor(getRows, onToggle, onAction, onCancel) {
1247
- this.getRows = getRows;
1248
- this.onToggle = onToggle;
1249
- this.onAction = onAction;
1250
- this.onCancel = onCancel;
1251
- }
1252
- invalidate() {
1253
- // No cached render state.
1254
- }
1255
- render(width) {
1256
- const rows = this.normalizedRows();
1257
- if (rows.length === 0) {
1258
- return [' No options'];
1259
- }
1260
- const lines = [];
1261
- for (let index = 0; index < rows.length; index += 1) {
1262
- const row = rows[index];
1263
- const cursor = index === this.selectedIndex ? '> ' : ' ';
1264
- const label = row.kind === 'item'
1265
- ? `${row.selected ? '✓' : '○'} ${row.label}`
1266
- : row.label;
1267
- lines.push(`${cursor}${truncateToWidth(label, Math.max(1, width - 2))}`);
1268
- if (row.description) {
1269
- lines.push(` ${truncateToWidth(row.description, Math.max(1, width - 2))}`);
1270
- }
1271
- }
1272
- lines.push('');
1273
- lines.push('↑↓ navigate Enter toggles items/actions Escape/Ctrl+C cancel');
1274
- return lines;
1275
- }
1276
- handleInput(keyData) {
1277
- const rows = this.normalizedRows();
1278
- if (rows.length === 0) {
1279
- return;
1280
- }
1281
- const keybindings = getKeybindings();
1282
- if (keybindings.matches(keyData, 'tui.select.up')) {
1283
- this.selectedIndex = this.selectedIndex === 0 ? rows.length - 1 : this.selectedIndex - 1;
1284
- return;
1285
- }
1286
- if (keybindings.matches(keyData, 'tui.select.down')) {
1287
- this.selectedIndex = this.selectedIndex === rows.length - 1 ? 0 : this.selectedIndex + 1;
1288
- return;
1289
- }
1290
- if (keybindings.matches(keyData, 'tui.select.confirm')) {
1291
- const selected = rows[this.selectedIndex];
1292
- if (selected?.kind === 'item') {
1293
- this.onToggle(selected.id);
1294
- }
1295
- else if (selected?.kind === 'action') {
1296
- this.onAction(selected.id);
1297
- }
1298
- return;
1299
- }
1300
- if (keybindings.matches(keyData, 'tui.select.cancel')) {
1301
- this.onCancel();
1302
- }
1303
- }
1304
- normalizedRows() {
1305
- const rows = this.getRows();
1306
- if (this.selectedIndex >= rows.length) {
1307
- this.selectedIndex = Math.max(0, rows.length - 1);
1308
- }
1309
- return rows;
1310
- }
1311
- }
1312
- class LinxStatusLineSelectorComponent extends Container {
1313
- list;
1314
- draftTokens;
1315
- draftUseColors;
1316
- notice = null;
1317
- constructor(config, onCommit, onCancel) {
1318
- super();
1319
- this.draftTokens = [...config.tokens];
1320
- this.draftUseColors = config.useColors;
1321
- this.addChild(new Spacer(1));
1322
- this.addChild(new Text('Status line', 1, 0));
1323
- this.addChild(new Text('Select the items that appear in the bottom TUI status line.', 1, 0));
1324
- this.addChild(new Text(`Current source: tokens ${config.tokenSource}, colors ${config.colorSource}.`, 1, 0));
1325
- this.addChild(new Spacer(1));
1326
- this.list = new LinxMultiSelectList(() => this.rows(), (id) => this.toggleToken(id), (id) => this.handleAction(id, onCommit), onCancel);
1327
- this.addChild(this.list);
1328
- }
1329
- getList() {
1330
- return this.list;
1331
- }
1332
- rows() {
1333
- const enabled = new Set(this.draftTokens);
1334
- const rows = LINX_STATUS_LINE_TOKEN_NAMES.map((token) => ({
1335
- kind: 'item',
1336
- id: token,
1337
- label: token,
1338
- selected: enabled.has(token),
1339
- }));
1340
- rows.push({
1341
- kind: 'action',
1342
- id: 'preset-codex',
1343
- label: STATUS_LINE_CODEX_PRESET_OPTION,
1344
- description: CODEX_STYLE_STATUS_LINE_TOKENS.join(', '),
1345
- }, {
1346
- kind: 'action',
1347
- id: 'preset-compact',
1348
- label: STATUS_LINE_COMPACT_PRESET_OPTION,
1349
- description: COMPACT_STATUS_LINE_TOKENS.join(', '),
1350
- }, {
1351
- kind: 'action',
1352
- id: 'toggle-colors',
1353
- label: `${STATUS_LINE_TOGGLE_COLORS_OPTION}: ${this.draftUseColors ? 'on' : 'off'}`,
1354
- }, {
1355
- kind: 'action',
1356
- id: 'reset',
1357
- label: STATUS_LINE_RESET_OPTION,
1358
- description: DEFAULT_STATUS_LINE_TOKENS.join(', '),
1359
- }, {
1360
- kind: 'action',
1361
- id: 'done',
1362
- label: STATUS_LINE_DONE_OPTION,
1363
- description: this.notice ?? 'Save changes and close.',
1364
- });
1365
- return rows;
1366
- }
1367
- toggleToken(id) {
1368
- const token = id;
1369
- if (!LINX_STATUS_LINE_TOKEN_NAMES.includes(token)) {
1370
- return;
1371
- }
1372
- const exists = this.draftTokens.includes(token);
1373
- if (exists && this.draftTokens.length <= 1) {
1374
- this.notice = 'Status line needs at least one item.';
1375
- return;
1376
- }
1377
- this.draftTokens = exists
1378
- ? this.draftTokens.filter((item) => item !== token)
1379
- : [...this.draftTokens, token];
1380
- this.notice = null;
1381
- }
1382
- handleAction(id, onCommit) {
1383
- if (id === 'preset-codex') {
1384
- this.draftTokens = [...CODEX_STYLE_STATUS_LINE_TOKENS];
1385
- this.notice = 'Draft changed to Codex-style preset.';
1386
- return;
1387
- }
1388
- if (id === 'preset-compact') {
1389
- this.draftTokens = [...COMPACT_STATUS_LINE_TOKENS];
1390
- this.notice = 'Draft changed to compact preset.';
1391
- return;
1392
- }
1393
- if (id === 'toggle-colors') {
1394
- this.draftUseColors = !this.draftUseColors;
1395
- this.notice = `Draft colors ${this.draftUseColors ? 'enabled' : 'disabled'}.`;
1396
- return;
1397
- }
1398
- if (id === 'reset') {
1399
- this.draftTokens = [...DEFAULT_STATUS_LINE_TOKENS];
1400
- this.draftUseColors = true;
1401
- this.notice = 'Draft reset to default.';
1402
- return;
1403
- }
1404
- if (id === 'done') {
1405
- onCommit({
1406
- tokens: this.draftTokens,
1407
- useColors: this.draftUseColors,
1408
- });
1409
- }
1410
- }
1411
- }
1412
- function collectRewindUserMessages(_session, sessionManager) {
1413
- return getActiveSessionBranch(sessionManager)
1414
- .filter((entry) => entry?.type === 'message' && entry.message?.role === 'user')
1415
- .map((entry) => ({
1416
- id: String(entry.id),
1417
- text: extractRewindMessageText(entry.message?.content) || '(empty user message)',
1418
- }));
1419
- }
1420
- function extractRewindMessageText(content) {
1421
- if (typeof content === 'string') {
1422
- return content;
1423
- }
1424
- if (!Array.isArray(content)) {
1425
- return '';
1426
- }
1427
- return content
1428
- .filter((part) => typeof part === 'object' && part !== null && part.type === 'text')
1429
- .map((part) => typeof part.text === 'string' ? part.text : '')
1430
- .join('');
1431
- }
1432
- class LinxRewindMessageList {
1433
- messages;
1434
- selectedIndex;
1435
- onSelect;
1436
- onCancel;
1437
- maxVisible = 10;
1438
- constructor(messages, initialSelectedId) {
1439
- this.messages = messages;
1440
- const initialIndex = initialSelectedId
1441
- ? messages.findIndex((message) => message.id === initialSelectedId)
1442
- : -1;
1443
- this.selectedIndex = initialIndex >= 0 ? initialIndex : Math.max(0, messages.length - 1);
1444
- }
1445
- invalidate() {
1446
- // No cached render state.
1447
- }
1448
- render(width) {
1449
- const lines = [];
1450
- if (this.messages.length === 0) {
1451
- return [' No user messages found'];
1452
- }
1453
- const startIndex = Math.max(0, Math.min(this.selectedIndex - Math.floor(this.maxVisible / 2), this.messages.length - this.maxVisible));
1454
- const endIndex = Math.min(startIndex + this.maxVisible, this.messages.length);
1455
- for (let index = startIndex; index < endIndex; index += 1) {
1456
- const message = this.messages[index];
1457
- const isSelected = index === this.selectedIndex;
1458
- const cursor = isSelected ? '> ' : ' ';
1459
- const normalized = message.text.replace(/\n/g, ' ').trim();
1460
- lines.push(`${cursor}${truncateToWidth(normalized, Math.max(1, width - 2))}`);
1461
- lines.push(` Rewind before message ${index + 1} of ${this.messages.length}`);
1462
- lines.push('');
1463
- }
1464
- if (startIndex > 0 || endIndex < this.messages.length) {
1465
- lines.push(` (${this.selectedIndex + 1}/${this.messages.length})`);
1466
- }
1467
- return lines;
1468
- }
1469
- handleInput(keyData) {
1470
- const keybindings = getKeybindings();
1471
- if (keybindings.matches(keyData, 'tui.select.up')) {
1472
- this.selectedIndex = this.selectedIndex === 0 ? this.messages.length - 1 : this.selectedIndex - 1;
1473
- return;
1474
- }
1475
- if (keybindings.matches(keyData, 'tui.select.down')) {
1476
- this.selectedIndex = this.selectedIndex === this.messages.length - 1 ? 0 : this.selectedIndex + 1;
1477
- return;
1478
- }
1479
- if (keybindings.matches(keyData, 'tui.select.confirm')) {
1480
- const selected = this.messages[this.selectedIndex];
1481
- if (selected) {
1482
- this.onSelect?.(selected.id);
1483
- }
1484
- return;
1485
- }
1486
- if (keybindings.matches(keyData, 'tui.select.cancel')) {
1487
- this.onCancel?.();
1488
- }
1489
- }
1490
- }
1491
- class LinxRewindMessageSelectorComponent extends Container {
1492
- messageList;
1493
- constructor(messages, onSelect, onCancel, initialSelectedId) {
1494
- super();
1495
- this.addChild(new Spacer(1));
1496
- this.addChild(new Text('Rewind to Message', 1, 0));
1497
- this.addChild(new Text('Select the first user message to remove from the active branch.', 1, 0));
1498
- this.addChild(new Text('The selected message and everything after it stay in history but leave the active context.', 1, 0));
1499
- this.addChild(new Spacer(1));
1500
- this.messageList = new LinxRewindMessageList(messages, initialSelectedId);
1501
- this.messageList.onSelect = onSelect;
1502
- this.messageList.onCancel = onCancel;
1503
- this.addChild(this.messageList);
1504
- this.addChild(new Spacer(1));
1505
- if (messages.length === 0) {
1506
- setTimeout(() => onCancel(), 100);
1507
- }
1508
- }
1509
- getMessageList() {
1510
- return this.messageList;
1511
- }
1512
- }
1513
- function installProjectedCommandRouter(interactive) {
1514
- interactive.__linxHandleProjectedCommand = async (text) => {
1515
- const command = text.trim();
1516
- if (!command.startsWith('/')) {
1517
- return false;
1518
- }
1519
- if (typeof interactive.__linxHandleProjectedGlobalCommand === 'function') {
1520
- const handled = await interactive.__linxHandleProjectedGlobalCommand(command);
1521
- if (handled === 'peer-command') {
1522
- return 'peer-command';
1523
- }
1524
- if (handled === true) {
1525
- return true;
1526
- }
1527
- }
1528
- if (typeof interactive.__linxHandleProjectedBackendCommand === 'function') {
1529
- const handled = await interactive.__linxHandleProjectedBackendCommand(command);
1530
- if (handled === true) {
1531
- return true;
1532
- }
1533
- }
1534
- return false;
1535
- };
1536
- }
1537
- async function handleInteractivePeerCommand(interactive, runtime, route) {
1538
- const goalMode = route.secretaryBehavior?.goalMode;
1539
- if (goalMode !== undefined) {
1540
- applyInteractiveGoalMode(interactive, runtime, goalMode);
1541
- interactive.showStatus?.(`Peer command routed; Secretary goal supervision mirror is ${goalMode ? 'active' : 'paused'}.`);
1542
- }
1543
- else {
1544
- interactive.showStatus?.('Peer command routed to current chat peer.');
1545
- }
1546
- await submitProjectedBackendInput(interactive, route.text);
1547
- interactive.ui?.requestRender?.();
1548
- }
1549
- function applyInteractiveGoalMode(interactive, runtime, enabled) {
1550
- interactive.__linxGoalModeEnabled = enabled;
1551
- if (enabled) {
1552
- interactive.__linxGoalModeSupervisorLastAt = Date.now();
1553
- }
1554
- else {
1555
- delete interactive.__linxGoalModeSupervisorLastAt;
1556
- }
1557
- if (runtime && typeof runtime === 'object') {
1558
- runtime.goalMode = enabled;
1559
- if (enabled) {
1560
- runtime.goalModeSupervisorLastAt = interactive.__linxGoalModeSupervisorLastAt;
1561
- }
1562
- else {
1563
- delete runtime.goalModeSupervisorLastAt;
1564
- }
1565
- }
1566
- }
1567
- async function submitProjectedBackendInput(interactive, text) {
1568
- const session = interactive?.session;
1569
- const sendUserMessage = typeof session?.__linxSendUserMessageWithoutCommandRouting === 'function'
1570
- ? session.__linxSendUserMessageWithoutCommandRouting
1571
- : session?.sendUserMessage;
1572
- if (typeof sendUserMessage === 'function') {
1573
- await sendUserMessage(text, session.isStreaming ? { deliverAs: 'followUp' } : undefined);
1574
- return;
1575
- }
1576
- const prompt = typeof session?.__linxPromptWithoutCommandRouting === 'function'
1577
- ? session.__linxPromptWithoutCommandRouting
1578
- : session?.prompt;
1579
- if (typeof prompt === 'function') {
1580
- await prompt(text, session.isStreaming ? { streamingBehavior: 'followUp' } : undefined);
1581
- return;
1582
- }
1583
- throw new Error('Active LinX session cannot accept peer goal input');
1584
- }
1585
- async function handleInteractiveAutoCommand(interactive, runtime, enabled, options = {}) {
1586
- if (enabled === undefined) {
1587
- const active = interactive.__autoEnabled === true;
1588
- interactive.showStatus?.(formatAutoModeChangeStatus(active));
1589
- interactive.ui?.requestRender?.();
1590
- return;
1591
- }
1592
- const control = getSessionControlManager(interactive, runtime);
1593
- control.setAutoEnabled(enabled);
1594
- interactive.__autoEnabled = enabled;
1595
- if (runtime && typeof runtime === 'object') {
1596
- runtime.autoEnabled = enabled;
1597
- }
1598
- const controller = getSecretaryAutoInputController(interactive, runtime, control);
1599
- if (enabled) {
1600
- controller.start({ scheduleImmediately: options.scheduleImmediately !== false });
1601
- }
1602
- else {
1603
- controller.stop();
1604
- }
1605
- interactive.showStatus?.(formatAutoModeChangeStatus(enabled));
1606
- interactive.ui?.requestRender?.();
1607
- await interactive.__linxOnAutoControlChange?.(enabled);
1608
- }
1609
- function formatAutoModeChangeStatus(enabled) {
1610
- return enabled
1611
- ? [
1612
- 'Auto is on.',
1613
- 'Auto on: Secretary drives the current session input loop.',
1614
- 'What changed: backend prompts and blocked approval/input requests go to Secretary first; Secretary answers in-policy and asks you only when blocked.',
1615
- 'User-visible state: the input bar shows auto; Ctrl+C or /auto off hands control back to you.',
1616
- 'Backend approval policy is unchanged.',
1617
- ].join('\n')
1618
- : [
1619
- 'Auto is off.',
1620
- 'Auto off: you drive the current session directly.',
1621
- 'What changed: backend prompts, approvals, and free-form input return to the local TUI unless another explicit control path handles them.',
1622
- 'Auto only controls input ownership; it does not change whether the current chat peer is Secretary or worker/backend.',
1623
- 'Use /auto on to hand control back to Secretary.',
1624
- ].join('\n');
1625
- }
1626
- async function changeInteractiveCwd(interactive, runtime, target) {
1627
- if (!target) {
1628
- interactive.showStatus?.(`Current workspace: ${resolveInteractiveCwd(interactive, runtime)}`);
1629
- interactive.ui?.requestRender?.();
1630
- return;
1631
- }
1632
- const nextCwd = resolve(resolveInteractiveCwd(interactive, runtime), target);
1633
- if (!existsSync(nextCwd)) {
1634
- interactive.showError?.(`Workspace not found: ${nextCwd}`);
1635
- interactive.ui?.requestRender?.();
1636
- return;
1637
- }
1638
- if (!statSync(nextCwd).isDirectory()) {
1639
- interactive.showError?.(`Workspace is not a directory: ${nextCwd}`);
1640
- interactive.ui?.requestRender?.();
1641
- return;
1642
- }
1643
- process.chdir(nextCwd);
1644
- setRuntimeCwd(interactive, runtime, nextCwd);
1645
- await runtime?.backendCommandRouter?.setCwd?.(nextCwd);
1646
- interactive.showStatus?.(`Workspace changed to ${nextCwd}. Session history stays in the current thread.`);
1647
- interactive.ui?.requestRender?.();
1648
- }
1649
- function resolveInteractiveCwd(interactive, runtime) {
1650
- const candidates = [
1651
- interactive?.session?.cwd,
1652
- runtime?.cwd,
1653
- interactive?.sessionManager?.getCwd?.(),
1654
- interactive?.session?.sessionManager?.getCwd?.(),
1655
- process.cwd(),
1656
- ];
1657
- for (const candidate of candidates) {
1658
- if (typeof candidate === 'string' && candidate.trim()) {
1659
- return candidate.trim();
1660
- }
1661
- }
1662
- return process.cwd();
1663
- }
1664
- function setRuntimeCwd(interactive, runtime, cwd) {
1665
- if (interactive?.session && typeof interactive.session === 'object') {
1666
- interactive.session.cwd = cwd;
1667
- }
1668
- if (runtime && typeof runtime === 'object') {
1669
- runtime.cwd = cwd;
1670
- }
1671
- }
1672
- export function installSymphonyCommand(interactive) {
1673
- if (interactive.__linxSymphonyCommandInstalled) {
1674
- return;
1675
- }
1676
- const originalSetup = interactive.setupEditorSubmitHandler?.bind(interactive);
1677
- if (typeof originalSetup !== 'function') {
1678
- return;
1679
- }
1680
- interactive.setupEditorSubmitHandler = function patchedSymphonySetupEditorSubmitHandler(...args) {
1681
- const result = originalSetup(...args);
1682
- const originalSubmit = this.defaultEditor?.onSubmit?.bind(this.defaultEditor);
1683
- if (typeof originalSubmit !== 'function') {
1684
- return result;
1685
- }
1686
- this.defaultEditor.onSubmit = async (text) => {
1687
- const input = text.trim();
1688
- const command = parseSymphonyCommand(input);
1689
- if (command) {
1690
- this.editor?.setText?.('');
1691
- await handleSymphonyCommand(this, command);
1692
- return;
1693
- }
1694
- if (this.__linxSymphonyModeEnabled && shouldProjectSymphonyInput(input)) {
1695
- getSessionControlManager(this, this.runtime).recordUserMessage({ text: input });
1696
- await originalSubmit(renderSymphonySecretaryProjection(input));
1697
- return;
1698
- }
1699
- await originalSubmit(text);
1700
- };
1701
- return result;
1702
- };
1703
- interactive.__linxSymphonyCommandInstalled = true;
1704
- }
1705
- export function installSymphonyAutocomplete(interactive) {
1706
- installLinxCommandAutocomplete(interactive);
1707
- }
1708
- export function installLinxCommandAutocomplete(interactive) {
1709
- if (interactive.__linxCommandAutocompleteInstalled || interactive.__linxSymphonyAutocompleteInstalled) {
1710
- return;
1711
- }
1712
- const setupName = typeof interactive.setupAutocompleteProvider === 'function'
1713
- ? 'setupAutocompleteProvider'
1714
- : 'setupAutocomplete';
1715
- const originalSetup = interactive[setupName]?.bind(interactive);
1716
- if (typeof originalSetup !== 'function') {
1717
- return;
1718
- }
1719
- interactive[setupName] = function patchedLinxSetupAutocompleteProvider(...args) {
1720
- const result = originalSetup(...args);
1721
- installLinxAutocompleteCommands(this.autocompleteProvider);
1722
- return result;
1723
- };
1724
- interactive.__linxCommandAutocompleteInstalled = true;
1725
- interactive.__linxSymphonyAutocompleteInstalled = true;
1726
- }
1727
- function installLinxAutocompleteCommands(provider) {
1728
- if (!Array.isArray(provider?.commands)) {
1729
- return;
1730
- }
1731
- for (const command of LINX_INTERACTIVE_SLASH_COMMANDS) {
1732
- if (!provider.commands.some((existing) => getAutocompleteCommandName(existing) === command.name)) {
1733
- provider.commands.push(command);
1734
- }
1735
- }
1736
- }
1737
- const LINX_INTERACTIVE_SLASH_COMMANDS = [
1738
- {
1739
- name: 'auto',
1740
- argumentHint: 'on|off|status',
1741
- description: 'toggle AI Secretary driving for this session',
1742
- getArgumentCompletions: (prefix) => completeStaticArguments(prefix, [
1743
- { value: 'on', description: 'Secretary drives the session and asks when blocked' },
1744
- { value: 'off', description: 'User drives the session directly' },
1745
- { value: 'status', description: 'Show whether Secretary driving is enabled' },
1746
- ]),
1747
- },
1748
- {
1749
- name: 'cd',
1750
- argumentHint: '<dir>',
1751
- description: 'change workspace for this LinX session',
1752
- },
1753
- {
1754
- name: 'goal',
1755
- argumentHint: '<peer-command>',
1756
- description: 'send a goal command to the current chat peer',
1757
- },
1758
- {
1759
- name: 'rewind',
1760
- description: 'select a user message and rewind the active branch before it',
1761
- },
1762
- {
1763
- name: 'statusline',
1764
- argumentHint: 'set|colors|tokens|reset',
1765
- description: 'configure which items appear in the status line',
1766
- getArgumentCompletions: (prefix) => completeStaticArguments(prefix, [
1767
- { value: 'set', description: 'Set status line tokens' },
1768
- { value: 'colors', description: 'Enable or disable status line colors' },
1769
- { value: 'tokens', description: 'List available status line tokens' },
1770
- { value: 'reset', description: 'Restore default status line tokens' },
1771
- ]),
1772
- },
1773
- {
1774
- name: 'update',
1775
- description: 'check for a LinX CLI update and install from the TUI',
1776
- },
1777
- {
1778
- name: 'ai',
1779
- argumentHint: 'connect <provider>',
1780
- description: 'connect AI provider credentials to LinX Pod settings',
1781
- getArgumentCompletions: completeAiArguments,
1782
- },
1783
- {
1784
- name: 'symphony',
1785
- argumentHint: 'on|off|status',
1786
- description: 'turn Secretary task handoff on/off, or show status',
1787
- getArgumentCompletions: (prefix) => completeStaticArguments(prefix, [
1788
- { value: 'on', description: 'Secretary can plan and hand off larger tasks' },
1789
- { value: 'off', description: 'Return to direct chat' },
1790
- { value: 'status', description: 'Show whether Symphony task handoff is enabled' },
1791
- ]),
1792
- },
1793
- ];
1794
- function completeStaticArguments(prefix, options) {
1795
- const normalized = prefix.trimStart().toLowerCase();
1796
- const matches = options.filter((option) => option.value.startsWith(normalized));
1797
- if (matches.length === 0) {
1798
- return null;
1799
- }
1800
- return matches.map((option) => ({
1801
- value: option.value,
1802
- label: option.value,
1803
- description: option.description,
1804
- }));
1805
- }
1806
- function completeAiArguments(prefix) {
1807
- const input = prefix.trimStart().toLowerCase();
1808
- if (!input || 'connect'.startsWith(input)) {
1809
- return [{
1810
- value: 'connect ',
1811
- label: 'connect',
1812
- description: 'Connect an AI provider key to LinX Pod AI settings',
1813
- }];
1814
- }
1815
- const connectPrefix = 'connect ';
1816
- if (!input.startsWith(connectPrefix)) {
1817
- return null;
1818
- }
1819
- const providerPrefix = input.slice(connectPrefix.length);
1820
- const providers = getAiConnectCompletionProviders();
1821
- const matches = providers.filter((provider) => provider.startsWith(providerPrefix));
1822
- if (matches.length === 0) {
1823
- return null;
1824
- }
1825
- return matches.map((provider) => ({
1826
- value: `connect ${provider}`,
1827
- label: provider,
1828
- description: `Connect ${provider} credentials`,
1829
- }));
1830
- }
1831
- function getAiConnectCompletionProviders() {
1832
- const providerIds = [];
1833
- const aliases = [];
1834
- for (const entry of getAIConfigProviderCatalog()) {
1835
- providerIds.push(entry.id);
1836
- aliases.push(...(entry.aliases ?? []));
1837
- }
1838
- return Array.from(new Set([...providerIds, ...aliases]));
1839
- }
1840
- function getAutocompleteCommandName(command) {
1841
- if (!command || typeof command !== 'object') {
1842
- return undefined;
1843
- }
1844
- const value = 'name' in command
1845
- ? command.name
1846
- : 'value' in command
1847
- ? command.value
1848
- : undefined;
1849
- return typeof value === 'string' ? value : undefined;
1850
- }
1851
- function parseSymphonyCommand(input) {
1852
- if (input !== '/symphony' && !input.startsWith('/symphony ')) {
1853
- return null;
1854
- }
1855
- const args = input === '/symphony' ? '' : input.slice('/symphony'.length).trim();
1856
- if (!args || args.toLowerCase() === 'on' || args.toLowerCase() === 'enable') {
1857
- return { action: 'enable' };
1858
- }
1859
- const normalized = args.toLowerCase();
1860
- if (normalized === 'off' || normalized === 'disable' || normalized === 'exit') {
1861
- return { action: 'disable' };
1862
- }
1863
- if (normalized === 'status') {
1864
- return { action: 'status' };
1865
- }
1866
- return { action: 'usage', input: args };
1867
- }
1868
- async function handleSymphonyCommand(interactive, command) {
1869
- if (command.action === 'enable') {
1870
- interactive.__linxSymphonyModeEnabled = true;
1871
- interactive.__linxSymphonyModeGeneration = (Number(interactive.__linxSymphonyModeGeneration) || 0) + 1;
1872
- if (interactive.runtime && typeof interactive.runtime === 'object') {
1873
- interactive.runtime.symphonyEnabled = true;
1874
- }
1875
- interactive.showStatus?.(formatSymphonyModeChangeStatus(true));
1876
- interactive.ui?.requestRender?.();
1877
- await interactive.__linxOnSymphonyControlChange?.(true);
1878
- return;
1879
- }
1880
- if (command.action === 'disable') {
1881
- interactive.__linxSymphonyModeEnabled = false;
1882
- interactive.__linxSymphonyModeGeneration = (Number(interactive.__linxSymphonyModeGeneration) || 0) + 1;
1883
- abortInteractiveSymphonyDispatches(interactive);
1884
- if (interactive.runtime && typeof interactive.runtime === 'object') {
1885
- interactive.runtime.symphonyEnabled = false;
1886
- }
1887
- interactive.showStatus?.(formatSymphonyModeChangeStatus(false));
1888
- interactive.ui?.requestRender?.();
1889
- await interactive.__linxOnSymphonyControlChange?.(false);
1890
- return;
1891
- }
1892
- if (command.action === 'status') {
1893
- interactive.showStatus?.(await formatSymphonyStatus(interactive));
1894
- interactive.ui?.requestRender?.();
1895
- return;
1896
- }
1897
- interactive.showStatus?.(formatSymphonyUsage(command.input));
1898
- interactive.ui?.requestRender?.();
1899
- }
1900
- function formatSymphonyModeChangeStatus(enabled) {
1901
- return enabled
1902
- ? 'Symphony is on. I will keep ordinary chat ordinary and only plan or hand off real work.'
1903
- : 'Symphony is off. Back to direct chat. Active handoffs from this window were stopped.';
1904
- }
1905
- function formatSymphonyUsage(input) {
1906
- return [
1907
- `Unsupported /symphony argument: ${input}`,
1908
- 'Use /symphony on to chat with Secretary, /symphony off to chat with the worker/backend peer, or /symphony status to inspect workers.',
1909
- 'After enabling Symphony, send the objective as a normal chat message to Secretary; Secretary will decide whether it is an Issue, update existing work, split tasks, and dispatch workers.',
1910
- ].join('\n');
1911
- }
1912
- function shouldProjectSymphonyInput(input) {
1913
- return Boolean(input)
1914
- && !input.startsWith('/')
1915
- && !input.startsWith('!');
1916
- }
1917
- function renderSymphonySecretaryProjection(input) {
1918
- return [
1919
- '# AI Secretary Symphony request',
1920
- '',
1921
- 'Symphony is on: the user is chatting with Secretary, not directly with the worker/backend peer.',
1922
- 'Treat the user message below as a Secretary-facing product message.',
1923
- 'Decide whether it is ordinary chat, an Idea, a change to existing work, or delegable work.',
1924
- 'Default response style: reply like normal chat.',
1925
- 'Do not print internal Symphony binding, Issue/Task routing, worker selection, or report-style sections unless a visible state change or blocker must be surfaced.',
1926
- 'If the message is ordinary chat or early exploration, answer directly and do not explain that it was not delegated.',
1927
- 'If real delegation is needed, summarize the visible handoff result briefly after updating control state.',
1928
- 'When you need to inspect or mutate Symphony Pod resources from the AI side, use the xpod CLI as the direct Pod tool surface.',
1929
- 'Prefer model-backed xpod obj commands for Idea, Issue, Task, Delivery, Run, RunStep, Report, Evidence, ApprovalRequest, InputRequest, and InboxNotification resources.',
1930
- 'xpod uses the same Solid authority as LinX inside the Agent Runtime; do not ask the model to handle tokens or client secrets.',
1931
- 'Before mutating Pod resources from tools, verify xpod auth status/whoami reports the same acting WebID/Pod root as the LinX session; stop on mismatch.',
1932
- 'Do not hand-patch TTL or guess Pod paths for modeled product resources; use xpod/model descriptors or inspect existing links first.',
1933
- '',
1934
- 'User message:',
1935
- input,
1936
- ].join('\n');
1937
- }
1938
- async function dispatchSymphonyWorkerFromInteractive(interactive, objective, source) {
1939
- const backend = resolveSymphonyWorkerBackend(interactive, objective);
1940
- const agentRuntime = resolveSymphonyControlAgentRuntime(interactive);
1941
- const workerModel = resolveSymphonyWorkerModel(interactive, objective, backend);
1942
- const workerCredentialSource = resolveSymphonyWorkerCredentialSource(interactive, backend);
1943
- const workerGoalMode = interactive.__autoEnabled === true;
1944
- const workerSupervisorIntervalMs = workerGoalMode ? resolveSymphonyWorkerSupervisorIntervalMs(interactive) : undefined;
1945
- const cwd = resolveInteractiveCwd(interactive, interactive.runtime);
1946
- const dispatchGeneration = Number(interactive.__linxSymphonyModeGeneration) || 0;
1947
- const dispatches = Array.isArray(interactive.__linxSymphonyDispatches)
1948
- ? interactive.__linxSymphonyDispatches
1949
- : [];
1950
- interactive.__linxSymphonyDispatches = dispatches;
1951
- const controller = new AbortController();
1952
- const controllers = getInteractiveSymphonyDispatchControllers(interactive);
1953
- controllers.add(controller);
1954
- interactive.showStatus?.(`Symphony handoff started: ${backend}${workerModel ? ` · ${workerModel}` : ''}`
1955
- + `${workerGoalMode ? ` · supervised every ${formatSymphonySupervisorInterval(workerSupervisorIntervalMs)}` : ''}.`
1956
- + ' Use /symphony status for details.');
1957
- interactive.ui?.requestRender?.();
1958
- const run = typeof interactive.__linxRunSymphony === 'function'
1959
- ? interactive.__linxRunSymphony
1960
- : runSymphony;
1961
- const dispatchArgs = {
1962
- objective: [objective],
1963
- backend,
1964
- auto: interactive.__autoEnabled === true,
1965
- cwd,
1966
- plain: true,
1967
- print: false,
1968
- quietProjectionErrors: true,
1969
- quietWorkers: true,
1970
- credentialSource: workerCredentialSource,
1971
- agentRuntime,
1972
- workerModel,
1973
- workerGoalMode,
1974
- workerSupervisorIntervalMs,
1975
- signal: controller.signal,
1976
- ...(source?.chat ? { chat: source.chat } : {}),
1977
- ...(source?.thread ? { thread: source.thread } : {}),
1978
- target: {
1979
- source: 'active-session',
1980
- backend,
1981
- agent: `${backend}-worker`,
1982
- label: `${backend} worker`,
1983
- ...(source?.chat ? { chat: source.chat } : {}),
1984
- ...(source?.thread ? { thread: source.thread } : {}),
1985
- },
1986
- };
1987
- const runtime = createInteractiveSymphonyRuntime(interactive);
1988
- const dispatch = run(dispatchArgs, runtime)
1989
- .then((plan) => {
1990
- if (!isCurrentSymphonyDispatch(interactive, dispatchGeneration)) {
1991
- return;
1992
- }
1993
- interactive.showStatus?.(formatSymphonyDispatchResult(plan));
1994
- })
1995
- .catch((error) => {
1996
- if (!isCurrentSymphonyDispatch(interactive, dispatchGeneration)) {
1997
- return;
1998
- }
1999
- if (isSymphonyAbortError(error)) {
2000
- interactive.showStatus?.('Symphony dispatch cancelled.');
2001
- return;
2002
- }
2003
- const message = error instanceof Error ? error.message : String(error);
2004
- interactive.showError?.(`Symphony dispatch failed: ${message}`);
2005
- })
2006
- .finally(() => {
2007
- controllers.delete(controller);
2008
- if (!isCurrentSymphonyDispatch(interactive, dispatchGeneration)) {
2009
- return;
2010
- }
2011
- interactive.ui?.requestRender?.();
2012
- });
2013
- dispatches.push(dispatch);
2014
- await Promise.resolve();
2015
- }
2016
- function getInteractiveSymphonyDispatchControllers(interactive) {
2017
- if (!(interactive.__linxSymphonyDispatchControllers instanceof Set)) {
2018
- interactive.__linxSymphonyDispatchControllers = new Set();
2019
- }
2020
- return interactive.__linxSymphonyDispatchControllers;
2021
- }
2022
- function abortInteractiveSymphonyDispatches(interactive) {
2023
- const controllers = getInteractiveSymphonyDispatchControllers(interactive);
2024
- for (const controller of controllers) {
2025
- if (!controller.signal.aborted) {
2026
- controller.abort(new Error('Symphony dispatch aborted by /symphony off'));
2027
- }
2028
- }
2029
- controllers.clear();
2030
- }
2031
- function isSymphonyAbortError(error) {
2032
- return error instanceof Error
2033
- && (error.name === 'AbortError' || error.message.toLowerCase().includes('aborted'));
2034
- }
2035
- function isCurrentSymphonyDispatch(interactive, generation) {
2036
- return interactive.__linxSymphonyModeEnabled === true
2037
- && (Number(interactive.__linxSymphonyModeGeneration) || 0) === generation;
2038
- }
2039
- function createInteractiveSymphonyRuntime(interactive) {
2040
- const projectionRuntime = interactive?.__linxSymphonyPodProjectionRuntime;
2041
- if (!projectionRuntime) {
2042
- return undefined;
2043
- }
2044
- return {
2045
- runAutoMode,
2046
- listAutoModeSessions: listArchivedAutoModeSessions,
2047
- persistSymphonyControlStateToPod(plan, options) {
2048
- return persistSymphonyControlStateToPod(plan, {
2049
- ...options,
2050
- runtime: projectionRuntime,
2051
- });
2052
- },
2053
- listOpenSymphonyIssuesFromPod() {
2054
- return listOpenSymphonyIssuesFromPod({ runtime: projectionRuntime });
2055
- },
2056
- mirrorSymphonyProjectionJsonLdFromPod(result) {
2057
- return mirrorSymphonyProjectionJsonLdFromPod(result, { runtime: projectionRuntime });
2058
- },
2059
- };
2060
- }
2061
- function resolveSymphonyWorkerBackend(interactive, objective) {
2062
- const candidates = [
2063
- interactive?.__linxSymphonyWorkerBackend,
2064
- interactive?.runtime?.symphonyWorkerBackend,
2065
- extractSymphonyWorkerBackendFromText(objective),
2066
- interactive?.runtime?.runtimeBackend,
2067
- interactive?.runtime?.workerBackend,
2068
- interactive?.runtime?.backendCommandRouter?.backend,
2069
- interactive?.runtime?.backendSessionRef?.backend,
2070
- ];
2071
- for (const candidate of candidates) {
2072
- if (candidate === 'cc') {
2073
- return 'claude';
2074
- }
2075
- if (isSymphonyWorkerBackend(candidate)) {
2076
- return candidate;
2077
- }
2078
- }
2079
- return 'codex';
2080
- }
2081
- function isSymphonyWorkerBackend(value) {
2082
- return value === 'linx' || value === 'codex' || value === 'claude' || value === 'codebuddy';
2083
- }
2084
- function resolveSymphonyWorkerCredentialSource(interactive, backend) {
2085
- const configured = normalizeSymphonyCredentialSource(interactive?.__linxSymphonyWorkerCredentialSource, interactive?.runtime?.symphonyWorkerCredentialSource, interactive?.runtime?.workerCredentialSource);
2086
- if (configured) {
2087
- return configured;
2088
- }
2089
- return backend === 'linx' ? 'cloud' : 'local';
2090
- }
2091
- function normalizeSymphonyCredentialSource(...values) {
2092
- for (const value of values) {
2093
- if (value === 'local' || value === 'cloud') {
2094
- return value;
2095
- }
2096
- if (typeof value === 'string') {
2097
- const normalized = value.trim().toLowerCase();
2098
- if (normalized === 'local' || normalized === 'cloud') {
2099
- return normalized;
2100
- }
2101
- }
2102
- }
2103
- return undefined;
2104
- }
2105
- function extractSymphonyWorkerBackendFromText(input) {
2106
- const normalized = input?.trim().toLowerCase();
2107
- if (!normalized) {
2108
- return undefined;
2109
- }
2110
- if (/\b(?:linx|pi)\s*(?:runtime|backend|worker|agent)\b/u.test(normalized)
2111
- || /\b(?:runtime|backend|worker|agent)\s*(?:=|:|:|是|用|使用|设为|指定为)\s*(?:linx|pi)\b/u.test(normalized)
2112
- || /(用|使用|让|派)\s*(linx|pi)\s*(runtime|后端|worker|agent|模型)?/u.test(normalized)) {
2113
- return 'linx';
2114
- }
2115
- if (/\b(?:claude|cc)\s*(?:code\s*)?(?:runtime|backend|worker|agent)\b/u.test(normalized)
2116
- || /\b(?:runtime|backend|worker|agent)\s*(?:=|:|:|是|用|使用|设为|指定为)\s*(?:claude|cc)\b/u.test(normalized)
2117
- || /(用|使用|让|派)\s*(?:claude|cc)\s*(?:code|runtime|后端|worker|agent|模型)?/u.test(normalized)) {
2118
- return 'claude';
2119
- }
2120
- if (/\bcodex\s*(?:runtime|backend|worker|agent)?\b/u.test(normalized)) {
2121
- return 'codex';
2122
- }
2123
- if (/\b(?:claude|cc)\s*(?:code|runtime|backend|worker|agent)?\b/u.test(normalized)) {
2124
- return 'claude';
2125
- }
2126
- if (/\bcodebuddy\s*(?:runtime|backend|worker|agent)?\b/u.test(normalized)) {
2127
- return 'codebuddy';
2128
- }
2129
- return undefined;
2130
- }
2131
- function resolveSymphonyControlAgentRuntime(interactive) {
2132
- const configured = normalizeSymphonyAgentRuntimeConfig(interactive?.__linxAgentRuntime, interactive?.__linxAgentRuntimeConfig, interactive?.runtime?.agentRuntime, interactive?.runtime?.agentRuntimeConfig);
2133
- const model = configured?.model ?? normalizeSymphonyConfigString(interactive?.session?.model?.id, interactive?.runtime?.model);
2134
- if (!configured && !model) {
2135
- return undefined;
2136
- }
2137
- return {
2138
- backend: configured?.backend ?? 'linx',
2139
- credentialSource: configured?.credentialSource ?? 'cloud',
2140
- ...configured,
2141
- ...(model ? { model } : {}),
2142
- };
2143
- }
2144
- function normalizeSymphonyAgentRuntimeConfig(...values) {
2145
- for (const value of values) {
2146
- if (!isRecord(value)) {
2147
- continue;
2148
- }
2149
- const metadata = isRecord(value.metadata) ? { ...value.metadata } : undefined;
2150
- const resolved = {
2151
- ...(normalizeSymphonyConfigString(value.backend) ? { backend: normalizeSymphonyConfigString(value.backend) } : {}),
2152
- ...(normalizeSymphonyConfigString(value.model) ? { model: normalizeSymphonyConfigString(value.model) } : {}),
2153
- ...(normalizeSymphonyConfigString(value.credentialSource) ? { credentialSource: normalizeSymphonyConfigString(value.credentialSource) } : {}),
2154
- ...(normalizeSymphonyConfigString(value.runtime) ? { runtime: normalizeSymphonyConfigString(value.runtime) } : {}),
2155
- ...(normalizeSymphonyConfigString(value.transport) ? { transport: normalizeSymphonyConfigString(value.transport) } : {}),
2156
- ...(normalizeSymphonyConfigString(value.endpoint) ? { endpoint: normalizeSymphonyConfigString(value.endpoint) } : {}),
2157
- ...(metadata ? { metadata } : {}),
2158
- };
2159
- if (Object.keys(resolved).length > 0) {
2160
- return resolved;
2161
- }
2162
- }
2163
- return undefined;
2164
- }
2165
- function formatSymphonyControlRuntime(runtime) {
2166
- return [
2167
- runtime.backend ?? 'linx',
2168
- runtime.model,
2169
- runtime.credentialSource ? `credentials=${runtime.credentialSource}` : undefined,
2170
- ].filter(Boolean).join(' · ');
2171
- }
2172
- function resolveSymphonyWorkerModel(interactive, objective, backend) {
2173
- const configured = normalizeSymphonyConfigString(interactive?.__linxSymphonyWorkerModel, interactive?.runtime?.symphonyWorkerModel, interactive?.runtime?.workerModel, extractSymphonyWorkerModelFromText(objective));
2174
- if (backend === 'claude' && configured && isProviderRoutedModel(configured)) {
2175
- return 'opus';
2176
- }
2177
- return configured;
2178
- }
2179
- function resolveSymphonyWorkerSupervisorIntervalMs(interactive) {
2180
- const value = Number(interactive?.__linxSymphonyWorkerSupervisorIntervalMs
2181
- ?? interactive?.runtime?.symphonyWorkerSupervisorIntervalMs
2182
- ?? DEFAULT_SYMPHONY_WORKER_SUPERVISOR_INTERVAL_MS);
2183
- if (!Number.isFinite(value) || value <= 0) {
2184
- return DEFAULT_SYMPHONY_WORKER_SUPERVISOR_INTERVAL_MS;
2185
- }
2186
- return Math.trunc(value);
2187
- }
2188
- function formatSymphonySupervisorInterval(value) {
2189
- const intervalMs = Number(value);
2190
- if (!Number.isFinite(intervalMs) || intervalMs <= 0) {
2191
- return `${DEFAULT_SYMPHONY_WORKER_SUPERVISOR_INTERVAL_MS / 60_000}m`;
2192
- }
2193
- if (intervalMs % 60_000 === 0) {
2194
- return `${intervalMs / 60_000}m`;
2195
- }
2196
- if (intervalMs % 1000 === 0) {
2197
- return `${intervalMs / 1000}s`;
2198
- }
2199
- return `${intervalMs}ms`;
2200
- }
2201
- function normalizeSymphonyConfigString(...values) {
2202
- for (const value of values) {
2203
- const normalized = typeof value === 'string' ? value.trim() : '';
2204
- if (normalized) {
2205
- return normalized;
2206
- }
2207
- }
2208
- return undefined;
2209
- }
2210
- function extractSymphonyWorkerModelFromText(input) {
2211
- const patterns = [
2212
- /(?:worker|agent|模型|model)\s*(?:=|:|:|是|用|使用|设为|指定为)\s*([A-Za-z0-9][A-Za-z0-9._/-]{1,80})/iu,
2213
- /(?:用|使用|让|派)\s*([A-Za-z0-9][A-Za-z0-9._/-]{1,80})\s*(?:作为)?\s*(?:worker|agent|模型|model)/iu,
2214
- /\b((?:deepseek|gpt|claude|qwen|gemini)[A-Za-z0-9._/-]{1,80})\s*(?:worker|agent)?/iu,
2215
- ];
2216
- for (const pattern of patterns) {
2217
- const match = input.match(pattern);
2218
- const normalized = normalizeSymphonyModelToken(match?.[1]);
2219
- if (normalized) {
2220
- return normalized;
2221
- }
2222
- }
2223
- return undefined;
2224
- }
2225
- function normalizeSymphonyModelToken(value) {
2226
- const normalized = typeof value === 'string'
2227
- ? value.trim().replace(/[,。,.、;;::!?!?))\]}】]+$/u, '')
2228
- : '';
2229
- return normalized || undefined;
2230
- }
2231
- function isProviderRoutedModel(model) {
2232
- return /(?:deepseek|qwen|gemini|kimi|moonshot|mistral|grok|glm|minimax)/iu.test(model);
2233
- }
2234
- function formatSymphonyDispatchResult(plan) {
2235
- const worker = plan.workers[0];
2236
- const session = worker?.session ?? plan.session;
2237
- const delivery = worker?.delivery ?? plan.delivery;
2238
- const lines = [
2239
- plan.issue.status === 'resolved' && delivery.status === 'completed'
2240
- ? `Symphony handoff completed: ${plan.issue.title}.`
2241
- : `Symphony handoff recorded: ${plan.issue.title}.`,
2242
- 'Use /symphony status for details.',
2243
- ];
2244
- if (session.error) {
2245
- lines.push(`Error: ${session.error}`);
2246
- }
2247
- return lines.join('\n');
2248
- }
2249
- async function captureSymphonyIdeaIfNeeded(input, source) {
2250
- if (!shouldCaptureSymphonyIdeaInput(input)) {
2251
- return undefined;
2252
- }
2253
- try {
2254
- const affectedArea = inferSymphonyIdeaAffectedArea(input);
2255
- const captureInput = {
2256
- input,
2257
- commitment: 'thought',
2258
- status: 'captured',
2259
- currentUnderstanding: input.trim(),
2260
- nextStep: 'Bind this Idea against existing control records before promoting it to work.',
2261
- ...(source?.chat ? { chat: source.chat } : {}),
2262
- ...(source?.thread ? { thread: source.thread } : {}),
2263
- ...(affectedArea ? { affectedArea } : {}),
2264
- };
2265
- const idea = createSymphonyIdeaRecord(captureInput);
2266
- const persisted = await persistSymphonyIdeaToPod(idea);
2267
- if (!persisted) {
2268
- throw new Error('No active Pod session; Symphony Idea records must be written to Pod in LinX runtime.');
2269
- }
2270
- return {
2271
- uri: idea.uri,
2272
- summary: idea.summary,
2273
- status: idea.status,
2274
- commitment: idea.commitment,
2275
- };
2276
- }
2277
- catch (error) {
2278
- process.emitWarning(error instanceof Error
2279
- ? new Error(`LinX Symphony Idea Pod write failed: ${error.message}`)
2280
- : new Error(`LinX Symphony Idea Pod write failed: ${String(error)}`));
2281
- return undefined;
2282
- }
2283
- }
2284
- function shouldCaptureSymphonyIdeaInput(input) {
2285
- const normalized = input.trim();
2286
- if (normalized.length < 12) {
2287
- return false;
2288
- }
2289
- return /\b(idea|maybe|perhaps|could we|should we|what if|proposal|direction)\b/iu.test(normalized)
2290
- || /(我觉得|感觉|也许|可能|考虑|想法|方向|要不要|能不能|是不是|是否|应该)/u.test(normalized);
2291
- }
2292
- function inferSymphonyIdeaAffectedArea(input) {
2293
- const normalized = input.toLowerCase();
2294
- if (/symphony|secretary|auto|approval|grant|pod|xpod|skill|worker|agent/u.test(normalized)) {
2295
- return normalized.match(/symphony|secretary|auto|approval|grant|pod|xpod|skill|worker|agent/u)?.[0];
2296
- }
2297
- if (/(建模|模型|数据|同步|权限|审批|托管|多端|工作流|指标|质检)/u.test(input)) {
2298
- return input.match(/建模|模型|数据|同步|权限|审批|托管|多端|工作流|指标|质检/u)?.[0];
2299
- }
2300
- return undefined;
2301
- }
2302
- async function formatSymphonyStatus(interactive) {
2303
- const enabled = interactive.__linxSymphonyModeEnabled === true;
2304
- const [source, workersRead, issuesRead, reportsRead] = await Promise.all([
2305
- resolveSymphonySourceContext(interactive),
2306
- listRunningSymphonyWorkers(interactive),
2307
- listOpenSymphonyIssues(interactive),
2308
- listRecentSymphonyReports(interactive),
2309
- ]);
2310
- const workers = workersRead.items;
2311
- const issues = issuesRead.items;
2312
- const reports = reportsRead.items;
2313
- const controlStateErrors = Array.from(new Set([
2314
- workersRead.error,
2315
- issuesRead.error,
2316
- reportsRead.error,
2317
- ].filter((item) => Boolean(item))));
2318
- const controlStateSources = new Set([workersRead.source, issuesRead.source, reportsRead.source]);
2319
- const lines = [
2320
- `Symphony is ${enabled ? 'on' : 'off'}.`,
2321
- `Current chat peer: ${enabled ? 'Secretary' : 'worker/backend peer'}.`,
2322
- `Open issues: ${issues.length}`,
2323
- `Running workers: ${workers.length}`,
2324
- `Recent reports: ${reports.length}`,
2325
- controlStateErrors.length > 0
2326
- ? `Pod control state: unavailable (${formatSymphonyStatusError(controlStateErrors[0])})`
2327
- : controlStateSources.has('pod')
2328
- ? 'Pod control state: active.'
2329
- : 'Pod control state: portable local archive mode.',
2330
- 'Skills: issue triage, existing issue lookup, create/update/ask decision, task split, worker dispatch, status/report tracking.',
2331
- 'Delegation target: AI Secretary must choose a Chat resource before dispatch.',
2332
- 'Allowed targets: personal AI contact chat or group chat.',
2333
- 'Thread role: concrete work timeline under the selected Chat.',
2334
- 'Session role: backend runtime lifecycle only.',
2335
- ];
2336
- for (const issue of issues.slice(0, 5)) {
2337
- lines.push(` - ${formatSymphonyIssueStatus(issue)}`);
2338
- }
2339
- if (issues.length > 5) {
2340
- lines.push(` ... ${issues.length - 5} more open issue(s)`);
2341
- }
2342
- for (const worker of workers.slice(0, 5)) {
2343
- lines.push(` - ${formatSymphonyWorkerStatus(worker)}`);
2344
- }
2345
- if (workers.length > 5) {
2346
- lines.push(` ... ${workers.length - 5} more running worker(s)`);
2347
- }
2348
- for (const report of reports.slice(0, 5)) {
2349
- lines.push(` - ${formatSymphonyReportStatus(report)}`);
2350
- }
2351
- if (reports.length > 5) {
2352
- lines.push(` ... ${reports.length - 5} more recent report(s)`);
2353
- }
2354
- if (source) {
2355
- lines.push('Source conversation:', ` Chat: ${source.chat}`, ` Thread: ${source.thread}`, ...(source.sessionId ? [` Runtime session: ${source.sessionId}`] : []));
2356
- }
2357
- else {
2358
- lines.push('Source conversation: unavailable until LinX has WebID and session id.');
2359
- }
2360
- lines.push('Commands: /symphony on chat with Secretary, /symphony status inspect workers, /symphony off chat with worker/backend.');
2361
- return lines.join('\n');
2362
- }
2363
- function formatSymphonyStatusError(message) {
2364
- return message.replace(/\s+/gu, ' ').trim().slice(0, 180);
2365
- }
2366
- function resolveSymphonyStatusPodTimeoutMs(interactive) {
2367
- const value = Number(interactive?.__linxSymphonyStatusPodTimeoutMs);
2368
- return Number.isFinite(value) && value > 0 ? value : SYMPHONY_STATUS_POD_TIMEOUT_MS;
2369
- }
2370
- async function withSymphonyStatusTimeout(interactive, label, task) {
2371
- const timeoutMs = resolveSymphonyStatusPodTimeoutMs(interactive);
2372
- let timer = null;
2373
- try {
2374
- return await Promise.race([
2375
- task,
2376
- new Promise((_, reject) => {
2377
- timer = setTimeout(() => {
2378
- reject(new Error(`${label} timed out after ${timeoutMs}ms`));
2379
- }, timeoutMs);
2380
- }),
2381
- ]);
2382
- }
2383
- finally {
2384
- if (timer) {
2385
- clearTimeout(timer);
2386
- }
2387
- }
2388
- }
2389
- async function listOpenSymphonyIssues(interactive) {
2390
- const controlRuntime = interactive?.__linxSymphonyPodProjectionRuntime;
2391
- try {
2392
- if (controlRuntime?.issueResource) {
2393
- const podIssues = await withSymphonyStatusTimeout(interactive, 'Symphony Pod issue status', listOpenSymphonyIssuesFromPod({ runtime: controlRuntime }));
2394
- if (podIssues) {
2395
- return { items: podIssues, source: 'pod' };
2396
- }
2397
- }
2398
- }
2399
- catch (error) {
2400
- return { items: [], source: 'none', error: error instanceof Error ? error.message : String(error) };
2401
- }
2402
- if (controlRuntime?.issueResource) {
2403
- return {
2404
- items: [],
2405
- source: 'none',
2406
- error: 'No active Pod session; Symphony control-plane state is Pod-authoritative.',
2407
- };
2408
- }
2409
- try {
2410
- const issues = typeof interactive?.__linxListSymphonyIssues === 'function'
2411
- ? interactive.__linxListSymphonyIssues()
2412
- : listSymphonyIssues();
2413
- return {
2414
- items: issues.filter((issue) => issue.status !== 'closed' && issue.status !== 'resolved'),
2415
- source: 'local',
2416
- };
2417
- }
2418
- catch {
2419
- return { items: [], source: 'none' };
2420
- }
2421
- }
2422
- async function listRunningSymphonyWorkers(interactive) {
2423
- const controlRuntime = interactive?.__linxSymphonyPodProjectionRuntime;
2424
- try {
2425
- if (controlRuntime?.sessionResource) {
2426
- const podWorkers = await withSymphonyStatusTimeout(interactive, 'Symphony Pod worker status', listRunningSymphonyWorkersFromPod({ runtime: controlRuntime }));
2427
- if (podWorkers) {
2428
- return { items: podWorkers, source: 'pod' };
2429
- }
2430
- }
2431
- }
2432
- catch (error) {
2433
- return { items: [], source: 'none', error: error instanceof Error ? error.message : String(error) };
2434
- }
2435
- if (controlRuntime?.sessionResource) {
2436
- return {
2437
- items: [],
2438
- source: 'none',
2439
- error: 'No active Pod session; Symphony control-plane state is Pod-authoritative.',
2440
- };
2441
- }
2442
- try {
2443
- if (typeof interactive?.__linxListSymphonySessions === 'function') {
2444
- const sessions = interactive.__linxListSymphonySessions();
2445
- return {
2446
- items: sessions.filter((session) => session.status === 'running'),
2447
- source: 'local',
2448
- };
2449
- }
2450
- return {
2451
- items: listSymphonySessions()
2452
- .filter((session) => session.status === 'running'),
2453
- source: 'local',
2454
- };
2455
- }
2456
- catch {
2457
- return { items: [], source: 'none' };
2458
- }
2459
- }
2460
- async function listRecentSymphonyReports(interactive) {
2461
- const controlRuntime = interactive?.__linxSymphonyPodProjectionRuntime;
2462
- try {
2463
- if (controlRuntime?.deliveryResource) {
2464
- const podReports = await withSymphonyStatusTimeout(interactive, 'Symphony Pod report status', listRecentSymphonyReportsFromPod({
2465
- runtime: controlRuntime,
2466
- limit: 5,
2467
- }));
2468
- if (podReports) {
2469
- return { items: podReports, source: 'pod' };
2470
- }
2471
- }
2472
- }
2473
- catch (error) {
2474
- return { items: [], source: 'none', error: error instanceof Error ? error.message : String(error) };
2475
- }
2476
- if (controlRuntime?.deliveryResource) {
2477
- return {
2478
- items: [],
2479
- source: 'none',
2480
- error: 'No active Pod session; Symphony control-plane state is Pod-authoritative.',
2481
- };
2482
- }
2483
- try {
2484
- const sessions = typeof interactive?.__linxListSymphonySessions === 'function'
2485
- ? interactive.__linxListSymphonySessions()
2486
- : listSymphonySessions();
2487
- return {
2488
- items: sessions
2489
- .filter((session) => session.status === 'completed' || session.status === 'failed')
2490
- .slice(0, 5),
2491
- source: 'local',
2492
- };
2493
- }
2494
- catch {
2495
- return { items: [], source: 'none' };
2496
- }
2497
- }
2498
- function formatSymphonyWorkerStatus(session) {
2499
- const target = session.target?.label
2500
- ?? session.target?.agent
2501
- ?? session.target?.chat
2502
- ?? session.backend;
2503
- const suffix = [
2504
- session.autoModeSessionId ? `runtime=${session.autoModeSessionId}` : undefined,
2505
- session.target?.chat ? `chat=${session.target.chat}` : undefined,
2506
- session.cwd ? `cwd=${session.cwd}` : undefined,
2507
- ].filter(Boolean).join(' · ');
2508
- return `${session.backend}/${session.mode} -> ${target}${suffix ? ` (${suffix})` : ''}`;
2509
- }
2510
- function formatSymphonyReportStatus(report) {
2511
- const status = report.status;
2512
- const reportRecord = report;
2513
- const target = reportRecord.agent
2514
- ?? reportRecord.target?.label
2515
- ?? reportRecord.target?.agent
2516
- ?? report.backend;
2517
- const title = 'summary' in report && report.summary
2518
- ? report.summary
2519
- : 'title' in report && report.title
2520
- ? report.title
2521
- : 'task' in report && report.task
2522
- ? formatSymphonyResourceTail(report.task)
2523
- : undefined;
2524
- const suffix = [
2525
- report.autoModeSessionId ? `runtime=${report.autoModeSessionId}` : undefined,
2526
- 'thread' in report && report.thread ? `thread=${report.thread}` : undefined,
2527
- 'completedAt' in report && report.completedAt ? `completed=${report.completedAt}` : undefined,
2528
- report.error ? `error=${report.error}` : undefined,
2529
- ].filter(Boolean).join(' · ');
2530
- return `${status} ${report.backend} -> ${target}${title ? `: ${title}` : ''}${suffix ? ` (${suffix})` : ''}`;
2531
- }
2532
- function formatSymphonyIssueStatus(issue) {
2533
- const taskCount = issue.tasks?.length ?? 0;
2534
- const suffix = [
2535
- formatSymphonyResourceTail(issue.uri),
2536
- taskCount > 0 ? `${taskCount} task${taskCount === 1 ? '' : 's'}` : undefined,
2537
- issue.thread ? `thread=${issue.thread}` : undefined,
2538
- ].filter(Boolean).join(' · ');
2539
- return `${issue.status} ${issue.title}${suffix ? ` (${suffix})` : ''}`;
2540
- }
2541
- function formatSymphonyResourceTail(uri) {
2542
- if (!uri) {
2543
- return undefined;
2544
- }
2545
- return uri.trim().match(/[:/#]([^:/#]+)$/u)?.[1] ?? uri;
2546
- }
2547
- async function resolveSymphonySourceContext(interactive) {
2548
- const sessionId = interactive?.sessionManager?.getSessionId?.()
2549
- ?? interactive?.session?.sessionManager?.getSessionId?.()
2550
- ?? interactive?.session?.sessionId;
2551
- const webId = await resolveSymphonyWebId(interactive);
2552
- if (typeof sessionId !== 'string' || !sessionId.trim() || !webId) {
2553
- return undefined;
2554
- }
2555
- const trimmedSessionId = sessionId.trim();
2556
- return {
2557
- chat: secretaryChatUri(webId),
2558
- thread: secretaryThreadUri(webId, trimmedSessionId, DEFAULT_SECRETARY_CHAT_ID),
2559
- sessionId: trimmedSessionId,
2560
- };
2561
- }
2562
- async function resolveSymphonyWebId(interactive) {
2563
- const candidates = [
2564
- interactive?.podSession?.webId,
2565
- interactive?.runtime?.podSession?.webId,
2566
- interactive?.session?.podSession?.webId,
2567
- interactive?.session?.runtime?.podSession?.webId,
2568
- interactive?.session?.state?.webId,
2569
- interactive?.state?.webId,
2570
- ];
2571
- for (const candidate of candidates) {
2572
- if (typeof candidate === 'string' && candidate.trim()) {
2573
- return candidate.trim();
2574
- }
2575
- }
2576
- const podSession = await interactive?.runtime?.getPodDataSession?.().catch(() => null);
2577
- if (typeof podSession?.webId === 'string' && podSession.webId.trim()) {
2578
- interactive.runtime.podSession = podSession;
2579
- return podSession.webId.trim();
2580
- }
2581
- return undefined;
2582
- }
2583
- async function promptForBackendCredential(interactive, details) {
2584
- const reason = details.reason ?? 'missing';
2585
- const repairLabel = formatBackendCredentialRepairReason(reason);
2586
- interactive.showStatus?.(`AI Secretary detected ${repairLabel} ${details.providerLabel} credentials before this backend can answer. ` +
2587
- 'Enter them here; LinX will save them to your Pod AI settings and retry the message.');
2588
- if (canRenderPiLoginDialog(interactive)) {
2589
- return promptForApiCredentialWithPiDialog(interactive, {
2590
- title: `Connect ${details.providerLabel}`,
2591
- providerId: details.providerId,
2592
- providerLabel: details.providerLabel,
2593
- providerIdPrompt: details.providerIdPrompt,
2594
- apiKeyPrompt: details.apiKeyPrompt,
2595
- baseUrlPrompt: details.baseUrlPrompt,
2596
- progress: [
2597
- `AI Secretary detected ${repairLabel} credentials.`,
2598
- 'LinX will save this with `linx ai connect` semantics into your Pod AI settings.',
2599
- ],
2600
- errorPrefix: `Failed to collect ${details.providerLabel} credentials`,
2601
- });
2602
- }
2603
- return promptForBackendCredentialWithExtensionInput(interactive, details, repairLabel);
2604
- }
2605
- async function handleInteractiveAiConnectCommand(interactive, runtime, command) {
2606
- const providerId = command.provider?.trim();
2607
- if (!providerId) {
2608
- interactive.showStatus?.('Usage: /ai connect <provider> [--base-url <url>] [--model <model>] - connect an AI provider key to LinX Pod AI settings.');
2609
- interactive.ui?.requestRender?.();
2610
- return;
2611
- }
2612
- const metadata = getAIConfigProviderMetadata(providerId);
2613
- const providerLabel = metadata.displayName ?? metadata.id;
2614
- const credential = canRenderPiLoginDialog(interactive)
2615
- ? await promptForApiCredentialWithPiDialog(interactive, {
2616
- title: `Connect ${providerLabel}`,
2617
- providerId: metadata.id,
2618
- providerLabel,
2619
- apiKeyPrompt: `${providerLabel} API key`,
2620
- baseUrlPrompt: command.baseUrl ? undefined : 'API base URL',
2621
- progress: [
2622
- `Connect ${providerLabel} with LinX AI connect.`,
2623
- 'LinX will save this provider key to your Pod AI settings, not Pi auth.json.',
2624
- ...(command.model ? [`Default model: ${command.model}`] : []),
2625
- ],
2626
- errorPrefix: `Failed to connect ${providerLabel}`,
2627
- })
2628
- : await promptForApiCredentialWithExtensionInput(interactive, {
2629
- providerId: metadata.id,
2630
- providerLabel,
2631
- apiKeyPrompt: `${providerLabel} API key`,
2632
- baseUrlPrompt: command.baseUrl ? undefined : 'API base URL',
2633
- repairLabel: 'connect',
2634
- });
2635
- const apiKey = credential?.apiKey?.trim();
2636
- if (!apiKey) {
2637
- interactive.showStatus?.(`${providerLabel} AI connect cancelled.`);
2638
- interactive.ui?.requestRender?.();
2639
- return;
2640
- }
2641
- try {
2642
- const saveCredential = resolveInteractiveAiConnectCredentialSaver(interactive, runtime);
2643
- const credentialProviderId = credential?.providerId?.trim();
2644
- const credentialBaseUrl = credential?.baseUrl?.trim() || command.baseUrl?.trim();
2645
- const model = command.model?.trim();
2646
- const result = await saveCredential({
2647
- provider: credentialProviderId || metadata.id,
2648
- apiKey,
2649
- ...(credentialBaseUrl ? { baseUrl: credentialBaseUrl } : {}),
2650
- ...(model ? { model } : {}),
2651
- });
2652
- interactive.showStatus?.(`Connected AI provider ${result.providerId} to LinX Pod AI settings. api-key: ${result.maskedApiKey}`);
2653
- interactive.session?.modelRegistry?.refresh?.();
2654
- await interactive.updateAvailableProviderCount?.();
2655
- }
2656
- catch (error) {
2657
- const message = error instanceof Error ? error.message : String(error);
2658
- interactive.showError?.(`LinX AI connect failed: ${message}`);
2659
- }
2660
- finally {
2661
- interactive.ui?.requestRender?.();
2662
- }
2663
- }
2664
- function resolveInteractiveAiConnectCredentialSaver(interactive, runtime) {
2665
- const candidates = [
2666
- runtime?.connectAiProviderCredential,
2667
- interactive?.__linxConnectAiProviderCredential,
2668
- interactive?.runtime?.connectAiProviderCredential,
2669
- ];
2670
- for (const candidate of candidates) {
2671
- if (typeof candidate === 'function') {
2672
- return candidate;
2673
- }
2674
- }
2675
- return connectAiProviderCredential;
2676
- }
2677
- async function promptForApiCredentialWithPiDialog(interactive, details) {
2678
- const dialog = new LoginDialogComponent(interactive.ui, details.providerId, () => undefined, details.providerLabel, details.title);
2679
- const restoreEditor = () => {
2680
- interactive.editorContainer.clear();
2681
- interactive.editorContainer.addChild(interactive.editor);
2682
- interactive.ui?.setFocus?.(interactive.editor);
2683
- interactive.ui?.requestRender?.();
2684
- };
2685
- interactive.editorContainer.clear();
2686
- interactive.editorContainer.addChild(dialog);
2687
- interactive.ui?.setFocus?.(dialog);
2688
- interactive.ui?.requestRender?.();
2689
- try {
2690
- for (const line of details.progress ?? []) {
2691
- dialog.showProgress(line);
2692
- }
2693
- let providerId = details.providerId;
2694
- if (details.providerIdPrompt) {
2695
- const providerIdValue = await dialog.showPrompt(`Enter ${details.providerIdPrompt}:`, details.providerId);
2696
- providerId = typeof providerIdValue === 'string' && providerIdValue.trim()
2697
- ? providerIdValue.trim()
2698
- : details.providerId;
2699
- }
2700
- const apiKeyValue = await dialog.showPrompt(`Enter ${details.apiKeyPrompt}:`);
2701
- const apiKey = typeof apiKeyValue === 'string' ? apiKeyValue.trim() : '';
2702
- if (!apiKey) {
2703
- return null;
2704
- }
2705
- let baseUrl;
2706
- if (details.baseUrlPrompt) {
2707
- const baseUrlValue = await dialog.showPrompt(`Enter ${details.baseUrlPrompt} (optional):`);
2708
- baseUrl = typeof baseUrlValue === 'string' && baseUrlValue.trim()
2709
- ? baseUrlValue.trim()
2710
- : undefined;
2711
- }
2712
- return { providerId, apiKey, ...(baseUrl ? { baseUrl } : {}) };
2713
- }
2714
- catch (error) {
2715
- const message = error instanceof Error ? error.message : String(error);
2716
- if (message !== 'Login cancelled') {
2717
- interactive.showError?.(`${details.errorPrefix}: ${message}`);
2718
- }
2719
- return null;
2720
- }
2721
- finally {
2722
- restoreEditor();
2723
- }
2724
- }
2725
- function canRenderPiLoginDialog(interactive) {
2726
- return Boolean(interactive?.isInitialized === true
2727
- && interactive?.ui
2728
- && interactive?.editor
2729
- && typeof interactive?.editorContainer?.clear === 'function'
2730
- && typeof interactive?.editorContainer?.addChild === 'function'
2731
- && typeof interactive?.ui?.setFocus === 'function'
2732
- && typeof interactive?.ui?.requestRender === 'function');
2733
- }
2734
- async function promptForBackendCredentialWithExtensionInput(interactive, details, repairLabel) {
2735
- return promptForApiCredentialWithExtensionInput(interactive, {
2736
- providerId: details.providerId,
2737
- providerLabel: details.providerLabel,
2738
- providerIdPrompt: details.providerIdPrompt,
2739
- apiKeyPrompt: details.apiKeyPrompt,
2740
- baseUrlPrompt: details.baseUrlPrompt,
2741
- repairLabel,
2742
- });
2743
- }
2744
- async function promptForApiCredentialWithExtensionInput(interactive, details) {
2745
- const repairLabel = details.repairLabel;
2746
- const apiKeyTitle = [
2747
- `${details.providerLabel} ${repairLabel} credential`,
2748
- `Paste an ${details.apiKeyPrompt}; LinX will save it to your Pod AI settings.`,
2749
- 'Press Escape to cancel.',
2750
- ].join('\n');
2751
- if (typeof interactive.showExtensionInput !== 'function') {
2752
- interactive.showError?.(`This terminal cannot collect ${details.providerLabel} credentials inside the TUI. Run \`linx ai connect ${details.providerId}\` first.`);
2753
- return null;
2754
- }
2755
- let providerId = details.providerId;
2756
- if (details.providerIdPrompt) {
2757
- const providerIdTitle = [
2758
- `${details.providerLabel} ${repairLabel} provider`,
2759
- 'Enter the provider id to store under /settings/providers/{provider}.ttl.',
2760
- `Default: ${details.providerId}`,
2761
- 'Press Escape to cancel.',
2762
- ].join('\n');
2763
- const providerIdValue = await interactive.showExtensionInput(providerIdTitle, details.providerIdPrompt);
2764
- providerId = typeof providerIdValue === 'string' && providerIdValue.trim()
2765
- ? providerIdValue.trim()
2766
- : details.providerId;
2767
- }
2768
- const apiKeyValue = await interactive.showExtensionInput(apiKeyTitle, details.apiKeyPrompt);
2769
- const apiKey = typeof apiKeyValue === 'string' ? apiKeyValue.trim() : '';
2770
- if (!apiKey) {
2771
- return null;
2772
- }
2773
- let baseUrl;
2774
- if (details.baseUrlPrompt) {
2775
- const baseUrlTitle = [
2776
- `${details.providerLabel} ${repairLabel} base URL`,
2777
- 'Optional. Leave empty to use the shared provider default.',
2778
- 'Press Escape to cancel.',
2779
- ].join('\n');
2780
- const baseUrlValue = await interactive.showExtensionInput(baseUrlTitle, details.baseUrlPrompt);
2781
- baseUrl = typeof baseUrlValue === 'string' && baseUrlValue.trim()
2782
- ? baseUrlValue.trim()
2783
- : undefined;
2784
- }
2785
- return { providerId, apiKey, ...(baseUrl ? { baseUrl } : {}) };
2786
- }
2787
- function formatBackendCredentialRepairReason(reason) {
2788
- return reason === 'invalid' ? 'invalid' : 'missing';
2789
- }
2790
- function installLinxCwdStartupNotice(interactive, sessionCwd) {
2791
- const originalInit = interactive.init?.bind(interactive);
2792
- if (typeof originalInit !== 'function')
2793
- return;
2794
- interactive.init = async function patchedInit(...args) {
2795
- const result = await originalInit(...args);
2796
- const storedCwd = interactive?.session?.cwd ?? sessionCwd;
2797
- const currentCwd = process.cwd();
2798
- if (currentCwd !== storedCwd) {
2799
- setTimeout(() => {
2800
- process.stdout.write(`\n\x1b[33m Session was at ${storedCwd}\x1b[0m\n` +
2801
- `\x1b[33m You're now at ${currentCwd}\x1b[0m\n`);
2802
- }, 300);
2803
- }
2804
- return result;
2805
- };
2806
- }
2807
- export function installLinxEscapeInterrupt(interactive) {
2808
- const editor = interactive?.defaultEditor;
2809
- if (!editor || editor.__linxEscapeInterruptInstalled) {
2810
- return;
2811
- }
2812
- const initialOnEscape = typeof editor.onEscape === 'function'
2813
- ? editor.onEscape
2814
- : undefined;
2815
- let currentOnEscape = isLinxEscapeInterruptWrapper(initialOnEscape)
2816
- ? undefined
2817
- : initialOnEscape;
2818
- let lastIdleEscapeTime = 0;
2819
- const linxEscapeInterrupt = function linxEscapeInterrupt() {
2820
- const session = interactive?.session;
2821
- if (handBackAutoControlOnInterrupt(interactive)) {
2822
- lastIdleEscapeTime = 0;
2823
- return;
2824
- }
2825
- if (session?.isBashRunning && typeof session.abortBash === 'function') {
2826
- lastIdleEscapeTime = 0;
2827
- void session.abortBash();
2828
- return;
2829
- }
2830
- if (isLinxSessionRunning(interactive) && typeof session?.abort === 'function') {
2831
- lastIdleEscapeTime = 0;
2832
- void session.abort();
2833
- return;
2834
- }
2835
- if (shouldHandleLinxIdleDoubleEscape(interactive)) {
2836
- const now = Date.now();
2837
- if (now - lastIdleEscapeTime < 500) {
2838
- lastIdleEscapeTime = 0;
2839
- void openInteractiveRewindFromEscape(interactive);
2840
- }
2841
- else {
2842
- lastIdleEscapeTime = now;
2843
- interactive?.showStatus?.('Press Escape again to rewind this session.');
2844
- interactive?.ui?.requestRender?.();
2845
- }
2846
- return;
2847
- }
2848
- lastIdleEscapeTime = 0;
2849
- currentOnEscape?.call(editor);
2850
- };
2851
- Object.defineProperty(linxEscapeInterrupt, '__linxEscapeInterruptWrapper', {
2852
- value: true,
2853
- });
2854
- Object.defineProperty(editor, 'onEscape', {
2855
- configurable: true,
2856
- get() {
2857
- return linxEscapeInterrupt;
2858
- },
2859
- set(next) {
2860
- if (isLinxEscapeInterruptWrapper(next)) {
2861
- return;
2862
- }
2863
- currentOnEscape = typeof next === 'function' ? next : undefined;
2864
- },
2865
- });
2866
- installLinxClearInterrupt(interactive, editor);
2867
- editor.__linxEscapeInterruptInstalled = true;
2868
- }
2869
- function shouldHandleLinxIdleDoubleEscape(interactive) {
2870
- if (typeof interactive?.editor?.getText !== 'function') {
2871
- return false;
2872
- }
2873
- const text = String(interactive.editor.getText() ?? '');
2874
- return text.trim().length === 0;
2875
- }
2876
- async function openInteractiveRewindFromEscape(interactive) {
2877
- try {
2878
- await handleInteractiveRewindSelector(interactive, interactive?.runtime);
2879
- }
2880
- catch (error) {
2881
- interactive?.showError?.(error instanceof Error ? error.message : String(error));
2882
- }
2883
- }
2884
- function isLinxEscapeInterruptWrapper(value) {
2885
- return typeof value === 'function'
2886
- && value.__linxEscapeInterruptWrapper === true;
2887
- }
2888
- function installLinxClearInterrupt(interactive, editor) {
2889
- const handlers = editor?.actionHandlers;
2890
- if (!(handlers instanceof Map) || editor.__linxClearInterruptInstalled) {
2891
- return;
2892
- }
2893
- const originalClear = handlers.get('app.clear');
2894
- handlers.set('app.clear', () => {
2895
- if (handBackAutoControlOnInterrupt(interactive)) {
2896
- return;
2897
- }
2898
- originalClear?.call(editor);
2899
- });
2900
- editor.__linxClearInterruptInstalled = true;
2901
- }
2902
- function handBackAutoControlOnInterrupt(interactive) {
2903
- if (interactive?.__autoEnabled !== true) {
2904
- return false;
2905
- }
2906
- const session = interactive?.session;
2907
- if (session?.isBashRunning && typeof session.abortBash === 'function') {
2908
- void session.abortBash();
2909
- }
2910
- else if (isLinxSessionRunning(interactive) && typeof session?.abort === 'function') {
2911
- void session.abort();
2912
- }
2913
- void handleInteractiveAutoCommand(interactive, interactive?.runtime, false);
2914
- return true;
2915
- }
2916
- function isLinxSessionRunning(interactive) {
2917
- return interactive?.session?.isStreaming === true
2918
- || Boolean(interactive?.loadingAnimation)
2919
- || Boolean(interactive?.autoCompactionEscapeHandler)
2920
- || Boolean(interactive?.retryEscapeHandler);
2921
- }
2922
- function patchInteractiveExitMessage(interactive) {
2923
- const originalInit = interactive.init?.bind(interactive);
2924
- const originalStop = interactive.stop?.bind(interactive);
2925
- let initialized = false;
2926
- let exitMessageWritten = false;
2927
- if (typeof originalInit === 'function') {
2928
- interactive.init = async function patchedInit(...args) {
2929
- const result = await originalInit(...args);
2930
- initialized = true;
2931
- return result;
2932
- };
2933
- }
2934
- if (typeof originalStop !== 'function') {
2935
- return;
2936
- }
2937
- interactive.stop = function patchedStop(...args) {
2938
- originalStop(...args);
2939
- if (!initialized || exitMessageWritten || process.env.LINX_TUI_NO_EXIT_MESSAGE === '1') {
2940
- return;
2941
- }
2942
- exitMessageWritten = true;
2943
- if (process.stdout.isTTY) {
2944
- process.stdout.write(`\n${buildLinxExitMessage(this)}\n`);
2945
- }
2946
- };
2947
- }
2948
- export function buildLinxExitMessage(interactive) {
2949
- const sessionId = interactive?.session?.sessionId
2950
- ?? interactive?.sessionManager?.getSessionId?.()
2951
- ?? interactive?.session?.sessionManager?.getSessionId?.();
2952
- const usage = calculateSessionUsage(interactive?.session);
2953
- const lines = ['LinX session closed.'];
2954
- if (usage.input > 0 || usage.output > 0 || usage.cacheRead > 0 || usage.cacheWrite > 0) {
2955
- const usageParts = [
2956
- `input ${formatTokenCount(usage.input)}`,
2957
- `output ${formatTokenCount(usage.output)}`,
2958
- ];
2959
- if (usage.cacheRead > 0 || usage.cacheWrite > 0) {
2960
- usageParts.push(`cache ${usage.cacheRate ?? 0}%`);
2961
- }
2962
- lines.push(`Token usage: ${usageParts.join(' · ')}`);
2963
- }
2964
- if (typeof sessionId === 'string' && sessionId.trim()) {
2965
- lines.push(`Resume: linx --session ${sessionId}`);
2966
- }
2967
- return lines.join('\n');
2968
- }
2969
- export function installLinxResumeOutputStyle() {
2970
- if (linxResumeOutputStyleRestore) {
2971
- return linxResumeOutputStyleRestore;
2972
- }
2973
- const originalWrite = process.stdout.write;
2974
- const originalErrorWrite = process.stderr.write;
2975
- const stdoutFilter = createPiResumeOutputFilter();
2976
- const stderrFilter = createPiResumeOutputFilter();
2977
- const patchedStdoutWrite = function patchedPersistentLinxStdoutWrite(chunk, encodingOrCallback, callback) {
2978
- return writeWithPiResumeFilter(process.stdout, originalWrite, stdoutFilter, chunk, encodingOrCallback, callback);
2979
- };
2980
- const patchedStderrWrite = function patchedPersistentLinxStderrWrite(chunk, encodingOrCallback, callback) {
2981
- return writeWithPiResumeFilter(process.stderr, originalErrorWrite, stderrFilter, chunk, encodingOrCallback, callback);
2982
- };
2983
- process.stdout.write = patchedStdoutWrite;
2984
- process.stderr.write = patchedStderrWrite;
2985
- linxResumeOutputStyleRestore = () => {
2986
- flushPiResumeOutputFilter(process.stdout, originalWrite, stdoutFilter);
2987
- flushPiResumeOutputFilter(process.stderr, originalErrorWrite, stderrFilter);
2988
- if (process.stdout.write === patchedStdoutWrite) {
2989
- process.stdout.write = originalWrite;
2990
- }
2991
- if (process.stderr.write === patchedStderrWrite) {
2992
- process.stderr.write = originalErrorWrite;
2993
- }
2994
- linxResumeOutputStyleRestore = null;
2995
- };
2996
- return linxResumeOutputStyleRestore;
2997
- }
2998
- export async function withLinxResumeOutputStyle(run) {
2999
- const originalWrite = process.stdout.write;
3000
- const originalErrorWrite = process.stderr.write;
3001
- const stdoutFilter = createPiResumeOutputFilter();
3002
- const stderrFilter = createPiResumeOutputFilter();
3003
- process.stdout.write = function patchedLinxStdoutWrite(chunk, encodingOrCallback, callback) {
3004
- return writeWithPiResumeFilter(process.stdout, originalWrite, stdoutFilter, chunk, encodingOrCallback, callback);
3005
- };
3006
- process.stderr.write = function patchedLinxStderrWrite(chunk, encodingOrCallback, callback) {
3007
- return writeWithPiResumeFilter(process.stderr, originalErrorWrite, stderrFilter, chunk, encodingOrCallback, callback);
3008
- };
3009
- try {
3010
- const result = await run();
3011
- await new Promise((resolve) => setImmediate(resolve));
3012
- return result;
3013
- }
3014
- finally {
3015
- flushPiResumeOutputFilter(process.stdout, originalWrite, stdoutFilter);
3016
- flushPiResumeOutputFilter(process.stderr, originalErrorWrite, stderrFilter);
3017
- process.stdout.write = originalWrite;
3018
- process.stderr.write = originalErrorWrite;
3019
- }
3020
- }
3021
- /** @deprecated Use withLinxResumeOutputStyle. */
3022
- export const withSuppressedPiResumeOutput = withLinxResumeOutputStyle;
3023
- function createPiResumeOutputFilter() {
3024
- return { pending: '', suppressing: false };
3025
- }
3026
- function writeWithPiResumeFilter(stream, originalWrite, filter, chunk, encodingOrCallback, callback) {
3027
- const text = typeof chunk === 'string'
3028
- ? chunk
3029
- : Buffer.isBuffer(chunk) || chunk instanceof Uint8Array
3030
- ? Buffer.from(chunk).toString('utf8')
3031
- : '';
3032
- if (!text) {
3033
- return originalWrite.call(stream, chunk, encodingOrCallback, callback);
3034
- }
3035
- const output = filterPiResumeOutputText(text, filter);
3036
- if (!output) {
3037
- const done = typeof encodingOrCallback === 'function' ? encodingOrCallback : callback;
3038
- done?.();
3039
- return true;
3040
- }
3041
- if (output === text && !filter.pending) {
3042
- return originalWrite.call(stream, chunk, encodingOrCallback, callback);
3043
- }
3044
- return originalWrite.call(stream, output, encodingOrCallback, callback);
3045
- }
3046
- function flushPiResumeOutputFilter(stream, originalWrite, filter) {
3047
- const pending = filter.pending;
3048
- filter.pending = '';
3049
- if (filter.suppressing) {
3050
- filter.suppressing = false;
3051
- return;
3052
- }
3053
- if (!pending || isPotentialPiResumeOutput(pending)) {
3054
- return;
3055
- }
3056
- originalWrite.call(stream, pending);
3057
- }
3058
- function filterPiResumeOutputText(text, filter) {
3059
- let input = filter.pending + text;
3060
- filter.pending = '';
3061
- let output = '';
3062
- while (input) {
3063
- const newlineIndex = input.indexOf('\n');
3064
- if (newlineIndex >= 0) {
3065
- const line = input.slice(0, newlineIndex + 1);
3066
- if (filter.suppressing) {
3067
- filter.suppressing = false;
3068
- }
3069
- else if (!isPiResumeOutput(line)) {
3070
- output += line;
3071
- }
3072
- input = input.slice(newlineIndex + 1);
3073
- continue;
3074
- }
3075
- if (filter.suppressing) {
3076
- return output;
3077
- }
3078
- if (isPiResumeOutput(input)) {
3079
- filter.suppressing = true;
3080
- return output;
3081
- }
3082
- if (isPotentialPiResumeOutput(input)) {
3083
- filter.pending = input;
3084
- return output;
3085
- }
3086
- output += input;
3087
- return output;
3088
- }
3089
- return output;
3090
- }
3091
- function isPiResumeOutput(text) {
3092
- if (!text) {
3093
- return false;
3094
- }
3095
- const plain = stripAnsi(text);
3096
- return /To resume this session:\s*pi\s+--session(?:-dir|\s)/u.test(plain)
3097
- || /To resume this session:\s*pi\s+/u.test(plain);
3098
- }
3099
- function isPotentialPiResumeOutput(text) {
3100
- const plain = stripAnsi(text).trimStart();
3101
- if (!plain || plain.length >= 512) {
3102
- return false;
3103
- }
3104
- const marker = 'To resume this session:';
3105
- if (marker.startsWith(plain)) {
3106
- return true;
3107
- }
3108
- if (!plain.startsWith(marker)) {
3109
- return false;
3110
- }
3111
- const commandPrefix = plain.slice(marker.length).trimStart();
3112
- return !commandPrefix
3113
- || 'pi --session-dir'.startsWith(commandPrefix)
3114
- || 'pi --session'.startsWith(commandPrefix);
3115
- }
3116
- function stripAnsi(text) {
3117
- return text.replace(/\x1b\[[0-9;]*m/gu, '');
3118
- }
3119
- function patchPiFooter() {
3120
- if (footerPatched) {
3121
- return;
3122
- }
3123
- const originalRender = FooterComponent.prototype.render;
3124
- FooterComponent.prototype.render = function patchedRender(width) {
3125
- const lines = originalRender.call(this, width);
3126
- if (Array.isArray(lines) && lines.length > 1 && typeof lines[1] === 'string') {
3127
- const session = this.session;
3128
- const autoCompactEnabled = this.autoCompactEnabled !== false;
3129
- const footerData = this.footerData;
3130
- const modePrefix = buildLinxFooterModePrefix();
3131
- const modeLen = visibleWidth(modePrefix);
3132
- const bulletLen = modeLen > 0 ? 3 : 0;
3133
- const statusWidth = Math.max(0, width - modeLen - bulletLen);
3134
- lines[1] = buildLinxFooterStatusLine({
3135
- session,
3136
- width: statusWidth,
3137
- autoCompactEnabled,
3138
- footerData: footerData,
3139
- });
3140
- if (modePrefix) {
3141
- lines[1] = modePrefix + ' • ' + lines[1];
3142
- }
3143
- }
3144
- return lines;
3145
- };
3146
- footerPatched = true;
3147
- }
3148
- function buildLinxFooterModePrefix() {
3149
- if (!_linxFooterInteractive)
3150
- return '';
3151
- const autoOn = _linxFooterInteractive.__autoEnabled === true;
3152
- const symphonyOn = _linxFooterInteractive.__linxSymphonyModeEnabled === true;
3153
- if (!autoOn && !symphonyOn)
3154
- return '';
3155
- if (autoOn && symphonyOn)
3156
- return 'Symphony · Auto';
3157
- if (autoOn)
3158
- return 'Auto';
3159
- return 'Symphony';
3160
- }
3161
- export function patchPiAssistantMessageRendering() {
3162
- if (assistantMessagePatched) {
3163
- return;
3164
- }
3165
- const originalUpdateContent = AssistantMessageComponent.prototype.updateContent;
3166
- AssistantMessageComponent.prototype.updateContent = function patchedUpdateContent(message) {
3167
- const sanitizedMessage = stripLinxHiddenAssistantContent(message);
3168
- return originalUpdateContent.call(this, sanitizedMessage);
3169
- };
3170
- assistantMessagePatched = true;
3171
- }
3172
- function stripLinxHiddenAssistantContent(message) {
3173
- if (!isRecord(message) || !Array.isArray(message.content)) {
3174
- return message;
3175
- }
3176
- const content = message.content.filter((part) => !isLinxHiddenAssistantContentPart(part));
3177
- if (content.length === message.content.length) {
3178
- return message;
3179
- }
3180
- return {
3181
- ...message,
3182
- content,
3183
- };
3184
- }
3185
- function isLinxHiddenAssistantContentPart(part) {
3186
- return isRecord(part) && part.type === 'thinking';
3187
- }
3188
- function isRecord(value) {
3189
- return typeof value === 'object' && value !== null;
3190
- }
3191
- //# sourceMappingURL=interactive.js.map