micro-models-agent 2.16.0 → 2.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (427) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/assets/app.js +1 -314
  3. package/dist/assets/hljs.js +5 -1244
  4. package/dist/assets/index.html +11 -0
  5. package/dist/assets/js/api.js +1 -28
  6. package/dist/assets/js/core/menus.js +1 -0
  7. package/dist/assets/js/core/store/feed.js +1 -0
  8. package/dist/assets/js/core/store/index.js +1 -0
  9. package/dist/assets/js/core/store/session.js +1 -0
  10. package/dist/assets/js/core/store/settings.js +1 -0
  11. package/dist/assets/js/core/store/tabs.js +1 -0
  12. package/dist/assets/js/dialog.js +1 -105
  13. package/dist/assets/js/dom.js +1 -178
  14. package/dist/assets/js/feed/core.js +1 -0
  15. package/dist/assets/js/feed/history.js +1 -0
  16. package/dist/assets/js/feed/plan.js +1 -0
  17. package/dist/assets/js/feed/question.js +3 -0
  18. package/dist/assets/js/feed/sidebar.js +1 -0
  19. package/dist/assets/js/feed/stream.js +1 -0
  20. package/dist/assets/js/feed/turn.js +5 -0
  21. package/dist/assets/js/feed.js +1 -1211
  22. package/dist/assets/js/format.js +2 -122
  23. package/dist/assets/js/i18n.js +1 -46
  24. package/dist/assets/js/rail.js +1 -114
  25. package/dist/assets/js/session/cmdk.js +1 -0
  26. package/dist/assets/js/session/composer.js +1 -0
  27. package/dist/assets/js/session/sessions-menu.js +1 -0
  28. package/dist/assets/js/session/tabs.js +1 -0
  29. package/dist/assets/js/settings/controls.js +1 -0
  30. package/dist/assets/js/settings/editors.js +1 -0
  31. package/dist/assets/js/settings/form.js +1 -0
  32. package/dist/assets/js/settings/modal.js +1 -0
  33. package/dist/assets/js/settings/panels.js +1 -0
  34. package/dist/assets/js/settings/providers.js +1 -0
  35. package/dist/assets/js/settings/search.js +1 -0
  36. package/dist/assets/js/settings/subdialog.js +1 -0
  37. package/dist/assets/js/settings/write.js +1 -0
  38. package/dist/assets/js/settings.js +1 -1914
  39. package/dist/assets/js/sidebar/core.js +1 -0
  40. package/dist/assets/js/sidebar/lsp.js +2 -0
  41. package/dist/assets/js/sidebar/map.js +1 -0
  42. package/dist/assets/js/sidebar/state.js +1 -0
  43. package/dist/assets/js/sidebar/trace.js +5 -0
  44. package/dist/assets/js/sidebar.js +1 -705
  45. package/dist/assets/js/store.js +1 -82
  46. package/dist/assets/js/subdialog.js +1 -0
  47. package/dist/assets/md.js +1 -277
  48. package/dist/assets/setup.js +3 -816
  49. package/dist/assets/smd.js +29 -1665
  50. package/dist/assets/styles.css +1 -1592
  51. package/dist/certification/certifications.json +1 -494
  52. package/dist/i18n/en.json +1 -1851
  53. package/dist/i18n/ru.json +1 -1854
  54. package/dist/main.js +815 -55797
  55. package/dist/modules/browser/bridge-server.mjs +2 -252
  56. package/package.json +10 -5
  57. package/dist/assets/js/settings-controls.js +0 -452
  58. package/dist/assets/js/tabs.js +0 -867
  59. package/dist/cli/cache-line.js +0 -31
  60. package/dist/cli/command-suggest.js +0 -39
  61. package/dist/cli/commands.js +0 -781
  62. package/dist/cli/completer.js +0 -268
  63. package/dist/cli/index.js +0 -3
  64. package/dist/cli/json-payload.js +0 -32
  65. package/dist/cli/main.js +0 -346
  66. package/dist/cli/onboarding-commands.js +0 -126
  67. package/dist/cli/plugin-commands.js +0 -38
  68. package/dist/cli/relaunch.js +0 -40
  69. package/dist/cli/repl-commands-lsp.js +0 -75
  70. package/dist/cli/repl-commands-skills.js +0 -105
  71. package/dist/cli/repl-commands.js +0 -1218
  72. package/dist/cli/repl.js +0 -1238
  73. package/dist/cli/run-result.js +0 -38
  74. package/dist/cli/security-commands.js +0 -177
  75. package/dist/cli/setup-order.js +0 -6
  76. package/dist/cli/setup-prompt.js +0 -50
  77. package/dist/cli/setup.js +0 -161
  78. package/dist/cli/web-command.js +0 -98
  79. package/dist/config/budget.js +0 -48
  80. package/dist/config/config.js +0 -345
  81. package/dist/config/defaults.js +0 -197
  82. package/dist/config/domains.js +0 -135
  83. package/dist/config/env.js +0 -46
  84. package/dist/config/experts.js +0 -17
  85. package/dist/config/index.js +0 -5
  86. package/dist/config/paths.js +0 -56
  87. package/dist/config/redact.js +0 -44
  88. package/dist/config/registry-extra.js +0 -334
  89. package/dist/config/registry.js +0 -584
  90. package/dist/config/security.js +0 -212
  91. package/dist/config/timeouts.js +0 -74
  92. package/dist/config/types.js +0 -1
  93. package/dist/config/utils.js +0 -57
  94. package/dist/core/agent/artifact-progress.js +0 -59
  95. package/dist/core/agent/artifact-resolve.js +0 -79
  96. package/dist/core/agent/audit-gate.js +0 -53
  97. package/dist/core/agent/compaction.js +0 -89
  98. package/dist/core/agent/constants.js +0 -90
  99. package/dist/core/agent/context-log.js +0 -454
  100. package/dist/core/agent/context-renderer.js +0 -45
  101. package/dist/core/agent/critic-gate.js +0 -132
  102. package/dist/core/agent/hallucination-gate.js +0 -197
  103. package/dist/core/agent/loop-state.js +0 -68
  104. package/dist/core/agent/prefix-monitor.js +0 -101
  105. package/dist/core/agent/reasoning-resolver.js +0 -65
  106. package/dist/core/agent/token-tracker.js +0 -99
  107. package/dist/core/agent/tool-batch.js +0 -312
  108. package/dist/core/agent/tool-images.js +0 -18
  109. package/dist/core/agent/tool-output.js +0 -116
  110. package/dist/core/agent/tool-pairing.js +0 -60
  111. package/dist/core/agent-moe.js +0 -248
  112. package/dist/core/agent.js +0 -1299
  113. package/dist/core/bootstrap/agent-phase.js +0 -66
  114. package/dist/core/bootstrap/config-phase.js +0 -86
  115. package/dist/core/bootstrap/modules-phase.js +0 -269
  116. package/dist/core/bootstrap/provider-phase.js +0 -183
  117. package/dist/core/bootstrap/session-phase.js +0 -204
  118. package/dist/core/bootstrap/state.js +0 -94
  119. package/dist/core/bootstrap/workspace-phase.js +0 -115
  120. package/dist/core/bootstrap.js +0 -112
  121. package/dist/core/crash-handler.js +0 -62
  122. package/dist/core/environment.js +0 -224
  123. package/dist/core/host-plan.js +0 -43
  124. package/dist/core/host.js +0 -23
  125. package/dist/core/index.js +0 -3
  126. package/dist/core/prompt-builder.js +0 -95
  127. package/dist/core/prompt-overflow.js +0 -306
  128. package/dist/core/session-logger.js +0 -375
  129. package/dist/core/system-prompt.js +0 -117
  130. package/dist/core/types.js +0 -1
  131. package/dist/core/version.js +0 -28
  132. package/dist/core/web-host.js +0 -34
  133. package/dist/core/workspace.js +0 -80
  134. package/dist/i18n/index.js +0 -80
  135. package/dist/index.js +0 -30
  136. package/dist/llm/cache-usage.js +0 -76
  137. package/dist/llm/image-utils.js +0 -138
  138. package/dist/llm/index.js +0 -4
  139. package/dist/llm/llm-errors.js +0 -50
  140. package/dist/llm/model-loader.js +0 -86
  141. package/dist/llm/models-dev.js +0 -121
  142. package/dist/llm/openai-compat.js +0 -756
  143. package/dist/llm/orchestrator.js +0 -294
  144. package/dist/llm/provider-budget.js +0 -68
  145. package/dist/llm/provider-context.js +0 -233
  146. package/dist/llm/provider.js +0 -9
  147. package/dist/llm/response.js +0 -44
  148. package/dist/llm/stream-state.js +0 -26
  149. package/dist/llm/token-counter.js +0 -83
  150. package/dist/llm/types.js +0 -1
  151. package/dist/logger/app-logger.js +0 -212
  152. package/dist/logger/file-log.js +0 -160
  153. package/dist/logger/index.js +0 -2
  154. package/dist/migration/backup.js +0 -56
  155. package/dist/migration/detect.js +0 -61
  156. package/dist/migration/index.js +0 -3
  157. package/dist/modules/artifacts/store.js +0 -62
  158. package/dist/modules/browser/actions.js +0 -79
  159. package/dist/modules/browser/bridge-client.js +0 -203
  160. package/dist/modules/browser/bridge-path.js +0 -10
  161. package/dist/modules/browser/console-buffer.js +0 -37
  162. package/dist/modules/browser/cookie-store.js +0 -29
  163. package/dist/modules/browser/driver-types.js +0 -1
  164. package/dist/modules/browser/driver.js +0 -186
  165. package/dist/modules/browser/index.js +0 -9
  166. package/dist/modules/browser/module.js +0 -30
  167. package/dist/modules/browser/session.js +0 -322
  168. package/dist/modules/browser/snapshot.js +0 -154
  169. package/dist/modules/browser/types.js +0 -13
  170. package/dist/modules/certification/cli.js +0 -264
  171. package/dist/modules/certification/fact-checker.js +0 -90
  172. package/dist/modules/certification/loader.js +0 -127
  173. package/dist/modules/certification/manifest.js +0 -231
  174. package/dist/modules/certification/runner.js +0 -233
  175. package/dist/modules/certification/scenarios.js +0 -586
  176. package/dist/modules/certification/syntax-scenarios.js +0 -50
  177. package/dist/modules/certification/types.js +0 -1
  178. package/dist/modules/context/chunk-query.js +0 -122
  179. package/dist/modules/context/fact-extractor.js +0 -170
  180. package/dist/modules/context/history.js +0 -15
  181. package/dist/modules/context/index.js +0 -2
  182. package/dist/modules/context/manager.js +0 -482
  183. package/dist/modules/critic/client.js +0 -176
  184. package/dist/modules/critic/evidence.js +0 -81
  185. package/dist/modules/critic/provider.js +0 -45
  186. package/dist/modules/critic/tools.js +0 -78
  187. package/dist/modules/critic/types.js +0 -1
  188. package/dist/modules/execution/audit-runners.js +0 -206
  189. package/dist/modules/execution/auditor.js +0 -222
  190. package/dist/modules/execution/execution-plugin.js +0 -478
  191. package/dist/modules/execution/index.js +0 -8
  192. package/dist/modules/execution/input-from.js +0 -70
  193. package/dist/modules/execution/module.js +0 -891
  194. package/dist/modules/execution/moe-executor.js +0 -443
  195. package/dist/modules/execution/plan-actions.js +0 -642
  196. package/dist/modules/execution/plan-checks.js +0 -35
  197. package/dist/modules/execution/plan-coverage.js +0 -71
  198. package/dist/modules/execution/plan-persister.js +0 -46
  199. package/dist/modules/execution/plan-steps.js +0 -31
  200. package/dist/modules/execution/plan-store.js +0 -274
  201. package/dist/modules/execution/plan-tool.js +0 -249
  202. package/dist/modules/execution/plan-validator.js +0 -197
  203. package/dist/modules/execution/planner.js +0 -103
  204. package/dist/modules/execution/stuck-detector.js +0 -834
  205. package/dist/modules/execution/tracker.js +0 -92
  206. package/dist/modules/execution/transient-error.js +0 -30
  207. package/dist/modules/execution/types.js +0 -1
  208. package/dist/modules/execution/verifier.js +0 -343
  209. package/dist/modules/execution/windows-commands.js +0 -52
  210. package/dist/modules/hallucination/artifact-claim.js +0 -93
  211. package/dist/modules/hallucination/confidence.js +0 -81
  212. package/dist/modules/hallucination/consistency.js +0 -37
  213. package/dist/modules/hallucination/detector.js +0 -57
  214. package/dist/modules/hallucination/factual.js +0 -205
  215. package/dist/modules/hallucination/index.js +0 -6
  216. package/dist/modules/hallucination/js-identifiers.js +0 -261
  217. package/dist/modules/hallucination/llm-judge.js +0 -116
  218. package/dist/modules/hallucination/success-claim.js +0 -59
  219. package/dist/modules/index.js +0 -5
  220. package/dist/modules/indexer/cache.js +0 -88
  221. package/dist/modules/indexer/index.js +0 -4
  222. package/dist/modules/indexer/map-command.js +0 -33
  223. package/dist/modules/indexer/map-select.js +0 -87
  224. package/dist/modules/indexer/module.js +0 -293
  225. package/dist/modules/indexer/project-profile.js +0 -184
  226. package/dist/modules/indexer/symbols.js +0 -189
  227. package/dist/modules/indexer/walker.js +0 -285
  228. package/dist/modules/lsp/check-tool.js +0 -61
  229. package/dist/modules/lsp/client.js +0 -406
  230. package/dist/modules/lsp/command.js +0 -53
  231. package/dist/modules/lsp/config.js +0 -189
  232. package/dist/modules/lsp/index.js +0 -4
  233. package/dist/modules/lsp/module.js +0 -295
  234. package/dist/modules/lsp/probe.js +0 -86
  235. package/dist/modules/lsp/project-root.js +0 -69
  236. package/dist/modules/lsp/startup-check.js +0 -154
  237. package/dist/modules/lsp/types.js +0 -1
  238. package/dist/modules/mcp/client.js +0 -445
  239. package/dist/modules/mcp/index.js +0 -4
  240. package/dist/modules/mcp/module.js +0 -295
  241. package/dist/modules/mcp/registry.js +0 -20
  242. package/dist/modules/memory/index.js +0 -2
  243. package/dist/modules/memory/module.js +0 -101
  244. package/dist/modules/memory/search.js +0 -45
  245. package/dist/modules/memory/store.js +0 -107
  246. package/dist/modules/onboarding/init.js +0 -47
  247. package/dist/modules/onboarding/meet.js +0 -30
  248. package/dist/modules/onboarding/mode.js +0 -30
  249. package/dist/modules/onboarding/prompts.js +0 -43
  250. package/dist/modules/pipelines/engine.js +0 -70
  251. package/dist/modules/pipelines/index.js +0 -4
  252. package/dist/modules/pipelines/parser.js +0 -59
  253. package/dist/modules/pipelines/template.js +0 -18
  254. package/dist/modules/plugins/builtin/lint-on-write.js +0 -377
  255. package/dist/modules/plugins/builtin/notify.js +0 -11
  256. package/dist/modules/plugins/index.js +0 -2
  257. package/dist/modules/plugins/loader.js +0 -85
  258. package/dist/modules/plugins/manager.js +0 -244
  259. package/dist/modules/plugins/types.js +0 -1
  260. package/dist/modules/pricing/index.js +0 -70
  261. package/dist/modules/pricing/prices.js +0 -147
  262. package/dist/modules/processes/detect.js +0 -34
  263. package/dist/modules/processes/index.js +0 -4
  264. package/dist/modules/processes/kill-tree.js +0 -52
  265. package/dist/modules/processes/registry.js +0 -294
  266. package/dist/modules/processes/runner.js +0 -23
  267. package/dist/modules/providers/cache.js +0 -23
  268. package/dist/modules/providers/capabilities.js +0 -28
  269. package/dist/modules/providers/create.js +0 -21
  270. package/dist/modules/providers/factory.js +0 -28
  271. package/dist/modules/providers/fallback.js +0 -81
  272. package/dist/modules/providers/health.js +0 -47
  273. package/dist/modules/providers/index.js +0 -6
  274. package/dist/modules/providers/manager.js +0 -207
  275. package/dist/modules/providers/naming.js +0 -15
  276. package/dist/modules/providers/preflight.js +0 -71
  277. package/dist/modules/providers/presets.js +0 -232
  278. package/dist/modules/providers/registry.js +0 -27
  279. package/dist/modules/providers/types.js +0 -1
  280. package/dist/modules/reasoning/policy.js +0 -42
  281. package/dist/modules/reasoning/probe.js +0 -116
  282. package/dist/modules/registry.js +0 -48
  283. package/dist/modules/security/audit-log.js +0 -119
  284. package/dist/modules/security/audit-notifier.js +0 -267
  285. package/dist/modules/security/command-validator.js +0 -272
  286. package/dist/modules/security/content-scanner.js +0 -53
  287. package/dist/modules/security/data-sanitizer.js +0 -82
  288. package/dist/modules/security/encryption.js +0 -216
  289. package/dist/modules/security/index.js +0 -15
  290. package/dist/modules/security/network-validator.js +0 -236
  291. package/dist/modules/security/path-validator.js +0 -261
  292. package/dist/modules/security/protected-paths.js +0 -73
  293. package/dist/modules/security/rate-limiter.js +0 -98
  294. package/dist/modules/security/security-policies.js +0 -499
  295. package/dist/modules/security/session-encryption.js +0 -182
  296. package/dist/modules/security/session-isolation.js +0 -75
  297. package/dist/modules/session/auto-name.js +0 -63
  298. package/dist/modules/session/export.js +0 -98
  299. package/dist/modules/session/index.js +0 -4
  300. package/dist/modules/session/manager.js +0 -234
  301. package/dist/modules/session/module.js +0 -33
  302. package/dist/modules/session/store.js +0 -295
  303. package/dist/modules/session/types.js +0 -1
  304. package/dist/modules/setup/answers.js +0 -35
  305. package/dist/modules/setup/apply.js +0 -79
  306. package/dist/modules/setup/discovery.js +0 -72
  307. package/dist/modules/skills/index.js +0 -3
  308. package/dist/modules/skills/loader.js +0 -86
  309. package/dist/modules/skills/matcher.js +0 -27
  310. package/dist/modules/skills/module.js +0 -141
  311. package/dist/modules/types.js +0 -1
  312. package/dist/modules/updater/changelog-reader.js +0 -93
  313. package/dist/modules/updater/checker.js +0 -101
  314. package/dist/modules/updater/dev-detect.js +0 -18
  315. package/dist/modules/updater/index.js +0 -4
  316. package/dist/modules/updater/module.js +0 -129
  317. package/dist/modules/user-profile/compressor.js +0 -18
  318. package/dist/modules/user-profile/index.js +0 -2
  319. package/dist/modules/user-profile/profile.js +0 -77
  320. package/dist/modules/webui/answer-api.js +0 -29
  321. package/dist/modules/webui/assets.js +0 -50
  322. package/dist/modules/webui/body.js +0 -21
  323. package/dist/modules/webui/commands-settings.js +0 -209
  324. package/dist/modules/webui/commands-shared.js +0 -60
  325. package/dist/modules/webui/commands.js +0 -174
  326. package/dist/modules/webui/config-api.js +0 -134
  327. package/dist/modules/webui/file-api.js +0 -202
  328. package/dist/modules/webui/forward.js +0 -159
  329. package/dist/modules/webui/history-api.js +0 -112
  330. package/dist/modules/webui/host-state.js +0 -60
  331. package/dist/modules/webui/i18n-api.js +0 -9
  332. package/dist/modules/webui/limits.js +0 -6
  333. package/dist/modules/webui/log-forward.js +0 -20
  334. package/dist/modules/webui/memory-commands.js +0 -64
  335. package/dist/modules/webui/model-cache.js +0 -18
  336. package/dist/modules/webui/module.js +0 -148
  337. package/dist/modules/webui/persist.js +0 -24
  338. package/dist/modules/webui/prompt-io.js +0 -97
  339. package/dist/modules/webui/providers-api.js +0 -337
  340. package/dist/modules/webui/providers-view.js +0 -56
  341. package/dist/modules/webui/reasoning-view.js +0 -15
  342. package/dist/modules/webui/run-agent.js +0 -81
  343. package/dist/modules/webui/server.js +0 -284
  344. package/dist/modules/webui/sessions-api.js +0 -178
  345. package/dist/modules/webui/settings-api.js +0 -561
  346. package/dist/modules/webui/setup-api.js +0 -201
  347. package/dist/modules/webui/state.js +0 -74
  348. package/dist/modules/webui/util.js +0 -261
  349. package/dist/output/bus.js +0 -37
  350. package/dist/output/channel.js +0 -285
  351. package/dist/output/format.js +0 -15
  352. package/dist/output/index.js +0 -8
  353. package/dist/output/json-sink.js +0 -24
  354. package/dist/output/machine.js +0 -9
  355. package/dist/output/session-sink.js +0 -27
  356. package/dist/output/types.js +0 -1
  357. package/dist/tools/approve.js +0 -41
  358. package/dist/tools/attach-image.js +0 -102
  359. package/dist/tools/auto-fixer.js +0 -180
  360. package/dist/tools/auto-tags.js +0 -83
  361. package/dist/tools/bash.js +0 -649
  362. package/dist/tools/browser.js +0 -160
  363. package/dist/tools/chunk-query.js +0 -112
  364. package/dist/tools/create-dir.js +0 -60
  365. package/dist/tools/delete-file.js +0 -67
  366. package/dist/tools/download-file.js +0 -142
  367. package/dist/tools/edit-file.js +0 -136
  368. package/dist/tools/enable-tools.js +0 -59
  369. package/dist/tools/executor.js +0 -304
  370. package/dist/tools/file-info.js +0 -49
  371. package/dist/tools/filter-tools.js +0 -17
  372. package/dist/tools/glob-tool.js +0 -37
  373. package/dist/tools/grep-tool.js +0 -142
  374. package/dist/tools/hidden-tools-block.js +0 -37
  375. package/dist/tools/index.js +0 -88
  376. package/dist/tools/list-dir.js +0 -53
  377. package/dist/tools/load-skill.js +0 -51
  378. package/dist/tools/mcp-call.js +0 -80
  379. package/dist/tools/meet-done.js +0 -14
  380. package/dist/tools/move-file.js +0 -94
  381. package/dist/tools/path-utils.js +0 -108
  382. package/dist/tools/pipeline-run.js +0 -167
  383. package/dist/tools/preview.js +0 -2
  384. package/dist/tools/process-ids.js +0 -31
  385. package/dist/tools/process-kill.js +0 -33
  386. package/dist/tools/process-list.js +0 -38
  387. package/dist/tools/process-log.js +0 -49
  388. package/dist/tools/process-wait.js +0 -63
  389. package/dist/tools/prompt-io.js +0 -29
  390. package/dist/tools/question.js +0 -288
  391. package/dist/tools/read-file.js +0 -200
  392. package/dist/tools/recall.js +0 -124
  393. package/dist/tools/registry.js +0 -48
  394. package/dist/tools/remember.js +0 -72
  395. package/dist/tools/scope-check.js +0 -32
  396. package/dist/tools/scope-request.js +0 -96
  397. package/dist/tools/search-history.js +0 -88
  398. package/dist/tools/session-info.js +0 -46
  399. package/dist/tools/set-thinking.js +0 -75
  400. package/dist/tools/subagent.js +0 -257
  401. package/dist/tools/syntax-validator.js +0 -379
  402. package/dist/tools/types.js +0 -1
  403. package/dist/tools/user-input.js +0 -134
  404. package/dist/tools/web-browse.js +0 -93
  405. package/dist/tools/web-fetch.js +0 -108
  406. package/dist/tools/web-search.js +0 -107
  407. package/dist/tools/write-file.js +0 -128
  408. package/dist/ui/action-log.js +0 -13
  409. package/dist/ui/box.js +0 -78
  410. package/dist/ui/colors.js +0 -6
  411. package/dist/ui/diff.js +0 -189
  412. package/dist/ui/index.js +0 -7
  413. package/dist/ui/line-editor.js +0 -1083
  414. package/dist/ui/line-math.js +0 -98
  415. package/dist/ui/md-formatter.js +0 -322
  416. package/dist/ui/output.js +0 -19
  417. package/dist/ui/plan-view.js +0 -109
  418. package/dist/ui/renderer.js +0 -589
  419. package/dist/ui/spinner.js +0 -91
  420. package/dist/ui/table.js +0 -142
  421. package/dist/ui/theme.js +0 -143
  422. package/dist/utils/child-run.js +0 -20
  423. package/dist/utils/error.js +0 -4
  424. package/dist/utils/index.js +0 -6
  425. package/dist/utils/retry.js +0 -17
  426. package/dist/utils/sleep.js +0 -23
  427. package/dist/utils/truncate.js +0 -9
@@ -1,1211 +1 @@
1
- // Feed, turn-stream handlers, history, question UI and sidebar renders.
2
- // All SSE `on*` handlers live here; `app.js` only wires them to EventSource.
3
- import { mountMarkdown, renderMarkdown, copyText } from "../md.js";
4
- import {
5
- msgs, planEl, planCountEl, usageEl, envEl, wsLine,
6
- el, icon, setIcon, link, announce,
7
- } from "./dom.js";
8
- import { api } from "./api.js";
9
- import { I18N, t, pluralCount } from "./i18n.js";
10
- import { S } from "./store.js";
11
- import {
12
- FILE_TOOLS, OPENABLE_TOOLS, IMAGE_TOOLS, LINK_TOOLS,
13
- categoryOf, labelOf, argText, formatMs, verbosity, firstOutputLine, fmtTokens,
14
- } from "./format.js";
15
-
16
- function insertAfter(ref, node) {
17
- if (ref && ref.parentNode) ref.parentNode.insertBefore(node, ref.nextSibling);
18
- else appendTo(liveFeed(), node);
19
- }
20
-
21
-
22
- function emptyView() {
23
- const wrap = el("div", "empty");
24
- wrap.appendChild(el("span", "g", "✦"));
25
- wrap.appendChild(document.createTextNode(" " + t("web.empty.title")));
26
- wrap.appendChild(document.createElement("br"));
27
- wrap.appendChild(el("span", "hint", t("web.empty.hint")));
28
- return wrap;
29
- }
30
-
31
- export function makeFeed() {
32
- const feed = el("div", "feed");
33
- feed.appendChild(emptyView());
34
- return feed;
35
- }
36
-
37
- export function showEmptyFeed() {
38
- msgs.replaceChildren(emptyView());
39
- follow = true;
40
- hideToLatest();
41
- syncUrl();
42
- }
43
-
44
- export function clearFeed(feed) {
45
- if (!feed) return;
46
- feed.replaceChildren(emptyView());
47
- follow = true;
48
- hideToLatest();
49
- const tab = S.tabs.find((x) => x.feed === feed);
50
- if (tab) tab.planCard = null;
51
- if (feed === activeFeed()) scroll();
52
- }
53
-
54
- export function activeFeed() {
55
- const tab = S.tabs.find((x) => x.id === S.activeTabId);
56
- return tab ? tab.feed : null;
57
- }
58
-
59
- export function liveFeed() {
60
- const tab = S.tabs.find((x) => x.id === S.liveTabId) || S.tabs.find((x) => x.id === S.activeTabId);
61
- return tab ? tab.feed : null;
62
- }
63
-
64
- // Route a server event to the tab that owns its session (item from `broadcast`
65
- // enrichment); fall back to the live/active tab for events without one.
66
- function feedForEvent(sessionId) {
67
- if (sessionId) {
68
- const tab = S.tabs.find((x) => x.id === sessionId);
69
- if (tab) return tab.feed;
70
- }
71
- return liveFeed();
72
- }
73
-
74
- function nearBottom() {
75
- return msgs.scrollHeight - msgs.scrollTop - msgs.clientHeight < 140;
76
- }
77
-
78
- // Sticky follow: while the reader is at the live edge we pin to the bottom
79
- // on every append; the moment they scroll up we leave them alone (the
80
- // "jump to latest" pill offers the trip back). A new user turn always
81
- // force-scrolls — otherwise one missed pin cascades into "no autoscroll"
82
- // for the whole turn, since every later `scroll()` would see `nearBottom()`
83
- // as false and skip.
84
- let follow = true;
85
-
86
- function scroll(force) {
87
- if (force || follow || nearBottom()) {
88
- msgs.scrollTop = msgs.scrollHeight;
89
- follow = true;
90
- hideToLatest();
91
- } else {
92
- maybeShowToLatest();
93
- }
94
- }
95
-
96
- // Force-pin to the live edge (send / history load / pill click).
97
- export function scrollToBottom(smooth) {
98
- follow = true;
99
- hideToLatest();
100
- if (smooth && typeof msgs.scrollTo === "function") {
101
- try {
102
- msgs.scrollTo({ top: msgs.scrollHeight, behavior: "smooth" });
103
- return;
104
- } catch (err) {
105
- /* fall through to instant */
106
- }
107
- }
108
- msgs.scrollTop = msgs.scrollHeight;
109
- }
110
-
111
- export function isFollowing() {
112
- return follow;
113
- }
114
-
115
- // Floating "jump to latest" pill: appears only when the reader scrolled up
116
- // and new content landed below. Created lazily so tests/SSR without `.chat`
117
- // never break.
118
- let toLatestBtn = null;
119
- function ensureToLatest() {
120
- if (toLatestBtn) return toLatestBtn;
121
- const chat = document.querySelector(".chat");
122
- if (!chat) return null;
123
- toLatestBtn = el("button", "to-latest");
124
- toLatestBtn.type = "button";
125
- toLatestBtn.hidden = true;
126
- toLatestBtn.addEventListener("click", () => scrollToBottom(true));
127
- chat.appendChild(toLatestBtn);
128
- return toLatestBtn;
129
- }
130
-
131
- function maybeShowToLatest() {
132
- const btn = ensureToLatest();
133
- if (!btn) return;
134
- btn.textContent = "↓ " + t("web.scroll.latest");
135
- btn.hidden = false;
136
- msgs.classList.add("unfollowed");
137
- }
138
-
139
- function hideToLatest() {
140
- if (toLatestBtn) toLatestBtn.hidden = true;
141
- msgs.classList.remove("unfollowed");
142
- }
143
-
144
- msgs.addEventListener("scroll", () => {
145
- const nb = nearBottom();
146
- follow = nb;
147
- msgs.classList.toggle("scrolled", msgs.scrollTop > 24);
148
- if (nb) hideToLatest();
149
- else if (liveFeed() && feedHasContent(liveFeed())) maybeShowToLatest();
150
- });
151
-
152
- export function appendTo(feed, node) {
153
- if (!feed) return node;
154
- feed.appendChild(node);
155
- if (feed === activeFeed()) scroll();
156
- return node;
157
- }
158
-
159
- function append(node) {
160
- return appendTo(liveFeed(), node);
161
- }
162
-
163
- export function clearEmpty(feed) {
164
- const empty = feed && feed.querySelector(".empty");
165
- if (empty) empty.remove();
166
- }
167
-
168
- function clearBusy(feed) {
169
- if (S.busyTimer) {
170
- clearInterval(S.busyTimer);
171
- S.busyTimer = null;
172
- }
173
- feed = feed || liveFeed();
174
- if (!feed) return;
175
- const line = feed.querySelector(".busy");
176
- if (line) line.remove();
177
- }
178
-
179
- // Show the in-feed "thinking" line. Called immediately after a successful
180
- // `/api/message` POST (202) so the wait during a slow first-run
181
- // summarization is visible before the first `phase` event arrives; the
182
- // normal event flow (`onText`/`onPhase`/`onTurnEnd`) clears it.
183
- export function showBusy(feed) {
184
- feed = feed || liveFeed();
185
- if (!feed) return;
186
- // The delayed post-send call must not flash a spinner over a stream that
187
- // already started (fast first token).
188
- if (S.curMd || S.curThink) return;
189
- clearEmpty(feed);
190
- if (feed.querySelector(".busy")) return;
191
- if (S.busyTimer) {
192
- clearInterval(S.busyTimer);
193
- S.busyTimer = null;
194
- }
195
- const line = el("div", "busy");
196
- line.appendChild(el("span", "spin", "✦"));
197
- line.appendChild(el("span", "lab", t("web.action.thinking")));
198
- const time = el("span", "busytime", "· 0s");
199
- line.appendChild(time);
200
- line.appendChild(el("span", "", "· " + t("web.input.esc")));
201
- appendTo(feed, line);
202
- const started = Date.now();
203
- S.busyTimer = setInterval(() => {
204
- if (!line.parentNode) {
205
- clearInterval(S.busyTimer);
206
- S.busyTimer = null;
207
- return;
208
- }
209
- time.textContent = "· " + Math.round((Date.now() - started) / 1000) + "s";
210
- }, 1000);
211
- }
212
-
213
- function flushRun() {
214
- if (S.runCount <= 0) {
215
- resetRun();
216
- return;
217
- }
218
- // Quiet: a run of successful read-only actions folds into one summary line,
219
- // replacing the per-action lines (mirrors `Renderer.flushRecon`). `normal`
220
- // and `verbose` keep the individual action lines and add nothing.
221
- if (verbosity() === "quiet" && S.runReconOnly && S.runFails === 0 && S.runCount > 1) {
222
- // Fade the folded lines out instead of snapping them away.
223
- const folding = S.runNodes.slice();
224
- folding.forEach((node) => node.classList.add("fold-out"));
225
- window.setTimeout(() => folding.forEach((node) => node.remove()), 160);
226
- const line = el("div", "grouped");
227
- line.appendChild(el("span", "pipe", "│"));
228
- const dur = formatMs(S.runDuration);
229
- line.appendChild(
230
- document.createTextNode(
231
- " " + pluralCount("web.action.actions", S.runCount) + " ✓" + (dur ? " · " + dur : ""),
232
- ),
233
- );
234
- appendTo(liveFeed(), line);
235
- }
236
- resetRun();
237
- }
238
-
239
- function resetRun() {
240
- S.runCount = 0;
241
- S.runFails = 0;
242
- S.runReconOnly = true;
243
- S.runNodes = [];
244
- S.runDuration = 0;
245
- }
246
-
247
- // Options passed to the streaming Markdown renderer: localized code-block
248
- // copy labels. Highlighting is applied by `md.js` once a fence closes.
249
- function mdOpts() {
250
- return {
251
- copyLabel: t("web.code.copy"),
252
- copiedLabel: t("web.code.copied"),
253
- copyFailedLabel: t("web.code.copy_failed"),
254
- };
255
- }
256
-
257
- // Close the active Markdown segment (if any) and clear the text S.state.
258
- export function finishMd() {
259
- if (S.curMd) {
260
- S.curMd.root.classList.remove("streaming");
261
- S.curMd.end();
262
- S.curMd = null;
263
- }
264
- S.curText = null;
265
- }
266
-
267
- export function onText(chunk, sid) {
268
- const feed = feedForEvent(sid);
269
- clearBusy(feed);
270
- if (!feed) return;
271
- clearEmpty(feed);
272
- if (S.curThink) {
273
- finishThink();
274
- }
275
- if (S.curText == null) {
276
- flushRun();
277
- S.curText = "";
278
- S.curMd = mountMarkdown(feed, mdOpts());
279
- S.curMd.root.classList.add("streaming");
280
- S.turnRoots.push(S.curMd.root);
281
- }
282
- S.curText += chunk;
283
- S.turnMd += chunk;
284
- S.curMd.write(chunk);
285
- if (feed === activeFeed()) scroll();
286
- }
287
-
288
- export function finishThink() {
289
- if (S.curThink) {
290
- if (S.curThink.wrap) S.curThink.wrap.classList.remove("live");
291
- if (S.curThink.startedAt) {
292
- const dur = S.curThink.head.querySelector(".dur");
293
- if (dur) dur.textContent = "· " + ((Date.now() - S.curThink.startedAt) / 1000).toFixed(1) + "s";
294
- }
295
- }
296
- S.curThink = null;
297
- }
298
-
299
- // Build the collapsible reasoning block. The global `.think-h` click handler
300
- // depends on this exact structure: wrap > [head, body], body carries `hidden`.
301
- // `live` marks an in-progress block (pulsing label); history renders static.
302
- function buildThinkBlock(withDur, live) {
303
- const wrap = el("div", "think" + (live === false ? "" : " live"));
304
- const head = el("button", "think-h");
305
- head.type = "button";
306
- head.setAttribute("aria-expanded", "false");
307
- head.appendChild(el("span", "g", "✦"));
308
- head.appendChild(el("span", "lab", t("web.action.thinking")));
309
- if (withDur) head.appendChild(el("span", "dur", ""));
310
- const tog = el("span", "tog");
311
- setIcon(tog, "chevron-right", t("web.action.show"));
312
- head.appendChild(tog);
313
- const body = el("div", "think-body hidden");
314
- wrap.appendChild(head);
315
- wrap.appendChild(body);
316
- return { wrap, head, body };
317
- }
318
-
319
- export function onReasoning(chunk, sid) {
320
- const feed = feedForEvent(sid);
321
- clearBusy(feed);
322
- if (!feed) return;
323
- clearEmpty(feed);
324
- finishMd();
325
- if (!S.curThink) {
326
- flushRun();
327
- const { wrap, head, body } = buildThinkBlock(true);
328
- appendTo(feed, wrap);
329
- S.curThink = { wrap, head, body, startedAt: Date.now() };
330
- }
331
- S.curThink.body.textContent += chunk;
332
- if (feed === activeFeed()) scroll();
333
- }
334
-
335
- // Generic agent status (context stats, compaction, MoE status, warnings):
336
- // always visible, one dim line per chunk, never collapsed. Tool bodies
337
- // (`kind: "tool"` — display/output/diff/failure-gist from ToolBatch) are
338
- // model-facing and hidden in quiet mode, mirroring the terminal renderer
339
- // (`writeInlineBody`); failures still surface once via `tool_end` ↳ cause.
340
- export function onMeta(chunk, sid, kind) {
341
- if (kind === "tool" && verbosity() === "quiet") return;
342
- const feed = feedForEvent(sid);
343
- clearBusy(feed);
344
- const text = (chunk || "").trim();
345
- if (!feed || !text) return;
346
- clearEmpty(feed);
347
- finishMd();
348
- const warn = text.indexOf("⚠") !== -1 || text.indexOf("✗") !== -1;
349
- appendTo(feed, el("div", warn ? "metaln warn" : "metaln", text));
350
- if (feed === activeFeed()) scroll();
351
- }
352
-
353
- // Гейт забраковал уже отстримленный черновик (SSE `retract`): убираем его
354
- // из ленты до переответа, иначе финал встанет дублем следом
355
- // (ses_munvrcfz_na9ze3). Копипаст хода тоже чистим — отклонённого текста
356
- // в «copy / copy as markdown» быть не должно. Без видимого черновика
357
- // (пустой ответ, только тулы) — тихо, без шумовой строки.
358
- export function onRetract(reason, sid) {
359
- const feed = feedForEvent(sid);
360
- if (!feed) return;
361
- clearBusy(feed);
362
- const draftRoot = S.curMd ? S.curMd.root : null;
363
- const hadDraft = !!draftRoot || (S.curText != null && S.curText !== "");
364
- const draftLen = S.curText ? S.curText.length : 0;
365
- finishMd();
366
- finishThink();
367
- if (draftRoot) {
368
- const idx = S.turnRoots.indexOf(draftRoot);
369
- if (idx !== -1) S.turnRoots.splice(idx, 1);
370
- draftRoot.remove();
371
- }
372
- if (draftLen > 0) S.turnMd = S.turnMd.slice(0, Math.max(0, S.turnMd.length - draftLen));
373
- if (!hadDraft) return;
374
- if (reason) appendTo(feed, el("div", "metaln", reason));
375
- if (feed === activeFeed()) scroll();
376
- }
377
-
378
- export function onPhase(phase, sid) {
379
- const feed = feedForEvent(sid);
380
- if (phase === "thinking") {
381
- showBusy(feed);
382
- } else {
383
- clearBusy(feed);
384
- }
385
- }
386
-
387
- export function onToolStart(data) {
388
- const feed = feedForEvent(data.sessionId);
389
- clearBusy(feed);
390
- if (!feed) return;
391
- clearEmpty(feed);
392
- finishMd();
393
- finishThink();
394
- S.pendingArgs.set(data.toolCallId, data.args || {});
395
- const line = el("div", "act " + categoryOf(data.name));
396
- line.appendChild(el("span", "g", "◇"));
397
- line.appendChild(el("span", "lab", labelOf(data.name)));
398
- const args = argText(data.args);
399
- if (args) {
400
- line.appendChild(el("span", "sep", "·"));
401
- line.appendChild(el("span", "args", args));
402
- }
403
- const status = el("span", "ok", "");
404
- line.appendChild(status);
405
- line.classList.add("running");
406
- appendTo(feed, line);
407
- S.toolNodes.set(data.toolCallId, { status, line });
408
- if (categoryOf(data.name) !== "recon") S.runReconOnly = false;
409
- S.runCount++;
410
- }
411
-
412
- function renderToolOutput(line, text) {
413
- if (typeof text !== "string" || !text) return;
414
- const wrap = el("div", "toolout");
415
- const code = el("div", "code");
416
- code.textContent = text;
417
- wrap.appendChild(code);
418
- const long = text.length > 600 || text.split("\n").length > 12;
419
- if (long) {
420
- wrap.classList.add("collapsed");
421
- const outLabel = "▸ " + pluralCount("web.tool.output", text.length);
422
- const more = el("span", "more", outLabel);
423
- more.addEventListener("click", () => {
424
- const collapsed = wrap.classList.toggle("collapsed");
425
- more.textContent = collapsed ? outLabel : "▾ " + t("web.tool.collapse");
426
- });
427
- wrap.appendChild(more);
428
- }
429
- const feed = line && line.parentNode ? null : liveFeed();
430
- if (line && line.parentNode) line.parentNode.insertBefore(wrap, line.nextSibling);
431
- else appendTo(feed, wrap);
432
- }
433
-
434
- function safeUrl(url) {
435
- if (typeof url !== "string" || !url) return null;
436
- try {
437
- const u = new URL(url, location.href);
438
- if (u.protocol === "http:" || u.protocol === "https:" || u.protocol === "file:") {
439
- return u.href;
440
- }
441
- } catch (err) {
442
- return null;
443
- }
444
- return null;
445
- }
446
-
447
- // File rows are always expanded: one `filerow` per file-tool call, no
448
- // collapsing group. Consecutive rows stay a plain expanded list (the old
449
- // E grouping with a chevron toggle hid the list and failed to open).
450
- function displayPathFromRoot(path) {
451
- const raw = String(path == null ? "" : path).replace(/\\/g, "/");
452
- try {
453
- const ws = S && S.state && S.state.host && S.state.host.workspace
454
- ? String(S.state.host.workspace).replace(/\\/g, "/").replace(/\/+$/, "")
455
- : "";
456
- if (ws && (raw === ws || raw.indexOf(ws + "/") === 0)) {
457
- const rel = raw === ws ? "" : raw.slice(ws.length + 1);
458
- if (rel) return rel;
459
- }
460
- } catch (err) {
461
- /* fail-open: show raw path */
462
- }
463
- return raw;
464
- }
465
-
466
- function cardFor(name, args, sid, ok) {
467
- const feed = feedForEvent(sid);
468
- if (!feed) return null;
469
- if (FILE_TOOLS.indexOf(name) !== -1) {
470
- const path = args.path || args.file || args.dir;
471
- if (!path) return null;
472
- const shown = displayPathFromRoot(path);
473
- const row = el("div", "filerow " + categoryOf(name));
474
- row.appendChild(el("span", "g", "◇"));
475
- row.appendChild(el("span", "lab", labelOf(name)));
476
- row.appendChild(el("span", "sep", " · "));
477
- if (OPENABLE_TOOLS.indexOf(name) !== -1) {
478
- const openLink = el("a", "path", shown || String(path));
479
- openLink.href = "/api/file?path=" + encodeURIComponent(String(path));
480
- openLink.target = "_blank";
481
- openLink.rel = "noopener noreferrer";
482
- openLink.title = t("web.card.open_title") + " · " + String(path);
483
- row.appendChild(openLink);
484
- } else {
485
- const s = el("span", "tail", shown || String(path));
486
- s.title = String(path);
487
- row.appendChild(s);
488
- }
489
- const copyBtn = el("button", "copy-btn");
490
- copyBtn.type = "button";
491
- copyBtn.title = t("web.card.copy_title");
492
- copyBtn.setAttribute("aria-label", t("web.card.copy_title"));
493
- copyBtn.appendChild(icon("copy"));
494
- copyBtn.addEventListener("click", () => {
495
- copyText(String(path)).then((ok) => {
496
- copyBtn.replaceChildren(icon(ok ? "check" : "copy"));
497
- if (!ok) { const st = row.querySelector(".st"); if (st) st.textContent = " ⚠"; }
498
- window.setTimeout(() => copyBtn.replaceChildren(icon("copy")), 1400);
499
- });
500
- });
501
- row.appendChild(copyBtn);
502
- row.appendChild(el("span", "st", ok === false ? " ✗" : ok === true ? " ✓" : ""));
503
- if (ok === false) row.dataset.fail = "1";
504
- if (/\.drawio(\.xml)?$/i.test(String(path)) && verbosity() === "verbose") {
505
- row.appendChild(el("span", "detail", "↳ " + t("web.card.drawio_hint")));
506
- }
507
- appendTo(feed, row);
508
- return row;
509
- }
510
- if (LINK_TOOLS.indexOf(name) !== -1 && args.url) {
511
- const line = el("div", "linkline");
512
- line.appendChild(document.createTextNode("↳ "));
513
- const safe = safeUrl(args.url);
514
- if (safe) {
515
- const a = el("a", "", args.url);
516
- a.href = safe;
517
- a.target = "_blank";
518
- a.rel = "noopener noreferrer";
519
- line.appendChild(a);
520
- try {
521
- const host = new URL(safe).hostname;
522
- if (host) line.appendChild(el("span", "dom", " · " + host));
523
- } catch (err) {
524
- /* no hostname */
525
- }
526
- } else {
527
- line.appendChild(el("span", "", args.url));
528
- }
529
- appendTo(feed, line);
530
- return line;
531
- }
532
- if (IMAGE_TOOLS.indexOf(name) !== -1) {
533
- const card = el("div", "imgcard");
534
- card.appendChild(el("div", "ph", "◧ " + t("web.card.screenshot")));
535
- card.appendChild(el("div", "cap", name === "attach_image" ? t("web.card.attached_image") : t("web.card.browser_viewport")));
536
- appendTo(feed, card);
537
- return card;
538
- }
539
- return null;
540
- }
541
-
542
- export function onToolEnd(data) {
543
- const node = S.toolNodes.get(data.toolCallId);
544
- const line = node ? node.line : null;
545
- if (node) {
546
- node.line.classList.remove("running");
547
- const dur = formatMs(data.durationMs);
548
- node.status.textContent = data.success ? " ✓" + (dur ? " " + dur : "") : " ✗" + (dur ? " " + dur : "");
549
- if (!data.success) {
550
- node.status.classList.add("fail");
551
- S.runFails++;
552
- S.runReconOnly = false;
553
- }
554
- }
555
- S.toolNodes.delete(data.toolCallId);
556
- const args = S.pendingArgs.get(data.toolCallId) || {};
557
- S.pendingArgs.delete(data.toolCallId);
558
- // File tools render as a single filerow (viewer link + copy): the `.act`
559
- // line from `onToolStart` is replaced, otherwise the feed shows the same
560
- // action twice (act + filerow). Without a path `cardFor` renders nothing,
561
- // so keep the generic act line in that case.
562
- const filePath = args.path || args.file || args.dir;
563
- if (FILE_TOOLS.indexOf(data.name) !== -1 && filePath) {
564
- if (line && line.parentNode) line.remove();
565
- if (verbosity() === "quiet") {
566
- if (data.success) {
567
- const card = cardFor(data.name, args, data.sessionId, data.success);
568
- if (card) S.runNodes.push(card);
569
- S.runDuration += typeof data.durationMs === "number" ? data.durationMs : 0;
570
- } else {
571
- const card = cardFor(data.name, args, data.sessionId, data.success);
572
- const cause = firstOutputLine(data.output);
573
- if (cause) insertAfter(card || line, el("div", "detail", "↳ " + cause));
574
- if (card) S.runNodes.push(card);
575
- }
576
- return;
577
- }
578
- const card = cardFor(data.name, args, data.sessionId, data.success);
579
- renderToolOutput(card || line, data.output);
580
- return;
581
- }
582
- if (verbosity() === "quiet") {
583
- // Quiet hides tool bodies. Track successful nodes so a run of read-only
584
- // actions can fold at the next segment; a failure keeps its line and adds
585
- // a single `↳` cause instead of the whole model-facing body.
586
- if (data.success) {
587
- if (line) S.runNodes.push(line);
588
- const card = cardFor(data.name, args, data.sessionId, data.success);
589
- if (card) S.runNodes.push(card);
590
- S.runDuration += typeof data.durationMs === "number" ? data.durationMs : 0;
591
- } else {
592
- const cause = firstOutputLine(data.output);
593
- if (cause) insertAfter(line, el("div", "detail", "↳ " + cause));
594
- }
595
- return;
596
- }
597
- if (data.success) cardFor(data.name, args, data.sessionId, data.success);
598
- renderToolOutput(line, data.output);
599
- }
600
-
601
- export function onTurnEnd(data) {
602
- S.state.busy = false;
603
- const feed = feedForEvent(data.sessionId);
604
- clearBusy(feed);
605
- finishThink();
606
- finishMd();
607
- flushRun();
608
- // Interrupted tools never get `tool_end`: drop their pulse so nothing
609
- // looks alive after the turn is over.
610
- S.toolNodes.forEach((n) => {
611
- if (n && n.line && feed && n.line.parentNode === feed) n.line.classList.remove("running");
612
- });
613
- const owner = S.tabs.find((x) => x.id === data.sessionId);
614
- if (owner) owner.planCard = null;
615
- if (!feed) {
616
- resetTurnText();
617
- return;
618
- }
619
- if (data.success === false) {
620
- if (data.interrupted) {
621
- appendTo(feed, el("div", "warnline", "⚠ " + t("web.turn.stopped")));
622
- announce(t("web.turn.stopped"));
623
- } else {
624
- appendTo(feed, el("div", "errline", "✗ " + t("web.turn.failed")));
625
- announce(t("web.turn.failed"));
626
- }
627
- } else {
628
- const parts = [];
629
- if (typeof data.toolCalls === "number" && data.toolCalls > 0) {
630
- parts.push(t("web.turn.tools", { count: data.toolCalls }));
631
- }
632
- if (typeof data.durationMs === "number") parts.push(formatMs(data.durationMs));
633
- if (parts.length) appendTo(feed, el("div", "metric", "⟡ " + parts.join(" · ")));
634
- // The visible turn-end banner is redundant noise; keep the screen-reader cue.
635
- announce(t("web.turn.end", { count: data.toolCalls || 0 }));
636
- }
637
- const plain = S.turnRoots.map((r) => r.innerText || r.textContent || "").join("\n\n");
638
- appendActionsRow(feed, plain, S.turnMd);
639
- resetTurnText();
640
- }
641
-
642
- export function resetTurnText() {
643
- S.turnMd = "";
644
- S.turnRoots = [];
645
- }
646
-
647
- // Per-message copy actions (whole response as plain text / as Markdown).
648
- function copyAction(payload, labelKey) {
649
- const btn = el("button", "act-btn", t(labelKey));
650
- btn.type = "button";
651
- btn.addEventListener("click", () => {
652
- copyText(payload).then((ok) => {
653
- btn.textContent = ok ? "✓ " + t("web.code.copied") : "⚠ " + t("web.code.copy_failed");
654
- window.setTimeout(() => {
655
- btn.textContent = t(labelKey);
656
- }, 1400);
657
- });
658
- });
659
- return btn;
660
- }
661
-
662
- function appendActionsRow(feed, plain, markdown) {
663
- const text = String(plain == null ? "" : plain);
664
- const md = String(markdown == null ? "" : markdown);
665
- if (!feed || (!text.trim() && !md.trim())) return;
666
- const bar = el("div", "msg-actions");
667
- if (text.trim()) bar.appendChild(copyAction(text, "web.msg.copy"));
668
- if (md.trim()) bar.appendChild(copyAction(md, "web.msg.copy_md"));
669
- appendTo(feed, bar);
670
- }
671
-
672
- export function onErrorEvent(data) {
673
- if (!data || !data.error) return;
674
- const feed = feedForEvent(data.sessionId);
675
- if (feed) appendTo(feed, el("div", "errline", "✗ " + data.error));
676
- announce(String(data.error));
677
- }
678
-
679
- export function onCommandOutput(text, sid) {
680
- const feed = feedForEvent(sid) || activeFeed();
681
- if (!feed) return;
682
- clearEmpty(feed);
683
- String(text)
684
- .split("\n")
685
- .forEach((line) => appendTo(feed, el("div", "cmdout", line)));
686
- }
687
-
688
- // Server-forwarded `OutputBus` line (system-prompt overflow, context probe,
689
- // summarization). Text is already redacted server-side; render via
690
- // `textContent` so nothing untrusted is parsed as HTML.
691
- export function onLog(data) {
692
- if (!data || typeof data.text !== "string" || !data.text) return;
693
- // Internal loop diagnostics (retry counters, audit/compaction internals) are
694
- // session-log material — the chat shows them only in verbose mode.
695
- if (data.diagnostic && verbosity() !== "verbose") return;
696
- const feed = feedForEvent(data.sessionId) || activeFeed();
697
- if (!feed) return;
698
- const level = data.level;
699
- const cls = level === "error" ? "errline" : level === "warn" ? "warnline" : "detail";
700
- const glyph = level === "error" ? "✗ " : level === "warn" ? "⚠ " : "↳ ";
701
- const line = el("div", cls);
702
- line.textContent = glyph + data.text;
703
- appendTo(feed, line);
704
- if (level === "error" || level === "warn") announce(data.text);
705
- }
706
-
707
- // In-loop critic verdicts are session-log only (`critic_verdict` in
708
- // `session.jsonl`): the chat keeps user-facing notices and verbose-mode
709
- // diagnostics, not verifier verdicts.
710
-
711
- function renderAssistantText(feed, text) {
712
- const raw = String(text == null ? "" : text);
713
- if (!raw.trim()) return;
714
- const root = renderMarkdown(feed, raw, mdOpts());
715
- appendActionsRow(feed, root.innerText || root.textContent || "", raw);
716
- }
717
-
718
- function renderThinkBlock(feed, text) {
719
- const { wrap, body } = buildThinkBlock(false, false);
720
- body.textContent = text || "";
721
- feed.appendChild(wrap);
722
- }
723
-
724
- function renderHistoryAct(feed, entry, statusNodes) {
725
- const line = el("div", "act " + categoryOf(entry.name));
726
- line.appendChild(el("span", "g", "◇"));
727
- line.appendChild(el("span", "lab", labelOf(entry.name)));
728
- const args = argText(entry.args);
729
- if (args) {
730
- line.appendChild(el("span", "sep", "·"));
731
- line.appendChild(el("span", "args", args));
732
- }
733
- const status = el("span", "ok", "");
734
- line.appendChild(status);
735
- feed.appendChild(line);
736
- if (entry.toolCallId) statusNodes.set(entry.toolCallId, status);
737
- }
738
-
739
- function renderHistoryResult(feed, entry, statusNodes) {
740
- const dur = formatMs(entry.duration);
741
- const mark = (entry.success ? " ✓" : " ✗") + (dur ? " " + dur : "");
742
- const status = entry.toolCallId ? statusNodes.get(entry.toolCallId) : null;
743
- if (status) {
744
- status.textContent = mark;
745
- if (!entry.success) status.classList.add("fail");
746
- statusNodes.delete(entry.toolCallId);
747
- return;
748
- }
749
- const line = el("div", "act " + categoryOf(entry.tool));
750
- line.appendChild(el("span", "g", "↳"));
751
- line.appendChild(el("span", "lab", labelOf(entry.tool)));
752
- line.appendChild(el("span", "ok" + (entry.success ? "" : " fail"), mark));
753
- feed.appendChild(line);
754
- }
755
-
756
- function renderHistory(feed, entries) {
757
- const statusNodes = new Map();
758
- entries.forEach((entry) => {
759
- if (!entry || typeof entry.kind !== "string") return;
760
- switch (entry.kind) {
761
- case "user":
762
- feed.appendChild(el("div", "u", entry.text || ""));
763
- break;
764
- case "assistant":
765
- renderAssistantText(feed, entry.text || "");
766
- break;
767
- case "reasoning":
768
- renderThinkBlock(feed, entry.text || "");
769
- break;
770
- case "tool_call":
771
- renderHistoryAct(feed, entry, statusNodes);
772
- break;
773
- case "tool_result":
774
- renderHistoryResult(feed, entry, statusNodes);
775
- break;
776
- case "plan":
777
- if (entry.text) feed.appendChild(el("div", "sysline", "▸ " + entry.text));
778
- break;
779
- case "context":
780
- if (entry.text) feed.appendChild(el("div", "detail", "↳ " + entry.text));
781
- break;
782
- case "error":
783
- feed.appendChild(el("div", "errline", "✗ " + (entry.text || "")));
784
- break;
785
- case "system":
786
- if (entry.text) feed.appendChild(el("div", "sysline", entry.text));
787
- break;
788
- }
789
- });
790
- }
791
-
792
- export function feedHasContent(feed) {
793
- return !!feed.querySelector(".u, .md, .ans, .code, .act, .think, .metaln, .errline, .sysline");
794
- }
795
-
796
- export async function loadHistory(tab) {
797
- if (!tab || tab.historyLoaded || tab.historyLoading) return;
798
- tab.historyLoading = true;
799
- const placeholder = el("div", "detail", t("web.history.loading"));
800
- if (!feedHasContent(tab.feed)) {
801
- clearEmpty(tab.feed);
802
- tab.feed.appendChild(placeholder);
803
- }
804
- const r = await api("/api/sessions/" + encodeURIComponent(tab.id) + "/history");
805
- tab.historyLoading = false;
806
- placeholder.remove();
807
- tab.historyLoaded = true;
808
- if (!r.ok || !r.data || !Array.isArray(r.data.history)) {
809
- if (!feedHasContent(tab.feed)) tab.feed.appendChild(el("div", "detail", t("web.history.unavailable")));
810
- return;
811
- }
812
- // A live session already rendered its content over SSE: don't duplicate it.
813
- if (feedHasContent(tab.feed)) return;
814
- renderHistory(tab.feed, r.data.history);
815
- if (!r.data.history.length) tab.feed.appendChild(el("div", "detail", t("web.history.empty")));
816
- if (tab.feed === activeFeed()) scrollToBottom();
817
- else if (tab.feed === liveFeed()) scrollToBottom();
818
- }
819
-
820
- export function syncUrl() {
821
- const url = new URL(location.href);
822
- const tab = S.tabs.find((x) => x.id === S.activeTabId);
823
- if (tab && tab.kind === "session") url.searchParams.set("session", tab.id);
824
- else url.searchParams.delete("session");
825
- history.replaceState(null, "", url);
826
- }
827
-
828
- // One marking for a plan step, shared by the sidebar (`renderPlan`) and the
829
- // feed card (`onPlan`): status class + leading glyph for done/failed/current.
830
- function planStepMark(step, isCurrent) {
831
- const status =
832
- step.status === "done" ? "done" : step.status === "failed" ? "fail" : isCurrent ? "cur" : "";
833
- const glyph =
834
- step.status === "done" ? "✓ " : step.status === "failed" ? "✗ " : isCurrent ? "▸ " : "○ ";
835
- return { status, glyph };
836
- }
837
-
838
- export function renderPlan(plan) {
839
- // The sidebar polls `/api/state` every 2s; skip a rebuild (and the scroll
840
- // reset it would cause) when the snapshot is unchanged.
841
- const sig = JSON.stringify([plan, I18N.locale]);
842
- if (sig === S.lastPlanSig) return;
843
- S.lastPlanSig = sig;
844
- planEl.textContent = "";
845
- if (!plan || !Array.isArray(plan.steps) || !plan.steps.length) {
846
- planCountEl.textContent = "";
847
- planEl.appendChild(el("div", "kv dim", t("web.plan.none")));
848
- return;
849
- }
850
- const done = plan.steps.filter((s) => s.status === "done").length;
851
- const total = plan.steps.length;
852
- const pct = total ? Math.round((done / total) * 100) : 0;
853
- planCountEl.textContent = done + " / " + total;
854
- const bar = el("div", "plan-bar");
855
- const track = el("span", "pbar");
856
- const fill = el("i");
857
- fill.style.width = pct + "%";
858
- track.appendChild(fill);
859
- bar.appendChild(track);
860
- bar.appendChild(el("span", "", pct + "%"));
861
- planEl.appendChild(bar);
862
- plan.steps.forEach((step, i) => {
863
- const current = i === plan.currentIndex && step.status !== "done" && step.status !== "failed";
864
- const { status, glyph } = planStepMark(step, current);
865
- planEl.appendChild(el("div", ("plan-step " + status).trim(), glyph + step.text));
866
- });
867
- }
868
-
869
- export function onPlan(data) {
870
- const feed = feedForEvent(data && data.sessionId);
871
- if (!feed || !data || !Array.isArray(data.steps) || !data.steps.length) return;
872
- clearEmpty(feed);
873
- const tab = S.tabs.find((x) => x.feed === feed);
874
- let card = tab ? tab.planCard : null;
875
- if (!card || card.parentNode !== feed) {
876
- card = el("div", "planblock");
877
- appendTo(feed, card);
878
- if (tab) tab.planCard = card;
879
- }
880
- card.textContent = "";
881
- const total = data.steps.length;
882
- const cur = typeof data.currentIndex === "number" ? data.currentIndex : -1;
883
- card.appendChild(el("div", "ph2", "◇ " + pluralCount("web.plan.heading", total)));
884
- data.steps.forEach((step, i) => {
885
- const { status, glyph } = planStepMark(step, i === cur);
886
- card.appendChild(el("div", ("st " + status).trim(), glyph + step.text));
887
- });
888
- if (feed === activeFeed()) scroll();
889
- }
890
-
891
- // `? ` glyph on the last line, matching the terminal menu (the tool may have
892
- // already prefixed it — don't double it).
893
- function questionLabel(text) {
894
- const lines = String(text).split("\n");
895
- const last = lines[lines.length - 1];
896
- if (/^\s*\?/.test(last)) return String(text);
897
- lines[lines.length - 1] = "? " + last;
898
- return lines.join("\n");
899
- }
900
-
901
- // Answered-state + settle wiring shared by both question kinds. `display` is
902
- // what the user sees; `raw` is exactly what goes to `/api/answer`.
903
- function makeQuestionBlock(feed, promptText, id) {
904
- const block = el("div", "q");
905
- block.appendChild(el("div", "qtext", promptText));
906
- let answered = false;
907
- const settle = (display, raw) => {
908
- if (answered) return;
909
- answered = true;
910
- block.classList.add("answered");
911
- block.textContent = "";
912
- block.appendChild(el("div", "qtext", promptText));
913
- block.appendChild(el("div", "qans", "↳ " + (String(display || "").trim() || t("web.question.answered"))));
914
- api("/api/answer", { method: "POST", body: { id, value: raw } });
915
- if (feed === activeFeed()) scroll();
916
- };
917
- return { block, feed, settle, isAnswered: () => answered };
918
- }
919
-
920
- // Free-text prompt (a `text` question, or the tool's follow-up after the
921
- // "own answer" choice — see `askUser`/`CUSTOM_INDEX`).
922
- function questionTextInput(q, placeholder) {
923
- const wrap = el("div", "qcustom");
924
- const input = el("input", "qinput");
925
- input.type = "text";
926
- input.placeholder = placeholder;
927
- input.setAttribute("aria-label", placeholder);
928
- input.addEventListener("keydown", (e) => {
929
- if (e.isComposing || e.keyCode === 229) return;
930
- if (e.key === "Enter") {
931
- e.preventDefault();
932
- const value = input.value.trim();
933
- if (value) q.settle(value, value);
934
- }
935
- });
936
- wrap.appendChild(input);
937
- wrap.appendChild(
938
- link(t("web.question.custom_send"), "", () => {
939
- const value = input.value.trim();
940
- if (value) q.settle(value, value);
941
- }),
942
- );
943
- if (q.feed === activeFeed()) setTimeout(() => input.focus(), 0);
944
- return wrap;
945
- }
946
-
947
- // Choice prompt: single-select settles on click, multi-select collects into a
948
- // send button. The "own answer" entry maps to CUSTOM_INDEX (-1) so the tool
949
- // then asks the free-text follow-up.
950
- function questionChoice(q, data, options) {
951
- const multiple = !!data.multiple;
952
- const allowCustom = !!data.allowCustom;
953
- const entries = options.map((label) => ({ label, raw: null }));
954
- if (allowCustom) entries.push({ label: t("web.question.custom"), raw: -1 });
955
- const rawOf = (i) => (entries[i].raw != null ? entries[i].raw : i);
956
- const opts = el("div", "opts");
957
- const selected = [];
958
- let sendBtn = null;
959
- entries.forEach((entry, i) => {
960
- const row = el("button", "qopt", i + 1 + ". " + entry.label);
961
- row.type = "button";
962
- row.setAttribute("aria-checked", "false");
963
- row.addEventListener("click", () => {
964
- if (q.isAnswered()) return;
965
- if (multiple) {
966
- const pos = selected.indexOf(i);
967
- if (pos === -1) {
968
- selected.push(i);
969
- row.classList.add("on");
970
- row.setAttribute("aria-checked", "true");
971
- } else {
972
- selected.splice(pos, 1);
973
- row.classList.remove("on");
974
- row.setAttribute("aria-checked", "false");
975
- }
976
- if (sendBtn) sendBtn.disabled = selected.length === 0;
977
- } else {
978
- row.setAttribute("aria-checked", "true");
979
- q.settle(entry.label, [rawOf(i)]);
980
- }
981
- });
982
- opts.appendChild(row);
983
- });
984
- q.block.appendChild(opts);
985
- if (multiple) {
986
- q.block.appendChild(el("div", "qmulti-hint", t("web.question.multi_hint")));
987
- sendBtn = el("button", "qsend", t("web.question.send"));
988
- sendBtn.type = "button";
989
- sendBtn.disabled = true;
990
- sendBtn.addEventListener("click", () => {
991
- if (q.isAnswered() || !selected.length) return;
992
- const chosen = selected.slice().sort((a, b) => a - b);
993
- q.settle(chosen.map((i) => entries[i].label).join(", "), chosen.map(rawOf));
994
- });
995
- q.block.appendChild(sendBtn);
996
- }
997
- }
998
-
999
- export function onQuestion(data) {
1000
- if (!data || typeof data.text !== "string") return;
1001
- const feed = feedForEvent(data.sessionId);
1002
- if (!feed) return;
1003
- clearEmpty(feed);
1004
- const q = makeQuestionBlock(feed, questionLabel(data.text), data.id);
1005
- const options = Array.isArray(data.options) ? data.options : [];
1006
- if (data.kind === "choice" && options.length) questionChoice(q, data, options);
1007
- else q.block.appendChild(questionTextInput(q, t("web.question.placeholder")));
1008
- appendTo(feed, q.block);
1009
- if (feed === activeFeed()) scroll();
1010
- }
1011
-
1012
- function usageRow(label, value, cls) {
1013
- const row = el("div", "kv");
1014
- row.appendChild(el("span", "", label));
1015
- row.appendChild(el("b", cls || "", value));
1016
- return row;
1017
- }
1018
-
1019
- // Segmented context bar: system · tools · history · reserve · free, widths
1020
- // relative to the full context window so the parts always sum to one bar.
1021
- function contextSegBar(h, used, limit) {
1022
- const bar = el("div", "bar segbar");
1023
- const bd = h.contextBreakdown;
1024
- if (!bd || typeof bd.window !== "number" || bd.window <= 0) {
1025
- // Legacy fallback (pre-breakdown payloads): single used/limit fill.
1026
- const fill = el("i");
1027
- fill.style.width = Math.min(100, Math.round((used / limit) * 100)) + "%";
1028
- bar.appendChild(fill);
1029
- return bar;
1030
- }
1031
- const denom = Math.max(bd.window, bd.used + bd.reserve);
1032
- const parts = [
1033
- ["seg-system", bd.system],
1034
- ["seg-tools", bd.tools],
1035
- ["seg-history", bd.history],
1036
- ["seg-reserve", bd.reserve],
1037
- ["seg-free", bd.free],
1038
- ];
1039
- for (const [cls, n] of parts) {
1040
- if (!n || n <= 0) continue;
1041
- const seg = el("i", cls);
1042
- seg.style.width = (n / denom) * 100 + "%";
1043
- bar.appendChild(seg);
1044
- }
1045
- return bar;
1046
- }
1047
-
1048
- // Dim legend under the segmented bar: one dot+label+value per context part.
1049
- function contextLegend(h) {
1050
- const box = el("div", "seglegend");
1051
- const bd = h.contextBreakdown;
1052
- if (!bd) return box;
1053
- const items = [
1054
- ["seg-system", "web.usage.part_system", bd.system],
1055
- ["seg-tools", "web.usage.part_tools", bd.tools],
1056
- ["seg-history", "web.usage.part_history", bd.history],
1057
- ["seg-reserve", "web.usage.part_reserve", bd.reserve],
1058
- ["seg-free", "web.usage.part_free", bd.free],
1059
- ];
1060
- for (const [cls, labelKey, n] of items) {
1061
- if (!n || n <= 0) continue;
1062
- const item = el("span", "segl-item");
1063
- item.appendChild(el("i", "segl-dot " + cls));
1064
- item.appendChild(el("span", "segl-label", t(labelKey)));
1065
- item.appendChild(el("b", "", fmtTokens(n)));
1066
- box.appendChild(item);
1067
- }
1068
- return box;
1069
- }
1070
-
1071
- // Session row with a copy button next to the id (icon-only, ✓ on success).
1072
- function usageSessionRow(sessionId) {
1073
- const row = el("div", "kv");
1074
- row.appendChild(el("span", "", t("web.usage.session")));
1075
- const val = el("span", "kv-val");
1076
- if (!sessionId) {
1077
- val.appendChild(el("b", "", "—"));
1078
- } else {
1079
- val.appendChild(el("b", "", sessionId));
1080
- const btn = el("button", "copy-btn");
1081
- btn.type = "button";
1082
- btn.title = t("web.usage.copy_id");
1083
- btn.setAttribute("aria-label", t("web.usage.copy_id"));
1084
- btn.appendChild(icon("copy"));
1085
- btn.addEventListener("click", () => {
1086
- copyText(sessionId).then((ok) => {
1087
- btn.replaceChildren(icon(ok ? "check" : "copy"));
1088
- setTimeout(() => btn.replaceChildren(icon("copy")), 1500);
1089
- });
1090
- });
1091
- val.appendChild(btn);
1092
- }
1093
- row.appendChild(val);
1094
- return row;
1095
- }
1096
-
1097
- export function renderUsage(h) {
1098
- const cfg = S.state.config || {};
1099
- const sig = JSON.stringify([
1100
- h.sessionId, h.model, h.contextUsed, h.contextLimit, h.cacheHitRate, h.costUsd,
1101
- h.compactionCount, h.contextQuality, h.contextProbe, h.contextBreakdown, cfg.model, cfg.contextWindow,
1102
- I18N.locale,
1103
- ]);
1104
- if (sig === S.lastUsageSig) return;
1105
- S.lastUsageSig = sig;
1106
- usageEl.textContent = "";
1107
- const sid = h.sessionId ? String(h.sessionId) : "";
1108
- usageEl.appendChild(usageSessionRow(sid));
1109
- usageEl.appendChild(usageRow(t("web.usage.model"), h.model || cfg.model || "—"));
1110
- const used = typeof h.contextUsed === "number" ? h.contextUsed : null;
1111
- const limit = typeof h.contextLimit === "number" ? h.contextLimit : cfg.contextWindow || null;
1112
- usageEl.appendChild(usageRow(t("web.usage.context"), used != null && limit ? fmtTokens(used) + " / " + fmtTokens(limit) : "—"));
1113
- if (used != null && limit) {
1114
- usageEl.appendChild(contextSegBar(h, used, limit));
1115
- usageEl.appendChild(contextLegend(h));
1116
- }
1117
- const hit = typeof h.cacheHitRate === "number" ? h.cacheHitRate : null;
1118
- usageEl.appendChild(usageRow(t("web.usage.cache"), hit != null ? Math.round(hit * 100) + "%" : "—", hit != null ? "" : "dim"));
1119
- usageEl.appendChild(usageRow(t("web.usage.cost"), typeof h.costUsd === "number" ? "$" + h.costUsd.toFixed(4) : "—"));
1120
- usageEl.appendChild(usageRow(t("web.usage.compactions"), "⟳ " + (h.compactionCount || 0), h.compactionCount ? "wn" : "dim"));
1121
- const q = typeof h.contextQuality === "number" ? h.contextQuality : null;
1122
- usageEl.appendChild(usageRow(t("web.usage.quality"), q != null ? Math.round(q) + "%" : "—", q != null ? "" : "dim"));
1123
-
1124
- // Context probe: real LM Studio loaded context vs configured. Pending/absent
1125
- // renders a dim dash; loaded < configured warns.
1126
- let probeText = "—";
1127
- let probeCls = "dim";
1128
- const probe = h.contextProbe;
1129
- if (probe && typeof probe.actual === "number") {
1130
- const actual = fmtTokens(probe.actual);
1131
- const configured = h.contextLimit || cfg.contextWindow || null;
1132
- if (configured && probe.actual < configured) {
1133
- probeText = t("web.usage.probe_low", { actual, configured: fmtTokens(configured) });
1134
- probeCls = "wn";
1135
- } else {
1136
- probeText = t("web.usage.probe_loaded", { actual });
1137
- probeCls = "";
1138
- }
1139
- }
1140
- usageEl.appendChild(usageRow(t("web.usage.probe"), probeText, probeCls));
1141
- }
1142
-
1143
- // Workspace path under the composer (dim, own line).
1144
- export function renderWorkspace(h) {
1145
- const ws = h.workspace || "";
1146
- if (wsLine.dataset.ws === ws) return;
1147
- wsLine.dataset.ws = ws;
1148
- wsLine.textContent = "";
1149
- wsLine.hidden = !ws;
1150
- if (!ws) return;
1151
- wsLine.appendChild(el("span", "ws-k", t("web.env.dir")));
1152
- const v = el("span", "ws-v", ws);
1153
- v.title = ws;
1154
- wsLine.appendChild(v);
1155
- }
1156
-
1157
- // Environment (sidebar): stacked name/value items — AGENTS.md sources, map size.
1158
- function envItem(label, values) {
1159
- const item = el("div", "env-item");
1160
- item.appendChild(el("div", "env-k", label));
1161
- values.forEach((val) => {
1162
- const v = el("div", "env-v", val);
1163
- v.title = val;
1164
- item.appendChild(v);
1165
- });
1166
- return item;
1167
- }
1168
-
1169
- export function renderEnvironment(h) {
1170
- const disabled = !!(h.instructions && h.instructions.disabled);
1171
- const files = h.instructions && !disabled ? h.instructions.files || [] : [];
1172
- const map = h.projectMap || null;
1173
- const sig = JSON.stringify([disabled, files, map, I18N.locale]);
1174
- if (sig === S.lastEnvSig) return;
1175
- S.lastEnvSig = sig;
1176
- envEl.textContent = "";
1177
- envEl.appendChild(
1178
- envItem(
1179
- t("web.env.agents"),
1180
- disabled ? [t("web.env.agents_disabled")] : files.length ? files : [t("web.env.agents_none")],
1181
- ),
1182
- );
1183
- envEl.appendChild(
1184
- envItem(
1185
- t("web.env.map"),
1186
- map && map.indexed
1187
- ? [
1188
- [
1189
- pluralCount("web.env.files", map.files),
1190
- pluralCount("web.env.symbols", map.symbols),
1191
- pluralCount("web.env.tokens", map.tokens),
1192
- ].join(" · "),
1193
- ]
1194
- : [t("web.env.map_not_indexed")],
1195
- ),
1196
- );
1197
- }
1198
-
1199
- // Global `.think-h` toggle (depends on the wrap > [head, body] structure
1200
- // built by `buildThinkBlock`).
1201
- msgs.addEventListener("click", (e) => {
1202
- const head = e.target.closest(".think-h");
1203
- if (!head) return;
1204
- const body = head.nextElementSibling;
1205
- if (!body) return;
1206
- const hidden = body.classList.toggle("hidden");
1207
- head.setAttribute("aria-expanded", hidden ? "false" : "true");
1208
- const tog = head.querySelector(".tog");
1209
- if (tog) setIcon(tog, hidden ? "chevron-right" : "chevron-down", t(hidden ? "web.action.show" : "web.action.hide"));
1210
- });
1211
-
1
+ export*from"./feed/core.js";export*from"./feed/sidebar.js";export*from"./feed/history.js";export*from"./feed/plan.js";export*from"./feed/question.js";export*from"./feed/stream.js";export*from"./feed/turn.js";