micro-models-agent 2.16.1 → 2.17.2

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