w-dispatch-ai 1.0.5 → 1.0.7

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.
@@ -504,7 +504,7 @@ export default dispatchApiOpenaiCompat
504
504
  <br class="clear">
505
505
 
506
506
  <footer>
507
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Aug 14 2026 12:01:07 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
507
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Aug 14 2026 15:11:06 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
508
508
  </footer>
509
509
 
510
510
  <script>prettyPrint();</script>
@@ -190,7 +190,7 @@ export default dispatchClaude
190
190
  <br class="clear">
191
191
 
192
192
  <footer>
193
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Aug 14 2026 12:01:07 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
193
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Aug 14 2026 15:11:06 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
194
194
  </footer>
195
195
 
196
196
  <script>prettyPrint();</script>
@@ -191,7 +191,7 @@ export default dispatchCodex
191
191
  <br class="clear">
192
192
 
193
193
  <footer>
194
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Aug 14 2026 12:01:07 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
194
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Aug 14 2026 15:11:06 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
195
195
  </footer>
196
196
 
197
197
  <script>prettyPrint();</script>
@@ -260,7 +260,7 @@ export default dispatchOpencode
260
260
  <br class="clear">
261
261
 
262
262
  <footer>
263
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Aug 14 2026 12:01:07 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
263
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Aug 14 2026 15:11:06 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
264
264
  </footer>
265
265
 
266
266
  <script>prettyPrint();</script>
@@ -113,7 +113,7 @@ export default getCliArgs
113
113
  <br class="clear">
114
114
 
115
115
  <footer>
116
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Aug 14 2026 12:01:07 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
116
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Aug 14 2026 15:11:06 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
117
117
  </footer>
118
118
 
119
119
  <script>prettyPrint();</script>
@@ -102,7 +102,7 @@ export default getErrorResult
102
102
  <br class="clear">
103
103
 
104
104
  <footer>
105
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Aug 14 2026 12:01:07 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
105
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Aug 14 2026 15:11:06 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
106
106
  </footer>
107
107
 
108
108
  <script>prettyPrint();</script>
package/docs/global.html CHANGED
@@ -376,7 +376,7 @@ console.log(dfTimeoutMs)
376
376
 
377
377
  <dt class="tag-source">Source:</dt>
378
378
  <dd class="tag-source"><ul class="dummy"><li>
379
- <a href="wkf_callAiWithFallback.mjs.html">wkf/callAiWithFallback.mjs</a>, <a href="wkf_callAiWithFallback.mjs.html#line62">line 62</a>
379
+ <a href="wkf_callAiWithFallback.mjs.html">wkf/callAiWithFallback.mjs</a>, <a href="wkf_callAiWithFallback.mjs.html#line70">line 70</a>
380
380
  </li></ul></dd>
381
381
 
382
382
 
@@ -736,7 +736,7 @@ parse+check接進遞補層之validate——非法回覆視為該家失敗而
736
736
 
737
737
  <dt class="tag-source">Source:</dt>
738
738
  <dd class="tag-source"><ul class="dummy"><li>
739
- <a href="wkf_callAiWithFallback.mjs.html">wkf/callAiWithFallback.mjs</a>, <a href="wkf_callAiWithFallback.mjs.html#line135">line 135</a>
739
+ <a href="wkf_callAiWithFallback.mjs.html">wkf/callAiWithFallback.mjs</a>, <a href="wkf_callAiWithFallback.mjs.html#line144">line 144</a>
740
740
  </li></ul></dd>
741
741
 
742
742
 
@@ -1250,6 +1250,46 @@ await test()
1250
1250
 
1251
1251
 
1252
1252
 
1253
+ <tr>
1254
+
1255
+ <td class="name"><code>minAttemptMs</code></td>
1256
+
1257
+
1258
+ <td class="type">
1259
+
1260
+
1261
+ <span class="param-type">Number</span>
1262
+
1263
+
1264
+
1265
+
1266
+ </td>
1267
+
1268
+
1269
+ <td class="attributes">
1270
+
1271
+ &lt;optional><br>
1272
+
1273
+
1274
+
1275
+
1276
+
1277
+ </td>
1278
+
1279
+
1280
+
1281
+ <td class="default">
1282
+
1283
+ <code>20000</code>
1284
+
1285
+ </td>
1286
+
1287
+
1288
+ <td class="description last"><p>輸入搭配budgetMs之開工門檻毫秒正整數,剩餘預算低於此值即不再開工,預設20000</p></td>
1289
+ </tr>
1290
+
1291
+
1292
+
1253
1293
  <tr>
1254
1294
 
1255
1295
  <td class="name"><code>maxRetries</code></td>
@@ -1405,7 +1445,7 @@ await test()
1405
1445
  </td>
1406
1446
 
1407
1447
 
1408
- <td class="description last"><p>輸入遞補層事件回調函數,預設null</p></td>
1448
+ <td class="description last"><p>輸入遞補層事件回調函數,預設null。除上列外之其餘鍵(retryDelayMs、maxBuffer、onStdout等)亦一律原樣轉傳dispatchAiFallback</p></td>
1409
1449
  </tr>
1410
1450
 
1411
1451
 
@@ -2238,7 +2278,7 @@ providers陣列順序即優先序,排前面的先用;
2238
2278
 
2239
2279
  <dt class="tag-source">Source:</dt>
2240
2280
  <dd class="tag-source"><ul class="dummy"><li>
2241
- <a href="dispatchAiFallback.mjs.html">dispatchAiFallback.mjs</a>, <a href="dispatchAiFallback.mjs.html#line189">line 189</a>
2281
+ <a href="dispatchAiFallback.mjs.html">dispatchAiFallback.mjs</a>, <a href="dispatchAiFallback.mjs.html#line199">line 199</a>
2242
2282
  </li></ul></dd>
2243
2283
 
2244
2284
 
@@ -2714,7 +2754,47 @@ await test()
2714
2754
  </td>
2715
2755
 
2716
2756
 
2717
- <td class="description last"><p>輸入狀態持久化物件{get:()=&gt;state,set:(state)=&gt;{}},state內含cursors(逐群組游標),省略代表用行程內記憶體(跨呼叫有效,重啟歸零)。假定單行程序列調用,並行請自行加鎖</p></td>
2757
+ <td class="description last"><p>輸入狀態持久化物件{get:()=&gt;state,set:(state)=&gt;{}},state內含cursors(逐群組游標)與cooling(供應商冷卻時間戳,僅cooldownMs&gt;0時使用),省略代表用行程內記憶體(跨呼叫有效,重啟歸零)。假定單行程序列調用,並行請自行加鎖</p></td>
2758
+ </tr>
2759
+
2760
+
2761
+
2762
+ <tr>
2763
+
2764
+ <td class="name"><code>cooldownMs</code></td>
2765
+
2766
+
2767
+ <td class="type">
2768
+
2769
+
2770
+ <span class="param-type">Number</span>
2771
+
2772
+
2773
+
2774
+
2775
+ </td>
2776
+
2777
+
2778
+ <td class="attributes">
2779
+
2780
+ &lt;optional><br>
2781
+
2782
+
2783
+
2784
+
2785
+
2786
+ </td>
2787
+
2788
+
2789
+
2790
+ <td class="default">
2791
+
2792
+ <code>0</code>
2793
+
2794
+ </td>
2795
+
2796
+
2797
+ <td class="description last"><p>輸入供應商冷卻視窗毫秒非負整數,&gt;0啟用:條目(限有明給id者)遭遇限流(HTTP 429,僅api類可偵測)或逾時(TIMEOUT開頭)後,於冷卻視窗內之後續呼叫中被移至鏈尾——只降序不移除,前面全敗時仍會被嘗試,任一次成功立即解除;注意啟用時「providers順序即優先序」會被暫時重排,此即本機制之目的;預設0代表不啟用</p></td>
2718
2798
  </tr>
2719
2799
 
2720
2800
 
@@ -7190,7 +7270,7 @@ console.log(getErrorResult(null).error)
7190
7270
 
7191
7271
  <dt class="tag-source">Source:</dt>
7192
7272
  <dd class="tag-source"><ul class="dummy"><li>
7193
- <a href="dispatchAiFallback.mjs.html">dispatchAiFallback.mjs</a>, <a href="dispatchAiFallback.mjs.html#line85">line 85</a>
7273
+ <a href="dispatchAiFallback.mjs.html">dispatchAiFallback.mjs</a>, <a href="dispatchAiFallback.mjs.html#line94">line 94</a>
7194
7274
  </li></ul></dd>
7195
7275
 
7196
7276
 
@@ -7978,7 +8058,7 @@ await test()
7978
8058
  </td>
7979
8059
 
7980
8060
 
7981
- <td class="description last"><p>輸入前段名額規格陣列,各元素{ use, fallback, maxRetries?, timeoutMs? }等(use/fallback外之鍵覆寫該名額呼叫設定)</p></td>
8061
+ <td class="description last"><p>輸入前段名額規格陣列,各元素{ use, fallback, check?, maxRetries?, timeoutMs? }等(check可覆寫頂層檢核;除use/fallback/check外之鍵覆寫該名額呼叫設定)</p></td>
7982
8062
  </tr>
7983
8063
 
7984
8064
 
@@ -8014,7 +8094,7 @@ await test()
8014
8094
  </td>
8015
8095
 
8016
8096
 
8017
- <td class="description last"><p>輸入整合名額規格物件{ use, fallback, prompt?, ... },prompt可為(candidates)=&gt;String自訂整合提示詞,省略用預設模板</p></td>
8097
+ <td class="description last"><p>輸入整合名額規格物件{ use, fallback, prompt?, check?, ... },prompt可為(candidates)=&gt;String自訂整合提示詞(省略用預設模板);check為終稿專屬檢核(終稿判準常與候選不同,如須含固定段落),未給則沿用頂層check</p></td>
8018
8098
  </tr>
8019
8099
 
8020
8100
 
@@ -8054,7 +8134,7 @@ await test()
8054
8134
  </td>
8055
8135
 
8056
8136
 
8057
- <td class="description last"><p>輸入候選與終稿共用之檢核函數(json)=&gt;Boolean,預設null</p></td>
8137
+ <td class="description last"><p>輸入檢核函數(json)=&gt;Boolean,作為候選與終稿之共用預設,名額規格與integrate可各自帶check覆寫,預設null</p></td>
8058
8138
  </tr>
8059
8139
 
8060
8140
 
@@ -8639,7 +8719,7 @@ await test()
8639
8719
  </td>
8640
8720
 
8641
8721
 
8642
- <td class="description last"><p>輸入前段共用檢核函數,後段各階段自帶check,預設null</p></td>
8722
+ <td class="description last"><p>輸入前段共用檢核函數(前段名額規格與integrate可各自帶check覆寫,同runFanout),後段各階段自帶check,預設null</p></td>
8643
8723
  </tr>
8644
8724
 
8645
8725
 
@@ -9230,7 +9310,7 @@ await test()
9230
9310
  <br class="clear">
9231
9311
 
9232
9312
  <footer>
9233
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Aug 14 2026 12:01:07 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
9313
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Aug 14 2026 15:11:06 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
9234
9314
  </footer>
9235
9315
 
9236
9316
  <script>prettyPrint();</script>
package/docs/index.html CHANGED
@@ -71,7 +71,7 @@
71
71
  <br class="clear">
72
72
 
73
73
  <footer>
74
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Aug 14 2026 12:01:07 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
74
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Aug 14 2026 15:11:06 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
75
75
  </footer>
76
76
 
77
77
  <script>prettyPrint();</script>
@@ -176,7 +176,7 @@ export default resolveProviders
176
176
  <br class="clear">
177
177
 
178
178
  <footer>
179
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Aug 14 2026 12:01:07 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
179
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Aug 14 2026 15:11:06 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
180
180
  </footer>
181
181
 
182
182
  <script>prettyPrint();</script>
@@ -46,6 +46,7 @@
46
46
  <section>
47
47
  <article>
48
48
  <pre class="prettyprint source linenums"><code>import get from 'lodash-es/get.js'
49
+ import omit from 'lodash-es/omit.js'
49
50
  import isearr from 'wsemi/src/isearr.mjs'
50
51
  import isestr from 'wsemi/src/isestr.mjs'
51
52
  import isfun from 'wsemi/src/isfun.mjs'
@@ -81,6 +82,13 @@ import extractJsonLoose from './extractJsonLoose.mjs'
81
82
  // 防副作用(建檔/改檔/刪檔/改動系統狀態)之本旨不變。
82
83
 
83
84
 
85
+ //本層自用之設定鍵, 其餘鍵(timeoutMs/budgetMs/minAttemptMs/maxRetries/cwd/store/onEvent/
86
+ //retryDelayMs/maxBuffer等)一律原樣轉傳dispatchAiFallback——與各轉接器「剔除自用鍵後
87
+ //原樣轉傳」同一約定; 曾因白名單式轉送漏掉minAttemptMs, 令README教學之工作流層
88
+ //budget保護靜默失效(2026-08-14使用端實測回報), 故改採omit式轉傳杜絕同類漏鍵
89
+ let OWN_KEYS = ['providers', 'spec', 'check', 'parse', 'rawText', 'promptPrefix']
90
+
91
+
84
92
  //預設防寫檔前綴(禁副作用, 但豁免唯讀查閱——codex以shell讀檔, 一律禁指令等同禁讀檔)
85
93
  let NO_SIDE_EFFECT = [
86
94
  '【執行約束】你只需把結果輸出在回覆內容中。',
@@ -146,10 +154,11 @@ function buildChain(providers, spec) {
146
154
  * @param {String} [opt.promptPrefix=防寫檔約束] 輸入prompt前綴字串,預設為防寫檔約束,傳''關閉
147
155
  * @param {Number} [opt.timeoutMs=300000] 輸入單次嘗試逾時毫秒正整數,全套件統一預設300000
148
156
  * @param {Number} [opt.budgetMs=null] 輸入整條遞補鏈之時間預算毫秒正整數,預設null代表不限
157
+ * @param {Number} [opt.minAttemptMs=20000] 輸入搭配budgetMs之開工門檻毫秒正整數,剩餘預算低於此值即不再開工,預設20000
149
158
  * @param {Number} [opt.maxRetries=0] 輸入同家重試次數非負整數,預設0(韌性交給遞補;端點不穩偶發空回之模型可調高令同鍵重試)
150
159
  * @param {String} [opt.cwd=process.cwd()] 輸入子進程工作目錄字串,預設process.cwd()
151
160
  * @param {Object} [opt.store=null] 輸入游標持久化物件{get,set},預設null代表用行程內記憶體
152
- * @param {Function} [opt.onEvent=null] 輸入遞補層事件回調函數,預設null
161
+ * @param {Function} [opt.onEvent=null] 輸入遞補層事件回調函數,預設null。除上列外之其餘鍵(retryDelayMs、maxBuffer、onStdout等)亦一律原樣轉傳dispatchAiFallback
153
162
  * @returns {Promise} 回傳Promise,resolve回傳結果物件,內含ok(是否取得可用結果布林值)、json(解析後物件,rawText模式下為文字)、providerId(實際使用之名稱)、keyIndex、keyId、ms(總耗時毫秒)、tried(遞補嘗試歷程陣列)、error(錯誤訊息字串),本函數不會reject
154
163
  * @example
155
164
  * //need cli in system PATH
@@ -232,15 +241,12 @@ async function callAiWithFallback(prompt, opt = {}) {
232
241
  return check ? check(j) === true : true
233
242
  }
234
243
 
244
+ //剔除本層自用鍵後原樣轉傳(含minAttemptMs/retryDelayMs/maxBuffer等), providers與validate由本層給定
235
245
  let r = await dispatchAiFallback(promptPrefix + prompt, {
246
+ ...omit(opt, OWN_KEYS),
236
247
  providers: chain,
237
248
  validate,
238
249
  timeoutMs: get(opt, 'timeoutMs', null) || dfTimeoutMs, //全套件統一預設300000
239
- budgetMs: get(opt, 'budgetMs', null) || undefined,
240
- maxRetries: get(opt, 'maxRetries', null) || 0,
241
- cwd: get(opt, 'cwd', null) || process.cwd(),
242
- store: get(opt, 'store', null) || undefined,
243
- onEvent: get(opt, 'onEvent', null) || undefined,
244
250
  })
245
251
 
246
252
  //result, 已過validate故此處parse必然成功(同一解析器), 重解析僅為取出物件
@@ -279,7 +285,7 @@ export { buildChain, NO_SIDE_EFFECT }
279
285
  <br class="clear">
280
286
 
281
287
  <footer>
282
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Aug 14 2026 12:01:07 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
288
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Aug 14 2026 15:11:06 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
283
289
  </footer>
284
290
 
285
291
  <script>prettyPrint();</script>
@@ -167,7 +167,7 @@ export default extractJsonLoose
167
167
  <br class="clear">
168
168
 
169
169
  <footer>
170
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Aug 14 2026 12:01:07 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
170
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Aug 14 2026 15:11:06 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
171
171
  </footer>
172
172
 
173
173
  <script>prettyPrint();</script>
@@ -97,9 +97,9 @@ ${candidates.map((c, i) => `【候選 ${i + 1}】\n${JSON.stringify(c)}`).join('
97
97
  * @param {Object} [opt={}] 輸入設定物件,預設{}
98
98
  * @param {Object} opt.providers 輸入provider定義表物件(名稱 → 條目),透傳callAiWithFallback
99
99
  * @param {String} opt.task 輸入前段各名額共用之任務提示詞字串
100
- * @param {Array} opt.agents 輸入前段名額規格陣列,各元素{ use, fallback, maxRetries?, timeoutMs? }等(use/fallback外之鍵覆寫該名額呼叫設定)
101
- * @param {Object} opt.integrate 輸入整合名額規格物件{ use, fallback, prompt?, ... },prompt可為(candidates)=>String自訂整合提示詞,省略用預設模板
102
- * @param {Function} [opt.check=null] 輸入候選與終稿共用之檢核函數(json)=>Boolean,預設null
100
+ * @param {Array} opt.agents 輸入前段名額規格陣列,各元素{ use, fallback, check?, maxRetries?, timeoutMs? }等(check可覆寫頂層檢核;除use/fallback/check外之鍵覆寫該名額呼叫設定)
101
+ * @param {Object} opt.integrate 輸入整合名額規格物件{ use, fallback, prompt?, check?, ... },prompt可為(candidates)=>String自訂整合提示詞(省略用預設模板);check為終稿專屬檢核(終稿判準常與候選不同,如須含固定段落),未給則沿用頂層check
102
+ * @param {Function} [opt.check=null] 輸入檢核函數(json)=>Boolean,作為候選與終稿之共用預設,名額規格與integrate可各自帶check覆寫,預設null
103
103
  * @param {String} [opt.schema=''] 輸入輸出格式示意字串,供預設整合模板嵌入,預設''
104
104
  * @param {Number} [opt.minCandidates=2] 輸入進入整合所需之最少成功候選數正整數,未達門檻以首位候選為成果,預設2
105
105
  * @param {Object} [opt.callOpt={}] 輸入透傳callAiWithFallback之共用設定(cwd、store、onEvent、timeoutMs、promptPrefix等),預設{}
@@ -161,9 +161,11 @@ async function runFanout(opt = {}) {
161
161
  }
162
162
 
163
163
  //前段: 並行多開, 個別失敗不炸整輪
164
+ //各名額可自帶check覆寫頂層(候選與終稿判準本可不同); 自spec抽出而非留在overrides,
165
+ //否則會被後方明給之check靜默覆蓋(曾為死鍵)
164
166
  let rsAgents = await Promise.all(agents.map((spec) => {
165
- let { use, fallback, ...overrides } = spec
166
- return callAiWithFallback(task, { ...callOpt, ...overrides, providers, spec: { use, fallback }, check })
167
+ let { use, fallback, check: checkAgent, ...overrides } = spec
168
+ return callAiWithFallback(task, { ...callOpt, ...overrides, providers, spec: { use, fallback }, check: isfun(checkAgent) ? checkAgent : check })
167
169
  }))
168
170
  let candidates = rsAgents.filter((r) => r.ok).map((r) => r.json)
169
171
 
@@ -181,9 +183,10 @@ async function runFanout(opt = {}) {
181
183
  if (!integrate || !isestr(get(integrate, 'use', ''))) {
182
184
  return { ok: false, result: null, integrated: false, agents: rsAgents, candidates, totalMs: Date.now() - t0, error: 'integrate spec (with use) is required' }
183
185
  }
184
- let { use, fallback, prompt: intPromptFn, ...intOverrides } = integrate
186
+ //整合可帶獨立check(終稿判準常較候選嚴, 如須含固定小標題), 未給則沿用頂層check
187
+ let { use, fallback, prompt: intPromptFn, check: checkInt, ...intOverrides } = integrate
185
188
  let intPrompt = isfun(intPromptFn) ? intPromptFn(candidates) : defaultIntegratePrompt(candidates, opt)
186
- let rInt = await callAiWithFallback(intPrompt, { ...callOpt, ...intOverrides, providers, spec: { use, fallback }, check })
189
+ let rInt = await callAiWithFallback(intPrompt, { ...callOpt, ...intOverrides, providers, spec: { use, fallback }, check: isfun(checkInt) ? checkInt : check })
187
190
 
188
191
  return {
189
192
  ok: rInt.ok,
@@ -214,7 +217,7 @@ export { defaultIntegratePrompt }
214
217
  <br class="clear">
215
218
 
216
219
  <footer>
217
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Aug 14 2026 12:01:07 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
220
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Aug 14 2026 15:11:06 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
218
221
  </footer>
219
222
 
220
223
  <script>prettyPrint();</script>
@@ -76,7 +76,7 @@ import runRolePipeline from './runRolePipeline.mjs'
76
76
  * @param {Array} opt.agents 輸入前段名額規格陣列(同runFanout)
77
77
  * @param {Object} opt.integrate 輸入前段整合名額規格物件(同runFanout)
78
78
  * @param {Array} opt.stages 輸入後段階段規格陣列(同runRolePipeline),各階段以ctx.input取得前段成果
79
- * @param {Function} [opt.check=null] 輸入前段共用檢核函數,後段各階段自帶check,預設null
79
+ * @param {Function} [opt.check=null] 輸入前段共用檢核函數(前段名額規格與integrate可各自帶check覆寫,同runFanout),後段各階段自帶check,預設null
80
80
  * @param {String} [opt.schema=''] 輸入輸出格式示意字串(供前段預設整合模板),預設''
81
81
  * @param {Number} [opt.minCandidates=2] 輸入前段整合門檻正整數,預設2
82
82
  * @param {Object} [opt.callOpt={}] 輸入透傳兩段之共用呼叫設定,預設{}
@@ -165,7 +165,7 @@ export default runFanoutPipeline
165
165
  <br class="clear">
166
166
 
167
167
  <footer>
168
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Aug 14 2026 12:01:07 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
168
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Aug 14 2026 15:11:06 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
169
169
  </footer>
170
170
 
171
171
  <script>prettyPrint();</script>
@@ -182,7 +182,7 @@ export default runRolePipeline
182
182
  <br class="clear">
183
183
 
184
184
  <footer>
185
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Aug 14 2026 12:01:07 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
185
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Aug 14 2026 15:11:06 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
186
186
  </footer>
187
187
 
188
188
  <script>prettyPrint();</script>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "w-dispatch-ai",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "main": "dist/w-dispatch-ai.umd.js",
5
5
  "dependencies": {
6
6
  "wsemi": "^1.8.70"
@@ -27,6 +27,15 @@ import dfTimeoutMs from './dfTimeoutMs.mjs'
27
27
  // 【跨次記憶只有游標】成功後游標推進至下一把, 令額度在同組多把金鑰間自動均攤;
28
28
  // 死金鑰的代價也被游標攤平——這輪從key1敗轉key2成功後游標停在key3, 下輪不會先碰key1。
29
29
  //
30
+ // 【供應商冷卻(選用, cooldownMs>0啟用, 預設關閉)】多階段工作流的每一階段都會從鏈首
31
+ // 重新探測同一家已失效的供應商——限流時每階段各踩一輪429、卡死時各燒一次完整逾時
32
+ // (使用端實測: 一次107秒的多階段請求中72秒耗在重複踩同一組429, 啟用冷卻後降至15秒)。
33
+ // 設計與「金鑰停用清單」(已否決)的關鍵差異: 以「條目」為單位、短視窗、且「只降序不移除」
34
+ // ——冷卻中的條目移到鏈尾而非移除, 前面全敗時照樣會被嘗試, 故不存在把已恢復服務冰住的問題;
35
+ // 任一次成功立即解除。觸發限於限流(HTTP 429, 僅api-openai-compat可靠偵測; CLI類之限流
36
+ // 埋在stderr文字中不納入)與逾時(TIMEOUT開頭, 各kind皆可)兩類——其餘失敗已有換金鑰換家
37
+ // 機制處理, 納入反而誤傷。狀態存於state.cooling, 與cursors同走store持久化。
38
+ //
30
39
  // 【時間預算】budgetMs限制整輪遞補的總時長, 剩餘預算會壓進每次呼叫的timeoutMs,
31
40
  // 防止多家連續卡逾時而撞破外部排程的執行上限。
32
41
  //
@@ -60,7 +69,7 @@ import dfTimeoutMs from './dfTimeoutMs.mjs'
60
69
 
61
70
 
62
71
  //fallback層自用之設定鍵, 其餘鍵作為各attempt之共用預設原樣轉傳
63
- let FALLBACK_KEYS = ['providers', 'budgetMs', 'minAttemptMs', 'store', 'onEvent']
72
+ let FALLBACK_KEYS = ['providers', 'budgetMs', 'minAttemptMs', 'cooldownMs', 'store', 'onEvent']
64
73
 
65
74
 
66
75
  //providers條目自用之設定鍵, 其餘鍵(含kind)即該條目之opt原樣轉傳對應轉接器
@@ -139,7 +148,8 @@ function isKeyIndependentFail(r) {
139
148
  * @param {Array} [opt.providers[].keys=[]] 輸入同一服務之多把API key字串陣列,逐次注入輪替(kind為opencode時須同時於條目給予provider),省略代表沿用CLI既有登入狀態之單一虛擬金鑰
140
149
  * @param {Number} [opt.budgetMs=null] 輸入整輪遞補之時間上限毫秒正整數,剩餘預算會壓進每次呼叫之timeoutMs,預設null代表不限
141
150
  * @param {Number} [opt.minAttemptMs=20000] 輸入單次嘗試之最低剩餘預算毫秒正整數,剩餘低於此值即停止嘗試回報budget exhausted,預設20000
142
- * @param {Object} [opt.store=null] 輸入狀態持久化物件{get:()=>state,set:(state)=>{}},state內含cursors(逐群組游標),省略代表用行程內記憶體(跨呼叫有效,重啟歸零)。假定單行程序列調用,並行請自行加鎖
151
+ * @param {Object} [opt.store=null] 輸入狀態持久化物件{get:()=>state,set:(state)=>{}},state內含cursors(逐群組游標)與cooling(供應商冷卻時間戳,僅cooldownMs>0時使用),省略代表用行程內記憶體(跨呼叫有效,重啟歸零)。假定單行程序列調用,並行請自行加鎖
152
+ * @param {Number} [opt.cooldownMs=0] 輸入供應商冷卻視窗毫秒非負整數,>0啟用:條目(限有明給id者)遭遇限流(HTTP 429,僅api類可偵測)或逾時(TIMEOUT開頭)後,於冷卻視窗內之後續呼叫中被移至鏈尾——只降序不移除,前面全敗時仍會被嘗試,任一次成功立即解除;注意啟用時「providers順序即優先序」會被暫時重排,此即本機制之目的;預設0代表不啟用
143
153
  * @param {Function} [opt.onEvent=null] 輸入事件回調函數(ev)=>{},ev.type可為'try'、'ok'、'next-key'、'skip-group'、'budget-out';失敗事件(next-key/skip-group)另帶stdout(被拒回覆)與stderr(錯誤輸出)供診斷,兩者於失敗路徑已由轉接器截斷;回調拋出例外不影響主流程,預設null
144
154
  * @param {Number} [opt.timeoutMs=300000] 輸入各attempt共用之逾時毫秒正整數,條目可覆寫,全套件統一預設300000
145
155
  * @param {String|Function} [opt.validate=undefined] 輸入各attempt共用之stdout驗證規則,條目可覆寫,預設undefined
@@ -235,6 +245,9 @@ async function dispatchAiFallback(prompt, opt = {}) {
235
245
  if (!isobj(state.cursors)) {
236
246
  state.cursors = {}
237
247
  }
248
+ if (!isobj(state.cooling)) {
249
+ state.cooling = {}
250
+ }
238
251
  let saveState = () => {
239
252
  if (useStore) {
240
253
  try {
@@ -244,6 +257,43 @@ async function dispatchAiFallback(prompt, opt = {}) {
244
257
  }
245
258
  }
246
259
 
260
+ //cooldownMs, 無效視為0=不啟用(現行行為零改變)
261
+ let cooldownMs = get(opt, 'cooldownMs', null)
262
+ if (!ispint(cooldownMs)) {
263
+ cooldownMs = 0
264
+ }
265
+ else {
266
+ cooldownMs = cint(cooldownMs)
267
+ }
268
+
269
+ //供應商冷卻: 冷卻中的條目「只降序不移除」——移到鏈尾, 前面全敗時仍會被嘗試,
270
+ //故不存在把已恢復服務冰住的問題(此為與「金鑰停用清單」的關鍵差異, 後者已被否決)。
271
+ //僅追蹤有明給id之條目(索引式id會因重排而錯位); 過期項順手清除
272
+ if (cooldownMs > 0) {
273
+ let now = Date.now()
274
+ let act = []
275
+ let cool = []
276
+ let dirty = false
277
+ for (let p of providers) {
278
+ let pid = get(p, 'id', null)
279
+ let ts = isestr(pid) ? get(state.cooling, pid, null) : null
280
+ if (ispint(ts) && (now - ts) < cooldownMs) {
281
+ cool.push(p)
282
+ }
283
+ else {
284
+ if (isestr(pid) && state.cooling[pid] !== undefined) {
285
+ delete state.cooling[pid] //冷卻已過期, 清除
286
+ dirty = true
287
+ }
288
+ act.push(p)
289
+ }
290
+ }
291
+ providers = [...act, ...cool]
292
+ if (dirty) {
293
+ saveState()
294
+ }
295
+ }
296
+
247
297
  //onEvent, 回調拋出例外不得中斷主流程
248
298
  let onEvent = get(opt, 'onEvent', null)
249
299
  let emit = (ev) => {
@@ -266,9 +316,10 @@ async function dispatchAiFallback(prompt, opt = {}) {
266
316
  for (let ig = 0; ig < providers.length; ig++) {
267
317
  let entry = providers[ig]
268
318
 
269
- //id, 無效回退條目索引字串
319
+ //id, 無效回退條目索引字串; idExplicit供冷卻機制判別(索引式id不參與冷卻)
270
320
  let id = get(entry, 'id', null)
271
- if (!isestr(id)) {
321
+ let idExplicit = isestr(id)
322
+ if (!idExplicit) {
272
323
  id = String(ig)
273
324
  }
274
325
 
@@ -331,8 +382,12 @@ async function dispatchAiFallback(prompt, opt = {}) {
331
382
  emit({ type: 'try', providerId: id, keyIndex, keyId, kind, model })
332
383
  let r = await dispatchAi(kind, prompt, attemptOpt)
333
384
 
334
- //成功, 推進游標(額度均攤)並回傳
385
+ //成功, 推進游標(額度均攤)並回傳; 任一次成功立即解除該家冷卻
335
386
  if (r.ok) {
387
+ if (cooldownMs > 0 && idExplicit && state.cooling[id] !== undefined) {
388
+ delete state.cooling[id]
389
+ saveState()
390
+ }
336
391
  if (nk > 0) {
337
392
  state.cursors[id] = (keyIndex + 1) % nk
338
393
  saveState()
@@ -345,6 +400,16 @@ async function dispatchAiFallback(prompt, opt = {}) {
345
400
  //失敗分流
346
401
  lastResult = r
347
402
  lastMeta = { providerId: id, keyIndex, kind, model }
403
+ //冷卻觸發: 限流(HTTP 429, 僅api類可偵測)與逾時(TIMEOUT開頭, CLI與api皆可)兩類——
404
+ //其餘失敗(金鑰無效/服務端錯誤)已有換金鑰換家機制處理, 納入冷卻反而誤傷
405
+ if (cooldownMs > 0 && idExplicit) {
406
+ let isCoolTrigger = (r.code === 429) || (isestr(r.error) && r.error.indexOf('TIMEOUT') === 0)
407
+ if (isCoolTrigger) {
408
+ state.cooling[id] = Date.now()
409
+ saveState()
410
+ }
411
+ }
412
+
348
413
  //失敗事件與tried一併帶被拒回覆(stdout)與錯誤輸出(stderr), 供呼叫端診斷失敗原因
349
414
  //(如驗證失敗時模型究竟回了什麼); 兩者於失敗路徑已由轉接器截斷(≤500/1000字元), 不會過大
350
415
  if (isKeyIndependentFail(r)) {
@@ -1,4 +1,5 @@
1
1
  import get from 'lodash-es/get.js'
2
+ import omit from 'lodash-es/omit.js'
2
3
  import isearr from 'wsemi/src/isearr.mjs'
3
4
  import isestr from 'wsemi/src/isestr.mjs'
4
5
  import isfun from 'wsemi/src/isfun.mjs'
@@ -34,6 +35,13 @@ import extractJsonLoose from './extractJsonLoose.mjs'
34
35
  // 防副作用(建檔/改檔/刪檔/改動系統狀態)之本旨不變。
35
36
 
36
37
 
38
+ //本層自用之設定鍵, 其餘鍵(timeoutMs/budgetMs/minAttemptMs/maxRetries/cwd/store/onEvent/
39
+ //retryDelayMs/maxBuffer等)一律原樣轉傳dispatchAiFallback——與各轉接器「剔除自用鍵後
40
+ //原樣轉傳」同一約定; 曾因白名單式轉送漏掉minAttemptMs, 令README教學之工作流層
41
+ //budget保護靜默失效(2026-08-14使用端實測回報), 故改採omit式轉傳杜絕同類漏鍵
42
+ let OWN_KEYS = ['providers', 'spec', 'check', 'parse', 'rawText', 'promptPrefix']
43
+
44
+
37
45
  //預設防寫檔前綴(禁副作用, 但豁免唯讀查閱——codex以shell讀檔, 一律禁指令等同禁讀檔)
38
46
  let NO_SIDE_EFFECT = [
39
47
  '【執行約束】你只需把結果輸出在回覆內容中。',
@@ -99,10 +107,11 @@ function buildChain(providers, spec) {
99
107
  * @param {String} [opt.promptPrefix=防寫檔約束] 輸入prompt前綴字串,預設為防寫檔約束,傳''關閉
100
108
  * @param {Number} [opt.timeoutMs=300000] 輸入單次嘗試逾時毫秒正整數,全套件統一預設300000
101
109
  * @param {Number} [opt.budgetMs=null] 輸入整條遞補鏈之時間預算毫秒正整數,預設null代表不限
110
+ * @param {Number} [opt.minAttemptMs=20000] 輸入搭配budgetMs之開工門檻毫秒正整數,剩餘預算低於此值即不再開工,預設20000
102
111
  * @param {Number} [opt.maxRetries=0] 輸入同家重試次數非負整數,預設0(韌性交給遞補;端點不穩偶發空回之模型可調高令同鍵重試)
103
112
  * @param {String} [opt.cwd=process.cwd()] 輸入子進程工作目錄字串,預設process.cwd()
104
113
  * @param {Object} [opt.store=null] 輸入游標持久化物件{get,set},預設null代表用行程內記憶體
105
- * @param {Function} [opt.onEvent=null] 輸入遞補層事件回調函數,預設null
114
+ * @param {Function} [opt.onEvent=null] 輸入遞補層事件回調函數,預設null。除上列外之其餘鍵(retryDelayMs、maxBuffer、onStdout等)亦一律原樣轉傳dispatchAiFallback
106
115
  * @returns {Promise} 回傳Promise,resolve回傳結果物件,內含ok(是否取得可用結果布林值)、json(解析後物件,rawText模式下為文字)、providerId(實際使用之名稱)、keyIndex、keyId、ms(總耗時毫秒)、tried(遞補嘗試歷程陣列)、error(錯誤訊息字串),本函數不會reject
107
116
  * @example
108
117
  * //need cli in system PATH
@@ -185,15 +194,12 @@ async function callAiWithFallback(prompt, opt = {}) {
185
194
  return check ? check(j) === true : true
186
195
  }
187
196
 
197
+ //剔除本層自用鍵後原樣轉傳(含minAttemptMs/retryDelayMs/maxBuffer等), providers與validate由本層給定
188
198
  let r = await dispatchAiFallback(promptPrefix + prompt, {
199
+ ...omit(opt, OWN_KEYS),
189
200
  providers: chain,
190
201
  validate,
191
202
  timeoutMs: get(opt, 'timeoutMs', null) || dfTimeoutMs, //全套件統一預設300000
192
- budgetMs: get(opt, 'budgetMs', null) || undefined,
193
- maxRetries: get(opt, 'maxRetries', null) || 0,
194
- cwd: get(opt, 'cwd', null) || process.cwd(),
195
- store: get(opt, 'store', null) || undefined,
196
- onEvent: get(opt, 'onEvent', null) || undefined,
197
203
  })
198
204
 
199
205
  //result, 已過validate故此處parse必然成功(同一解析器), 重解析僅為取出物件