mlx-bun 0.5.0 → 1.0.0

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 (640) hide show
  1. package/README.md +1449 -109
  2. package/THIRD_PARTY_NOTICES.md +882 -0
  3. package/bin/mlx-bun.convert.mjs +4 -0
  4. package/bin/mlx-bun.fuse.mjs +4 -0
  5. package/bin/mlx-bun.generate.mjs +4 -0
  6. package/bin/mlx-bun.lora.mjs +4 -0
  7. package/bin/mlx-bun.mjs +13 -21
  8. package/bin/mlx-bun.server.mjs +4 -0
  9. package/bin/mlx-bun.upload.mjs +4 -0
  10. package/dist/web/app.js +9584 -0
  11. package/package.json +50 -53
  12. package/src/cli/ane-install.ts +76 -0
  13. package/src/cli/args.ts +179 -0
  14. package/src/cli/engine-entry.ts +68 -0
  15. package/src/cli/inference.ts +214 -0
  16. package/src/cli/job-entry.ts +97 -0
  17. package/src/cli/library-host.ts +117 -0
  18. package/src/cli/main.ts +57 -0
  19. package/src/cli/memory-engine.ts +163 -0
  20. package/src/cli/memory.ts +36 -0
  21. package/src/cli/mlx-lm-aliases.ts +309 -0
  22. package/src/cli/model-choice.ts +33 -0
  23. package/src/cli/model-selection.ts +84 -0
  24. package/src/cli/module-host.ts +11 -0
  25. package/src/cli/module-verbs.ts +50 -0
  26. package/src/cli/numerical-policy.ts +12 -0
  27. package/src/cli/perplexity.ts +89 -0
  28. package/src/cli/serve-host.ts +423 -0
  29. package/src/cli/serve-isolated.ts +217 -0
  30. package/src/cli/serve-options.ts +106 -0
  31. package/src/cli/serve-state.ts +205 -0
  32. package/src/cli/serve.ts +500 -0
  33. package/src/cli/served-model-host.ts +102 -0
  34. package/src/cli/server-entry.ts +148 -0
  35. package/src/cli/serving-unit.ts +177 -0
  36. package/src/{tui.ts → cli/terminal.ts} +13 -8
  37. package/src/cli/worker-adapters.ts +34 -0
  38. package/src/cli/worker-entry.ts +272 -0
  39. package/src/cli/worker-unit.ts +179 -0
  40. package/src/engine/adapter-operation.ts +26 -0
  41. package/src/engine/cache-services.ts +114 -0
  42. package/src/engine/completion.ts +39 -0
  43. package/src/engine/generation-gateway.ts +358 -0
  44. package/src/engine/index.ts +59 -0
  45. package/src/engine/kv-budget.ts +120 -0
  46. package/src/engine/media-preparation.ts +232 -0
  47. package/src/engine/model-binding.ts +24 -0
  48. package/src/engine/model-host.ts +513 -0
  49. package/src/engine/model-serving.ts +89 -0
  50. package/src/{serve → engine}/preparation.ts +7 -5
  51. package/src/{serve → engine}/session-completion-engine.ts +6 -6
  52. package/src/engine/telemetry.ts +109 -0
  53. package/src/hub/downloads.ts +116 -0
  54. package/src/jobs/db.ts +16 -7
  55. package/src/jobs/events.ts +29 -0
  56. package/src/jobs/executable.ts +5 -0
  57. package/src/jobs/host.ts +51 -0
  58. package/src/jobs/in-process.ts +55 -0
  59. package/src/{contracts/jobs.ts → jobs/protocol.ts} +7 -9
  60. package/src/jobs/runner.ts +150 -231
  61. package/src/jobs/service.ts +75 -0
  62. package/src/jobs/sse.ts +59 -23
  63. package/src/jobs/worker-memory.ts +46 -0
  64. package/src/jobs/worker-process.ts +163 -0
  65. package/src/jobs/worker-supervisor.ts +258 -0
  66. package/src/modules.ts +58 -0
  67. package/src/publishing/catalog-hub.ts +18 -0
  68. package/src/publishing/credentials.ts +31 -0
  69. package/src/publishing/upload.ts +37 -0
  70. package/src/residency/leased-adapters.ts +21 -0
  71. package/src/residency/local-records.ts +16 -0
  72. package/src/residency/model-residency.ts +405 -0
  73. package/src/residency/record-index.ts +28 -0
  74. package/src/residency/resident-estimate.ts +50 -0
  75. package/src/{anthropic.ts → server/anthropic.ts} +3 -81
  76. package/src/server/cache-routes.ts +36 -0
  77. package/src/{serve → server}/chat-request.ts +10 -10
  78. package/src/{serve → server}/chat-stage.ts +14 -14
  79. package/src/{client.ts → server/client.ts} +28 -2
  80. package/src/{serve → server}/completion-executor.ts +33 -41
  81. package/src/server/decision-body.ts +55 -0
  82. package/src/server/decision-routes.ts +134 -0
  83. package/src/server/decision-wire.ts +228 -0
  84. package/src/{serve → server}/discovery-routes.ts +25 -82
  85. package/src/{serve → server}/http.ts +25 -13
  86. package/src/{serve → server}/inference-request.ts +5 -5
  87. package/src/server/job-routes.ts +20 -0
  88. package/src/server/media-prompt.ts +63 -0
  89. package/src/server/memory-completion-client.ts +46 -0
  90. package/src/server/model-routes.ts +192 -0
  91. package/src/{serve → server}/openai-wire.ts +3 -2
  92. package/src/{serve → server}/pipeline.ts +1 -1
  93. package/src/server/prompt-builder.ts +13 -0
  94. package/src/server/prompt-contracts.ts +17 -0
  95. package/src/server/proxy-routes.ts +187 -0
  96. package/src/server/publishing-routes.ts +41 -0
  97. package/src/{serve → server}/request-plan.ts +6 -8
  98. package/src/{serve → server}/request-prep.ts +47 -50
  99. package/src/{serve → server}/responses-client.ts +30 -13
  100. package/src/{responses.ts → server/responses.ts} +12 -93
  101. package/src/server/routes.ts +205 -0
  102. package/src/server/start.ts +72 -0
  103. package/src/server/status-routes.ts +108 -0
  104. package/src/{serve → server}/text-completion-stage.ts +25 -4
  105. package/src/server/text-prompt.ts +12 -0
  106. package/src/{serve → server}/token-streams.ts +36 -38
  107. package/src/server/worker-routes.ts +311 -0
  108. package/src/storage/paths.ts +61 -0
  109. package/src/web/assets.ts +37 -0
  110. package/src/web/{src → browser}/api.ts +5 -23
  111. package/src/web/browser/chat-host.ts +17 -0
  112. package/src/web/browser/installed-panels.ts +6 -0
  113. package/src/web/browser/main.ts +55 -0
  114. package/src/web/browser/markdown.ts +279 -0
  115. package/src/web/browser/memory-panel.ts +9 -0
  116. package/src/web/browser/palette.ts +64 -0
  117. package/src/web/browser/protocol.ts +13 -0
  118. package/src/web/browser/shell.ts +467 -0
  119. package/src/web/{src → browser}/status.ts +32 -36
  120. package/src/web/build.ts +51 -0
  121. package/src/web/public/app.html +1054 -0
  122. package/src/web/{sw.js → public/sw.js} +4 -3
  123. package/src/web/{vendor → public/vendor}/hljs-theme.css +2 -2
  124. package/src/web/{vendor → public/vendor}/hljs.js +2 -2
  125. package/THIRD_PARTY_LICENSES.md +0 -51
  126. package/docs/README.md +0 -107
  127. package/docs/archive/hlg-sampling.md +0 -350
  128. package/docs/archive/investigations/audio-input-plan.md +0 -356
  129. package/docs/archive/investigations/benchmark-ledger-through-v0.4.0.md +0 -3617
  130. package/docs/archive/investigations/chunk-finetune-experiment.md +0 -84
  131. package/docs/archive/investigations/colibri-glm52-port.md +0 -1486
  132. package/docs/archive/investigations/colibri-oracle-pin.md +0 -1284
  133. package/docs/archive/investigations/curve-bisector-routing.md +0 -123
  134. package/docs/archive/investigations/decode-roofline-lookagain.md +0 -348
  135. package/docs/archive/investigations/diffusion-gemma-port.md +0 -602
  136. package/docs/archive/investigations/dspark-handoff.md +0 -224
  137. package/docs/archive/investigations/expert-offload-single-user-moe.md +0 -292
  138. package/docs/archive/investigations/faithful-l1-consolidation.md +0 -310
  139. package/docs/archive/investigations/hlg-sampling-investigation.md +0 -970
  140. package/docs/archive/investigations/indexshare-performance-spike.md +0 -357
  141. package/docs/archive/investigations/kernel-perf-review-2026-07.md +0 -171
  142. package/docs/archive/investigations/lab-build-journal.md +0 -157
  143. package/docs/archive/investigations/minicpm5-decode-megakernel.md +0 -489
  144. package/docs/archive/investigations/openjarvis-fit.md +0 -198
  145. package/docs/archive/investigations/openwiki-evaluation.md +0 -249
  146. package/docs/archive/investigations/optimization_plan.md +0 -346
  147. package/docs/archive/investigations/orpo-base-uf-experiment-and-directions.md +0 -120
  148. package/docs/archive/investigations/orpo-flash-cce-pin-leak.md +0 -167
  149. package/docs/archive/investigations/orpo-uf-testing-handoff.md +0 -126
  150. package/docs/archive/investigations/pareto-specialized-runtime-findings.md +0 -92
  151. package/docs/archive/investigations/pi-builtin-investigation.md +0 -308
  152. package/docs/archive/investigations/pre-colibri-stabilization.md +0 -329
  153. package/docs/archive/investigations/qwen38-27b-campaign-log.md +0 -1092
  154. package/docs/archive/investigations/segmented-backward-handoff.md +0 -148
  155. package/docs/archive/investigations/starter-model-port-handoff.md +0 -342
  156. package/docs/archive/investigations/steel-flash-cce-handoff.md +0 -489
  157. package/docs/archive/investigations/the-dreaming-handoff.md +0 -141
  158. package/docs/archive/investigations/trainer-validation-experiment.md +0 -173
  159. package/docs/archive/planning/chat-ui-vision-memory-first.md +0 -199
  160. package/docs/archive/planning/chat-ui-vision.md +0 -50
  161. package/docs/archive/planning/chunk-task-roadmap.md +0 -77
  162. package/docs/archive/planning/curve-sampler-research-plan.md +0 -331
  163. package/docs/archive/planning/journal.md +0 -316
  164. package/docs/archive/planning/memory-docs-and-dag-plan.md +0 -194
  165. package/docs/archive/planning/pi-integration-review.md +0 -178
  166. package/docs/archive/planning/release-notes-v0.0.10.md +0 -57
  167. package/docs/archive/planning/release-notes-v0.0.11.md +0 -239
  168. package/docs/archive/planning/release-notes-v0.0.12.md +0 -80
  169. package/docs/archive/planning/release-notes-v0.0.13.md +0 -77
  170. package/docs/archive/planning/release-notes-v0.0.9.md +0 -122
  171. package/docs/archive/planning/release-notes-v0.2.0.md +0 -49
  172. package/docs/archive/planning/release-notes-v0.3.0.md +0 -35
  173. package/docs/archive/planning/release-notes-v0.4.0.md +0 -45
  174. package/docs/archive/planning/release-notes-v0.5.0.md +0 -37
  175. package/docs/archive/planning/web-ui-pass-plan.md +0 -213
  176. package/docs/archive/planning/website-readme-pass-plan.md +0 -224
  177. package/docs/design/batching.md +0 -2054
  178. package/docs/design/decode-speed-program.md +0 -2404
  179. package/docs/design/dreaming-nightly-pipeline.md +0 -684
  180. package/docs/design/generic-model-support.md +0 -821
  181. package/docs/design/kv-cache.md +0 -993
  182. package/docs/design/orpo-training.md +0 -680
  183. package/docs/design/speculative-decoding.md +0 -2129
  184. package/docs/design/turboquant.md +0 -2225
  185. package/docs/design/unified-engine-frontier-plan.md +0 -1987
  186. package/docs/design/web-chat-redesign.md +0 -695
  187. package/docs/planning/PRODUCT_ROADMAP.md +0 -593
  188. package/docs/planning/ResearchTopics.md +0 -119
  189. package/docs/planning/public-readiness-handoff.md +0 -244
  190. package/docs/planning/public-readiness.md +0 -149
  191. package/docs/planning/video-series.md +0 -421
  192. package/docs/reference/benchmarks.md +0 -3587
  193. package/docs/reference/cli.md +0 -646
  194. package/docs/reference/distribution.md +0 -380
  195. package/docs/reference/environment.md +0 -494
  196. package/docs/reference/glossary.md +0 -39
  197. package/docs/reference/library-api.md +0 -688
  198. package/docs/reference/memory.md +0 -260
  199. package/docs/reference/models.md +0 -346
  200. package/docs/reference/server-api.md +0 -1945
  201. package/docs/reference/server-config.md +0 -973
  202. package/docs/reference/training.md +0 -462
  203. package/docs/reference/troubleshooting.md +0 -158
  204. package/src/admission-plan.ts +0 -18
  205. package/src/audio/audiotoolbox.ts +0 -112
  206. package/src/audio/conformer.ts +0 -655
  207. package/src/audio/decode.ts +0 -133
  208. package/src/audio/features.ts +0 -202
  209. package/src/audio/mic-capture.ts +0 -98
  210. package/src/audio/silero-vad.ts +0 -379
  211. package/src/audio/transcode.ts +0 -67
  212. package/src/audio/whisper-decode.ts +0 -774
  213. package/src/audio/whisper-fast.ts +0 -732
  214. package/src/audio/whisper-format.ts +0 -73
  215. package/src/audio/whisper-mel.ts +0 -185
  216. package/src/audio/whisper-timing.ts +0 -274
  217. package/src/audio/whisper-tokenizer.ts +0 -239
  218. package/src/audio/whisper-transcribe.ts +0 -410
  219. package/src/backends/mlx/assistant-target.ts +0 -46
  220. package/src/backends/mlx/autoregressive-draft-rows.ts +0 -120
  221. package/src/backends/mlx/autoregressive.ts +0 -129
  222. package/src/backends/mlx/batch-group.ts +0 -1874
  223. package/src/backends/mlx/cache-attachment.ts +0 -67
  224. package/src/backends/mlx/cache-layout.ts +0 -31
  225. package/src/backends/mlx/checkpoint-state.ts +0 -53
  226. package/src/backends/mlx/continuation-persistence.ts +0 -107
  227. package/src/backends/mlx/continuation-request.ts +0 -43
  228. package/src/backends/mlx/continuation.ts +0 -77
  229. package/src/backends/mlx/diffusion.ts +0 -39
  230. package/src/backends/mlx/draft-target.ts +0 -32
  231. package/src/backends/mlx/encoder-cache.ts +0 -25
  232. package/src/backends/mlx/evaluation.ts +0 -105
  233. package/src/backends/mlx/fill-append.ts +0 -58
  234. package/src/backends/mlx/fill-group.ts +0 -497
  235. package/src/backends/mlx/full-kv-row-append.ts +0 -32
  236. package/src/backends/mlx/full-kv-row-donor.ts +0 -54
  237. package/src/backends/mlx/full-transitioning-kv-rows.ts +0 -65
  238. package/src/backends/mlx/gateway-binding.ts +0 -174
  239. package/src/backends/mlx/graph.ts +0 -38
  240. package/src/backends/mlx/kv-codec.ts +0 -9
  241. package/src/backends/mlx/kv-maintenance.ts +0 -161
  242. package/src/backends/mlx/kv-tensor-rows.ts +0 -212
  243. package/src/backends/mlx/memory-client.ts +0 -189
  244. package/src/backends/mlx/methods.ts +0 -90
  245. package/src/backends/mlx/mixed-iteration.ts +0 -62
  246. package/src/backends/mlx/model-prompt.ts +0 -178
  247. package/src/backends/mlx/model-serving.ts +0 -80
  248. package/src/backends/mlx/paged-cache-codec.ts +0 -27
  249. package/src/backends/mlx/prefill-cohort.ts +0 -100
  250. package/src/backends/mlx/prefill-policy.ts +0 -26
  251. package/src/backends/mlx/prefill-rows.ts +0 -244
  252. package/src/backends/mlx/prefill.ts +0 -33
  253. package/src/backends/mlx/projected-context-rows.ts +0 -72
  254. package/src/backends/mlx/prompt-input.ts +0 -29
  255. package/src/backends/mlx/qwen-prompt-input.ts +0 -40
  256. package/src/backends/mlx/request-policy.ts +0 -20
  257. package/src/backends/mlx/request-state-policy.ts +0 -54
  258. package/src/backends/mlx/rollback.ts +0 -92
  259. package/src/backends/mlx/row-sampling.ts +0 -49
  260. package/src/backends/mlx/serial-executor.ts +0 -251
  261. package/src/backends/mlx/speculative-group.ts +0 -438
  262. package/src/backends/mlx/speculative-round.ts +0 -161
  263. package/src/backends/mlx/speculative.ts +0 -131
  264. package/src/backends/mlx/state-rows.ts +0 -104
  265. package/src/backends/mlx/state-views.ts +0 -46
  266. package/src/backends/mlx/target-layout-capability.ts +0 -35
  267. package/src/backends/mlx/text-engine.ts +0 -73
  268. package/src/backends/mlx/transitioning-kv-rows.ts +0 -173
  269. package/src/chat-template.ts +0 -382
  270. package/src/cli.ts +0 -3490
  271. package/src/config.ts +0 -399
  272. package/src/contracts/completion.ts +0 -16
  273. package/src/contracts/execution.ts +0 -70
  274. package/src/contracts/generation.ts +0 -103
  275. package/src/contracts/host.ts +0 -7
  276. package/src/contracts/object-cache.ts +0 -8
  277. package/src/contracts/pi-provider.ts +0 -30
  278. package/src/contracts/pi.ts +0 -261
  279. package/src/contracts/prefix-cache.ts +0 -33
  280. package/src/contracts/resources.ts +0 -25
  281. package/src/contracts/sampling.ts +0 -23
  282. package/src/contracts/scheduling.ts +0 -54
  283. package/src/contracts/tsconfig.json +0 -14
  284. package/src/dataset/generators.ts +0 -1087
  285. package/src/dataset/index.ts +0 -12
  286. package/src/dataset/job.ts +0 -81
  287. package/src/dataset/llm.ts +0 -71
  288. package/src/dataset/registry.ts +0 -528
  289. package/src/diffusion/diffusion-generate.ts +0 -507
  290. package/src/download.ts +0 -505
  291. package/src/embed.ts +0 -77
  292. package/src/engine/admission.ts +0 -59
  293. package/src/engine/cancellation.ts +0 -38
  294. package/src/engine/engine.ts +0 -56
  295. package/src/engine/execution-coordinator.ts +0 -70
  296. package/src/engine/execution-plan.ts +0 -64
  297. package/src/engine/execution-tasks.ts +0 -44
  298. package/src/engine/namespaced-cache.ts +0 -13
  299. package/src/engine/resources.ts +0 -53
  300. package/src/engine/scheduler.ts +0 -92
  301. package/src/engine/session.ts +0 -247
  302. package/src/eval/README.md +0 -130
  303. package/src/eval/ifeval.ts +0 -81
  304. package/src/eval/kl-prompts.ts +0 -29
  305. package/src/eval/kl.ts +0 -590
  306. package/src/eval/perplexity.ts +0 -164
  307. package/src/eval/quality-db.ts +0 -92
  308. package/src/eval/reference-logits.ts +0 -207
  309. package/src/eval/runner.ts +0 -124
  310. package/src/eval/score.ts +0 -49
  311. package/src/eval/tasks/bfcl.ts +0 -535
  312. package/src/eval/tasks/gsm8k.ts +0 -98
  313. package/src/eval/tasks/hashhop.ts +0 -139
  314. package/src/eval/tasks/humaneval.ts +0 -275
  315. package/src/eval/tasks/ifeval.ts +0 -525
  316. package/src/eval/tasks/mmlu.ts +0 -157
  317. package/src/eval/ultrafeedback.ts +0 -60
  318. package/src/evaldb.ts +0 -199
  319. package/src/expert-io.ts +0 -393
  320. package/src/expert-offload-build.ts +0 -77
  321. package/src/expert-offload.ts +0 -76
  322. package/src/expert-residency.ts +0 -1134
  323. package/src/expert-usage.ts +0 -415
  324. package/src/fill/echo-index.ts +0 -188
  325. package/src/fill/fill-session.ts +0 -463
  326. package/src/fill/proposal.ts +0 -68
  327. package/src/fill/schema-rows.ts +0 -506
  328. package/src/fill/tool-boundary.ts +0 -103
  329. package/src/fit.ts +0 -249
  330. package/src/generate.ts +0 -1352
  331. package/src/grammar.ts +0 -604
  332. package/src/harness-pi.ts +0 -184
  333. package/src/hf-push.ts +0 -628
  334. package/src/hub-rest.ts +0 -242
  335. package/src/index.ts +0 -63
  336. package/src/inference/denoising.ts +0 -17
  337. package/src/inference/draft-acceptance.ts +0 -41
  338. package/src/inference/draft-output.ts +0 -49
  339. package/src/inference/graph.ts +0 -33
  340. package/src/inference/prefill.ts +0 -45
  341. package/src/inference/rollback.ts +0 -7
  342. package/src/jobs/index.ts +0 -31
  343. package/src/jobs/job-entry.ts +0 -107
  344. package/src/jobs/task-client.ts +0 -19
  345. package/src/jobs/types.ts +0 -2
  346. package/src/kv-scheme.ts +0 -279
  347. package/src/kv-store.ts +0 -1135
  348. package/src/lab/README.md +0 -22
  349. package/src/lab/curve/curve-designer.html +0 -303
  350. package/src/lab/curve/curve-sampler.ts +0 -122
  351. package/src/lab/expert-trace/expert-trace.ts +0 -69
  352. package/src/lab/paged-kv/paged-attention.ts +0 -117
  353. package/src/lab/paged-kv/paged-kv-rows.ts +0 -111
  354. package/src/lab/paged-kv/paged-kv.ts +0 -336
  355. package/src/library.ts +0 -40
  356. package/src/lora.ts +0 -408
  357. package/src/md-modules.d.ts +0 -54
  358. package/src/media-fetch.ts +0 -442
  359. package/src/memory/article.ts +0 -558
  360. package/src/memory/chunk-validation.ts +0 -150
  361. package/src/memory/chunk.ts +0 -318
  362. package/src/memory/cluster.ts +0 -351
  363. package/src/memory/coverage.ts +0 -134
  364. package/src/memory/crosslink.ts +0 -0
  365. package/src/memory/db.ts +0 -253
  366. package/src/memory/entity.ts +0 -203
  367. package/src/memory/gate.ts +0 -187
  368. package/src/memory/history.ts +0 -152
  369. package/src/memory/ingest.ts +0 -335
  370. package/src/memory/model.ts +0 -183
  371. package/src/memory/normalize.ts +0 -671
  372. package/src/memory/parse.ts +0 -22
  373. package/src/memory/pipeline.ts +0 -275
  374. package/src/memory/prompts.ts +0 -65
  375. package/src/memory/query.ts +0 -334
  376. package/src/memory/reconcile.ts +0 -734
  377. package/src/memory/redirect.ts +0 -108
  378. package/src/memory/reindex.ts +0 -325
  379. package/src/memory/resolve.ts +0 -307
  380. package/src/memory/rest.ts +0 -245
  381. package/src/memory/route.ts +0 -272
  382. package/src/memory/schedule.ts +0 -165
  383. package/src/memory/stages.ts +0 -777
  384. package/src/memory/synthesize.ts +0 -1638
  385. package/src/memory/tools.ts +0 -749
  386. package/src/memory/vault.ts +0 -1194
  387. package/src/memory/wikify.ts +0 -1064
  388. package/src/mlx/array.ts +0 -425
  389. package/src/mlx/autograd.ts +0 -304
  390. package/src/mlx/checkpoint.ts +0 -149
  391. package/src/mlx/compile.ts +0 -168
  392. package/src/mlx/custom-vjp.ts +0 -188
  393. package/src/mlx/ffi.ts +0 -531
  394. package/src/mlx/materialize.ts +0 -23
  395. package/src/mlx/metal-kernel.ts +0 -159
  396. package/src/mlx/normalized-argmax.ts +0 -116
  397. package/src/mlx/ops.ts +0 -1108
  398. package/src/mlx/quantized-rows.ts +0 -36
  399. package/src/mlx/token-bitmask.ts +0 -52
  400. package/src/mlx/turboquant-kv-decode.ts +0 -82
  401. package/src/mlx/turboquant-kv-inverse.ts +0 -122
  402. package/src/mlx/turboquant-ops.ts +0 -861
  403. package/src/mlx/turboquant-tables.ts +0 -237
  404. package/src/mmap.ts +0 -90
  405. package/src/model/artifact-identity.ts +0 -73
  406. package/src/model/batched-kv.ts +0 -235
  407. package/src/model/batched-mask.ts +0 -279
  408. package/src/model/batched-quant.ts +0 -212
  409. package/src/model/batched-quantized-kv.ts +0 -144
  410. package/src/model/batched-rotating-quant.ts +0 -353
  411. package/src/model/batched-rotating-state.ts +0 -149
  412. package/src/model/batched-rotating.ts +0 -338
  413. package/src/model/batched-row-storage.ts +0 -179
  414. package/src/model/batched-ssm.ts +0 -52
  415. package/src/model/batched-turboquant-kv.ts +0 -94
  416. package/src/model/compiled-decode.ts +0 -712
  417. package/src/model/decoded-kv-donor.ts +0 -12
  418. package/src/model/delayed-quantized-kv.ts +0 -72
  419. package/src/model/delayed-rotating-quantized-kv.ts +0 -93
  420. package/src/model/delayed-turboquant-kv.ts +0 -57
  421. package/src/model/diffusion-gemma.ts +0 -1026
  422. package/src/model/factory.ts +0 -215
  423. package/src/model/fingerprint.ts +0 -45
  424. package/src/model/flash-attention.ts +0 -694
  425. package/src/model/full-prefill-padding.ts +0 -56
  426. package/src/model/full-prefill-row.ts +0 -105
  427. package/src/model/gemma4-base.ts +0 -2370
  428. package/src/model/gemma4.ts +0 -1395
  429. package/src/model/generated/gemma4-12b.ts +0 -429
  430. package/src/model/generated/gemma4-26b.ts +0 -448
  431. package/src/model/generated/gemma4-e4b.ts +0 -717
  432. package/src/model/generated/index.ts +0 -18
  433. package/src/model/glm52-atlas.ts +0 -511
  434. package/src/model/glm52-cache.ts +0 -792
  435. package/src/model/glm52-config.ts +0 -262
  436. package/src/model/glm52-container.ts +0 -305
  437. package/src/model/glm52-coupling.ts +0 -388
  438. package/src/model/glm52-dsa.ts +0 -606
  439. package/src/model/glm52-expert-layout.ts +0 -253
  440. package/src/model/glm52-layout.ts +0 -152
  441. package/src/model/glm52-memory.ts +0 -500
  442. package/src/model/glm52-mla.ts +0 -792
  443. package/src/model/glm52-moe.ts +0 -223
  444. package/src/model/glm52-pilot.ts +0 -537
  445. package/src/model/glm52-reference.ts +0 -445
  446. package/src/model/glm52-residency.ts +0 -557
  447. package/src/model/glm52-resident-weights.ts +0 -135
  448. package/src/model/glm52-streamed-experts.ts +0 -453
  449. package/src/model/glm52-streamed-metal.ts +0 -610
  450. package/src/model/glm52-weights.ts +0 -503
  451. package/src/model/glm52.ts +0 -1132
  452. package/src/model/implementation.ts +0 -69
  453. package/src/model/kv-attention-view.ts +0 -70
  454. package/src/model/minicpm5.ts +0 -376
  455. package/src/model/profile.ts +0 -468
  456. package/src/model/quantized-append-attention.ts +0 -31
  457. package/src/model/qwen-append.ts +0 -12
  458. package/src/model/qwen-conv.ts +0 -52
  459. package/src/model/qwen3-delta-state.ts +0 -74
  460. package/src/model/qwen3-delta.ts +0 -451
  461. package/src/model/qwen3-moe.ts +0 -404
  462. package/src/model/qwen3-mrope.ts +0 -250
  463. package/src/model/qwen3.ts +0 -277
  464. package/src/model/qwen3_5-checkpoint.ts +0 -164
  465. package/src/model/qwen3_5.ts +0 -881
  466. package/src/model/rotating-kv-donor.ts +0 -35
  467. package/src/model/rotating-kv-layout.ts +0 -262
  468. package/src/model/rotating-row-transaction.ts +0 -35
  469. package/src/model/speculative-rotating-kv.ts +0 -101
  470. package/src/model/ssm-prefill-padding.ts +0 -39
  471. package/src/model/support.ts +0 -110
  472. package/src/model/token-groups.ts +0 -66
  473. package/src/model/trellis-balanced-scatter.ts +0 -51
  474. package/src/model/trellis-linear.ts +0 -672
  475. package/src/model/trellis-shared-m.ts +0 -151
  476. package/src/model/trellis-shared-scatter.ts +0 -144
  477. package/src/model/trellis-splitk-prefill.ts +0 -158
  478. package/src/model/trellis-tiled-prefill.ts +0 -136
  479. package/src/model/trellis-vector-expand.ts +0 -62
  480. package/src/model/trellis-wide-prefill.ts +0 -94
  481. package/src/model/turboquant-codec.ts +0 -89
  482. package/src/model/universal/archs.ts +0 -332
  483. package/src/model/universal/dense.ts +0 -612
  484. package/src/model/universal/modules.ts +0 -170
  485. package/src/model/universal/rope.ts +0 -381
  486. package/src/model/whisper.ts +0 -435
  487. package/src/native/expert_io.c +0 -523
  488. package/src/native/frame_extract.swift +0 -140
  489. package/src/native/mic_capture.swift +0 -92
  490. package/src/native-pack.ts +0 -163
  491. package/src/pi-provider.ts +0 -53
  492. package/src/pi-session.ts +0 -81
  493. package/src/pi-terminal.ts +0 -274
  494. package/src/pi-web.ts +0 -1940
  495. package/src/preflight.ts +0 -158
  496. package/src/prompt-cache.ts +0 -682
  497. package/src/quantize/allocator.ts +0 -627
  498. package/src/quantize/atomic-output.ts +0 -38
  499. package/src/quantize/calibration.ts +0 -181
  500. package/src/quantize/config-writer.ts +0 -145
  501. package/src/quantize/index.ts +0 -84
  502. package/src/quantize/job.ts +0 -162
  503. package/src/quantize/quantizer.ts +0 -666
  504. package/src/quantize/rotate.ts +0 -603
  505. package/src/quantize/safetensors-writer.ts +0 -249
  506. package/src/quantize/sensitivity.ts +0 -216
  507. package/src/quantize/trellis.ts +0 -586
  508. package/src/quantize/weight-transform.ts +0 -292
  509. package/src/registry.ts +0 -602
  510. package/src/runtime-config.ts +0 -77
  511. package/src/safetensors.ts +0 -165
  512. package/src/sampler.ts +0 -1012
  513. package/src/serve/admin-routes.ts +0 -184
  514. package/src/serve/assets/curve-terrain.html +0 -105
  515. package/src/serve/assets/training-inference-map.html +0 -575
  516. package/src/serve/audio-routes.ts +0 -323
  517. package/src/serve/aux-routes.ts +0 -132
  518. package/src/serve/batch-scheduler.ts +0 -8
  519. package/src/serve/checkpoint-identity.ts +0 -64
  520. package/src/serve/generation-gateway.ts +0 -537
  521. package/src/serve/isolate.ts +0 -609
  522. package/src/serve/kv-budget.ts +0 -14
  523. package/src/serve/lab-routes.ts +0 -341
  524. package/src/serve/lane-registry.ts +0 -45
  525. package/src/serve/model-admin-routes.ts +0 -155
  526. package/src/serve/model-binding.ts +0 -49
  527. package/src/serve/model-host.ts +0 -511
  528. package/src/serve/parent-application.ts +0 -56
  529. package/src/serve/prompt-response-trace.ts +0 -161
  530. package/src/serve/response-lease.ts +0 -24
  531. package/src/serve/session-search.ts +0 -326
  532. package/src/serve/static-routes.ts +0 -101
  533. package/src/serve/transcription-server.ts +0 -64
  534. package/src/serve/transcription-service.ts +0 -502
  535. package/src/server.ts +0 -1282
  536. package/src/spec/assistant-rows.ts +0 -93
  537. package/src/spec/assistant-source.ts +0 -126
  538. package/src/spec/cache-identity.ts +0 -22
  539. package/src/spec/deepspec-rows.ts +0 -29
  540. package/src/spec/deepspec-source.ts +0 -145
  541. package/src/spec/dflash-source.ts +0 -149
  542. package/src/spec/drafter.ts +0 -304
  543. package/src/spec/dspark/ab-stats.ts +0 -136
  544. package/src/spec/dspark/calibration.ts +0 -112
  545. package/src/spec/dspark/data-dflash.ts +0 -156
  546. package/src/spec/dspark/data.ts +0 -201
  547. package/src/spec/dspark/deepspec-module.ts +0 -776
  548. package/src/spec/dspark/generate-dflash.ts +0 -245
  549. package/src/spec/dspark/generate.ts +0 -290
  550. package/src/spec/dspark/loader.ts +0 -23
  551. package/src/spec/dspark/loss.ts +0 -213
  552. package/src/spec/dspark/module-dflash.ts +0 -560
  553. package/src/spec/dspark/module.ts +0 -540
  554. package/src/spec/dspark/quantize-drafter.ts +0 -91
  555. package/src/spec/dspark/sample.ts +0 -112
  556. package/src/spec/generate.ts +0 -276
  557. package/src/spec/glm52-mtp-graph.ts +0 -29
  558. package/src/spec/glm52-mtp-rows.ts +0 -132
  559. package/src/spec/glm52-mtp-source.ts +0 -318
  560. package/src/spec/glm52-mtp-state.ts +0 -33
  561. package/src/spec/ngram-source.ts +0 -226
  562. package/src/spec/projected-draft-rows.ts +0 -73
  563. package/src/spec/qwen-mtp-module.ts +0 -171
  564. package/src/spec/qwen-mtp-rows.ts +0 -230
  565. package/src/spec/qwen-mtp-source.ts +0 -386
  566. package/src/spec/qwen-mtp-state.ts +0 -30
  567. package/src/spec/serve-loop.ts +0 -587
  568. package/src/spec/source.ts +0 -228
  569. package/src/spec/two-model-rows.ts +0 -62
  570. package/src/spec/two-model.ts +0 -113
  571. package/src/ssd-cache.ts +0 -431
  572. package/src/ssd-durability.ts +0 -209
  573. package/src/storage/host-buffer.ts +0 -24
  574. package/src/storage/kv-writer.ts +0 -83
  575. package/src/storage/kv-writer.worker.js +0 -264
  576. package/src/storage/retention-policy.ts +0 -37
  577. package/src/tiered-prompt-cache.ts +0 -45
  578. package/src/tokenizer.ts +0 -335
  579. package/src/tool-approvals.ts +0 -131
  580. package/src/tool-call.ts +0 -695
  581. package/src/train/dataset.ts +0 -484
  582. package/src/train/diffusion-lora.ts +0 -216
  583. package/src/train/export.ts +0 -32
  584. package/src/train/flash-cce.ts +0 -1234
  585. package/src/train/forward.ts +0 -266
  586. package/src/train/fuse.ts +0 -243
  587. package/src/train/index.ts +0 -46
  588. package/src/train/job.ts +0 -205
  589. package/src/train/lora-params.ts +0 -254
  590. package/src/train/loss.ts +0 -1591
  591. package/src/train/merge.ts +0 -195
  592. package/src/train/optimizer.ts +0 -183
  593. package/src/train/prefix-shared.ts +0 -490
  594. package/src/train/rank.ts +0 -137
  595. package/src/train/segmented.ts +0 -1828
  596. package/src/train/steel-qmm-header.ts +0 -1461
  597. package/src/train/trainer.ts +0 -1221
  598. package/src/train/watch.ts +0 -542
  599. package/src/vision/diffusion-vision.ts +0 -620
  600. package/src/vision/embedder.ts +0 -176
  601. package/src/vision/preprocess.ts +0 -247
  602. package/src/vision/prompt.ts +0 -409
  603. package/src/vision/qwen3vl-preprocess.ts +0 -409
  604. package/src/vision/qwen3vl-prompt.ts +0 -233
  605. package/src/vision/qwen3vl-tower.ts +0 -566
  606. package/src/vision/siglip.ts +0 -607
  607. package/src/vision/video-frames.ts +0 -148
  608. package/src/web/app.html +0 -2155
  609. package/src/web/app.js +0 -5863
  610. package/src/web/skills/memory/SKILL.md +0 -120
  611. package/src/web/skills/web-research/SKILL.md +0 -46
  612. package/src/web/skills.ts +0 -97
  613. package/src/web/src/adapters-panel.ts +0 -288
  614. package/src/web/src/assistant.ts +0 -451
  615. package/src/web/src/chat.ts +0 -1060
  616. package/src/web/src/composer.ts +0 -962
  617. package/src/web/src/dataset.ts +0 -132
  618. package/src/web/src/finetune.ts +0 -267
  619. package/src/web/src/hub.ts +0 -428
  620. package/src/web/src/main.ts +0 -98
  621. package/src/web/src/markdown.ts +0 -0
  622. package/src/web/src/memory-panel.ts +0 -572
  623. package/src/web/src/model-picker.ts +0 -159
  624. package/src/web/src/palette.ts +0 -346
  625. package/src/web/src/protocol.ts +0 -26
  626. package/src/web/src/quantize.ts +0 -167
  627. package/src/web/src/rag.ts +0 -274
  628. package/src/web/src/sessions.ts +0 -469
  629. package/src/web/src/shell.ts +0 -804
  630. package/src/web/src/ui-catalog.ts +0 -78
  631. package/src/web/src/voice.ts +0 -141
  632. package/src/web/vendor/README.md +0 -61
  633. package/src/web-assets.ts +0 -47
  634. package/src/web-tools.ts +0 -478
  635. package/src/weights.ts +0 -255
  636. /package/src/{serve → server}/completion-sink.ts +0 -0
  637. /package/src/{serve → server}/generated-token-history.ts +0 -0
  638. /package/src/web/{icon.svg → public/icon.svg} +0 -0
  639. /package/src/web/{manifest.webmanifest → public/manifest.webmanifest} +0 -0
  640. /package/src/web/{vendor → public/vendor}/hljs-LICENSE +0 -0
@@ -1,1060 +0,0 @@
1
- // GENERATED-ADJACENT source module — part of the src/web/src/* split (plan
2
- // §7/§9 Phase 2). Built into src/web/app.js by scripts/build-web.ts.
3
- //
4
- // The chat controller: WS wiring, thread render, streaming, tool cards,
5
- // approval card, queue bar. This is the orchestrator for composer.ts and
6
- // sessions.ts — it owns the WebSocket and the per-turn mutable state
7
- // (curAssistant, lastUserMsgEl, etc.) that both of those modules act on via
8
- // explicit function calls rather than shared closures (a deliberate change
9
- // from the original single-IIFE app.html: the split's whole point is
10
- // typed, checkable seams between these concerns).
11
- //
12
- // Typed WS contract (plan §9 Phase 2 item 2): ClientMessage/ServerMessage
13
- // and the history/session shapes are imported TYPE-ONLY from ../../contracts/pi
14
- // so this module (and the whole bundle) contains zero server code — the
15
- // bundler errors on bun:ffi if a value import ever sneaks in here, which is
16
- // the intended guard (see scripts/build-web.ts).
17
-
18
- import type {
19
- ClientMessage, HistoryItem, ReadyGenDefaults, ServerMessage, SessionListItem,
20
- } from "../../contracts/pi";
21
- import type { Lane } from "../../contracts/pi";
22
- import {
23
- $, el, toast, currentRoute, closeDrawer,
24
- storedCodingToolsPreference, renderCodingToolsState, renderToolApprovals,
25
- } from "./shell";
26
- import { esc, mdToHtml, highlightIn, linkifyCitations, makeFrameScheduler, renderBlocksIncremental, wireCanvasToggle, captureCanvasViewStates, restoreCanvasViewStates, type BlockState } from "./markdown";
27
- import {
28
- ComposerState, addFiles, buildMessageText, clearAttachments, clearOneShotArmed, initMentionPicker,
29
- initSampling, initSystemPrompt, onSelectAdapter, refreshAdapters as composerRefreshAdapters, refreshSamplingRecs,
30
- renderAttachments, renderContext, renderLane, renderQueue, updateAttachHint,
31
- updateThinkingToggle, type ContextFrame,
32
- } from "./composer";
33
- import { initVoiceInput } from "./voice";
34
- import {
35
- addMsgActions, newSiblingInfo, newSidebarState, renderSessions, renderSiblingToggle,
36
- switchSiblingTarget, applySessSearch, type SidebarState, type SiblingInfo,
37
- } from "./sessions";
38
- import { AdaptersPanelState, initAdaptersPanel, refreshAdaptersPanel } from "./adapters-panel";
39
- import { initMemoryPanel, isMemoryToolName, memoryToolChip, type MemChipHandle } from "./memory-panel";
40
- import { api } from "./api";
41
- import type { ApiEnvelope } from "./protocol";
42
- import type { Citation } from "./rag";
43
- import { buildAppContext, resolveSpotlightTarget, showSpotlight, type UiSnapshot } from "./assistant";
44
- import { isRouteId } from "./ui-catalog";
45
-
46
- /* ────────────────────────────────────────────────────────────────────
47
- Chat-with-files RAG v1 — Sources panel (plan §9 Phase 3, beat matrix
48
- Axis 5). Pure HTML builder so tests/using/web-app.test.ts can exercise the
49
- esc() discipline directly, same reasoning as renderAdapterOptionsHtml
50
- etc. in composer.ts.
51
- ──────────────────────────────────────────────────────────────────── */
52
-
53
- /** Pure: builds the collapsed "Sources · K" summary line + expandable list
54
- * of [n] filename + snippet for a turn's citations. Snippets are
55
- * truncated (a citation's chunk can be ~1200 chars — the panel is a
56
- * pointer back to the source, not a document viewer). */
57
- export function renderSourcesHtml(citations: Citation[]): string {
58
- if (!citations.length) return "";
59
- const rows = citations.map((c) => {
60
- const snippet = c.text.length > 240 ? c.text.slice(0, 240).trim() + "…" : c.text.trim();
61
- return '<div class="src-row" data-cite-row="' + c.n + '">' +
62
- '<span class="src-n">[' + c.n + "]</span>" +
63
- '<span class="src-meta"><span class="src-file">' + esc(c.fileName) + '</span>' +
64
- '<span class="src-range">chars ' + c.start + "–" + c.end + "</span></span>" +
65
- '<div class="src-snippet">' + esc(snippet) + "</div></div>";
66
- }).join("");
67
- return '<details class="sources"><summary>Sources · ' + citations.length + "</summary>" +
68
- '<div class="src-list">' + rows + "</div></details>";
69
- }
70
-
71
- /* ────────────────────────────────────────────────────────────────────
72
- Tool arg formatting helpers (shared by streaming + replayed history)
73
- ──────────────────────────────────────────────────────────────────── */
74
-
75
- function argSummary(args: unknown): string {
76
- if (!args) return "";
77
- if (typeof args === "string") return args.slice(0, 120);
78
- const a = args as Record<string, unknown>;
79
- if (a.command || a.cmd) return String(a.command || a.cmd).slice(0, 120);
80
- if (a.file_path || a.path) return String(a.file_path || a.path).slice(0, 120);
81
- if (a.query || a.url || a.location) return String(a.query || a.url || a.location).slice(0, 120);
82
- const s = JSON.stringify(args); return s.length > 120 ? s.slice(0, 117) + "…" : s;
83
- }
84
- function prettyArgs(args: unknown): string {
85
- if (args == null) return "";
86
- if (typeof args === "string") return args;
87
- try { return JSON.stringify(args, null, 2); } catch { return String(args); }
88
- }
89
- function diffView(args: unknown): string {
90
- // Best-effort diff-ish view for edit/write tools.
91
- if (!args) return "";
92
- const a = args as Record<string, unknown>;
93
- if (a.old_string != null || a.new_string != null) {
94
- const o = String(a.old_string ?? "").split("\n").map((l) => '<span class="diffdel">- ' + esc(l) + "</span>").join("\n");
95
- const n = String(a.new_string ?? "").split("\n").map((l) => '<span class="diffadd">+ ' + esc(l) + "</span>").join("\n");
96
- return (a.file_path ? esc(a.file_path) + "\n\n" : "") + o + (o && n ? "\n" : "") + n;
97
- }
98
- if (a.content != null) {
99
- return (a.file_path ? esc(a.file_path) + "\n\n" : "") +
100
- String(a.content).split("\n").map((l) => '<span class="diffadd">+ ' + esc(l) + "</span>").join("\n");
101
- }
102
- return esc(prettyArgs(args));
103
- }
104
-
105
- /* ────────────────────────────────────────────────────────────────────
106
- Per-assistant-message streaming state
107
- ──────────────────────────────────────────────────────────────────── */
108
-
109
- // Two distinct card kinds share one map (curAssistant.tools): the generic
110
- // "wrench" tool card and the memory provenance chip (plan §5.4.2 — the
111
- // single highest-leverage rendering change: a citation, not a shell
112
- // command). Dispatch on `.kind` at each lifecycle point (toolCard/
113
- // toolUpdate/toolEnd) rather than keeping two parallel maps, since a card's
114
- // kind is fixed at tool_start and every later frame (tool_update/tool_end)
115
- // only carries the callId to look it up by.
116
- interface WrenchCardState {
117
- kind: "wrench";
118
- wrap: HTMLElement;
119
- res: HTMLElement;
120
- resBlk: HTMLElement;
121
- label: HTMLElement;
122
- chunks: string;
123
- }
124
- interface MemChipCardState {
125
- kind: "memchip";
126
- wrap: HTMLElement;
127
- handle: MemChipHandle;
128
- chunks: string;
129
- }
130
- type ToolCardState = WrenchCardState | MemChipCardState;
131
-
132
- interface AssistantMsgState {
133
- m: HTMLElement;
134
- bubble: HTMLElement;
135
- thinkBox: HTMLDetailsElement;
136
- thinkBody: HTMLElement;
137
- textNode: HTMLElement;
138
- cursor: HTMLElement;
139
- meta: HTMLElement;
140
- tools: Map<string, ToolCardState>;
141
- text: string;
142
- thinking: string;
143
- t0: number;
144
- tFirst: number;
145
- tokens: number;
146
- settled: boolean;
147
- blockState: BlockState;
148
- scheduleText: () => void;
149
- scheduleThinking: () => void;
150
- // Chat-with-files RAG v1 (plan §9 Phase 3, beat matrix Axis 5): the
151
- // citation list retrieved for the prompt that started THIS turn, if any
152
- // (composer.ts's buildMessageText only returns a non-empty list when
153
- // retrieval mode fired). Empty for the common case — no Sources panel,
154
- // no [n] linkification, zero visual change from before this feature.
155
- citations: Citation[];
156
- }
157
-
158
- export function createChatController() {
159
- let ws: WebSocket | null = null, connected = false, reconnectTimer: ReturnType<typeof setTimeout> | undefined, manualClose = false;
160
- let curAssistant: AssistantMsgState | null = null;
161
- let turnActive = false;
162
- // App-aware assistant (plan §6.6): the last uiSnapshot pushed to the
163
- // server, kept client-side too so spotlight_ui's ref resolution
164
- // (resolveSpotlightTarget) doesn't need a round-trip — the browser
165
- // already has the freshest snapshot it just sent.
166
- let lastSnapshot: UiSnapshot | null = null;
167
- // Citations retrieved for the prompt currently in flight (set in submit(),
168
- // just before send()) — startAssistant() picks this up when turn_start
169
- // arrives and attaches it to the new AssistantMsgState, then clears it so
170
- // a later turn with no attachments doesn't inherit a stale citation set.
171
- let pendingCitations: Citation[] = [];
172
- // This connection's active session path (from `sessions` frames). Each
173
- // connect gets a fresh server session; on a transient reconnect we re-open
174
- // our own session so a blip doesn't strand us on a blank one.
175
- let currentSessionPath: string | null = null, pendingResumePath: string | null = null;
176
- const thread = () => $("chat-thread");
177
- const composer = new ComposerState();
178
- const sidebar: SidebarState = newSidebarState();
179
- const adaptersPanel = new AdaptersPanelState();
180
-
181
- function wsUrl(): string {
182
- const proto = location.protocol === "https:" ? "wss:" : "ws:";
183
- return proto + "//" + location.host + "/ws/chat";
184
- }
185
-
186
- function connect(): void {
187
- manualClose = false;
188
- // Remember our session across this (re)connect: if we were already in a
189
- // session, re-open it once the new connection is ready (the server starts
190
- // each connection on a fresh session by default).
191
- pendingResumePath = currentSessionPath;
192
- try { ws = new WebSocket(wsUrl()); } catch { scheduleReconnect(); return; }
193
- ws.onopen = () => { connected = true; setChatStatus("connected"); };
194
- ws.onclose = () => { connected = false; setChatStatus("disconnected"); if (!manualClose) scheduleReconnect(); };
195
- ws.onerror = () => { setChatStatus("error"); };
196
- ws.onmessage = (ev) => { let m: ServerMessage; try { m = JSON.parse(ev.data); } catch { return; } handle(m); };
197
- }
198
- function scheduleReconnect(): void {
199
- clearTimeout(reconnectTimer);
200
- reconnectTimer = setTimeout(() => { if (currentRoute() === "chat") connect(); }, 2200);
201
- }
202
- function send(obj: ClientMessage): boolean {
203
- if (ws && ws.readyState === 1) { ws.send(JSON.stringify(obj)); return true; }
204
- return false;
205
- }
206
-
207
- /* ── App-aware assistant (plan §6.6, §9 Phase 3, beat matrix Axis 12) ──
208
- "See": push a structured context frame on every route change and every
209
- wizard-step change. Never on a timer — only these two triggers, so the
210
- server's ambient one-liner always reflects a real, user-caused state
211
- change. */
212
-
213
- /** Which non-route overlay (if any) is currently open, layered on top of
214
- * whatever route is active underneath — same "where is the user, really"
215
- * question navigate_app/spotlight_ui need answered. Checked in a fixed
216
- * order; the first open one wins (only one of these opens at a time in
217
- * practice — each is closed by shell.ts's closeTopOverlay sweep before
218
- * another opens). */
219
- function currentView(): string | null {
220
- if ($("mem-overlay")?.classList.contains("open")) return "memory-panel";
221
- if ($("hub-overlay")?.classList.contains("open")) return "hub-panel";
222
- if ($("adapters-overlay")?.classList.contains("open")) return "adapters-panel";
223
- return null;
224
- }
225
-
226
- function pushAppContext(): void {
227
- const route = currentRoute();
228
- if (!isRouteId(route)) return; // "routes" (DAG diagram) isn't a useful assistant destination
229
- const ctx = buildAppContext(route, currentView());
230
- lastSnapshot = ctx.snapshot;
231
- send({ type: "context", context: ctx });
232
- }
233
-
234
- /** MutationObserver on the three wizard step-indicator containers: their
235
- * innerHTML is fully rewritten by markdown.ts's renderSteps() on every
236
- * show(n) call in quantize.ts/finetune.ts/dataset.ts, so a childList+
237
- * subtree observer fires exactly on step change — no callback hook
238
- * needed in those three controllers (outside this wave's file scope). */
239
- function watchWizardSteps(): void {
240
- const mo = new MutationObserver(() => pushAppContext());
241
- for (const id of ["q-steps", "f-steps", "d-steps"]) {
242
- const el = $(id);
243
- if (el) mo.observe(el, { childList: true, subtree: true });
244
- }
245
- }
246
-
247
- function setChatStatus(s: "connected" | "disconnected" | "error"): void {
248
- const line = $("chat-status-line");
249
- // The "#" hint lives HERE, not in the composer placeholder — a long
250
- // placeholder wraps + clips at phone widths (the 2026-07-06 jank class).
251
- // This status line ellipsizes gracefully instead (.chat-hint CSS).
252
- if (s === "connected") line.textContent = "connected · type to send · Shift+Enter for newline · # attaches files or recalls memory";
253
- else if (s === "disconnected") line.textContent = "reconnecting to agent…";
254
- else if (s === "error") line.textContent = "connection error — retrying";
255
- }
256
-
257
- /* — message actions: copy / regenerate / edit-and-resend (plan §5.2) —
258
- * Only the LAST assistant message gets "regenerate" and only the LAST
259
- * user message gets "edit" + the sibling `< i/n >` toggle. Both "last"
260
- * pointers are tracked module-side and re-derived whenever a `history`
261
- * frame rebuilds #chat-thread wholesale (see renderHistory/endTurn below).
262
- * Regenerate/edit-resend re-prompt on a new sibling branch WITHOUT a
263
- * `history` frame (resendFrom in pi-web.ts), so their click handlers below
264
- * drop the stale lastAssistantMsgEl (and, for edit, update the surviving
265
- * user bubble's text) themselves before the new reply streams in. */
266
- let lastUserMsgEl: HTMLElement | null = null; // the <div class="msg user"> for the last user message
267
- let lastUserEntryId: string | null = null; // its session entryId (from history/siblings frames)
268
- let siblingInfo: SiblingInfo = newSiblingInfo();
269
- let lastAssistantMsgEl: HTMLElement | null = null; // the <div class="msg assistant"> eligible for regenerate
270
- // Citations for the reply currently sitting in lastAssistantMsgEl (kept in
271
- // lockstep with it — set alongside it in endTurn, cleared alongside it
272
- // everywhere it's nulled). Regenerate/edit-resend re-prompt the SAME
273
- // underlying (possibly RAG'd) user turn without going through submit()/
274
- // buildMessageText, so pendingCitations would otherwise be the stale `[]`
275
- // left over from consumption at the prior startAssistant() call — see the
276
- // pendingCitations declaration below. Re-seeding from here means a
277
- // citation-bearing turn keeps its Sources panel across regenerate/edit.
278
- let lastAssistantCitations: Citation[] = [];
279
-
280
- function doRegenerate(): void {
281
- if (turnActive) return;
282
- if (!send({ type: "regenerate" })) { toast("Not connected to the agent yet.", "err"); return; }
283
- // The server re-prompts on a new sibling branch — it does not send a
284
- // `history` frame for this (see resendFrom in pi-web.ts), so the old
285
- // reply would otherwise linger above the new one as it streams in.
286
- // Drop it now; startAssistant() appends the fresh reply right after.
287
- if (lastAssistantMsgEl) { lastAssistantMsgEl.remove(); lastAssistantMsgEl = null; }
288
- pendingCitations = lastAssistantCitations;
289
- }
290
-
291
- function switchSiblingDir(delta: number): void {
292
- const target = switchSiblingTarget(siblingInfo, delta);
293
- if (!target) return;
294
- send({ type: "switch_sibling", entryId: target });
295
- }
296
-
297
- /** Turn the last user message bubble into an inline editor. Cancel restores
298
- * the original text view; Send fires edit_resend, updates the surviving
299
- * user bubble to the edited text in place, and drops the stale assistant
300
- * reply — the server re-prompts on a new sibling branch without sending a
301
- * `history` frame (see resendFrom in pi-web.ts), so the old reply/text
302
- * would otherwise linger next to the new one as it streams in. */
303
- function startEditLast(m: HTMLElement): void {
304
- if (turnActive) return;
305
- const bubble = m.querySelector(".bubble") as HTMLElement;
306
- const textEl = bubble.querySelector(".msg-text") as HTMLElement | null;
307
- if (!textEl) return;
308
- const original = textEl.textContent || "";
309
- const box = document.createElement("textarea");
310
- box.className = "msg-edit-box";
311
- box.value = original;
312
- const actions = el("div", "msg-edit-actions", null);
313
- const cancelBtn = el("button", "btn ghost sm", actions);
314
- cancelBtn.type = "button"; cancelBtn.textContent = "Cancel";
315
- const sendBtn = el("button", "btn primary sm", actions);
316
- sendBtn.type = "button"; sendBtn.textContent = "Send";
317
- textEl.replaceWith(box);
318
- box.after(actions);
319
- box.focus(); box.setSelectionRange(box.value.length, box.value.length);
320
- const restore = () => { box.replaceWith(textEl); actions.remove(); };
321
- const doSend = () => {
322
- const text = box.value.trim();
323
- if (!text) return;
324
- if (!send({ type: "edit_resend", text })) { toast("Not connected to the agent yet.", "err"); return; }
325
- textEl.textContent = text;
326
- box.replaceWith(textEl); actions.remove();
327
- if (lastAssistantMsgEl) { lastAssistantMsgEl.remove(); lastAssistantMsgEl = null; }
328
- pendingCitations = lastAssistantCitations;
329
- };
330
- cancelBtn.onclick = restore;
331
- sendBtn.onclick = doSend;
332
- box.addEventListener("keydown", (e) => {
333
- if (e.key === "Escape") { e.preventDefault(); restore(); }
334
- else if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); doSend(); }
335
- });
336
- }
337
-
338
- /* — message + tool DOM construction — */
339
- function addUserMsg(text: string, atts: { kind: string; data?: string; mimeType?: string; name: string }[] | null, opts: { isLast?: boolean; entryId?: string }): void {
340
- const m = el("div", "msg user", thread());
341
- el("div", "who", m).textContent = "you";
342
- const bubble = el("div", "bubble", m);
343
- if (atts && atts.length) {
344
- const wrap = el("div", "msg-atts", bubble);
345
- for (const a of atts) {
346
- if (a.kind === "image") {
347
- const im = el("img", "msg-att-img", wrap);
348
- im.src = "data:" + a.mimeType + ";base64," + a.data; im.alt = a.name;
349
- } else {
350
- el("span", "msg-att-file", wrap).textContent = "📎 " + a.name;
351
- }
352
- }
353
- }
354
- if (text) el("div", "msg-text", bubble).textContent = text;
355
- if (opts.isLast) {
356
- lastUserMsgEl = m;
357
- lastUserEntryId = opts.entryId || null;
358
- addMsgActions(m, text, { edit: true }, { onEdit: startEditLast });
359
- renderSiblingToggle(lastUserMsgEl, lastUserEntryId, siblingInfo, switchSiblingDir);
360
- }
361
- stick(true);
362
- }
363
- function startAssistant(): void {
364
- $("chat-hello").style.display = "none";
365
- const m = el("div", "msg assistant", thread());
366
- el("div", "who", m).textContent = "local agent";
367
- const bubble = el("div", "bubble", m);
368
- const thinkBox = el("details", "thinkbox", bubble);
369
- thinkBox.open = false; // collapsed by default; the reasoning stays tucked behind "Thinking" — click to expand
370
- el("summary", "", thinkBox).textContent = "Thinking";
371
- const thinkBody = el("div", "thinkbody", thinkBox);
372
- thinkBox.style.display = "none";
373
- const textNode = el("span", "atext", bubble);
374
- const cursor = el("span", "cursor", bubble);
375
- const meta = el("div", "meta", m);
376
- curAssistant = {
377
- m, bubble, thinkBox, thinkBody, textNode, cursor, meta, tools: new Map(),
378
- text: "", thinking: "", t0: performance.now(), tFirst: 0, tokens: 0, settled: false,
379
- // Block-memoization state (see renderBlocksIncremental) for the main
380
- // markdown text stream, plus a shared rAF scheduler so a burst of
381
- // deltas within one frame (e.g. spec-decode emitting several tokens
382
- // back-to-back) collapses into a single re-render instead of one per
383
- // token. The thinking channel is plain text (no markdown — see
384
- // appendThinkingDelta), so it only needs the textContent set
385
- // throttled, not block splitting; it gets its own scheduler since
386
- // both channels can stream concurrently (interleaved thinking/text).
387
- blockState: { blocks: [] },
388
- scheduleText: () => {},
389
- scheduleThinking: () => {},
390
- citations: pendingCitations,
391
- };
392
- // Consumed — a later turn with no RAG'd attachments must not inherit
393
- // this turn's citation set (see the pendingCitations declaration above).
394
- pendingCitations = [];
395
- const assistant = curAssistant;
396
- assistant.scheduleText = makeFrameScheduler(
397
- () => {
398
- if (!assistant.settled) renderBlocksIncremental(assistant.textNode, assistant.text, assistant.blockState);
399
- },
400
- atBottom, stick);
401
- assistant.scheduleThinking = makeFrameScheduler(
402
- () => {
403
- if (!assistant.settled) assistant.thinkBody.textContent = assistant.thinking;
404
- },
405
- atBottom, stick);
406
- stick(true);
407
- }
408
- function ensureAssistant(): void { if (!curAssistant) startAssistant(); }
409
-
410
- function appendDelta(delta: string): void {
411
- ensureAssistant();
412
- const a = curAssistant!;
413
- if (!a.tFirst) a.tFirst = performance.now();
414
- a.tokens++;
415
- a.text += delta;
416
- // Block-memoized + rAF-throttled render (see renderBlocksIncremental):
417
- // only the live tail block re-parses per delta; completed blocks are
418
- // never touched again. A final full mdToHtml pass runs at turn-end
419
- // (finishStreaming) to guarantee the streamed result is byte-identical
420
- // to a non-streaming render. Autoscroll is handled INSIDE the scheduler
421
- // (its shouldStick/doStick args) — not here — because the render itself
422
- // is rAF-deferred: checking atBottom() synchronously at this point would
423
- // measure scrollHeight from before the queued render grows it, which
424
- // desyncs autoscroll once several deltas land within one frame.
425
- a.scheduleText();
426
- updateTps();
427
- }
428
- function appendThinkingDelta(delta: string): void {
429
- ensureAssistant();
430
- const a = curAssistant!;
431
- if (!a.tFirst) a.tFirst = performance.now();
432
- a.tokens++;
433
- a.thinking += delta;
434
- a.thinkBox.style.display = a.thinking.trim() ? "" : "none";
435
- // Plain text (no markdown here), but still rAF-throttled: a long
436
- // thinking trace can otherwise do one textContent-set per token.
437
- // Autoscroll: see the comment in appendDelta above — handled inside the
438
- // scheduler, not with a synchronous atBottom() check here.
439
- a.scheduleThinking();
440
- updateTps();
441
- }
442
-
443
- /* tool card lifecycle — dispatches to the memory provenance chip (plan
444
- * §5.4.2) for memory_ and reference_ tool calls, the generic wrench card
445
- * for everything else. Both kinds live in the same a.tools map (see
446
- * ToolCardState above). */
447
- function toolCard(callId: string, tool: string, args: unknown): ToolCardState {
448
- ensureAssistant();
449
- const a = curAssistant!;
450
- let t = a.tools.get(callId);
451
- if (t) return t;
452
- if (isMemoryToolName(tool || "")) {
453
- const handle = memoryToolChip(a.bubble, tool, args);
454
- t = { kind: "memchip", wrap: handle.wrap, handle, chunks: "" };
455
- a.tools.set(callId, t);
456
- stick();
457
- return t;
458
- }
459
- const wrap = el("div", "tool running open", a.bubble);
460
- wrap.innerHTML =
461
- '<div class="thead"><div class="ticon">⚙</div>' +
462
- '<span class="tname">' + esc(tool || "tool") + '</span>' +
463
- '<span class="targs">' + esc(argSummary(args)) + '</span>' +
464
- '<span class="tstat"><span class="sdot"></span><span class="slabel">running</span></span>' +
465
- '<span class="caret">›</span></div>' +
466
- '<div class="tbody">' +
467
- '<div class="blk"><div class="blbl">arguments</div><pre class="aargs">' + esc(prettyArgs(args)) + '</pre></div>' +
468
- '<div class="blk tresult" style="display:none"><div class="blbl">result</div><pre class="ares"></pre></div>' +
469
- '</div>';
470
- wrap.querySelector(".thead")!.addEventListener("click", () => wrap.classList.toggle("open"));
471
- t = {
472
- kind: "wrench", wrap,
473
- res: wrap.querySelector(".ares") as HTMLElement,
474
- resBlk: wrap.querySelector(".tresult") as HTMLElement,
475
- label: wrap.querySelector(".slabel") as HTMLElement,
476
- chunks: "",
477
- };
478
- a.tools.set(callId, t);
479
- stick();
480
- return t;
481
- }
482
- function toolUpdate(callId: string, chunk: string): void {
483
- const t = curAssistant && curAssistant.tools.get(callId);
484
- if (!t) return;
485
- t.chunks += chunk;
486
- if (t.kind === "memchip") { t.handle.setResult(t.chunks); if (atBottom()) stick(); return; }
487
- t.resBlk.style.display = "";
488
- t.res.textContent = t.chunks.slice(-8000);
489
- if (atBottom()) stick();
490
- }
491
- function toolEnd(callId: string, ok: boolean, result: unknown): void {
492
- const t = curAssistant && curAssistant.tools.get(callId);
493
- if (!t) return;
494
- if (t.kind === "memchip") {
495
- t.wrap.classList.toggle("fail", !ok);
496
- t.handle.setResult(result != null ? result : t.chunks);
497
- return;
498
- }
499
- t.wrap.classList.remove("running");
500
- t.wrap.classList.add(ok ? "ok" : "fail");
501
- t.label.textContent = ok ? "done" : "failed";
502
- const body = result != null ? (typeof result === "string" ? result : JSON.stringify(result, null, 2)) : t.chunks;
503
- if (body) { t.resBlk.style.display = ""; t.res.textContent = String(body).slice(-12000); }
504
- if (!ok) t.wrap.classList.add("open");
505
- }
506
-
507
- /* approval dialog (plan §5.4/§6.5: editable arguments + durable "always
508
- * allow this tool", on top of the existing running/ok/fail visual
509
- * language). The diff view (edit/write) stays as a read-only visual aid
510
- * ABOVE the editable textarea — losing the diff would make large
511
- * file-content proposals unreadable as raw JSON — but the textarea,
512
- * pre-filled with the proposed args as JSON, is what actually gets sent:
513
- * editing it and clicking Allow re-parses it and ships the edited object
514
- * as `editedArgs`, which pi-web.ts mutates onto the SDK's ToolCallEvent.input
515
- * in place (the SDK's documented tool_call mutation contract). */
516
- function approvalCard(callId: string, tool: string, args: unknown): void {
517
- ensureAssistant();
518
- const a = curAssistant!;
519
- const wrap = el("div", "approval", a.bubble);
520
- const isBash = /bash|shell|exec|run/i.test(tool || "");
521
- const isEdit = /edit|write|patch|create/i.test(tool || "");
522
- const diffHtml = isEdit ? '<pre class="a-diff">' + diffView(args) + '</pre>' : "";
523
- const argsJson = prettyArgs(args);
524
- wrap.innerHTML =
525
- '<div class="ahead"><div class="ai">⚠</div><div class="at">Approval required</div>' +
526
- '<div class="as">' + esc(tool || "tool") + '</div></div>' +
527
- '<div class="abody">' + diffHtml +
528
- '<div class="blbl" style="margin-bottom:7px">' +
529
- (isBash ? "command (edit before approving if you like)" : isEdit ? "proposed change — arguments below, editable" : "arguments — editable before approving") +
530
- '</div>' +
531
- '<textarea class="a-args" spellcheck="false">' + esc(argsJson) + '</textarea>' +
532
- '<div class="a-argerr" style="display:none"></div>' +
533
- '</div>' +
534
- '<label class="chk a-always"><input type="checkbox" class="a-always-cb">Always allow ' + esc(tool || "this tool") + ' (skip this card next time)</label>' +
535
- '<div class="actions">' +
536
- '<button class="btn ghost sm a-deny">Deny</button>' +
537
- '<button class="btn primary sm a-allow">Allow</button></div>';
538
- const textarea = wrap.querySelector(".a-args") as HTMLTextAreaElement;
539
- const errEl = wrap.querySelector(".a-argerr") as HTMLElement;
540
- const alwaysCb = wrap.querySelector(".a-always-cb") as HTMLInputElement;
541
- const resolve = (decision: "allow" | "deny") => {
542
- let editedArgs: Record<string, unknown> | undefined;
543
- if (decision === "allow") {
544
- const raw = textarea.value.trim();
545
- if (raw && raw !== argsJson.trim()) {
546
- try {
547
- const parsed = JSON.parse(raw);
548
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
549
- editedArgs = parsed as Record<string, unknown>;
550
- } else {
551
- errEl.textContent = "Arguments must be a JSON object — sending the original instead.";
552
- errEl.style.display = "";
553
- }
554
- } catch {
555
- errEl.textContent = "Could not parse the edited arguments as JSON — sending the original instead.";
556
- errEl.style.display = "";
557
- }
558
- }
559
- }
560
- send({ type: "approval", callId, decision, editedArgs, alwaysAllow: decision === "allow" && alwaysCb.checked });
561
- wrap.querySelector(".actions")!.remove();
562
- wrap.querySelector(".a-always")?.remove();
563
- const r = el("div", "resolved " + (decision === "allow" ? "allow" : "deny"), wrap);
564
- r.textContent = decision === "allow" ? (editedArgs ? "✓ allowed (edited)" : "✓ allowed") : "✕ denied";
565
- };
566
- (wrap.querySelector(".a-allow") as HTMLButtonElement).onclick = () => resolve("allow");
567
- (wrap.querySelector(".a-deny") as HTMLButtonElement).onclick = () => resolve("deny");
568
- stick(true);
569
- }
570
-
571
- function showAgentError(message: string): void {
572
- const m = el("div", "msg assistant", thread());
573
- el("div", "who", m).textContent = "local agent";
574
- const bubble = el("div", "bubble", m);
575
- bubble.innerHTML = '<strong style="color:var(--red)">Agent error</strong><br>' + esc(message || "unknown error");
576
- stick(true);
577
- }
578
-
579
- /** Chat-with-files RAG v1 (plan §9 Phase 3, beat matrix Axis 5): append a
580
- * collapsed "Sources · K" panel under the bubble and linkify any [n]
581
- * markers the model actually used in its reply. Only ever called with a
582
- * non-empty citation list (finishStreaming guards this) — an ordinary
583
- * reply with no attachments gets neither the panel nor any [n]
584
- * rewriting, so a message that happens to contain literal "[1]" text
585
- * never gets a false citation link. */
586
- function attachSourcesPanel(a: AssistantMsgState): void {
587
- const validNs = new Set(a.citations.map((c) => c.n));
588
- a.textNode.innerHTML = linkifyCitations(a.textNode.innerHTML, validNs);
589
- const panel = el("div", "", null);
590
- panel.innerHTML = renderSourcesHtml(a.citations);
591
- const detailsEl = panel.firstElementChild as HTMLElement | null;
592
- if (!detailsEl) return;
593
- a.bubble.appendChild(detailsEl);
594
- // Clicking a [n] marker in the rendered text expands the panel (if
595
- // collapsed) and scrolls/highlights the matching source row.
596
- a.textNode.querySelectorAll<HTMLElement>(".cite-mark").forEach((btn) => {
597
- btn.addEventListener("click", () => {
598
- const n = btn.dataset.cite;
599
- (detailsEl as HTMLDetailsElement).open = true;
600
- const row = detailsEl.querySelector<HTMLElement>('.src-row[data-cite-row="' + n + '"]');
601
- if (row) {
602
- row.scrollIntoView({ block: "nearest", behavior: "smooth" });
603
- row.classList.add("pulse");
604
- setTimeout(() => row.classList.remove("pulse"), 1000);
605
- }
606
- });
607
- });
608
- }
609
-
610
- /** Turn-end finalization for the streamed text channel: replace the
611
- * block-memoized incremental render with ONE full non-memoized
612
- * mdToHtml(fullText) pass. This is the acceptance test for block
613
- * memoization — whatever the incremental path did while streaming,
614
- * the final DOM must match exactly what a plain, non-streaming
615
- * render of the same text would have produced. Cheap (happens once
616
- * per turn, not per token) and removes any residual "done" block
617
- * wrappers so the settled message's markup matches replayed history
618
- * (renderAssistantStatic / renderHistory), which also call mdToHtml
619
- * directly on the full string. */
620
- function finishStreaming(a: AssistantMsgState | null): void {
621
- if (!a) return;
622
- a.settled = true;
623
- // Preserve any Canvas Preview toggle the user made mid-stream (Principle
624
- // 9: this full non-memoized re-render must not throw away user
625
- // interaction state) — mdCodeBlock always emits qualifying fences back
626
- // in their default Source view, so without this a live Preview click
627
- // silently reverts the instant the turn finalizes.
628
- const previewIdx = captureCanvasViewStates(a.textNode);
629
- a.textNode.innerHTML = mdToHtml(a.text);
630
- restoreCanvasViewStates(a.textNode, previewIdx);
631
- highlightIn(a.textNode);
632
- a.thinkBody.textContent = a.thinking;
633
- if (a.citations.length) attachSourcesPanel(a);
634
- }
635
-
636
- /** Demote the previously-"last" assistant message: only ONE message ever
637
- * shows Regenerate (plan §5.2 scopes it to the last assistant turn). */
638
- function clearRegenerateAffordance(): void {
639
- if (!lastAssistantMsgEl) return;
640
- const row = lastAssistantMsgEl.querySelector(".msg-actions");
641
- const btn = row && [...row.querySelectorAll(".maction")].find((b) => b.textContent === "Regenerate");
642
- if (btn) btn.remove();
643
- }
644
-
645
- function endTurn(): void {
646
- turnActive = false;
647
- if (curAssistant) {
648
- finishStreaming(curAssistant);
649
- curAssistant.cursor.remove();
650
- if (!curAssistant.text && !curAssistant.thinking && curAssistant.tools.size === 0) {
651
- curAssistant.m.remove();
652
- } else {
653
- // A settled assistant reply becomes the new regenerate target.
654
- // Works after abort/stop too — a partial reply still gets the
655
- // action row, and the server resolves regenerate from the session's
656
- // last user message regardless of whether this reply finished.
657
- clearRegenerateAffordance();
658
- addMsgActions(curAssistant.m, curAssistant.text, { regenerate: true }, { onRegenerate: doRegenerate });
659
- lastAssistantMsgEl = curAssistant.m;
660
- lastAssistantCitations = curAssistant.citations;
661
- }
662
- }
663
- curAssistant = null;
664
- ($("chat-send") as HTMLButtonElement).disabled = false;
665
- $("chat-stop").style.display = "none";
666
- renderQueue({});
667
- }
668
-
669
- // Perf strip (plan §5.2/§6.4): tok/s + TTFT update live during streaming;
670
- // context-fill is driven separately by the `context` frame (renderContext).
671
- // All client-computed from data the composer already has — the lane badge
672
- // is the only server-driven field (see renderLane / the turn_end handler).
673
- function updateTps(): void {
674
- const a = curAssistant; if (!a) return;
675
- const secs = (performance.now() - (a.tFirst || a.t0)) / 1000;
676
- if (a.tokens > 1 && secs > 0) {
677
- const tps = $("pf-tps"); if (tps) tps.innerHTML = "<b style='color:var(--green)'>" + (a.tokens / secs).toFixed(1) + " tok/s</b>";
678
- }
679
- if (a.tFirst) {
680
- const ttft = $("pf-ttft"); if (ttft) ttft.textContent = "TTFT " + Math.round(a.tFirst - a.t0) + "ms";
681
- }
682
- }
683
- function finalizeMeta(): void {
684
- const a = curAssistant; if (!a) return;
685
- const secs = (performance.now() - (a.tFirst || a.t0)) / 1000;
686
- if (a.tokens > 1 && secs > 0)
687
- a.meta.innerHTML = "<b>" + (a.tokens / secs).toFixed(1) + " tok/s</b> · " + a.tokens +
688
- " tokens · first token " + Math.round((a.tFirst - a.t0)) + "ms";
689
- }
690
-
691
- function newChat(): void {
692
- if (!send({ type: "new_session" })) { toast("Not connected to the agent yet.", "err"); return; }
693
- $("chat-box").focus();
694
- closeDrawer(); // a fresh chat is the natural "I'm done browsing" signal on mobile
695
- }
696
-
697
- /** Render a finished assistant turn from replayed history (no streaming).
698
- * `opts.isLast` attaches the Regenerate action (plan §5.2: last turn only). */
699
- function renderAssistantStatic(item: HistoryItem, opts: { isLast?: boolean }): void {
700
- const m = el("div", "msg assistant", thread());
701
- el("div", "who", m).textContent = "local agent";
702
- const bubble = el("div", "bubble", m);
703
- if (item.thinking && item.thinking.trim()) {
704
- const tb = el("details", "thinkbox", bubble); tb.open = false;
705
- el("summary", "", tb).textContent = "Thinking";
706
- el("div", "thinkbody", tb).textContent = item.thinking;
707
- }
708
- if (item.text) { const tn = el("span", "atext", bubble); tn.innerHTML = mdToHtml(item.text); highlightIn(tn); }
709
- for (const t of (item.tools || [])) {
710
- if (isMemoryToolName(t.name || "")) {
711
- const handle = memoryToolChip(bubble, t.name, t.args);
712
- if (t.result) handle.setResult(t.result);
713
- continue;
714
- }
715
- const wrap = el("div", "tool ok", bubble); // collapsed; click to expand
716
- wrap.innerHTML =
717
- '<div class="thead"><div class="ticon">⚙</div>' +
718
- '<span class="tname">' + esc(t.name || "tool") + '</span>' +
719
- '<span class="targs">' + esc(argSummary(t.args)) + '</span>' +
720
- '<span class="tstat"><span class="sdot"></span><span class="slabel">done</span></span>' +
721
- '<span class="caret">›</span></div>' +
722
- '<div class="tbody">' +
723
- '<div class="blk"><div class="blbl">arguments</div><pre class="aargs">' + esc(prettyArgs(t.args)) + '</pre></div>' +
724
- (t.result ? '<div class="blk tresult"><div class="blbl">result</div><pre class="ares">' + esc(String(t.result).slice(-12000)) + '</pre></div>' : '') +
725
- '</div>';
726
- wrap.querySelector(".thead")!.addEventListener("click", () => wrap.classList.toggle("open"));
727
- }
728
- if (item.text) {
729
- addMsgActions(m, item.text, { regenerate: !!opts.isLast }, { onRegenerate: doRegenerate });
730
- if (opts.isLast) {
731
- lastAssistantMsgEl = m;
732
- // History replay doesn't carry citations (HistoryItem has no
733
- // citations field — the server doesn't persist them across
734
- // reload), so there's nothing to restore here; this keeps
735
- // lastAssistantCitations honest (no stale carry-over from a
736
- // previous session) rather than claiming a citation set for a
737
- // reply that regenerate can't actually attribute one to.
738
- lastAssistantCitations = [];
739
- }
740
- }
741
- }
742
-
743
- /** Authoritative thread rebuild from a server `history` frame. */
744
- function renderHistory(items: HistoryItem[]): void {
745
- turnActive = false;
746
- curAssistant = null;
747
- ($("chat-send") as HTMLButtonElement).disabled = false;
748
- $("chat-stop").style.display = "none";
749
- renderQueue({});
750
- thread().innerHTML = "";
751
- lastUserMsgEl = null; lastAssistantMsgEl = null;
752
- if (!items || !items.length) { $("chat-hello").style.display = ""; return; }
753
- $("chat-hello").style.display = "none";
754
- const lastUserIdx = items.map((it) => it.role).lastIndexOf("user");
755
- const lastAssistantIdx = items.map((it) => it.role).lastIndexOf("assistant");
756
- items.forEach((it, i) => {
757
- if (it.role === "user") addUserMsg(it.text, null, { isLast: i === lastUserIdx, entryId: it.entryId });
758
- else renderAssistantStatic(it, { isLast: i === lastAssistantIdx });
759
- });
760
- stick(true);
761
- }
762
-
763
- /* — server -> client dispatch — */
764
- function handle(m: ServerMessage): void {
765
- switch (m.type) {
766
- case "ready":
767
- $("nav-model").textContent = m.model || ($("nav-model").textContent as string) || "no model";
768
- composer.visionCapable = !!m.vision; composer.transcriptionCapable = !!m.transcription; updateAttachHint(composer);
769
- composer.thinkingCapable = !!m.thinking; updateThinkingToggle(composer);
770
- // Per-model recommended sampling (generation_config.json, server-CLI
771
- // overrides applied) — replaces the old hardcoded SAMP_REC shape.
772
- composer.genDefaults = m.genDefaults || ({ temperature: null, topP: null, topK: null } as ReadyGenDefaults);
773
- refreshSamplingRecs(composer);
774
- if (composer.thinkingCapable) send({ type: "set_thinking", enabled: composer.thinkingOn });
775
- composerRefreshAdapters();
776
- // Restore this browser's codingTools preference (plan §5.4/§6.5).
777
- // Sent every `ready` (including reconnects): a fresh WS session
778
- // always starts codingToolsRequested=false server-side, so a
779
- // previously-on browser must re-assert it, and the coding_tools
780
- // frame that follows will report whether it's active yet or only
781
- // pending for the next new/opened chat.
782
- send({ type: "set_coding_tools", enabled: storedCodingToolsPreference() });
783
- // App-aware assistant (plan §6.6): a fresh WS session starts with no
784
- // stored context server-side (currentAppContext is null until the
785
- // first push) — every `ready` (initial connect AND reconnect) must
786
- // re-push so get_current_app_context / the ambient line are never
787
- // blind while a browser tab sits idle on a non-chat route.
788
- pushAppContext();
789
- // Reconnect: re-open the session we were in (fresh load has none).
790
- if (pendingResumePath) { send({ type: "open_session", path: pendingResumePath }); pendingResumePath = null; }
791
- break;
792
- case "coding_tools": renderCodingToolsState(m.active, m.pending); break;
793
- case "tool_approvals":
794
- renderToolApprovals(m.alwaysAllow, (tool) => forgetToolApproval(tool));
795
- break;
796
- case "history": renderHistory(m.items); break;
797
- case "sessions":
798
- currentSessionPath = m.activePath || null;
799
- renderSessions(sidebar, m.items, m.activePath, {
800
- onOpen: (path) => { send({ type: "open_session", path }); closeDrawer(); },
801
- onFork: (path) => send({ type: "fork_session", path }),
802
- onDelete: (path) => send({ type: "delete_session", path }),
803
- });
804
- break;
805
- case "context": renderContext(m as ContextFrame); break;
806
- // Sibling group for the last user message (edit-and-resend's `< i/n >`
807
- // toggle, plan §5.2) — see renderSiblingToggle/switchSiblingDir.
808
- case "siblings":
809
- siblingInfo = { entryId: m.entryId || null, index: m.index, count: m.count, siblingIds: m.siblingIds || [] };
810
- lastUserEntryId = siblingInfo.entryId;
811
- renderSiblingToggle(lastUserMsgEl, lastUserEntryId, siblingInfo, switchSiblingDir);
812
- break;
813
- case "turn_start": turnActive = true; startAssistant(); ($("chat-send") as HTMLButtonElement).disabled = true; $("chat-stop").style.display = "block"; break;
814
- case "text_delta": appendDelta(m.delta || ""); break;
815
- case "thinking_delta": appendThinkingDelta(m.delta || ""); break;
816
- case "tool_start": toolCard(m.callId, m.tool, m.args); break;
817
- case "tool_approval_request": approvalCard(m.callId, m.tool, m.args); break;
818
- case "tool_update": toolUpdate(m.callId, (m.chunk as string) || ""); break;
819
- case "tool_end": toolEnd(m.callId, m.ok, m.result); break;
820
- // clearOneShotArmed: a one-shot next_turn sampling override (if any
821
- // was armed) has now been consumed by the server for exactly this
822
- // turn — clear the local indicator in lockstep (plan §9 Phase 3 item 2).
823
- case "turn_end": finalizeMeta(); renderLane(m.lane as Lane | undefined); clearOneShotArmed(composer); endTurn(); break;
824
- case "queue_update": renderQueue(m); break;
825
- // A prompt/regenerate/edit_resend that fails before the server ever
826
- // reaches turn_start (no model selected, auth failure, etc.) never
827
- // sends turn_end, so this is also where a one-shot next_turn sampling
828
- // override must be cleared client-side — the server clears its own
829
- // copy on this same failure path (clearArmedOneShotOnFailedPrompt in
830
- // pi-web.ts), so the two stay in lockstep instead of the UI's "armed"
831
- // pill lingering after the override it referred to is already gone.
832
- case "error": showAgentError(m.message || "agent error"); toast(m.message || "agent error", "err"); clearOneShotArmed(composer); if (turnActive) endTurn(); break;
833
- // App-aware assistant (plan §6.6): navigate_app / spotlight_ui tool
834
- // calls arrive as these two frames. Navigation always routes through
835
- // location.hash (the SAME mechanism a nav-tab click uses — shell.ts's
836
- // hashchange listener does the actual section toggle), so it's
837
- // indistinguishable from the user clicking the tab themselves.
838
- case "ui_navigate":
839
- location.hash = `#/${m.route}`;
840
- break;
841
- // spotlight_ui: navigate first if the tool asked (route present), THEN
842
- // resolve+show — every route's section already lives in the DOM,
843
- // toggled by CSS (shell.ts's router()), so no waitForElement polling
844
- // is needed the way a client-side-routed app would require.
845
- case "ui_spotlight": {
846
- if (m.route) location.hash = `#/${m.route}`;
847
- const resolved = resolveSpotlightTarget(
848
- { ref: m.ref, label: m.label, selector: m.selector, target: m.target, message: m.message },
849
- lastSnapshot,
850
- );
851
- if (!resolved || !showSpotlight(resolved)) {
852
- toast("Couldn't find that on screen to point at.", "err");
853
- }
854
- break;
855
- }
856
- }
857
- }
858
-
859
- /* — composer submit — */
860
- function submit(): void {
861
- const box = $("chat-box") as HTMLTextAreaElement;
862
- const text = box.value.trim();
863
- // Client-side slash commands for session control (not sent to the model).
864
- const cmd = text.toLowerCase();
865
- if (cmd === "/reload" || cmd === "/new" || cmd === "/clear") {
866
- box.value = ""; box.style.height = "auto"; clearAttachments(composer); newChat();
867
- return;
868
- }
869
- if (!text && composer.attachments.length === 0) return;
870
- if (!connected) { toast("Not connected to the agent yet — reconnecting.", "err"); return; }
871
- const imgs = composer.attachments.filter((a) => a.kind === "image").map((a) => ({ data: a.data as string, mimeType: a.mimeType as string }));
872
- const combined = buildMessageText(composer, text);
873
- // entryId isn't known yet (assigned server-side); the next `siblings`
874
- // frame (sent on turn_end) backfills it via renderSiblingToggle's
875
- // entryId-mismatch check, same as a fresh page load's history replay.
876
- addUserMsg(text, composer.attachments as unknown as { kind: string; data?: string; mimeType?: string; name: string }[], { isLast: true });
877
- box.value = ""; box.style.height = "auto";
878
- // buildMessageText returns { text, citations } (RAG v1, plan §9 Phase 3
879
- // Axis 5): .text is the WS-outgoing prompt string (with the numbered
880
- // context block injected when retrieval mode fired); .citations stash
881
- // into pendingCitations so startAssistant() can attach them to the
882
- // AssistantMsgState this prompt's reply streams into once turn_start
883
- // arrives (empty array — the common case — is a no-op all the way
884
- // through: no Sources panel, no [n] linkification).
885
- pendingCitations = combined.citations;
886
- const frame: ClientMessage = imgs.length ? { type: "prompt", text: combined.text, images: imgs } : { type: "prompt", text: combined.text };
887
- // A composer submission is always a new user turn. If the model is still
888
- // streaming, the server passes it to pi with streamingBehavior:"followUp"
889
- // so it becomes the next turn instead of steering/mutating the current one.
890
- send(frame);
891
- clearAttachments(composer);
892
- }
893
- const scroll = () => $("chat-scroll");
894
- const atBottom = () => scroll().scrollHeight - scroll().scrollTop - scroll().clientHeight < 90;
895
- function stick(force?: boolean): void { if (force || atBottom()) scroll().scrollTop = scroll().scrollHeight; }
896
-
897
- /** Cmd/Ctrl+Shift+C (plan §7): reuses the exact per-message Copy button
898
- * (addMsgActions) on the last assistant message rather than duplicating
899
- * its clipboard logic — a synthetic click keeps the "Copied" flash
900
- * feedback consistent with the mouse path. */
901
- function copyLastResponse(): void {
902
- if (!lastAssistantMsgEl) { toast("No response to copy yet.", "err"); return; }
903
- const btn = lastAssistantMsgEl.querySelector(".msg-actions .maction") as HTMLButtonElement | null;
904
- if (btn) btn.click();
905
- }
906
-
907
- /** Cross-controller call from shell.ts's settings toggle (plan §5.4/§6.5)
908
- * — shell.ts owns the checkbox + localStorage mirror but no WebSocket, so
909
- * it calls this via controllers.chat.setCodingTools, same pattern as
910
- * newChat/copyLastResponse below. */
911
- function setCodingTools(enabled: boolean): void {
912
- send({ type: "set_coding_tools", enabled });
913
- }
914
-
915
- /** "Forget" a durable always-allow entry from the settings panel. REST,
916
- * not a WS frame — src/tool-approvals.ts is a plain server-side file, no
917
- * live session state to touch, matching the hf-token settings routes'
918
- * own GET/POST-over-fetch pattern. Re-renders the list from the response
919
- * rather than waiting on a tool_approvals WS frame (this connection's
920
- * session may not be the one that granted it, and REST is instant). */
921
- async function forgetToolApproval(tool: string): Promise<void> {
922
- const d = await api("/api/settings/tool-approvals", { method: "DELETE", body: { tool } })
923
- .catch((): ApiEnvelope => ({ ok: false, error: "request failed" }));
924
- if (!d.ok) { toast(d.error || "could not forget that tool", "err"); return; }
925
- renderToolApprovals((d as { alwaysAllow?: string[] }).alwaysAllow || [], (t) => forgetToolApproval(t));
926
- toast("Forgot “" + tool + "” — it will ask again next time", "ok");
927
- }
928
-
929
- return {
930
- init() {
931
- ($("chat-send") as HTMLButtonElement).onclick = submit;
932
- ($("chat-new") as HTMLButtonElement).onclick = newChat;
933
- ($("chat-stop") as HTMLButtonElement).onclick = () => { send({ type: "abort" }); $("chat-stop").style.display = "none"; };
934
- const thinkToggle = () => { composer.thinkingOn = !composer.thinkingOn; updateThinkingToggle(composer); send({ type: "set_thinking", enabled: composer.thinkingOn }); };
935
- ($("chat-think") as HTMLButtonElement).onclick = thinkToggle;
936
- $("chat-think").addEventListener("keydown", (e) => { if ((e as KeyboardEvent).key === "Enter" || (e as KeyboardEvent).key === " ") { e.preventDefault(); thinkToggle(); } });
937
- const adapterSel = $("chat-adapter") as HTMLSelectElement | null;
938
- if (adapterSel) adapterSel.onchange = () => {
939
- // Keep the routing table's notion of "selected for this chat" in
940
- // sync with the quick <select> — one source of truth for what's
941
- // selected, read by both surfaces (plan §5.6: "keep them consistent").
942
- adaptersPanel.selectedSpec = adapterSel.value || null;
943
- adaptersPanel.stackPicks.clear();
944
- onSelectAdapter(adapterSel, send as (obj: unknown) => boolean);
945
- };
946
- initAdaptersPanel(adaptersPanel, send as (obj: unknown) => boolean);
947
- initSampling(composer, send as (obj: unknown) => boolean);
948
- initSystemPrompt(composer, send as (obj: unknown) => boolean);
949
- composerRefreshAdapters();
950
- const sessSearch = $("chat-sess-search");
951
- if (sessSearch) sessSearch.addEventListener("input", () => applySessSearch(sidebar));
952
- const box = $("chat-box") as HTMLTextAreaElement;
953
- // Mention-picker keydown (arrows/Enter/Escape) must see Enter BEFORE
954
- // the submit-on-Enter handler below — addEventListener preserves
955
- // registration order, and initMentionPicker's handler only
956
- // preventDefault()s when its popover is actually open, so a plain
957
- // Enter with no picker open falls through to submit() untouched.
958
- initMentionPicker(composer, box, {
959
- onFileSelected: (att) => {
960
- const chip = document.querySelector<HTMLElement>('.attach-chip[data-att-id="' + att.id + '"]');
961
- if (chip) { chip.scrollIntoView({ block: "nearest", behavior: "smooth" }); chip.classList.add("pulse"); setTimeout(() => chip.classList.remove("pulse"), 1000); }
962
- },
963
- });
964
- box.addEventListener("keydown", (e) => {
965
- if ($("chat-mention-pop").classList.contains("open")) return; // picker owns Enter/arrows/Escape while open
966
- if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); submit(); }
967
- });
968
- box.addEventListener("input", () => { box.style.height = "auto"; box.style.height = Math.min(box.scrollHeight, 200) + "px"; });
969
- // Delegated (not per-chip) so the personalized chips memory-panel.ts
970
- // inserts on top of these four static ones (plan §5.1) work with zero
971
- // extra wiring — #chat-hello-chips is stable even though its contents
972
- // change (static chips today, +2 personalized ones once the first
973
- // /api/memory/status + /v1/adapters/available round trip resolves).
974
- $("chat-hello-chips").addEventListener("click", (e) => {
975
- const chip = (e.target as HTMLElement).closest<HTMLElement>(".chip");
976
- if (!chip) return;
977
- ($("chat-box") as HTMLTextAreaElement).value = chip.dataset.q || "";
978
- submit();
979
- });
980
- initMemoryPanel();
981
- // Attachments: + button, file picker, clipboard paste, drag-and-drop.
982
- ($("chat-attach-btn") as HTMLButtonElement).onclick = () => ($("chat-file-input") as HTMLInputElement).click();
983
- // Hold-to-talk mic → /v1/audio/sessions → text into the composer (voice.ts).
984
- initVoiceInput($("chat-mic-btn") as HTMLButtonElement, box as HTMLTextAreaElement);
985
- $("chat-file-input").addEventListener("change", (e) => { addFiles(composer, [...(e.target as HTMLInputElement).files || []]); (e.target as HTMLInputElement).value = ""; });
986
- box.addEventListener("paste", (e) => {
987
- const items = e.clipboardData && e.clipboardData.items;
988
- if (!items) return;
989
- const files: File[] = [];
990
- for (const it of items) if (it.kind === "file") { const f = it.getAsFile(); if (f) files.push(f); }
991
- if (files.length) { e.preventDefault(); addFiles(composer, files); }
992
- });
993
- const composerEl = document.querySelector<HTMLElement>("#s-chat .composer");
994
- if (composerEl) {
995
- ["dragenter", "dragover"].forEach((ev) => composerEl.addEventListener(ev, (e) => { e.preventDefault(); composerEl.classList.add("dragover"); }));
996
- ["dragleave", "drop"].forEach((ev) => composerEl.addEventListener(ev, (e) => {
997
- e.preventDefault();
998
- const de = e as DragEvent;
999
- if (ev === "drop" && de.dataTransfer && de.dataTransfer.files.length) addFiles(composer, [...de.dataTransfer.files]);
1000
- if (ev === "drop" || !composerEl.contains(de.relatedTarget as Node)) composerEl.classList.remove("dragover");
1001
- }));
1002
- }
1003
- // Delegated copy buttons for rendered code blocks (the thread element
1004
- // is stable even though its contents re-render on every token).
1005
- thread().addEventListener("click", (e) => {
1006
- const target = e.target as HTMLElement;
1007
- const btn = target.closest && target.closest(".cbcopy");
1008
- if (!btn) return;
1009
- const code = btn.closest(".codeblock")?.querySelector("code");
1010
- if (!code || !navigator.clipboard) return;
1011
- navigator.clipboard.writeText(code.textContent || "").then(() => {
1012
- btn.textContent = "Copied"; setTimeout(() => { btn.textContent = "Copy"; }, 1200);
1013
- }).catch(() => {});
1014
- });
1015
- // Canvas v1 (plan §9 Phase 3): Preview|Source toggle for html/svg
1016
- // fences, delegated the same way as the copy button above.
1017
- wireCanvasToggle(thread());
1018
-
1019
- // App-aware assistant (plan §6.6): registered at document/window level
1020
- // (not scoped to the chat route being active) since chat.ts owns the
1021
- // one WebSocket for the whole app and must push context on every
1022
- // route, regardless of which route the user navigates TO or FROM.
1023
- // hashchange covers route changes; the MutationObserver covers wizard
1024
- // step changes (quantize/finetune/dataset); a MutationObserver on each
1025
- // overlay's `class` attribute covers view changes (memory/hub/adapters
1026
- // panel open/close) without touching those three modules.
1027
- window.addEventListener("hashchange", pushAppContext);
1028
- watchWizardSteps();
1029
- const overlayMo = new MutationObserver(() => pushAppContext());
1030
- for (const id of ["mem-overlay", "hub-overlay", "adapters-overlay"]) {
1031
- const el = $(id);
1032
- if (el) overlayMo.observe(el, { attributes: true, attributeFilter: ["class"] });
1033
- }
1034
- },
1035
- enter() {
1036
- if (!ws || ws.readyState > 1) connect();
1037
- $("chat-box").focus();
1038
- // Refresh the adapter list on every chat enter (web-ui-pass-plan.md
1039
- // #15's staleness half): a training job that completed while the user
1040
- // was elsewhere, or a manual drop into the adapters directory, should
1041
- // show up without needing a full page reload. Cheap (one GET) and the
1042
- // list is short.
1043
- composerRefreshAdapters();
1044
- },
1045
- leave() { /* keep WS warm; closing on every nav would drop in-flight turns */ },
1046
- // Exposed so the finetune controller can refresh the chat adapter chip
1047
- // immediately when a training job finishes, instead of waiting for the
1048
- // user to navigate back to Chat (which would also refresh it via enter()).
1049
- refreshAdapters: composerRefreshAdapters,
1050
- // Exposed for the global keyboard-shortcut sweep (plan §7): Cmd/Ctrl+Shift+O
1051
- // and Cmd/Ctrl+Shift+C, bound at document level so they work regardless
1052
- // of which control has focus while on the chat route.
1053
- newChat,
1054
- copyLastResponse,
1055
- // Exposed for shell.ts's settings modal (plan §5.4/§6.5) — it owns the
1056
- // checkbox/list DOM and localStorage, but has no WebSocket of its own.
1057
- setCodingTools,
1058
- forgetToolApproval,
1059
- };
1060
- }