@pasko70/pibo 1.11.2 → 1.11.4

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 (183) hide show
  1. package/dist/apps/chat/data/chat-data-mappers.js +18 -6
  2. package/dist/apps/chat/loop-api.js +22 -6
  3. package/dist/apps/chat-ui/assets/dist-B5PTVnox.js +1 -0
  4. package/dist/apps/chat-ui/assets/dist-B5ZAfwf5.js +1 -0
  5. package/dist/apps/{context-files-ui/assets/dist-ZsBbt0uY.js → chat-ui/assets/dist-BXjLdcn6.js} +1 -1
  6. package/dist/apps/{context-files-ui/assets/dist-DqYw0LH7.js → chat-ui/assets/dist-BeKdUsyg.js} +1 -1
  7. package/dist/apps/chat-ui/assets/dist-BkUONE3N.js +1 -0
  8. package/dist/apps/{context-files-ui/assets/dist-vnd7PMu3.js → chat-ui/assets/dist-BtQ20SMU.js} +1 -1
  9. package/dist/apps/{context-files-ui/assets/dist-DPG9jOQ-.js → chat-ui/assets/dist-CNv02m3N.js} +1 -1
  10. package/dist/apps/{context-files-ui/assets/dist-Dp5AH00p.js → chat-ui/assets/dist-CSBDio7C.js} +1 -1
  11. package/dist/apps/{context-files-ui/assets/dist-kmsvtHkH.js → chat-ui/assets/dist-CjXSEbsr.js} +1 -1
  12. package/dist/apps/chat-ui/assets/{dist-SVPsM5Oi.js → dist-D1KyNDg5.js} +2 -2
  13. package/dist/apps/{context-files-ui/assets/dist-DK01b7x4.js → chat-ui/assets/dist-D9B41N2f.js} +1 -1
  14. package/dist/apps/chat-ui/assets/dist-DKoyMSNr.js +1 -0
  15. package/dist/apps/chat-ui/assets/dist-DLMjd26T.js +1 -0
  16. package/dist/apps/chat-ui/assets/dist-DotDq_kD.js +1 -0
  17. package/dist/apps/chat-ui/assets/dist-QQuDy4KA.js +1 -0
  18. package/dist/apps/{context-files-ui/assets/dist-BOiGdrG-.js → chat-ui/assets/dist-TAhl1jC0.js} +1 -1
  19. package/dist/apps/{context-files-ui/assets/dist-TbjAhles.js → chat-ui/assets/dist-TS4EA6YQ.js} +1 -1
  20. package/dist/apps/{context-files-ui/assets/dist-TF5iO5ML.js → chat-ui/assets/dist-yjwDcRbw.js} +1 -1
  21. package/dist/apps/chat-ui/assets/{dockerfile-CUaqzcqp.js → dockerfile-CJb2sekW.js} +1 -1
  22. package/dist/apps/chat-ui/assets/{factor-C2tAxNob.js → factor-B2EMQZkP.js} +1 -1
  23. package/dist/apps/chat-ui/assets/index-BoRl0OB_.js +473 -0
  24. package/dist/apps/chat-ui/assets/index-aTOOomMF.css +1 -0
  25. package/dist/apps/chat-ui/assets/{nsis-SMDwKltR.js → nsis-DOo3SJmd.js} +1 -1
  26. package/dist/apps/chat-ui/assets/{pug-Bo3wfL-f.js → pug-QU72BPAa.js} +1 -1
  27. package/dist/apps/chat-ui/index.html +3 -4
  28. package/dist/apps/chat-vscode-web/assets/index-DGcSd2ZH.js +275 -0
  29. package/dist/apps/chat-vscode-web/assets/{index-Bf2JvJ9z.css → index-Dupu1DzQ.css} +1 -1
  30. package/dist/apps/chat-vscode-web/index.html +2 -2
  31. package/dist/apps/context-files-ui/assets/dist-B07Whoa6.js +1 -0
  32. package/dist/apps/context-files-ui/assets/dist-BACxmdQ2.js +9 -0
  33. package/dist/apps/context-files-ui/assets/dist-BFlEjppv.js +1 -0
  34. package/dist/apps/{chat-ui/assets/dist-CuGiEm5l.js → context-files-ui/assets/dist-BQK_wUMR.js} +2 -2
  35. package/dist/apps/context-files-ui/assets/dist-CigbyRK5.js +1 -0
  36. package/dist/apps/context-files-ui/assets/dist-D68eIqmX.js +1 -0
  37. package/dist/apps/context-files-ui/assets/dist-DT9QkgTN.js +1 -0
  38. package/dist/apps/context-files-ui/assets/dist-De6eI3Cr.js +1 -0
  39. package/dist/apps/context-files-ui/assets/dist-Dtv5S2qd.js +11 -0
  40. package/dist/apps/context-files-ui/assets/dist-TG9QLHKj.js +1 -0
  41. package/dist/apps/context-files-ui/assets/index-B5j33Q2B.js +438 -0
  42. package/dist/apps/context-files-ui/assets/{index-B92tL3Pp.css → index-CukPnd6A.css} +1 -1
  43. package/dist/apps/context-files-ui/index.html +2 -2
  44. package/dist/apps/vscode-artifacts/latest.vsix +0 -0
  45. package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.11.4.vsix +0 -0
  46. package/dist/core/routed-session.js +143 -24
  47. package/dist/core/runtime-telemetry.js +90 -0
  48. package/dist/core/runtime.js +1 -0
  49. package/dist/core/session-router.js +262 -50
  50. package/dist/debug/web-render-order-analysis.js +307 -0
  51. package/dist/debug/web-snapshot-browser-scripts.js +10 -1
  52. package/dist/debug/web-streaming-benchmark-analysis.js +12 -1
  53. package/dist/debug/web-streaming-browser-library.js +268 -58
  54. package/dist/debug/web-streaming-report.js +24 -0
  55. package/dist/debug/web.js +14 -3
  56. package/dist/gateway/server.js +2 -0
  57. package/dist/gateway/web.js +1 -0
  58. package/dist/loops/accounting.js +8 -1
  59. package/dist/loops/cli.js +1 -1
  60. package/dist/loops/service.js +167 -27
  61. package/dist/loops/store.js +229 -17
  62. package/dist/loops/tools.js +30 -9
  63. package/dist/plugins/context-files.js +3 -1
  64. package/dist/runs/registry.js +19 -0
  65. package/package.json +1 -1
  66. package/dist/apps/chat-ui/assets/dist--55mTWk3.js +0 -1
  67. package/dist/apps/chat-ui/assets/dist-BBVpFHAq.js +0 -11
  68. package/dist/apps/chat-ui/assets/dist-BXyVMdHv.js +0 -1
  69. package/dist/apps/chat-ui/assets/dist-BZ39rB1W.js +0 -1
  70. package/dist/apps/chat-ui/assets/dist-BiGfVaXN.js +0 -1
  71. package/dist/apps/chat-ui/assets/dist-BsedZGJo.js +0 -1
  72. package/dist/apps/chat-ui/assets/dist-C5YJOwhH.js +0 -1
  73. package/dist/apps/chat-ui/assets/dist-C5oXtFSP.js +0 -1
  74. package/dist/apps/chat-ui/assets/dist-CCGKu-Wj.js +0 -9
  75. package/dist/apps/chat-ui/assets/dist-CE0MvPLM.js +0 -1
  76. package/dist/apps/chat-ui/assets/dist-CKM5j-iy.js +0 -1
  77. package/dist/apps/chat-ui/assets/dist-CUp9us2q.js +0 -1
  78. package/dist/apps/chat-ui/assets/dist-CZ9T4Cly.js +0 -1
  79. package/dist/apps/chat-ui/assets/dist-DBnh8gXR.js +0 -1
  80. package/dist/apps/chat-ui/assets/dist-DSKQpPaz.js +0 -1
  81. package/dist/apps/chat-ui/assets/dist-DeOnZ-pw.js +0 -23
  82. package/dist/apps/chat-ui/assets/dist-DnQYnLQS.js +0 -1
  83. package/dist/apps/chat-ui/assets/dist-VT4x40uL.js +0 -6
  84. package/dist/apps/chat-ui/assets/dist-Zqt9kNG9.js +0 -1
  85. package/dist/apps/chat-ui/assets/dist-tGfufz3b.js +0 -1
  86. package/dist/apps/chat-ui/assets/dist-xtnVygdr.js +0 -1
  87. package/dist/apps/chat-ui/assets/index-DNeE4HrG.css +0 -1
  88. package/dist/apps/chat-ui/assets/index-vcg8JNj9.js +0 -173
  89. package/dist/apps/chat-ui/assets/rolldown-runtime-S-ySWqyJ.js +0 -1
  90. package/dist/apps/chat-vscode-web/assets/index-DVlCO7v_.js +0 -41
  91. package/dist/apps/context-files-ui/assets/index-BhAWD3dv.js +0 -202
  92. package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.11.2.vsix +0 -0
  93. /package/dist/apps/chat-ui/assets/{apl-BxEMbctQ.js → apl-14BqhLYK.js} +0 -0
  94. /package/dist/apps/chat-ui/assets/{asciiarmor-eFjeGRuZ.js → asciiarmor-WLvjqI7d.js} +0 -0
  95. /package/dist/apps/chat-ui/assets/{asn1-Cfx-0pt9.js → asn1-Bg4FbdCg.js} +0 -0
  96. /package/dist/apps/chat-ui/assets/{asterisk-B5BHTsBr.js → asterisk-B2MO-X4d.js} +0 -0
  97. /package/dist/apps/chat-ui/assets/{brainfuck-CAA2xofr.js → brainfuck-CV9kufud.js} +0 -0
  98. /package/dist/apps/chat-ui/assets/{clike-BoDCOLzN.js → clike-CjmcUpt6.js} +0 -0
  99. /package/dist/apps/chat-ui/assets/{clojure-kVFSWX0o.js → clojure-De3QGfx6.js} +0 -0
  100. /package/dist/apps/chat-ui/assets/{cmake-BUPAhezI.js → cmake-i2VDNaRq.js} +0 -0
  101. /package/dist/apps/chat-ui/assets/{cobol-BfgHZgup.js → cobol-BwO6DF08.js} +0 -0
  102. /package/dist/apps/chat-ui/assets/{coffeescript-G1Pe_1AZ.js → coffeescript-BtivDvRz.js} +0 -0
  103. /package/dist/apps/chat-ui/assets/{commonlisp-B3N0CjHn.js → commonlisp-DFkEQdLP.js} +0 -0
  104. /package/dist/apps/chat-ui/assets/{crystal-C15ndaGM.js → crystal-CU0NkcVL.js} +0 -0
  105. /package/dist/apps/chat-ui/assets/{css-BqOeyZOV.js → css-CbMZAQ1W.js} +0 -0
  106. /package/dist/apps/chat-ui/assets/{cypher-KjIKv-wW.js → cypher-Dke5QOzv.js} +0 -0
  107. /package/dist/apps/chat-ui/assets/{d-BlT5AEC-.js → d-TPqSVaHM.js} +0 -0
  108. /package/dist/apps/chat-ui/assets/{diff-oF98tbqj.js → diff-B365LSoZ.js} +0 -0
  109. /package/dist/apps/chat-ui/assets/{dtd-VCA9yr61.js → dtd-CBMymJ7O.js} +0 -0
  110. /package/dist/apps/chat-ui/assets/{dylan-O-V5Dw5W.js → dylan-08Azy1NK.js} +0 -0
  111. /package/dist/apps/chat-ui/assets/{ebnf-SPmdBkvT.js → ebnf-Dkum_1J8.js} +0 -0
  112. /package/dist/apps/chat-ui/assets/{ecl-ChfCttfI.js → ecl-dhxjHL8y.js} +0 -0
  113. /package/dist/apps/chat-ui/assets/{eiffel-cB9EbjeN.js → eiffel-C446m6dI.js} +0 -0
  114. /package/dist/apps/chat-ui/assets/{elm-BTiVflBU.js → elm-po1E9de0.js} +0 -0
  115. /package/dist/apps/chat-ui/assets/{erlang-egf0TXFm.js → erlang-BD5LkVkR.js} +0 -0
  116. /package/dist/apps/chat-ui/assets/{fcl-BiCjh1tj.js → fcl-DkEOamHd.js} +0 -0
  117. /package/dist/apps/chat-ui/assets/{forth-C4kDGW6u.js → forth-dCg2_HVB.js} +0 -0
  118. /package/dist/apps/chat-ui/assets/{fortran-DzkLaPnd.js → fortran-3ZpXLFh1.js} +0 -0
  119. /package/dist/apps/chat-ui/assets/{gas-DMOxskQF.js → gas-DMmxLZRI.js} +0 -0
  120. /package/dist/apps/chat-ui/assets/{gherkin-BPxBpp-k.js → gherkin-DUSFg2R6.js} +0 -0
  121. /package/dist/apps/chat-ui/assets/{groovy-CkEnc0ZY.js → groovy-Cmduloe1.js} +0 -0
  122. /package/dist/apps/chat-ui/assets/{haskell-Dadpo9-w.js → haskell-Cyr255R9.js} +0 -0
  123. /package/dist/apps/chat-ui/assets/{haxe-CDNZL9MW.js → haxe-BJVKtprM.js} +0 -0
  124. /package/dist/apps/chat-ui/assets/{http-jy9Zxxt3.js → http-DszHFpc-.js} +0 -0
  125. /package/dist/apps/chat-ui/assets/{idl-CGSB6iiy.js → idl-Cati9Nrv.js} +0 -0
  126. /package/dist/apps/chat-ui/assets/{javascript-DSlzRUd8.js → javascript-C5pDVaoP.js} +0 -0
  127. /package/dist/apps/chat-ui/assets/{julia-DTiF9v6R.js → julia-DlqY1cTU.js} +0 -0
  128. /package/dist/apps/chat-ui/assets/{livescript-Ci8Xxna6.js → livescript-C6p8-2W1.js} +0 -0
  129. /package/dist/apps/chat-ui/assets/{lua-eVxIrt9t.js → lua-BxuTHaF7.js} +0 -0
  130. /package/dist/apps/chat-ui/assets/{mathematica-Da3grj36.js → mathematica-nDHZUMJT.js} +0 -0
  131. /package/dist/apps/chat-ui/assets/{mbox-BWBMSafM.js → mbox-DAaR0Hcz.js} +0 -0
  132. /package/dist/apps/chat-ui/assets/{mirc-CtHu9S1B.js → mirc-CCA0FRfl.js} +0 -0
  133. /package/dist/apps/chat-ui/assets/{mllike-wTFlPWXS.js → mllike-Cw3Iccsb.js} +0 -0
  134. /package/dist/apps/chat-ui/assets/{modelica-cKbQ0UXu.js → modelica-Czx80fNR.js} +0 -0
  135. /package/dist/apps/chat-ui/assets/{mscgen-C5pXQCMK.js → mscgen-BmfuTbzG.js} +0 -0
  136. /package/dist/apps/chat-ui/assets/{mumps-CKBHkOQq.js → mumps-Ds_ZaumL.js} +0 -0
  137. /package/dist/apps/chat-ui/assets/{nginx-B00AiQel.js → nginx-D9M2PQ4U.js} +0 -0
  138. /package/dist/apps/chat-ui/assets/{ntriples-B_36EBV0.js → ntriples-DLHGTvHs.js} +0 -0
  139. /package/dist/apps/chat-ui/assets/{octave-BXPjzAif.js → octave-8jj96JiC.js} +0 -0
  140. /package/dist/apps/chat-ui/assets/{oz-Dedpat1M.js → oz-BmMq7iC4.js} +0 -0
  141. /package/dist/apps/chat-ui/assets/{pascal-CMcUg72K.js → pascal-BgMr6XpT.js} +0 -0
  142. /package/dist/apps/chat-ui/assets/{perl-DoVTbnzt.js → perl-D6YJObi_.js} +0 -0
  143. /package/dist/apps/chat-ui/assets/{pig-CULXT1YV.js → pig-nx7uchs8.js} +0 -0
  144. /package/dist/apps/chat-ui/assets/{powershell-Cv5Uhf6b.js → powershell-B4IPkVxR.js} +0 -0
  145. /package/dist/apps/chat-ui/assets/{properties-CQ1C2P-M.js → properties-CoavQdLC.js} +0 -0
  146. /package/dist/apps/chat-ui/assets/{protobuf-SRlvzrl6.js → protobuf-D5EWhcmc.js} +0 -0
  147. /package/dist/apps/chat-ui/assets/{puppet-ClSqCGj6.js → puppet-B_0GFuyD.js} +0 -0
  148. /package/dist/apps/chat-ui/assets/{python-C2_X1GpX.js → python-Cs7sNzy_.js} +0 -0
  149. /package/dist/apps/chat-ui/assets/{q-B_6Gdyzl.js → q-BIB9pPtI.js} +0 -0
  150. /package/dist/apps/chat-ui/assets/{r-BD07NGED.js → r-CN5s4f3o.js} +0 -0
  151. /package/dist/apps/chat-ui/assets/{rpm-CRu5Z2Jl.js → rpm-Ct_Ck8mQ.js} +0 -0
  152. /package/dist/apps/chat-ui/assets/{ruby-dZPnOz8h.js → ruby-BVPradRk.js} +0 -0
  153. /package/dist/apps/chat-ui/assets/{sas-S2t8_sDI.js → sas-DN4j74IF.js} +0 -0
  154. /package/dist/apps/chat-ui/assets/{scheme-WcyFWLW2.js → scheme-CvNgHAnh.js} +0 -0
  155. /package/dist/apps/chat-ui/assets/{shell-DUelnYSh.js → shell-BjiMLAaM.js} +0 -0
  156. /package/dist/apps/chat-ui/assets/{sieve-jDjlVhNh.js → sieve-C2U1O5Z4.js} +0 -0
  157. /package/dist/apps/chat-ui/assets/{simple-mode-CL04yRVp.js → simple-mode-DnMbQuKW.js} +0 -0
  158. /package/dist/apps/chat-ui/assets/{smalltalk-ddWDYy10.js → smalltalk-DkYwYufV.js} +0 -0
  159. /package/dist/apps/chat-ui/assets/{solr-C7rQhLuW.js → solr-BopnSD-v.js} +0 -0
  160. /package/dist/apps/chat-ui/assets/{sparql--fPCCgVd.js → sparql-Cpg3YQfd.js} +0 -0
  161. /package/dist/apps/chat-ui/assets/{spreadsheet-DjthqD5G.js → spreadsheet-kRxtVt9J.js} +0 -0
  162. /package/dist/apps/chat-ui/assets/{sql-D_dieYbr.js → sql-DYI2GwJR.js} +0 -0
  163. /package/dist/apps/chat-ui/assets/{stex-rHcFYcA0.js → stex-BCRT8IJ3.js} +0 -0
  164. /package/dist/apps/chat-ui/assets/{stylus-DxF70RNr.js → stylus-CARoeVE9.js} +0 -0
  165. /package/dist/apps/chat-ui/assets/{swift-T0hDoOb-.js → swift-DKNaLAK6.js} +0 -0
  166. /package/dist/apps/chat-ui/assets/{tcl-Dlzy1mPk.js → tcl-CQyXkgMH.js} +0 -0
  167. /package/dist/apps/chat-ui/assets/{textile-Dy5rFZe5.js → textile-Yg_UWn-t.js} +0 -0
  168. /package/dist/apps/chat-ui/assets/{tiddlywiki-DQP1OymS.js → tiddlywiki-dqVbkfX4.js} +0 -0
  169. /package/dist/apps/chat-ui/assets/{tiki-0vOj3DB2.js → tiki-CC_ic8g0.js} +0 -0
  170. /package/dist/apps/chat-ui/assets/{toml-DEltN0lI.js → toml-B7gGzVkJ.js} +0 -0
  171. /package/dist/apps/chat-ui/assets/{troff-CRbBpEqY.js → troff-Bq3z6oAu.js} +0 -0
  172. /package/dist/apps/chat-ui/assets/{ttcn-M5q1nLKg.js → ttcn-GvRgs2AT.js} +0 -0
  173. /package/dist/apps/chat-ui/assets/{ttcn-cfg-Rmw9NWq9.js → ttcn-cfg-CGCXz7Vo.js} +0 -0
  174. /package/dist/apps/chat-ui/assets/{turtle-BCDWhJK0.js → turtle-CwIpz2yy.js} +0 -0
  175. /package/dist/apps/chat-ui/assets/{vb-QQ7LGlOz.js → vb-BmcHUM2C.js} +0 -0
  176. /package/dist/apps/chat-ui/assets/{vbscript-BiSEdUzz.js → vbscript-Cs9dylre.js} +0 -0
  177. /package/dist/apps/chat-ui/assets/{velocity-CzMg5_6w.js → velocity-CBWU0VCy.js} +0 -0
  178. /package/dist/apps/chat-ui/assets/{verilog-BJOO83gk.js → verilog-DVCHmufx.js} +0 -0
  179. /package/dist/apps/chat-ui/assets/{vhdl-ed4zAZAA.js → vhdl-BGDk4f1d.js} +0 -0
  180. /package/dist/apps/chat-ui/assets/{webidl-Ddy6hbV6.js → webidl-egXQEWlC.js} +0 -0
  181. /package/dist/apps/chat-ui/assets/{xquery-Bw1zwhh6.js → xquery-5f0hn7cg.js} +0 -0
  182. /package/dist/apps/chat-ui/assets/{yacas-DZdTXy3s.js → yacas-DNs9XkmQ.js} +0 -0
  183. /package/dist/apps/chat-ui/assets/{z80-DMG02_YV.js → z80-B7Dfb4UO.js} +0 -0
@@ -33,8 +33,17 @@ function stats(values) {
33
33
  function fetchWithTimeout(url, init, timeoutMs) {
34
34
  if (typeof AbortController === 'undefined') return fetch(url, init);
35
35
  const controller = new AbortController();
36
+ const externalSignal = init && init.signal;
37
+ const abortFromExternal = () => controller.abort();
38
+ if (externalSignal) {
39
+ if (externalSignal.aborted) controller.abort();
40
+ else externalSignal.addEventListener('abort', abortFromExternal, { once: true });
41
+ }
36
42
  const timer = setTimeout(() => controller.abort(), timeoutMs);
37
- return fetch(url, { ...init, signal: controller.signal }).finally(() => clearTimeout(timer));
43
+ return fetch(url, { ...init, signal: controller.signal }).finally(() => {
44
+ clearTimeout(timer);
45
+ if (externalSignal) externalSignal.removeEventListener('abort', abortFromExternal);
46
+ });
38
47
  }
39
48
  function installOverlayDropSimulation(enabled) {
40
49
  if (!enabled) {
@@ -107,6 +116,8 @@ function createSseProbe(piboSessionId, startedAt) {
107
116
  let buffer = '';
108
117
  let stopped = false;
109
118
  let finished = false;
119
+ let reader;
120
+ let stoppedResult;
110
121
  const controller = typeof AbortController === 'undefined' ? undefined : new AbortController();
111
122
  const decoder = typeof TextDecoder === 'undefined' ? undefined : new TextDecoder();
112
123
  const encoder = typeof TextEncoder === 'undefined' ? undefined : new TextEncoder();
@@ -169,7 +180,7 @@ function createSseProbe(piboSessionId, startedAt) {
169
180
  response.headers.forEach((value, key) => { result.headers[key] = value; });
170
181
  if (!response.body || typeof response.body.getReader !== 'function') throw new Error('ReadableStream unavailable');
171
182
  result.installed = true;
172
- const reader = response.body.getReader();
183
+ reader = response.body.getReader();
173
184
  while (true) {
174
185
  const next = await reader.read();
175
186
  if (next.done) break;
@@ -197,21 +208,33 @@ function createSseProbe(piboSessionId, startedAt) {
197
208
  if (stopped || (error && error.name === 'AbortError')) result.aborted = true;
198
209
  else result.errors.push(String(error && error.message ? error.message : error));
199
210
  } finally {
211
+ reader = undefined;
200
212
  finished = true;
201
213
  finalize();
202
214
  }
203
215
  })();
204
216
  return {
205
- result,
217
+ get result() { return stoppedResult || result; },
206
218
  stop: async () => {
219
+ if (stoppedResult) return stoppedResult;
207
220
  stopped = true;
208
221
  if (controller) controller.abort();
209
- await Promise.race([done.catch(() => {}), new Promise((resolve) => setTimeout(resolve, 2500))]);
222
+ try { if (reader && typeof reader.cancel === 'function') void Promise.resolve(reader.cancel()).catch(() => {}); } catch {}
223
+ let stopTimeout;
224
+ try {
225
+ await Promise.race([
226
+ done.catch(() => {}),
227
+ new Promise((resolve) => { stopTimeout = setTimeout(resolve, 2500); }),
228
+ ]);
229
+ } finally {
230
+ if (stopTimeout !== undefined) clearTimeout(stopTimeout);
231
+ }
210
232
  if (!finished) {
211
233
  result.aborted = true;
212
234
  result.errors.push('stop timeout after abort');
213
235
  }
214
- return finalize();
236
+ stoppedResult = cloneDebugSnapshot(finalize()) || { ...finalize() };
237
+ return stoppedResult;
215
238
  },
216
239
  };
217
240
  }
@@ -235,50 +258,69 @@ function createTraceProbe(piboSessionId, startedAt, intervalMs) {
235
258
  maxAssistantOutputLength: 0,
236
259
  samples: [],
237
260
  };
238
- let sampling = false;
239
- const sample = async () => {
240
- if (sampling) return;
241
- sampling = true;
242
- try {
243
- const t = Math.round(performance.now() - startedAt);
244
- const response = await fetchWithTimeout('/api/chat/trace?piboSessionId=' + encodeURIComponent(piboSessionId) + '&includeRawEvents=true&rawEventsLimit=80', {}, 2500);
245
- if (!response.ok) throw new Error(response.status + ' ' + response.statusText);
246
- const trace = await response.json();
247
- result.fetchCount += 1;
248
- const version = typeof trace.version === 'string' ? trace.version : undefined;
249
- const rawEvents = Array.isArray(trace.rawEvents) ? trace.rawEvents : [];
250
- const assistantOutputLength = maxTraceAssistantOutputLength(trace);
251
- const liveVersion = Boolean(version && version.includes(':live:'));
252
- const sampleResult = {
253
- t,
254
- version,
255
- eventCount: typeof trace.eventCount === 'number' ? trace.eventCount : undefined,
256
- rawEventCount: rawEvents.length,
257
- assistantOutputLength,
258
- liveVersion,
259
- rawEventTypes: countRawTraceEventTypes(rawEvents),
260
- };
261
- result.samples.push(sampleResult);
262
- if (result.samples.length > 80) result.samples.shift();
263
- result.sampleCount = result.samples.length;
264
- result.maxAssistantOutputLength = Math.max(result.maxAssistantOutputLength, assistantOutputLength);
265
- result.finalAssistantOutputLength = assistantOutputLength;
266
- if (typeof sampleResult.eventCount === 'number') {
267
- result.durableEventCountStart ??= sampleResult.eventCount;
268
- result.durableEventCountEnd = sampleResult.eventCount;
269
- }
270
- if (liveVersion) {
271
- result.liveVersionCount += 1;
272
- result.firstLiveVersionMs ??= t;
261
+ let stopped = false;
262
+ let activeController;
263
+ let activeSample;
264
+ const sample = () => {
265
+ if (stopped) return Promise.resolve();
266
+ if (activeSample) return activeSample;
267
+ const controller = typeof AbortController === 'undefined' ? undefined : new AbortController();
268
+ activeController = controller;
269
+ const currentSample = (async () => {
270
+ try {
271
+ const t = Math.round(performance.now() - startedAt);
272
+ const response = await fetchWithTimeout('/api/chat/trace?piboSessionId=' + encodeURIComponent(piboSessionId) + '&includeRawEvents=true&rawEventsLimit=80', { signal: controller && controller.signal }, 2500);
273
+ if (!response.ok) throw new Error(response.status + ' ' + response.statusText);
274
+ const trace = await response.json();
275
+ result.fetchCount += 1;
276
+ const version = typeof trace.version === 'string' ? trace.version : undefined;
277
+ const rawEvents = Array.isArray(trace.rawEvents) ? trace.rawEvents : [];
278
+ const assistantOutputLength = maxTraceAssistantOutputLength(trace);
279
+ const liveVersion = Boolean(version && version.includes(':live:'));
280
+ const sampleResult = {
281
+ t,
282
+ version,
283
+ eventCount: typeof trace.eventCount === 'number' ? trace.eventCount : undefined,
284
+ rawEventCount: rawEvents.length,
285
+ assistantOutputLength,
286
+ liveVersion,
287
+ rawEventTypes: countRawTraceEventTypes(rawEvents),
288
+ };
289
+ result.samples.push(sampleResult);
290
+ if (result.samples.length > 80) result.samples.shift();
291
+ result.sampleCount = result.samples.length;
292
+ result.maxAssistantOutputLength = Math.max(result.maxAssistantOutputLength, assistantOutputLength);
293
+ result.finalAssistantOutputLength = assistantOutputLength;
294
+ if (typeof sampleResult.eventCount === 'number') {
295
+ result.durableEventCountStart ??= sampleResult.eventCount;
296
+ result.durableEventCountEnd = sampleResult.eventCount;
297
+ }
298
+ if (liveVersion) {
299
+ result.liveVersionCount += 1;
300
+ result.firstLiveVersionMs ??= t;
301
+ }
302
+ } catch (error) {
303
+ if (!(stopped && error && error.name === 'AbortError')) result.failedFetchCount += 1;
273
304
  }
274
- } catch {
275
- result.failedFetchCount += 1;
276
- } finally {
277
- sampling = false;
278
- }
305
+ })();
306
+ const trackedSample = currentSample.finally(() => {
307
+ if (activeSample === trackedSample) activeSample = undefined;
308
+ if (activeController === controller) activeController = undefined;
309
+ });
310
+ activeSample = trackedSample;
311
+ return trackedSample;
279
312
  };
280
313
  const timer = setInterval(() => { sample().catch(() => {}); }, intervalMs);
281
- return { result, sample, stop: () => clearInterval(timer) };
314
+ return {
315
+ result,
316
+ sample,
317
+ stop: async () => {
318
+ stopped = true;
319
+ clearInterval(timer);
320
+ if (activeController) activeController.abort();
321
+ if (activeSample) await activeSample.catch(() => {});
322
+ },
323
+ };
282
324
  }
283
325
  function countRawTraceEventTypes(rawEvents) {
284
326
  const counts = {};
@@ -649,6 +691,124 @@ async function waitForAssistantDomSettle(timeoutMs) {
649
691
  }
650
692
  }
651
693
  }
694
+ function startRenderOrderCapture(startedAt) {
695
+ const piboSessionId = document.querySelector('[data-pibo-debug="chat-shell"]')?.getAttribute('data-pibo-session-id')
696
+ || document.querySelector('[data-pibo-selected-session-id]')?.getAttribute('data-pibo-selected-session-id')
697
+ || undefined;
698
+ const capturedSessionIds = new Set(piboSessionId ? [piboSessionId] : []);
699
+ const traceSnapshotsApi = window.__piboTraceSnapshots;
700
+ if (piboSessionId && traceSnapshotsApi && typeof traceSnapshotsApi.clearSnapshots === 'function') {
701
+ try { traceSnapshotsApi.clearSnapshots(piboSessionId); } catch {}
702
+ }
703
+ const domStates = [];
704
+ let omittedDomStates = 0;
705
+ let previousSignature;
706
+ const root = document.querySelector('[data-pibo-debug="chat-shell"]') || document.body;
707
+ const atBottomFor = (element) => {
708
+ let current = element && element.parentElement;
709
+ while (current && current !== document.body) {
710
+ if (current.scrollHeight > current.clientHeight + 2) return current.scrollHeight - current.scrollTop - current.clientHeight <= 180;
711
+ current = current.parentElement;
712
+ }
713
+ return undefined;
714
+ };
715
+ const rowFromElement = (element) => {
716
+ const rect = element.getBoundingClientRect();
717
+ const sourceNodeIds = String(element.getAttribute('data-trace-node-id') || '').split(/\s+/).filter(Boolean);
718
+ const numberAttr = (name) => {
719
+ const raw = element.getAttribute(name);
720
+ if (raw === null || raw === '') return undefined;
721
+ const value = Number(raw);
722
+ return Number.isFinite(value) ? value : undefined;
723
+ };
724
+ return {
725
+ id: element.getAttribute('data-row-id') || element.getAttribute('data-trace-node-id') || '',
726
+ kind: element.getAttribute('data-row-kind') || element.getAttribute('data-span-type') || undefined,
727
+ status: element.getAttribute('data-row-status') || element.getAttribute('data-span-status') || undefined,
728
+ sourceNodeIds,
729
+ eventId: element.getAttribute('data-event-id') || undefined,
730
+ runId: element.getAttribute('data-run-id') || undefined,
731
+ stableKey: element.getAttribute('data-stable-key') || undefined,
732
+ orderSource: element.getAttribute('data-order-source') || undefined,
733
+ orderStreamId: numberAttr('data-order-stream-id'),
734
+ orderStreamFrameIndex: numberAttr('data-order-frame-index'),
735
+ top: Math.round(rect.top * 1000) / 1000,
736
+ left: Math.round(rect.left * 1000) / 1000,
737
+ };
738
+ };
739
+ const capture = (reason) => {
740
+ const shell = document.querySelector('[data-pibo-debug="chat-shell"]');
741
+ const activePiboSessionId = shell?.getAttribute('data-pibo-session-id')
742
+ || document.querySelector('[data-pibo-selected-session-id]')?.getAttribute('data-pibo-selected-session-id')
743
+ || piboSessionId;
744
+ if (activePiboSessionId) capturedSessionIds.add(activePiboSessionId);
745
+ const terminalRows = shell ? Array.from(shell.querySelectorAll('[data-pibo-terminal-row="true"][data-row-id]')) : [];
746
+ const traceRows = shell ? Array.from(shell.querySelectorAll('[data-pibo-debug="trace-span"][data-trace-node-id]')) : [];
747
+ const elements = terminalRows.length ? terminalRows : traceRows;
748
+ const rows = elements.map(rowFromElement).filter((row) => row.id);
749
+ const view = terminalRows.length ? 'compact-terminal' : traceRows.length ? 'trace-timeline' : 'unknown';
750
+ const visualRows = rows.slice().sort((left, right) => (left.top || 0) - (right.top || 0) || (left.left || 0) - (right.left || 0));
751
+ let traceSequence;
752
+ if (activePiboSessionId && traceSnapshotsApi && typeof traceSnapshotsApi.getLatestSequence === 'function') {
753
+ try { traceSequence = traceSnapshotsApi.getLatestSequence(activePiboSessionId); } catch {}
754
+ }
755
+ const state = {
756
+ t: Math.round((performance.now() - startedAt) * 1000) / 1000,
757
+ timestamp: Date.now(),
758
+ traceSequence,
759
+ reason,
760
+ piboSessionId: activePiboSessionId,
761
+ view,
762
+ shellState: shell?.getAttribute('data-pibo-state') || undefined,
763
+ atBottom: rows.length ? atBottomFor(elements[0]) : undefined,
764
+ rows,
765
+ rowIds: rows.map((row) => row.id),
766
+ visualRowIds: visualRows.map((row) => row.id),
767
+ };
768
+ const signature = JSON.stringify({ traceSequence: state.traceSequence, view: state.view, shellState: state.shellState, atBottom: state.atBottom, rows: rows.map((row) => [row.id, row.kind, row.status, row.sourceNodeIds, row.eventId, row.runId, row.stableKey, row.orderSource, row.orderStreamId, row.orderStreamFrameIndex]), visual: state.visualRowIds });
769
+ if (signature === previousSignature) return;
770
+ previousSignature = signature;
771
+ if (domStates.length >= 1000) omittedDomStates += 1;
772
+ else domStates.push(state);
773
+ };
774
+ capture('start');
775
+ const observer = new MutationObserver(() => capture('mutation'));
776
+ if (root) observer.observe(root, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class', 'data-row-id', 'data-row-kind', 'data-row-status', 'data-trace-node-id', 'data-event-id', 'data-run-id', 'data-stable-key', 'data-order-source', 'data-order-stream-id', 'data-order-frame-index', 'data-span-type', 'data-span-status'] });
777
+ const interval = setInterval(() => capture('interval'), 50);
778
+ return {
779
+ stop: () => {
780
+ let traceSnapshots = [];
781
+ try {
782
+ try { capture('stop'); } catch {}
783
+ if (traceSnapshotsApi && typeof traceSnapshotsApi.getSnapshots === 'function') {
784
+ for (const sessionId of capturedSessionIds) {
785
+ try {
786
+ const sessionSnapshots = cloneDebugSnapshot(traceSnapshotsApi.getSnapshots(sessionId)) || [];
787
+ traceSnapshots.push(...sessionSnapshots);
788
+ } catch {}
789
+ }
790
+ }
791
+ return {
792
+ requested: true,
793
+ available: Boolean(root),
794
+ piboSessionId,
795
+ domStates,
796
+ traceSnapshots,
797
+ omittedDomStates,
798
+ warning: traceSnapshotsApi ? undefined : 'window.__piboTraceSnapshots unavailable',
799
+ };
800
+ } finally {
801
+ observer.disconnect();
802
+ clearInterval(interval);
803
+ if (traceSnapshotsApi && typeof traceSnapshotsApi.clearSnapshots === 'function') {
804
+ for (const sessionId of capturedSessionIds) {
805
+ try { traceSnapshotsApi.clearSnapshots(sessionId); } catch {}
806
+ }
807
+ }
808
+ }
809
+ },
810
+ };
811
+ }
652
812
  async function runStreamingBenchmark(options) {
653
813
  const warnings = [];
654
814
  const selectedSessionId = () => document.querySelector('[data-pibo-debug="chat-shell"]')?.getAttribute('data-pibo-session-id')
@@ -657,7 +817,38 @@ async function runStreamingBenchmark(options) {
657
817
  let reset = false;
658
818
  let backendPreludeError;
659
819
  let backendPreludeConfig;
660
- try { localStorage.setItem('pibo.chat.debugStreaming', '1'); } catch (error) { warnings.push('failed to set debugStreaming localStorage: ' + String(error)); }
820
+ let debugStreamingBefore;
821
+ const traceCollectionBefore = window.__piboTraceSnapshotCollectionEnabled;
822
+ try {
823
+ debugStreamingBefore = localStorage.getItem('pibo.chat.debugStreaming');
824
+ localStorage.setItem('pibo.chat.debugStreaming', '1');
825
+ } catch (error) {
826
+ warnings.push('failed to set debugStreaming localStorage: ' + String(error));
827
+ }
828
+ window.__piboTraceSnapshotCollectionEnabled = true;
829
+ const restoreDebugCollection = () => {
830
+ try {
831
+ if (debugStreamingBefore === null || debugStreamingBefore === undefined) localStorage.removeItem('pibo.chat.debugStreaming');
832
+ else localStorage.setItem('pibo.chat.debugStreaming', debugStreamingBefore);
833
+ } catch {}
834
+ if (traceCollectionBefore === undefined) delete window.__piboTraceSnapshotCollectionEnabled;
835
+ else window.__piboTraceSnapshotCollectionEnabled = traceCollectionBefore;
836
+ const piboSessionId = selectedSessionId();
837
+ const snapshots = window.__piboTraceSnapshots;
838
+ if (piboSessionId && snapshots && typeof snapshots.clearSnapshots === 'function') {
839
+ try { snapshots.clearSnapshots(piboSessionId); } catch {}
840
+ }
841
+ };
842
+ let benchmarkObserver;
843
+ let renderOrderCapture;
844
+ let rafHandle;
845
+ let perfObserver;
846
+ let traceProbe;
847
+ let traceSummary;
848
+ let sseProbe;
849
+ let reconnectTimer;
850
+ let fixtureStarted = false;
851
+ try {
661
852
  if (options.startBackendFixture && options.fixturePreludeMessages > 0) {
662
853
  const piboSessionId = selectedSessionId();
663
854
  if (!piboSessionId) {
@@ -689,6 +880,7 @@ async function runStreamingBenchmark(options) {
689
880
  try { window.__piboStreamingDebugReset(); reset = true; } catch (error) { warnings.push('failed to reset __piboStreamingDebug: ' + String(error)); }
690
881
  }
691
882
  const overlayDrop = installOverlayDropSimulation(Boolean(options.simulateOverlayDrop));
883
+ renderOrderCapture = startRenderOrderCapture(startedAt);
692
884
 
693
885
  const debugBefore = cloneDebugSnapshot(window.__piboStreamingDebug);
694
886
  const initialText = selectedAssistantText(fixtureDomInitialTargets, ignorePreludeDomTargets);
@@ -714,13 +906,12 @@ async function runStreamingBenchmark(options) {
714
906
  }
715
907
  currentLength = length;
716
908
  };
717
- const observer = new MutationObserver(sample);
718
- observer.observe(document.body, { childList: true, subtree: true, characterData: true });
909
+ benchmarkObserver = new MutationObserver(sample);
910
+ benchmarkObserver.observe(document.body, { childList: true, subtree: true, characterData: true });
719
911
 
720
912
  const rafGaps = [];
721
913
  let rafCount = 0;
722
914
  let lastRaf;
723
- let rafHandle;
724
915
  const onRaf = (t) => {
725
916
  rafCount += 1;
726
917
  if (lastRaf !== undefined) rafGaps.push(t - lastRaf);
@@ -730,7 +921,6 @@ async function runStreamingBenchmark(options) {
730
921
  rafHandle = requestAnimationFrame(onRaf);
731
922
 
732
923
  const longTasks = [];
733
- let perfObserver;
734
924
  if (typeof PerformanceObserver !== 'undefined') {
735
925
  try {
736
926
  perfObserver = new PerformanceObserver((list) => {
@@ -744,11 +934,8 @@ async function runStreamingBenchmark(options) {
744
934
  warnings.push('PerformanceObserver unavailable');
745
935
  }
746
936
 
747
- let fixtureStarted = false;
748
937
  let fixtureConfig;
749
938
  let backendFixtureError;
750
- let traceProbe;
751
- let sseProbe;
752
939
  if (options.startFixture) {
753
940
  if (typeof window.__piboStreamingFixtureStart === 'function') {
754
941
  try { fixtureConfig = window.__piboStreamingFixtureStart(); fixtureStarted = true; } catch (error) { warnings.push('failed to start streaming fixture: ' + String(error)); }
@@ -757,7 +944,8 @@ async function runStreamingBenchmark(options) {
757
944
  }
758
945
  } else if (options.startBackendFixture) {
759
946
  if (options.simulateReconnect && typeof window.__piboStreamingBenchmarkForceReconnect === 'function') {
760
- setTimeout(() => {
947
+ reconnectTimer = setTimeout(() => {
948
+ reconnectTimer = undefined;
761
949
  try { window.__piboStreamingBenchmarkForceReconnect(); } catch (error) { warnings.push('failed to force EventSource reconnect: ' + String(error)); }
762
950
  }, options.reconnectAtMs || 325);
763
951
  } else if (options.simulateReconnect) {
@@ -800,12 +988,20 @@ async function runStreamingBenchmark(options) {
800
988
  sample();
801
989
  if (traceProbe) {
802
990
  await traceProbe.sample();
803
- traceProbe.stop();
991
+ traceSummary = traceProbe.result;
992
+ await traceProbe.stop();
993
+ traceProbe = undefined;
804
994
  }
805
995
  const sseSummary = sseProbe ? await sseProbe.stop() : undefined;
806
- observer.disconnect();
996
+ sseProbe = undefined;
997
+ benchmarkObserver.disconnect();
998
+ benchmarkObserver = undefined;
999
+ const renderOrder = renderOrderCapture.stop();
1000
+ renderOrderCapture = undefined;
807
1001
  if (rafHandle !== undefined) cancelAnimationFrame(rafHandle);
1002
+ rafHandle = undefined;
808
1003
  try { perfObserver && perfObserver.disconnect(); } catch {}
1004
+ perfObserver = undefined;
809
1005
 
810
1006
  const debugAfter = cloneDebugSnapshot(window.__piboStreamingDebug);
811
1007
  const overlayDropSummary = overlayDrop ? cloneDebugSnapshot(window.__piboStreamingBenchmarkOverlayDrop) : undefined;
@@ -869,7 +1065,6 @@ async function runStreamingBenchmark(options) {
869
1065
  error: backendPreludeError || backendFixtureError,
870
1066
  } : undefined;
871
1067
  const eventSourceSummary = summarizeEventSourceProbe(startedAt, Boolean(options.startBackendFixture), options.reconnectAtMs, Boolean(options.simulateTraceCatchup), options.traceCatchupDropMs);
872
- const traceSummary = traceProbe && traceProbe.result;
873
1068
  const regressions = streamingBenchmarkRegressions({
874
1069
  debugAfter,
875
1070
  debugDelta,
@@ -920,9 +1115,24 @@ async function runStreamingBenchmark(options) {
920
1115
  sse: sseSummary,
921
1116
  trace: traceSummary,
922
1117
  overlayDrop: overlayDropSummary,
1118
+ renderOrder,
923
1119
  regressions,
924
1120
  warnings,
925
1121
  };
1122
+ } finally {
1123
+ try { benchmarkObserver && benchmarkObserver.disconnect(); } catch {}
1124
+ try { renderOrderCapture && renderOrderCapture.stop(); } catch {}
1125
+ if (rafHandle !== undefined) cancelAnimationFrame(rafHandle);
1126
+ if (reconnectTimer !== undefined) clearTimeout(reconnectTimer);
1127
+ try { perfObserver && perfObserver.disconnect(); } catch {}
1128
+ try { if (traceProbe) await traceProbe.stop(); } catch {}
1129
+ try { if (sseProbe) await sseProbe.stop(); } catch {}
1130
+ try { installOverlayDropSimulation(false); } catch {}
1131
+ if (fixtureStarted && options.startFixture && typeof window.__piboStreamingDebugReset === 'function') {
1132
+ try { window.__piboStreamingDebugReset(); } catch {}
1133
+ }
1134
+ restoreDebugCollection();
1135
+ }
926
1136
  }
927
1137
  `;
928
1138
  }
@@ -117,6 +117,7 @@ function streamingBenchmarkCompactRunRows(benchmark) {
117
117
  { section: "compact", metric: "Live trace compute", preservation: liveTraceComputeCount !== undefined ? `count ${jsonShort(liveTraceComputeCount)}, total ${jsonShort(liveTraceComputeTotalMs)}ms` : "n/a", cadenceLatency: liveTraceComputeMaxMs !== undefined ? `max ${jsonShort(liveTraceComputeMaxMs)}ms` : "n/a" },
118
118
  { section: "compact", metric: "Markdown render", preservation: markdownRenderCount !== undefined ? `count ${jsonShort(markdownRenderCount)}, plain ${jsonShort(markdownRenderPlainCount)}, commonmark ${jsonShort(markdownRenderCommonMarkCount)}, gfm ${jsonShort(markdownRenderGfmCount)}, gfmFast ${jsonShort(markdownRenderGfmFastCount)}, full ${jsonShort(markdownRenderFullCount)}` : "n/a", cadenceLatency: markdownRenderMaxMs !== undefined ? `total ${jsonShort(markdownRenderTotalMs)}ms, max ${jsonShort(markdownRenderMaxMs)}ms` : "n/a" },
119
119
  { section: "compact", metric: "DOM", preservation: `positive ${benchmark.dom.positiveUpdateCount}, max jump ${jsonShort(benchmark.dom.positiveCharJumps.max)} chars`, cadenceLatency: `p90 gap ${statP90(benchmark.dom.gapsMs, "ms")}, first visible ${jsonShort(benchmark.dom.firstPositiveUpdateMs)}ms` },
120
+ { section: "compact", metric: "Render order", preservation: renderOrderPreservation(benchmark), cadenceLatency: renderOrderEvidence(benchmark) },
120
121
  { section: "compact", metric: "Score", preservation: `smoothness ${benchmark.score.smoothness}`, cadenceLatency: `regressions ${benchmark.regressions.length}, warnings ${benchmark.warnings.length}` },
121
122
  ];
122
123
  }
@@ -131,6 +132,7 @@ function streamingBenchmarkCompactGroupRows(group) {
131
132
  { section: "compact", metric: "Live trace compute", preservation: `count ${statP50(group.summary.debugLiveTraceComputeCount)}, total ${statP50(group.summary.debugLiveTraceComputeDurationTotalMs, "ms")}`, cadenceLatency: `max ${statP50(group.summary.debugLiveTraceComputeDurationMaxMs, "ms")}` },
132
133
  { section: "compact", metric: "Markdown render", preservation: `count ${statP50(group.summary.debugMarkdownRenderCount)}, plain ${statP50(group.summary.debugMarkdownRenderPlainCount)}, commonmark ${statP50(group.summary.debugMarkdownRenderCommonMarkCount)}, gfm ${statP50(group.summary.debugMarkdownRenderGfmCount)}, gfmFast ${statP50(group.summary.debugMarkdownRenderGfmFastCount)}, full ${statP50(group.summary.debugMarkdownRenderFullCount)}`, cadenceLatency: `total ${statP50(group.summary.debugMarkdownRenderDurationTotalMs, "ms")}, max ${statP50(group.summary.debugMarkdownRenderDurationMaxMs, "ms")}` },
133
134
  { section: "compact", metric: "DOM", preservation: `positive ${statP50(group.summary.domPositiveUpdateCount)}, max jump ${statP50(group.summary.domJumpMaxChars, " chars")}`, cadenceLatency: `p90 gap ${statP50(group.summary.domGapP90Ms, "ms")}, first visible ${statP50(group.summary.firstVisibleMs, "ms")}` },
135
+ { section: "compact", metric: "Render order", preservation: `reorder ${statP50(group.summary.renderOrderReorderCount)}, reappear ${statP50(group.summary.renderOrderDisappearReappearCount)}, identity ${statP50(group.summary.renderOrderIdentityReplacementCount)}, mismatch ${statP50(group.summary.renderOrderStateDomMismatchCount)}`, cadenceLatency: `DOM states ${statP50(group.summary.renderOrderDomStateCount)}, trace snapshots ${statP50(group.summary.renderOrderTraceSnapshotCount)}` },
134
136
  { section: "compact", metric: "Score", preservation: `smoothness ${statP50(group.summary.smoothness)}`, cadenceLatency: `regressions ${statP50(group.summary.regressionCount)}, warnings ${group.warnings.length}` },
135
137
  ];
136
138
  }
@@ -182,6 +184,7 @@ function formatStreamingBenchmarkCompactRun(benchmark, target) {
182
184
  ["Live trace compute", liveTraceComputeCount !== undefined ? `count ${jsonShort(liveTraceComputeCount)}, total ${jsonShort(liveTraceComputeTotalMs)}ms` : "n/a", liveTraceComputeMaxMs !== undefined ? `max ${jsonShort(liveTraceComputeMaxMs)}ms` : "n/a"],
183
185
  ["Markdown render", markdownRenderCount !== undefined ? `count ${jsonShort(markdownRenderCount)}, plain ${jsonShort(markdownRenderPlainCount)}, commonmark ${jsonShort(markdownRenderCommonMarkCount)}, gfm ${jsonShort(markdownRenderGfmCount)}, gfmFast ${jsonShort(markdownRenderGfmFastCount)}, full ${jsonShort(markdownRenderFullCount)}` : "n/a", markdownRenderMaxMs !== undefined ? `total ${jsonShort(markdownRenderTotalMs)}ms, max ${jsonShort(markdownRenderMaxMs)}ms` : "n/a"],
184
186
  ["DOM", `positive ${benchmark.dom.positiveUpdateCount}, max jump ${jsonShort(benchmark.dom.positiveCharJumps.max)} chars`, `p90 gap ${statP90(benchmark.dom.gapsMs, "ms")}, first visible ${jsonShort(benchmark.dom.firstPositiveUpdateMs)}ms`],
187
+ ["Render order", renderOrderPreservation(benchmark), renderOrderEvidence(benchmark)],
185
188
  ["Score", `smoothness ${benchmark.score.smoothness}`, `regressions ${benchmark.regressions.length}, warnings ${benchmark.warnings.length}`],
186
189
  ]),
187
190
  ];
@@ -203,6 +206,7 @@ function formatStreamingBenchmarkCompactGroup(group, target) {
203
206
  ["Live trace compute", `count ${statP50(group.summary.debugLiveTraceComputeCount)}, total ${statP50(group.summary.debugLiveTraceComputeDurationTotalMs, "ms")}`, `max ${statP50(group.summary.debugLiveTraceComputeDurationMaxMs, "ms")}`],
204
207
  ["Markdown render", `count ${statP50(group.summary.debugMarkdownRenderCount)}, plain ${statP50(group.summary.debugMarkdownRenderPlainCount)}, commonmark ${statP50(group.summary.debugMarkdownRenderCommonMarkCount)}, gfm ${statP50(group.summary.debugMarkdownRenderGfmCount)}, gfmFast ${statP50(group.summary.debugMarkdownRenderGfmFastCount)}, full ${statP50(group.summary.debugMarkdownRenderFullCount)}`, `total ${statP50(group.summary.debugMarkdownRenderDurationTotalMs, "ms")}, max ${statP50(group.summary.debugMarkdownRenderDurationMaxMs, "ms")}`],
205
208
  ["DOM", `positive ${statP50(group.summary.domPositiveUpdateCount)}, max jump ${statP50(group.summary.domJumpMaxChars, " chars")}`, `p90 gap ${statP50(group.summary.domGapP90Ms, "ms")}, first visible ${statP50(group.summary.firstVisibleMs, "ms")}`],
209
+ ["Render order", `reorder ${statP50(group.summary.renderOrderReorderCount)}, reappear ${statP50(group.summary.renderOrderDisappearReappearCount)}, identity ${statP50(group.summary.renderOrderIdentityReplacementCount)}, mismatch ${statP50(group.summary.renderOrderStateDomMismatchCount)}`, `DOM states ${statP50(group.summary.renderOrderDomStateCount)}, trace snapshots ${statP50(group.summary.renderOrderTraceSnapshotCount)}`],
206
210
  ["Score", `smoothness ${statP50(group.summary.smoothness)}`, `regressions ${statP50(group.summary.regressionCount)}, warnings ${group.warnings.length}`],
207
211
  ]),
208
212
  ];
@@ -241,6 +245,18 @@ function formatStreamingBenchmarkCompactUrlComparison(comparison, target) {
241
245
  appendCompactBenchmarkNotes(lines, comparison.negativeProfile, comparison.regressions, comparison.warnings);
242
246
  return lines.join("\n");
243
247
  }
248
+ function renderOrderPreservation(benchmark) {
249
+ const analysis = benchmark.renderOrder?.analysis;
250
+ if (!analysis)
251
+ return benchmark.renderOrder?.warning ?? "n/a";
252
+ return `reorder ${analysis.reorderCount}, reappear ${analysis.disappearReappearCount}, identity ${analysis.identityReplacementCount}, mismatch ${analysis.stateDomMismatchCount}`;
253
+ }
254
+ function renderOrderEvidence(benchmark) {
255
+ const analysis = benchmark.renderOrder?.analysis;
256
+ if (!analysis)
257
+ return "n/a";
258
+ return `DOM states ${analysis.domStateCount}, trace snapshots ${analysis.traceSnapshotCount}`;
259
+ }
244
260
  function appendCompactBenchmarkNotes(lines, negativeProfile, regressions, warnings) {
245
261
  if (negativeProfile)
246
262
  lines.push("", `Negative profile: ${negativeProfile}`);
@@ -308,6 +324,7 @@ function formatStreamingBenchmark(benchmark, target) {
308
324
  `dom jumps: ${formatStats(benchmark.dom.positiveCharJumps)} chars`,
309
325
  `raf: count=${benchmark.raf.count}, gaps=${formatStats(benchmark.raf.gapsMs)}`,
310
326
  `longTasks: count=${benchmark.longTasks.count}, max=${benchmark.longTasks.maxMs}ms, total=${benchmark.longTasks.totalMs}ms`,
327
+ `render order: ${renderOrderPreservation(benchmark)}, ${renderOrderEvidence(benchmark)}`,
311
328
  ];
312
329
  if (benchmark.negativeProfile)
313
330
  lines.push(`negative profile: ${benchmark.negativeProfile}`);
@@ -331,6 +348,12 @@ function formatStreamingBenchmark(benchmark, target) {
331
348
  lines.push(`sse: requested=${benchmark.sse.requested} installed=${benchmark.sse.installed} status=${jsonShort(benchmark.sse.status)} firstChunk=${jsonShort(benchmark.sse.firstChunkMs)}ms firstText=${jsonShort(benchmark.sse.firstTextEventMs)}ms chunks=${benchmark.sse.chunkCount} chunkBytes=${formatStats(benchmark.sse.chunkBytes)} chunkGaps=${formatStats(benchmark.sse.chunkGapsMs)} textPerChunk=${formatStats(benchmark.sse.textEventsPerChunk)} text=${benchmark.sse.textEventCount} reasoning=${benchmark.sse.reasoningEventCount} textGaps=${formatStats(benchmark.sse.textEventGapsMs)} transient=${benchmark.sse.transientIdCount} durable=${benchmark.sse.durableIdCount} errors=${benchmark.sse.errors.length}`);
332
349
  if (benchmark.trace)
333
350
  lines.push(`trace: requested=${benchmark.trace.requested} samples=${benchmark.trace.sampleCount} fetches=${benchmark.trace.fetchCount} failed=${benchmark.trace.failedFetchCount} liveVersions=${benchmark.trace.liveVersionCount} firstLive=${jsonShort(benchmark.trace.firstLiveVersionMs)}ms assistantMax=${benchmark.trace.maxAssistantOutputLength} assistantFinal=${jsonShort(benchmark.trace.finalAssistantOutputLength)} durableEvents=${jsonShort(benchmark.trace.durableEventCountStart)}->${jsonShort(benchmark.trace.durableEventCountEnd)} session=${jsonShort(benchmark.trace.piboSessionId)}`);
351
+ if (benchmark.renderOrder?.analysis?.findings.length) {
352
+ lines.push("", "Render order findings:");
353
+ for (const finding of benchmark.renderOrder.analysis.findings.slice(0, 20)) {
354
+ lines.push(`- ${finding.source}/${finding.kind} @ ${new Date(finding.timestamp).toISOString()}: ${finding.detail}`);
355
+ }
356
+ }
334
357
  if (benchmark.regressions.length) {
335
358
  lines.push("", "Regressions:");
336
359
  for (const regression of benchmark.regressions)
@@ -357,6 +380,7 @@ function formatStreamingBenchmarkGroup(group, target) {
357
380
  `dom gaps p50=${formatStats(group.summary.domGapP50Ms)}, p90=${formatStats(group.summary.domGapP90Ms)}, max=${formatStats(group.summary.domGapMaxMs)}`,
358
381
  `dom jumps p90=${formatStats(group.summary.domJumpP90Chars)}, max=${formatStats(group.summary.domJumpMaxChars)} chars`,
359
382
  `firstVisible=${formatStats(group.summary.firstVisibleMs)}, longTaskMax=${formatStats(group.summary.longTaskMaxMs)}`,
383
+ `render order: reorder=${formatStats(group.summary.renderOrderReorderCount)}, reappear=${formatStats(group.summary.renderOrderDisappearReappearCount)}, identity=${formatStats(group.summary.renderOrderIdentityReplacementCount)}, mismatch=${formatStats(group.summary.renderOrderStateDomMismatchCount)}, DOM states=${formatStats(group.summary.renderOrderDomStateCount)}, trace snapshots=${formatStats(group.summary.renderOrderTraceSnapshotCount)}`,
360
384
  ];
361
385
  if (group.negativeProfile)
362
386
  lines.push(`negative profile: ${group.negativeProfile}`);
package/dist/debug/web.js CHANGED
@@ -7,6 +7,7 @@ import { applyExpectedStreamingRegressions, attachStreamingProviderTelemetryToBe
7
7
  import { collectStreamingProviderTelemetry, collectStreamingProviderTelemetryFromSelectedBrowserSession, collectStreamingProviderTelemetryFromSession, collectStreamingProviderTelemetryFromTurn, } from "./web-streaming-provider-telemetry.js";
8
8
  import { buildStreamingBenchmarkExpression, streamingBenchmarkEventSourceProbeScript, streamingBenchmarkFixtureHtml } from "./web-streaming-browser-scripts.js";
9
9
  import { buildSnapshotExpression, buildWatchExpression } from "./web-snapshot-browser-scripts.js";
10
+ import { analyzeStreamingRenderOrderCapture } from "./web-render-order-analysis.js";
10
11
  import { compactTarget, limitStdout, readBaselineSnapshot, writeArtifact, writeLastSnapshot, writeReportOutput, writeTextArtifact } from "./web-artifacts.js";
11
12
  import { applyNegativeStreamingProfile, DEFAULT_DEPTH_LIMIT, DEFAULT_EVENT_LIMIT, DEFAULT_NODE_LIMIT, DEFAULT_TEXT_LIMIT, parseDuration, parseFixtureMix, parseFixturePreludeMessages, parseFixtureProfile, parseNegativeProfile, parseOptions, parseRuns, presetScope, resolveScope, resolveStreamingBenchmarkCompareUrl, resolveStreamingBenchmarkHostedCompareUrl } from "./web-options.js";
12
13
  export { formatStreamingBenchmarkAssertionSummary, formatStreamingBenchmarkUrlComparison, summarizeStreamingSelectedLiveEventSource } from "./web-streaming-report.js";
@@ -14,6 +15,7 @@ export { resolveStreamingBenchmarkHostedCompareUrlFromValues } from "./web-optio
14
15
  export { attachStreamingProviderTelemetryToBenchmark, evaluateStreamingBenchmarkAssertion, evaluateStreamingBenchmarkUrlComparisonRegressions, evaluateStreamingLivePipelineRegressions, evaluateStreamingProviderRegressions, summarizeStreamingBenchmarkUrlComparison, summarizeStreamingBenchmarks, summarizeStreamingLivePipeline, summarizeStreamingProviderPreservation } from "./web-streaming-benchmark-analysis.js";
15
16
  export { collectStreamingProviderTelemetryFromSelectedBrowserSession, collectStreamingProviderTelemetryFromSession, collectStreamingProviderTelemetryFromTurn, summarizeStreamingProviderTelemetry } from "./web-streaming-provider-telemetry.js";
16
17
  export { formatWatch, inferWatchFlickers } from "./web-render-analysis.js";
18
+ export { analyzeStreamingRenderOrderCapture } from "./web-render-order-analysis.js";
17
19
  export async function runDebugWeb(args) {
18
20
  if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
19
21
  printWebDiscovery();
@@ -110,7 +112,7 @@ Usage:
110
112
  Defaults:
111
113
  new-session --manual waits while you click New Session yourself.
112
114
  new-session --act clicks the discovered New Session button after the watcher starts.
113
- streaming-benchmark enables debugStreaming for future events, observes assistant DOM increments, and snapshots window.__piboStreamingDebug.
115
+ streaming-benchmark enables debugStreaming and traceDebug, observes assistant DOM increments, and records correlated base/overlay/current trace state plus terminal/span DOM and visual order transitions.
114
116
  streaming-benchmark --fixture navigates the target to a deterministic in-browser stream fixture before measuring.
115
117
  streaming-benchmark --backend-fixture posts to /api/chat/debug/streaming-fixture and records EventSource metrics while the real app consumes deterministic /api/chat/events frames.
116
118
  streaming-benchmark --fixture-profile selects steady cadence, deterministic jitter, bursty timing, or intentional batch stress.
@@ -485,11 +487,20 @@ async function runStreamingBenchmark(client, durationMs, options = {}) {
485
487
  await client.send("Page.bringToFront").catch(() => undefined);
486
488
  const benchmarkTimeoutMs = durationMs + (options.startBackendFixture ? 20_000 : 10_000);
487
489
  const benchmark = await client.evaluate(buildStreamingBenchmarkExpression(durationMs, options), benchmarkTimeoutMs);
488
- const withProvider = { ...benchmark, provider: options.providerTelemetry };
490
+ const withRenderOrder = { ...benchmark, renderOrder: analyzeStreamingRenderOrderCapture(benchmark.renderOrder) };
491
+ const withProvider = { ...withRenderOrder, provider: options.providerTelemetry };
489
492
  const scored = { ...withProvider, score: scoreStreamingBenchmark(withProvider), providerPreservation: summarizeStreamingProviderPreservation(withProvider) };
490
493
  const withLivePipeline = { ...scored, livePipeline: summarizeStreamingLivePipeline(scored) };
491
494
  const withCadence = { ...withLivePipeline, cadence: summarizeStreamingCadence(withLivePipeline), negativeProfile: options.negativeProfile };
492
- return { ...withCadence, regressions: [...withCadence.regressions, ...evaluateStreamingLivePipelineRegressions(withCadence), ...evaluateStreamingProviderRegressions(withCadence)] };
495
+ return {
496
+ ...withCadence,
497
+ regressions: [
498
+ ...withCadence.regressions,
499
+ ...(withCadence.renderOrder?.analysis?.regressions ?? []),
500
+ ...evaluateStreamingLivePipelineRegressions(withCadence),
501
+ ...evaluateStreamingProviderRegressions(withCadence),
502
+ ],
503
+ };
493
504
  }
494
505
  async function currentBrowserUrl(client) {
495
506
  const state = await client.evaluate(`(() => ({ href: location.href }))()`, 5_000);
@@ -1,6 +1,7 @@
1
1
  import { createServer } from "node:net";
2
2
  import { createDefaultPiboPluginRegistry, createPiboProfileFromRegistryOrDefault, resolvePiboProfileNameFromRegistryOrDefault } from "../plugins/builtin.js";
3
3
  import { PiboSessionRouter } from "../core/session-router.js";
4
+ import { createLoopMessagePreflight } from "../loops/store.js";
4
5
  import { loadPiboModelDefaults, selectRequestedModelProfile } from "../core/model-defaults.js";
5
6
  import { ResourceReaperService } from "../resources/reaper.js";
6
7
  import { DEFAULT_GATEWAY_HOST, DEFAULT_GATEWAY_PORT, encodeFrame, errorResponse, isGatewayRequestFrame, isGatewaySubscribeFrame, } from "./protocol.js";
@@ -147,6 +148,7 @@ export class PiboGatewayServer {
147
148
  persistSession: this.options.persistSession,
148
149
  pluginRegistry: this.pluginRegistry,
149
150
  sessionStore: this.sessionStore,
151
+ messagePreflight: createLoopMessagePreflight({ path: this.options.loopStorePath }),
150
152
  });
151
153
  this.unsubscribe = this.router.subscribe((event) => this.broadcastRouterEvent(event));
152
154
  this.server = createServer((socket) => this.handleSocket(socket));
@@ -197,6 +197,7 @@ export async function runWebGatewayServer(options = {}) {
197
197
  ...resolvedOptions,
198
198
  pluginRegistry,
199
199
  resourceReaper: resolveGatewayResourceReaperOptions(resolvedOptions),
200
+ loopStorePath: resolvedOptions.chat?.ralphStorePath,
200
201
  });
201
202
  await server.start();
202
203
  }
@@ -13,7 +13,14 @@ export function goalElapsedWallClockSeconds(job, now = new Date()) {
13
13
  export function goalRemainingTokens(job) {
14
14
  return job.tokenBudget === undefined ? undefined : Math.max(0, job.tokenBudget - (job.state.tokensUsed ?? 0));
15
15
  }
16
- export function goalCanStartNextTurn(job) {
16
+ export function goalCanStartNextTurn(job, now = new Date()) {
17
+ if (job.mode !== 'goal' || !job.enabled)
18
+ return false;
19
+ const status = job.state.goalStatus ?? 'paused';
20
+ if (status !== 'active')
21
+ return false;
22
+ if (job.state.nextAttemptAt && Date.parse(job.state.nextAttemptAt) > now.getTime())
23
+ return false;
17
24
  const remaining = goalRemainingTokens(job);
18
25
  return remaining === undefined || remaining > (job.tokenReserve ?? 0);
19
26
  }
package/dist/loops/cli.js CHANGED
@@ -207,7 +207,7 @@ export async function runLoopCli(argv = process.argv, defaults = {}) {
207
207
  printJson(job);
208
208
  else
209
209
  console.log(`${job.id}\tcancel-requested\t${job.name}`); store.close(); });
210
- program.command('remove').argument('<id>').description('Delete a Loop job').option('--json', 'Print JSON').action((id, options) => { const store = createDefaultPiboLoopStore({ path: program.opts().store }); const removed = store.removeJob(id); if (options.json)
210
+ program.command('remove').argument('<id>').description('Delete a Loop job and all of its runs and facts; active runs must be cancelled first').option('--json', 'Print JSON').action((id, options) => { const store = createDefaultPiboLoopStore({ path: program.opts().store }); const removed = store.removeJob(id); if (options.json)
211
211
  printJson({ removed });
212
212
  else
213
213
  console.log(removed ? 'removed' : 'not found'); store.close(); });