@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
@@ -19,7 +19,37 @@ class LoopRunTimeoutError extends Error {
19
19
  this.name = 'LoopRunTimeoutError';
20
20
  }
21
21
  }
22
+ class LoopContinuationInvalidatedError extends Error {
23
+ constructor(message) { super(message); this.name = 'LoopContinuationInvalidatedError'; }
24
+ }
25
+ class LoopSessionError extends Error {
26
+ details;
27
+ constructor(message, details) {
28
+ super(message);
29
+ this.details = details;
30
+ this.name = 'LoopSessionError';
31
+ }
32
+ }
22
33
  function isUnknownProfileErrorMessage(message) { return /^Unknown profile "[^"]+"/.test(message); }
34
+ function failureKey(details) {
35
+ return details?.code ?? details?.category ?? details?.errorClass ?? 'session-error';
36
+ }
37
+ function failureRecovery(details) {
38
+ if (details?.category === 'quota_exhausted' || details?.code === 'quota_exhausted')
39
+ return 'Restore provider quota or billing, then explicitly restart the Goal.';
40
+ if (details?.category === 'auth' || details?.errorClass === 'provider_auth')
41
+ return 'Repair provider authentication, then explicitly restart the Goal.';
42
+ if (details?.category === 'context_overflow' || details?.errorClass === 'provider_context')
43
+ return 'Reduce or reset the session context, then explicitly restart the Goal.';
44
+ return details?.retryable === false ? 'Resolve the reported non-retryable failure, then explicitly restart the Goal.' : 'Pibo will retry automatically after the recorded backoff.';
45
+ }
46
+ function isTerminalGoalStatus(status) { return status === 'complete' || status === 'blocked' || status === 'budget_limited'; }
47
+ function retryBackoffMs(consecutiveErrors, baseMs, maxMs, jitterRatio, random) {
48
+ const exponent = Math.max(0, Math.min(30, consecutiveErrors - 1));
49
+ const bounded = Math.min(maxMs, baseMs * (2 ** exponent));
50
+ const jitter = bounded * jitterRatio * ((Math.max(0, Math.min(1, random())) * 2) - 1);
51
+ return Math.min(maxMs, Math.max(1, Math.round(bounded + jitter)));
52
+ }
23
53
  function isJsonObject(value) { return !!value && typeof value === 'object' && !Array.isArray(value); }
24
54
  function mergeRunResources(jobResources, runResources) {
25
55
  if (!jobResources && !runResources)
@@ -44,6 +74,11 @@ export class PiboLoopService {
44
74
  intervalMs;
45
75
  maxConcurrentRuns;
46
76
  runTimeoutMs;
77
+ retryBackoffBaseMs;
78
+ retryBackoffMaxMs;
79
+ retryBackoffJitterRatio;
80
+ now;
81
+ random;
47
82
  timer;
48
83
  activeRuns = 0;
49
84
  stopped = true;
@@ -60,9 +95,14 @@ export class PiboLoopService {
60
95
  this.intervalMs = options.intervalMs ?? 5_000;
61
96
  this.maxConcurrentRuns = Math.max(1, options.maxConcurrentRuns ?? 2);
62
97
  this.runTimeoutMs = options.runTimeoutMs;
98
+ this.retryBackoffBaseMs = Math.max(1, options.retryBackoffBaseMs ?? 5_000);
99
+ this.retryBackoffMaxMs = Math.max(this.retryBackoffBaseMs, options.retryBackoffMaxMs ?? 5 * 60_000);
100
+ this.retryBackoffJitterRatio = Math.max(0, Math.min(1, options.retryBackoffJitterRatio ?? 0.2));
101
+ this.now = options.now ?? (() => new Date());
102
+ this.random = options.random ?? Math.random;
63
103
  }
64
104
  start() { if (!this.stopped)
65
- return; this.stopped = false; this.store.recoverInterruptedRuns(); this.unsubscribeProductEvents = this.options.context.subscribeProductEvents?.((event) => this.handleProductEvent(event)); this.unsubscribeOutputEvents = this.options.context.subscribe((event) => this.handleOutputEvent(event)); this.arm(250); }
105
+ return; this.stopped = false; this.store.recoverInterruptedRuns(); this.repairGoalSessionMetadata(); this.unsubscribeProductEvents = this.options.context.subscribeProductEvents?.((event) => this.handleProductEvent(event)); this.unsubscribeOutputEvents = this.options.context.subscribe((event) => this.handleOutputEvent(event)); this.arm(250); }
66
106
  stop() { this.stopped = true; if (this.timer)
67
107
  clearTimeout(this.timer); this.timer = undefined; for (const timer of this.browserLeaseHeartbeatTimers.values())
68
108
  clearInterval(timer); this.browserLeaseHeartbeatTimers.clear(); this.unsubscribeProductEvents?.(); this.unsubscribeProductEvents = undefined; this.unsubscribeOutputEvents?.(); this.unsubscribeOutputEvents = undefined; this.dataStore.close(); this.store.close(); }
@@ -71,6 +111,32 @@ export class PiboLoopService {
71
111
  return undefined; const reserved = await this.reserveAfterBeforeRunEvaluation(job); if (!reserved)
72
112
  return undefined; void this.executeReserved(reserved.job, reserved.run).finally(() => this.armSoon()); return reserved.run; }
73
113
  stopJob(id) { const job = this.store.requestStop(id); this.armSoon(); return job; }
114
+ removeJob(id) {
115
+ const job = this.store.getJob(id);
116
+ const removed = this.store.removeJob(id);
117
+ if (removed && job?.mode === 'goal' && job.state.lastPiboSessionId)
118
+ this.clearGoalSessionMetadata(job.state.lastPiboSessionId);
119
+ return removed;
120
+ }
121
+ reopenGoal(id, input) {
122
+ if (!input.confirmed)
123
+ throw new Error('Explicit terminal reopen confirmation is required');
124
+ const job = this.store.getJob(id);
125
+ if (!job || job.mode !== 'goal')
126
+ throw new Error('Goal not found');
127
+ const piboSessionId = job.state.lastPiboSessionId;
128
+ if (!piboSessionId)
129
+ throw new Error('Goal cannot be reopened because it has no originating Pibo Session');
130
+ const runtime = this.options.context.getSessionRuntimeStatus?.(piboSessionId);
131
+ if (runtime && (runtime.disposed || runtime.processing || runtime.streaming || runtime.queuedMessages > 0))
132
+ throw new Error('Goal cannot be reopened while its Pibo Session is active, queued, draining, or disposing');
133
+ const controllerRun = this.options.context.listRuns?.({ includeConsumed: true, includeDetached: true }).find((run) => run.controllerPiboSessionId === piboSessionId && (!['completed', 'failed', 'timed_out', 'cancelled'].includes(run.status) || !run.consumed));
134
+ if (controllerRun)
135
+ throw new Error(`Goal cannot be reopened while controller run ${controllerRun.runId} is active or unconsumed`);
136
+ const reopened = this.store.reopenGoal(id, { actorId: input.actorId });
137
+ this.armSoon();
138
+ return reopened;
139
+ }
74
140
  async cancelJob(id) {
75
141
  const job = this.store.requestCancel(id);
76
142
  if (!job)
@@ -79,6 +145,19 @@ export class PiboLoopService {
79
145
  this.armSoon();
80
146
  return this.store.getJob(id);
81
147
  }
148
+ repairGoalSessionMetadata() {
149
+ for (const session of this.options.context.listSessions?.() ?? []) {
150
+ if (session.metadata?.loopMode === 'goal' && (session.metadata.loopJobId !== undefined || session.metadata.loopRunId !== undefined))
151
+ this.clearGoalSessionMetadata(session.id);
152
+ }
153
+ }
154
+ clearGoalSessionMetadata(piboSessionId) {
155
+ const session = this.options.context.getSession(piboSessionId);
156
+ if (!session || !this.options.context.updateSession)
157
+ return;
158
+ const { loopJobId: _loopJobId, loopRunId: _loopRunId, ...metadata } = session.metadata ?? {};
159
+ this.options.context.updateSession(piboSessionId, { metadata });
160
+ }
82
161
  arm(delayMs) { if (this.stopped)
83
162
  return; if (this.timer)
84
163
  clearTimeout(this.timer); this.timer = setTimeout(() => void this.tick(), delayMs ?? this.intervalMs); }
@@ -104,7 +183,8 @@ export class PiboLoopService {
104
183
  } }
105
184
  async reserveAfterBeforeRunEvaluation(job) {
106
185
  const fresh = this.store.getJob(job.id) ?? job;
107
- if (!fresh.enabled || fresh.state.runningAt)
186
+ const now = this.now();
187
+ if (!fresh.enabled || fresh.state.runningAt || (fresh.state.nextAttemptAt !== undefined && fresh.state.nextAttemptAt > now.toISOString()))
108
188
  return undefined;
109
189
  const { evaluation, conditionStates } = await this.evaluateStopPolicy(fresh, 'before-run');
110
190
  if (evaluation.finalAction !== 'continue') {
@@ -114,7 +194,7 @@ export class PiboLoopService {
114
194
  this.store.applyStopEvaluation({ jobId: fresh.id, evaluation, conditionStates, disable: false });
115
195
  if (fresh.mode === 'goal' && !await this.renewGoalBrowserLeases(fresh))
116
196
  return undefined;
117
- const reserved = this.store.reserveRun(fresh.id);
197
+ const reserved = this.store.reserveRun(fresh.id, now);
118
198
  if (reserved)
119
199
  this.startGoalBrowserLeaseHeartbeat(reserved.job, reserved.run);
120
200
  return reserved;
@@ -214,20 +294,65 @@ export class PiboLoopService {
214
294
  const cancelled = this.cancelledRuns.delete(run.id);
215
295
  const outcome = { status: cancelled ? 'cancelled' : 'ok', piboSessionId: result.piboSessionId, finalAnswer: result.finalAnswer };
216
296
  const { evaluation, conditionStates } = await this.evaluateStopPolicy(this.store.getJob(job.id) ?? job, 'after-run', run, outcome);
217
- this.store.completeRun({ jobId: job.id, runId: run.id, status: outcome.status, piboSessionId: result.piboSessionId, reason: cancelled ? 'cancelled' : evaluation.reason, stopAfterRun: evaluation.finalAction !== 'continue', stopEvaluation: evaluation, conditionStates });
297
+ this.store.completeRun({ jobId: job.id, runId: run.id, status: outcome.status, piboSessionId: result.piboSessionId, reason: cancelled ? 'cancelled' : evaluation.reason, stopAfterRun: evaluation.finalAction !== 'continue', stopEvaluation: evaluation, conditionStates }, this.now());
218
298
  await this.cleanupRunResources(job, run);
219
299
  }
220
300
  catch (error) {
221
301
  recordActiveTime();
222
302
  const cancelled = this.cancelledRuns.delete(run.id);
223
303
  const message = errorMessage(error);
224
- const fatalProfileError = !cancelled && isUnknownProfileErrorMessage(message);
304
+ const invalidated = error instanceof LoopContinuationInvalidatedError;
305
+ const errorDetails = error instanceof LoopSessionError ? error.details : undefined;
306
+ const fatalProfileError = !cancelled && !invalidated && isUnknownProfileErrorMessage(message);
225
307
  const timeoutAbortFailed = error instanceof LoopRunTimeoutError && error.abortFailed;
226
- const outcome = { status: cancelled ? 'cancelled' : 'error', error: cancelled ? undefined : message };
227
- const { evaluation, conditionStates } = await this.evaluateStopPolicy(this.store.getJob(job.id) ?? job, 'after-run', run, outcome);
228
- this.store.completeRun({ jobId: job.id, runId: run.id, status: outcome.status, error: outcome.error, reason: cancelled ? 'cancelled' : fatalProfileError ? 'unknown-profile' : timeoutAbortFailed ? 'timeout-abort-failed' : evaluation.reason, stopAfterRun: fatalProfileError || timeoutAbortFailed || evaluation.finalAction !== 'continue', stopEvaluation: evaluation, conditionStates });
308
+ const nonRetryable = !cancelled && !invalidated && errorDetails?.retryable === false;
309
+ const retryable = !cancelled && !invalidated && errorDetails?.retryable !== false;
310
+ const outcome = { status: cancelled ? 'cancelled' : 'error', error: cancelled ? undefined : message, ...(errorDetails ? { errorDetails } : {}) };
311
+ const latestJob = this.store.getJob(job.id) ?? job;
312
+ const latestGoalStatus = latestJob.mode === 'goal' ? latestJob.state.goalStatus ?? (latestJob.enabled ? 'active' : 'paused') : undefined;
313
+ const shouldBlockGoal = nonRetryable && latestJob.mode === 'goal' && !isTerminalGoalStatus(latestGoalStatus);
314
+ const { evaluation, conditionStates } = await this.evaluateStopPolicy(latestJob, 'after-run', run, outcome);
315
+ const automaticRetry = retryable && !fatalProfileError && !timeoutAbortFailed && evaluation.finalAction === 'continue';
316
+ const now = this.now();
317
+ const backoffMs = automaticRetry
318
+ ? retryBackoffMs((latestJob.state.consecutiveErrors ?? 0) + 1, this.retryBackoffBaseMs, this.retryBackoffMaxMs, this.retryBackoffJitterRatio, this.random)
319
+ : undefined;
320
+ const nextAttemptAt = backoffMs === undefined ? undefined : new Date(now.getTime() + backoffMs).toISOString();
321
+ const failure = cancelled || invalidated ? undefined : {
322
+ message,
323
+ ...(errorDetails ? { details: errorDetails } : {}),
324
+ recovery: automaticRetry ? failureRecovery(errorDetails) : retryable ? 'The configured stop policy stopped automatic retries; restart the Loop explicitly.' : failureRecovery(errorDetails),
325
+ at: now.toISOString(),
326
+ ...(nextAttemptAt ? { nextAttemptAt } : {}),
327
+ ...(backoffMs !== undefined ? { retryBackoffMs: backoffMs } : {}),
328
+ };
329
+ this.store.completeRun({
330
+ jobId: job.id,
331
+ runId: run.id,
332
+ status: outcome.status,
333
+ error: outcome.error,
334
+ errorDetails,
335
+ failure,
336
+ ...(shouldBlockGoal ? { goalStatus: 'blocked' } : {}),
337
+ reason: cancelled
338
+ ? 'cancelled'
339
+ : invalidated
340
+ ? 'continuation-invalidated'
341
+ : nonRetryable
342
+ ? `non-retryable-${failureKey(errorDetails)}`
343
+ : automaticRetry
344
+ ? `retry-backoff-${failureKey(errorDetails)}`
345
+ : fatalProfileError
346
+ ? 'unknown-profile'
347
+ : timeoutAbortFailed
348
+ ? 'timeout-abort-failed'
349
+ : evaluation.reason,
350
+ stopAfterRun: invalidated || nonRetryable || fatalProfileError || timeoutAbortFailed || (!automaticRetry && evaluation.finalAction !== 'continue'),
351
+ stopEvaluation: evaluation,
352
+ conditionStates,
353
+ }, now);
229
354
  await this.cleanupRunResources(job, run);
230
- if (!cancelled)
355
+ if (!cancelled && !invalidated)
231
356
  console.error(`[loop] job ${job.id} failed`, error);
232
357
  }
233
358
  finally {
@@ -307,18 +432,26 @@ export class PiboLoopService {
307
432
  }
308
433
  getStopConditionDefinitions() { return this.options.context.getLoopStopConditionDefinitions?.() ?? this.options.context.getRalphStopConditionDefinitions?.() ?? createBuiltInLoopStopConditions(); }
309
434
  handleOutputEvent(event) {
435
+ const eventId = 'eventId' in event ? event.eventId : undefined;
436
+ if (!eventId)
437
+ return;
438
+ if (event.type === 'message_queued')
439
+ this.store.updateRunMessageState(eventId, 'queued');
440
+ else if (event.type === 'message_started')
441
+ this.store.updateRunMessageState(eventId, 'active');
442
+ else if (event.type === 'message_finished')
443
+ this.store.updateRunMessageState(eventId, 'finished');
444
+ else if (event.type === 'session_error' && event.errorDetails?.code === 'loop_continuation_invalidated')
445
+ this.store.updateRunMessageState(eventId, 'invalidated');
310
446
  if (event.type !== 'assistant_usage')
311
447
  return;
312
- const job = this.store.getLatestGoalForSession(event.piboSessionId);
313
- if (!job)
448
+ const run = this.store.getRunByMessageEventId(eventId);
449
+ if (!run || run.piboSessionId !== event.piboSessionId)
314
450
  return;
315
- const status = job.state.goalStatus ?? (job.enabled ? 'active' : 'paused');
316
- if (!job.state.runningAt && status !== 'active')
451
+ const job = this.store.getJob(run.jobId);
452
+ if (!job || job.mode !== 'goal')
317
453
  return;
318
- if (job.state.runningAt && job.state.lastRunId)
319
- this.store.recordGoalTurnUsage(job.id, job.state.lastRunId, event.totalTokens);
320
- else
321
- this.store.recordGoalProgress(job.id, { tokens: event.totalTokens });
454
+ this.store.recordGoalTurnUsage(job.id, run.id, event.totalTokens);
322
455
  }
323
456
  handleProductEvent(event) {
324
457
  if (event.type !== 'pibo.loop.fact' && event.type !== 'loop.fact' && event.type !== 'pibo.ralph.fact' && event.type !== 'ralph.fact')
@@ -342,14 +475,15 @@ export class PiboLoopService {
342
475
  const continuation = reusableSession !== undefined;
343
476
  const session = reusableSession ?? this.createLoopSession(job, run);
344
477
  if (reusableSession) {
478
+ const { loopJobId: _loopJobId, loopRunId: _loopRunId, ...metadata } = session.metadata ?? {};
345
479
  this.options.context.updateSession?.(session.id, {
346
480
  title: job.name,
347
- metadata: { ...(session.metadata ?? {}), loopRunId: run.id, loopMode: job.mode },
481
+ metadata: { ...metadata, loopMode: job.mode },
348
482
  ...(job.modelOverride ? { activeModel: { ...job.modelOverride } } : {}),
349
483
  });
350
484
  }
351
485
  this.store.attachRunSession(job.id, run.id, session.id);
352
- const result = await this.emitMessageAndWait(session.id, buildLoopTurnPrompt(job, continuation, this.goalToolsAvailable(job)));
486
+ const result = await this.emitMessageAndWait(job, run, session.id, buildLoopTurnPrompt(job, continuation, this.goalToolsAvailable(job)));
353
487
  return { piboSessionId: session.id, ...result };
354
488
  }
355
489
  createLoopSession(job, run) {
@@ -364,8 +498,7 @@ export class PiboLoopService {
364
498
  metadata: {
365
499
  ...(target.metadata ?? {}),
366
500
  chatRoomId: target.roomId,
367
- loopJobId: job.id,
368
- loopRunId: run.id,
501
+ ...(job.mode === 'ralph' ? { loopJobId: job.id, loopRunId: run.id } : {}),
369
502
  loopMode: job.mode,
370
503
  loopTargetKind: job.target.kind,
371
504
  ...(job.mode === 'ralph' ? { ralphJobId: job.id, ralphRunId: run.id } : {}),
@@ -392,8 +525,10 @@ export class PiboLoopService {
392
525
  throw new Error('Target room is archived');
393
526
  return { roomId: room.id, workspace: room.workspace ?? getDefaultPiboWorkspace() };
394
527
  } const room = this.roomService.ensureDefaultRoom({ name: 'Shared Chat' }); return { roomId: room.id, workspace: room.workspace ?? getDefaultPiboWorkspace() }; }
395
- async emitMessageAndWait(piboSessionId, text) {
528
+ async emitMessageAndWait(job, run, piboSessionId, text) {
396
529
  const eventId = `loop_msg_${randomUUID()}`;
530
+ if (!this.store.attachRunMessage(job.id, run.id, eventId))
531
+ throw new Error(`Loop run ${run.id} is no longer available for message binding`);
397
532
  return await new Promise((resolve, reject) => {
398
533
  let settled = false;
399
534
  let deltaAnswer = '';
@@ -417,7 +552,7 @@ export class PiboLoopService {
417
552
  if (this.runTimeoutMs !== undefined) {
418
553
  timeout = setTimeout(() => {
419
554
  timingOut = true;
420
- const message = lastSessionError ? `Loop run timed out after session error: ${lastSessionError}` : 'Loop run timed out';
555
+ const message = lastSessionError ? `Loop run timed out after session error: ${lastSessionError.message}` : 'Loop run timed out';
421
556
  void this.options.context.emit({ type: 'execution', piboSessionId, action: 'abort', id: `loop_timeout_${randomUUID()}` })
422
557
  .then(() => finish(new LoopRunTimeoutError(message)), (abortError) => finish(new LoopRunTimeoutError(`${message}; session abort failed: ${errorMessage(abortError)}`, true)));
423
558
  }, this.runTimeoutMs);
@@ -434,13 +569,18 @@ export class PiboLoopService {
434
569
  lastSessionError = undefined;
435
570
  }
436
571
  if (event.type === 'message_finished')
437
- finish(lastSessionError ? new Error(lastSessionError) : undefined);
572
+ finish(lastSessionError ? new LoopSessionError(lastSessionError.message, lastSessionError.details) : undefined);
438
573
  if (event.type === 'session_error') {
439
- lastSessionError = event.error;
440
- finish(new Error(event.error));
574
+ if (event.errorDetails?.code === 'loop_continuation_invalidated') {
575
+ finish(new LoopContinuationInvalidatedError(event.error));
576
+ }
577
+ else {
578
+ lastSessionError = { message: event.error, details: event.errorDetails };
579
+ finish(new LoopSessionError(event.error, event.errorDetails));
580
+ }
441
581
  }
442
582
  });
443
- this.options.context.emit({ type: 'message', piboSessionId, id: eventId, source: 'service', text }).catch((error) => finish(error instanceof Error ? error : new Error(String(error))));
583
+ this.options.context.emit({ type: 'message', piboSessionId, id: eventId, source: 'service', text, provenance: { kind: 'loop-run', jobId: job.id, runId: run.id } }).catch((error) => finish(error instanceof Error ? error : new Error(String(error))));
444
584
  });
445
585
  }
446
586
  }