w-dispatch-ai 1.0.33 → 1.0.35

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 (57) hide show
  1. package/README.md +8 -8
  2. package/dist/w-dispatch-ai.umd.js +2 -2
  3. package/dist/w-dispatch-ai.umd.js.map +1 -1
  4. package/docs/WDispatchAi.mjs.html +1 -1
  5. package/docs/adapters.mjs.html +1 -1
  6. package/docs/budgetFor.mjs.html +1 -1
  7. package/docs/buildValidator.mjs.html +1 -1
  8. package/docs/castPintOr.mjs.html +1 -1
  9. package/docs/dfTimeoutMs.mjs.html +1 -1
  10. package/docs/dispatchAi.mjs.html +1 -1
  11. package/docs/dispatchAiFallback.mjs.html +1 -1
  12. package/docs/dispatchAiWkf.mjs.html +1 -1
  13. package/docs/dispatchAntigravity.mjs.html +1 -1
  14. package/docs/dispatchApiOpenaiCompat.mjs.html +1 -1
  15. package/docs/dispatchApiOpenaiResponses.mjs.html +1 -1
  16. package/docs/dispatchApiTypesafeSystemone.mjs.html +1 -1
  17. package/docs/dispatchClaude.mjs.html +13 -3
  18. package/docs/dispatchCodex.mjs.html +2 -2
  19. package/docs/dispatchOpencode.mjs.html +43 -11
  20. package/docs/getCliArgs.mjs.html +1 -1
  21. package/docs/getErrorResult.mjs.html +1 -1
  22. package/docs/getErrorType.mjs.html +1 -1
  23. package/docs/global.html +9 -9
  24. package/docs/index.html +1 -1
  25. package/docs/quota_dfQuotaTimeoutMs.mjs.html +1 -1
  26. package/docs/quota_fetchQuotaJson.mjs.html +1 -1
  27. package/docs/quota_fromCodexUsageHttp.mjs.html +1 -1
  28. package/docs/quota_getQuotaAntigravity.mjs.html +1 -1
  29. package/docs/quota_getQuotaClaude.mjs.html +1 -1
  30. package/docs/quota_getQuotaCodex.mjs.html +1 -1
  31. package/docs/quota_readJsonOrNull.mjs.html +1 -1
  32. package/docs/quota_toQuotaLabel.mjs.html +1 -1
  33. package/docs/quota_toQuotaResult.mjs.html +1 -1
  34. package/docs/quota_toQuotaScopedLabel.mjs.html +1 -1
  35. package/docs/quota_toQuotaWindow.mjs.html +1 -1
  36. package/docs/readEnvFile.mjs.html +1 -1
  37. package/docs/resolveProviders.mjs.html +2 -2
  38. package/docs/wkf_callAiWithFallback.mjs.html +2 -2
  39. package/docs/wkf_createFileStore.mjs.html +1 -1
  40. package/docs/wkf_createUsageCounter.mjs.html +1 -1
  41. package/docs/wkf_extractJsonLoose.mjs.html +1 -1
  42. package/docs/wkf_noSideEffectPrefix.mjs.html +4 -3
  43. package/docs/wkf_runFanout.mjs.html +1 -1
  44. package/docs/wkf_runFanoutPipeline.mjs.html +1 -1
  45. package/docs/wkf_runRolePipeline.mjs.html +1 -1
  46. package/docs/wkf_salvageTruncatedArray.mjs.html +1 -1
  47. package/package.json +1 -1
  48. package/src/dispatchClaude.mjs +12 -2
  49. package/src/dispatchCodex.mjs +1 -1
  50. package/src/dispatchOpencode.mjs +41 -9
  51. package/src/providers.mjs +41 -2
  52. package/src/resolveProviders.mjs +1 -1
  53. package/src/wkf/callAiWithFallback.mjs +1 -1
  54. package/src/wkf/noSideEffectPrefix.mjs +3 -2
  55. package/test/tools/fakeCliForTest.mjs +1 -0
  56. package/test/unit-dispatchOpencode.test.mjs +21 -0
  57. package/test/unit-providers.test.mjs +6 -1
@@ -162,7 +162,7 @@ export default createFileStore
162
162
  <br class="clear">
163
163
 
164
164
  <footer>
165
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Tue Sep 22 2026 17:42:28 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
165
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Wed Sep 23 2026 18:02:52 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
166
166
  </footer>
167
167
 
168
168
  <script>prettyPrint();</script>
@@ -200,7 +200,7 @@ export default createUsageCounter
200
200
  <br class="clear">
201
201
 
202
202
  <footer>
203
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Tue Sep 22 2026 17:42:28 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
203
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Wed Sep 23 2026 18:02:52 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
204
204
  </footer>
205
205
 
206
206
  <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 Tue Sep 22 2026 17:42:28 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 Wed Sep 23 2026 18:02:52 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
171
171
  </footer>
172
172
 
173
173
  <script>prettyPrint();</script>
@@ -53,8 +53,9 @@
53
53
  // 本套件早已修正, 形成「套件修了、消費端沒跟上」的分岔。抽成獨立模組後
54
54
  // 所有路徑引用同一來源, 措辭再修正時全體同步。
55
55
  //
56
- // 【為何需要此前綴】agentic CLI自帶檔案讀寫工具且對cwd隔離免疫(會自行解析
57
- // 專案根目錄以絕對路徑寫檔), 實測會「順手」把結果另存孤兒檔,
56
+ // 【為何需要此前綴】agentic CLI自帶檔案讀寫工具, 且cwd不是隔離邊界(可用絕對路徑
57
+ // 寫到cwd外; 另opencode曾因沿用父進程之PWD而在啟動目錄作業, 2026-09-23已於
58
+ // dispatchOpencode修正, 見該檔檔頭), 實測會「順手」把結果另存孤兒檔,
58
59
  // 甚至可能覆蓋呼叫端資料本體(殷鑑: 2026-08-10執行任務歷史.md遭AI覆寫、
59
60
  // 評比腳本繞過前綴又產生根目錄孤兒檔); 各家CLI的權限機制彼此不同(codex有sandbox、
60
61
  // claude有--disallowedTools、opencode有config.permission),
@@ -108,7 +109,7 @@ export default NO_SIDE_EFFECT
108
109
  <br class="clear">
109
110
 
110
111
  <footer>
111
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Tue Sep 22 2026 17:42:28 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
112
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Wed Sep 23 2026 18:02:52 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
112
113
  </footer>
113
114
 
114
115
  <script>prettyPrint();</script>
@@ -217,7 +217,7 @@ export { defaultIntegratePrompt }
217
217
  <br class="clear">
218
218
 
219
219
  <footer>
220
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Tue Sep 22 2026 17:42:28 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 Wed Sep 23 2026 18:02:52 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
221
221
  </footer>
222
222
 
223
223
  <script>prettyPrint();</script>
@@ -161,7 +161,7 @@ export default runFanoutPipeline
161
161
  <br class="clear">
162
162
 
163
163
  <footer>
164
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Tue Sep 22 2026 17:42:28 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
164
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Wed Sep 23 2026 18:02:52 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
165
165
  </footer>
166
166
 
167
167
  <script>prettyPrint();</script>
@@ -183,7 +183,7 @@ export default runRolePipeline
183
183
  <br class="clear">
184
184
 
185
185
  <footer>
186
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Tue Sep 22 2026 17:42:28 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
186
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Wed Sep 23 2026 18:02:52 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
187
187
  </footer>
188
188
 
189
189
  <script>prettyPrint();</script>
@@ -154,7 +154,7 @@ export default salvageTruncatedArray
154
154
  <br class="clear">
155
155
 
156
156
  <footer>
157
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Tue Sep 22 2026 17:42:28 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
157
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Wed Sep 23 2026 18:02:52 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
158
158
  </footer>
159
159
 
160
160
  <script>prettyPrint();</script>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "w-dispatch-ai",
3
- "version": "1.0.33",
3
+ "version": "1.0.35",
4
4
  "main": "dist/w-dispatch-ai.umd.js",
5
5
  "dependencies": {
6
6
  "wsemi": "^1.9.3"
@@ -19,6 +19,16 @@ import dfTimeoutMs from './dfTimeoutMs.mjs'
19
19
  //
20
20
  // 【認證】沿用Claude Code既有登入狀態(帳號層級),不需也不支援逐次注入API key,
21
21
  // 故本轉接器無key參數——輪替時它是「一個獨立的供應商」而非「另一把金鑰」。
22
+ //
23
+ // 【勿帶--bare, 並留意其未來成為-p預設(2026-09-23查官方headless文件)】文件原文:
24
+ // 「--bare is the recommended mode for scripted and SDK calls, and will become the default for -p in a
25
+ // future release」且「In bare mode, Claude Code never reads OAuth credentials or the system keychain」。
26
+ // 本轉接器靠訂閱登入(OAuth), 故extraArgs勿帶--bare(會直接認證失敗); 若日後某版把-p預設改為bare,
27
+ // 所有訂閱條目會一併失效——徵狀為非零離開碼, 失敗訊息印在stdout而非stderr(官方: 執行期失敗如未登入
28
+ // 以result印在stdout)。2.1.280尚無退出bare之旗標, 無法預先處理; 每次升級Claude Code後跑一次claude
29
+ // 條目即可偵測, 屆時再查該版之退出方式調整此處。
30
+ // 另兩點同日查證: stdin傳入之prompt上限10MB(超過即非零離開, 見@param prompt);
31
+ // 2.1.280起Pro/Team Standard方案未給model時預設由Sonnet改為Opus(只影響未帶--model之呼叫)。
22
32
 
23
33
 
24
34
  //預設值
@@ -41,10 +51,10 @@ let OWN_KEYS = ['exe', 'model', 'skipPermissions', 'extraArgs', 'input']
41
51
  * 可給予skipPermissions為false保留CLI權限閘門;
42
52
  * 本函數不會reject,一律以結果物件之ok與error欄位回報成敗
43
53
  *
44
- * @param {String} prompt 輸入提示詞字串,一律以stdin傳入子進程
54
+ * @param {String} prompt 輸入提示詞字串,一律以stdin傳入子進程(Claude Code之stdin上限10MB,超過即非零離開)
45
55
  * @param {Object} [opt={}] 輸入設定物件,預設{}
46
56
  * @param {String} [opt.exe='claude'] 輸入claude執行檔名稱或絕對路徑字串,給予名稱時由execCli自系統PATH解析,預設'claude'
47
- * @param {String} [opt.model=''] 輸入模型別名或模型ID字串,例如'sonnet'、'opus',預設''代表不帶`--model`旗標
57
+ * @param {String} [opt.model=''] 輸入模型別名或模型ID字串,例如'claude-opus-5-5'(全名, 固定版本)、'opus'或'sonnet'(別名, 隨CLI指向最新版),預設''代表不帶`--model`旗標,由CLI依方案決定(2.1.280起Pro/Team Standard亦預設Opus)
48
58
  * @param {Boolean} [opt.skipPermissions=true] 輸入是否帶`--dangerously-skip-permissions`旗標布林值,false代表保留CLI權限閘門,預設true
49
59
  * @param {Array} [opt.extraArgs=[]] 輸入額外命令列旗標字串陣列,將接於固定旗標之後,預設[]
50
60
  * @param {Number} [opt.timeoutMs=300000] 輸入逾時毫秒正整數,逾時將強制關閉子進程及其子孫程序,全套件統一預設300000
@@ -54,7 +54,7 @@ let OWN_KEYS = ['exe', 'model', 'sandbox', 'extraArgs', 'input']
54
54
  * @param {String} prompt 輸入提示詞字串,一律以stdin傳入子進程
55
55
  * @param {Object} [opt={}] 輸入設定物件,預設{}
56
56
  * @param {String} [opt.exe='codex'] 輸入codex執行檔名稱或絕對路徑字串,給予名稱時由execCli自系統PATH解析,預設'codex'
57
- * @param {String} [opt.model=''] 輸入模型ID字串,例如'gpt-5.6-luna',預設''代表不帶`-m`旗標
57
+ * @param {String} [opt.model=''] 輸入模型ID字串,例如'gpt-5.6-luna'、'gpt-6-sol',預設''代表不帶`-m`旗標
58
58
  * @param {String} [opt.sandbox='workspace-write'] 輸入沙箱模式字串,例如'read-only'、'workspace-write'、'danger-full-access',預設'workspace-write'
59
59
  * @param {Array} [opt.extraArgs=[]] 輸入額外命令列旗標字串陣列,例如['--config', 'model_reasoning_effort="max"'],將接於固定旗標之後,預設[]
60
60
  * @param {Number} [opt.timeoutMs=300000] 輸入逾時毫秒正整數,逾時將強制關閉子進程及其子孫程序,全套件統一預設300000
@@ -1,3 +1,4 @@
1
+ import path from 'path'
1
2
  import get from 'lodash-es/get.js'
2
3
  import omit from 'lodash-es/omit.js'
3
4
  import isobj from 'wsemi/src/isobj.mjs'
@@ -22,16 +23,30 @@ import dfTimeoutMs from './dfTimeoutMs.mjs'
22
23
  // `if (process.env.OPENCODE_AUTH_CONTENT) return JSON.parse(...)`, 解析成功即整份取代, 不與auth.json合併;
23
24
  // 空字串為falsy、非法JSON會落回讀auth.json。auth.json位於<XDG_DATA_HOME|~/.local/share>/opencode/。
24
25
  //
25
- // 【本轉接器只驗證過opencode v1(實測至1.18.31), v2尚未支援(2026-09-22查證)】
26
- // opencode已另發布v2(當時2.0.6, npm套件@opencode/cli, 文件自成一棵樹 https://opencode.ai/v2/docs),
27
- // 官方〈Migrate from V1〉明載「OpenCode 1 and OpenCode 2 both use the opencode command and are no longer
28
- // installed side by side by default... the V2 curl installer replaces the V1 binary」——即升級v2會取代同名執行檔。
29
- // v2仍有`opencode run`(文件列為自動化用途)與`--agent`, 但至少三處與本轉接器之假設不同, 升級前須逐一實測:
26
+ // 【本轉接器只驗證過opencode v1(實測至1.18.32), v2尚未支援(2026-09-22查證, 2026-09-23補④)】
27
+ // opencode已另發布v2(2026-09-23已至2.0.15, npm套件@opencode/cli, bin另含opencode2; 文件自成一棵樹
28
+ // https://opencode.ai/v2/docs), 官方〈Migrate from V1〉明載「OpenCode 1 and OpenCode 2 both use the opencode
29
+ // command and are no longer installed side by side by default... the V2 curl installer replaces the V1 binary」
30
+ // ——即升級v2會取代同名執行檔。v1之npm套件opencode-ai(1.18.32)仍為latest且未標deprecated。
31
+ // v2仍有`opencode run`(文件列為自動化用途)與`--agent`, 但至少四處與本轉接器之假設不同, 升級前須逐一實測:
30
32
  // ① 模型旗標: v2文件一律寫`--model provider/model`, 本轉接器用`-m`(v2是否保留為別名未驗證);
31
- // ② provider設定形狀: v2改為`providers.<id>.package: "@opencode/ai/providers/openai-compatible"`,
33
+ // ② provider設定形狀: v2改為`providers.<id>.package`(單數provider→providers, npm→package並加aisdk:前綴),
32
34
  // 與本轉接器經OPENCODE_CONFIG_CONTENT注入之v1形狀(provider.<id>.npm)不同;
33
- // ③ 金鑰注入: v2憑證走/connect與新憑證庫, OPENCODE_AUTH_CONTENT是否仍被讀取未驗證。
34
- // 故本機若升v2, 請先以單一呼叫實測上述三項再調整本檔; 條目端可先以exe指定v1執行檔路徑過渡。
35
+ // ③ 金鑰注入: v2憑證走/connect與新憑證庫, OPENCODE_AUTH_CONTENT是否仍被讀取未驗證;
36
+ // ④ 權限設定形狀: v2改為單一有序`permissions`陣列, 動作亦改名(bash→shell、task→subagent、write/patch→edit),
37
+ // providers.mjs之OC_READONLY({edit:'deny', bash:'ask'})為v1形狀, 在v2不會被認得——唯讀鎖等同失效,
38
+ // 升v2前須以金絲雀(要求寫檔, 驗未落地)重驗防寫, 不可假設仍有效。
39
+ // 故本機若升v2, 請先以單一呼叫實測上述四項再調整本檔; 條目端可先以exe指定v1執行檔路徑過渡。
40
+ //
41
+ // 【cwd須同步PWD環境變數(2026-09-23實測後新增)】opencode run決定session目錄之原始碼為
42
+ // `const root = Filesystem.resolve(process.env.PWD ?? process.cwd())`(packages/opencode/src/cli/cmd/run.ts),
43
+ // 即「繼承來的PWD優先於子進程真實cwd」。Git Bash與Linux/macOS之shell皆會設PWD, 故呼叫端給opt.cwd而
44
+ // 父進程PWD指向他處時, opencode會在父進程目錄作業: 讀相對路徑落空(回NOTFOUND且ok:true, 靜默失敗),
45
+ // 允許寫檔時檔案落在父進程目錄(本專案曾因此於根目錄留下金絲雀孤兒檔, 當時誤判為「以git根目錄為準」)。
46
+ // 實測(cwd指向探針目錄, 父PWD=專案根): 不處理→working directory為專案根、讀MARKER.txt失敗;
47
+ // 注入PWD=cwd→正確; 帶--dir亦正確。採注入PWD: 不動命令列(與--attach等extraArgs無衝突), 語意即shell
48
+ // 於該目錄啟動程式時之環境; 且以本函數之值蓋過opt.env之PWD(呼叫端常整份展開process.env, 若讓其覆寫
49
+ // 則缺陷靜默回歸)。claude與codex以同組探針實測皆遵循spawn cwd, 不受影響, 故只在本轉接器處理。
35
50
  //
36
51
  // 【Zen免費層閘門與config注入(2026-09-18實測)】opencode自2026-09-17起限制免費模型只准在opencode本體內使用
37
52
  // (403 FreeTierError: free tier can only be used from within OpenCode)。CLI本身可通, 但經OPENCODE_CONFIG_CONTENT
@@ -39,6 +54,14 @@ import dfTimeoutMs from './dfTimeoutMs.mjs'
39
54
  // 少了bash即被判非opencode而403; 改permission.bash:'ask'則通過, 且非互動run下ask自動拒絕, 仍為機械防寫。
40
55
  // 詳見providers.mjs檔頭【Zen免費層閘門】與OC_READONLY常數。
41
56
  //
57
+ // 【Zen免費層429限流時run不會結束(2026-09-23實測, 1.18.32)】REST探測0.7s即回429 FreeUsageLimitError
58
+ // (Rate limit exceeded), 但`opencode run`收到同一錯誤後不退出: 預設stderr只有session標頭, 直到呼叫端逾時;
59
+ // 帶--print-logs --log-level DEBUG可見`stream error ... AI_APICallError: Rate limit exceeded`, 其後再無輸出。
60
+ // 不經本套件直接跑原始CLI亦同, 非本轉接器所致。對本套件之意義: 此情形以TIMEOUT回報(errorType 'timeout'),
61
+ // dispatchAiFallback內建之TIMEOUT冷卻觸發可涵蓋, 但每次嘗試都耗滿timeoutMs——免費oc:條目宜以
62
+ // resolveProviders之patch給較短timeoutMs; 需在stderr看到字樣(供coolDetect)時於extraArgs加
63
+ // --print-logs --log-level ERROR。
64
+ //
42
65
  // 【不讀auth.json之匿名呼叫: useStoredAuth:false(2026-09-17實測後新增)】
43
66
  // 未注入金鑰時opencode會自動沿用auth.json之登入; 該帳號工作區若未開某免費模型(如union-alpha),
44
67
  // 就回`Error: Model is disabled`, 而同一模型在「無auth.json」之機器上匿名可用——結果隨執行機器而異。
@@ -95,7 +118,7 @@ let OWN_KEYS = ['exe', 'model', 'key', 'provider', 'useStoredAuth', 'agent', 'co
95
118
  * @param {Array} [opt.extraArgs=[]] 輸入額外命令列旗標字串陣列,將接於固定旗標之後,預設[]
96
119
  * @param {Object} [opt.env=undefined] 輸入本次調用額外注入之環境變數物件,同時給予key與provider時會再併入OPENCODE_AUTH_CONTENT,預設undefined
97
120
  * @param {Number} [opt.timeoutMs=300000] 輸入逾時毫秒正整數,逾時將強制關閉子進程及其子孫程序,全套件統一預設300000
98
- * @param {String} [opt.cwd=process.cwd()] 輸入子進程工作目錄字串,預設process.cwd()
121
+ * @param {String} [opt.cwd=process.cwd()] 輸入子進程工作目錄字串,預設process.cwd();其絕對路徑並同步注入環境變數PWD(opencode以繼承之PWD優先於真實cwd決定session目錄,見檔頭),呼叫端env內之PWD會被覆寫
99
122
  * @param {String|Function} [opt.validate=undefined] 輸入stdout驗證規則字串或自訂驗證函數,規則字串支援'nonempty'、'json'、'min:100',多規則可用逗號串接,預設undefined代表不驗證
100
123
  * @param {Number} [opt.maxRetries=0] 輸入失敗後最大重試次數非負整數,預設0
101
124
  * @returns {Promise} 回傳Promise,resolve回傳結果物件,內含ok(是否成功布林值)、stdout(標準輸出字串)、stderr(標準錯誤字串)、code(離開碼)、error(錯誤訊息字串,成功時為空字串)、durationMs(耗時毫秒)、attempts(實際嘗試次數),本函數不會reject
@@ -214,6 +237,15 @@ async function dispatchOpencode(prompt, opt = {}) {
214
237
  }
215
238
  }
216
239
 
240
+ //PWD同步為有效cwd之絕對路徑(有給cwd則解析之, 否則process.cwd(), 與execCli之spawn cwd同源),
241
+ //以本值蓋過呼叫端env之PWD——opencode以繼承之PWD優先於真實cwd決定session目錄(見檔頭【cwd須同步PWD】)
242
+ let cwdEff = get(opt, 'cwd', null)
243
+ cwdEff = isestr(cwdEff) ? path.resolve(cwdEff) : process.cwd()
244
+ env = {
245
+ ...env,
246
+ PWD: cwdEff,
247
+ }
248
+
217
249
  //timeoutMs, 無效回退全套件統一預設(dfTimeoutMs=300000), 各轉接器一致令呼叫方無須記多套數字
218
250
  let timeoutMs = castPintOr(get(opt, 'timeoutMs', null), dfTimeoutMs)
219
251
 
package/src/providers.mjs CHANGED
@@ -164,11 +164,29 @@
164
164
  // bash:'ask' 而非'deny'——2026-09-18實測: Zen免費層閘門以「bash工具是否存在」判定是否為opencode本體,
165
165
  // deny會把bash自工具清單移除而被判非opencode(403 FreeTierError), ask則工具仍在;
166
166
  // 而`opencode run`為非互動, ask一律自動拒絕(stderr: The user rejected permission),
167
- // 金絲雀實測寫檔與shell建檔皆未落地, 故仍為機械鎖。注意呼叫端勿另傳--auto(會把ask放行)。
167
+ // 金絲雀實測寫檔與shell建檔皆未落地, 故仍為機械鎖(2026-09-23於1.18.32重驗仍自動拒絕)。
168
+ // 注意呼叫端勿另傳--auto(2026-09-23實測: 帶上即放行ask, 模型以node寫檔落地)。
168
169
  // agnes/poolside走opencode但非Zen免費層, 不受閘門影響, 為對稱亦用同一鎖(bash:ask之拒絕行為相同)。
169
170
  let OC_READONLY = { edit: 'deny', bash: 'ask' }
170
171
 
171
172
 
173
+ //claude條目共用之機械防寫(單一來源): 白名單而非黑名單。
174
+ // 原為黑名單['--disallowedTools','Write,Edit,NotebookEdit,Bash'], 2026-09-23金絲雀實測已失效——
175
+ // Windows版Claude Code(2.1.280)另有PowerShell工具不在黑名單內, opus-5.5與sonnet皆改用
176
+ // PowerShell之Set-Content寫檔落地(以--output-format stream-json之tool_use確認); 此外工具清單尚含
177
+ // Workflow/Task/Cron/SendMessage/Artifact等35項及claude.ai連接器之MCP工具(含create/update/delete,
178
+ // 可寫入外部服務)。黑名單每逢CLI新增工具即破, 故改白名單:
179
+ // --tools Read,Glob,Grep 只開放讀檔三工具(內建工具之白名單);
180
+ // --strict-mcp-config 排除所有MCP工具(--tools不管MCP, 單用時claude.ai Docs之寫入工具仍在)。
181
+ // 同日實測: 工具清單恰為Glob,Grep,Read; 要求寫檔(含指明可用PowerShell)未落地; 讀檔正常作答。
182
+ // 代價: 失去WebFetch/WebSearch等網路讀取工具; 需要時於條目覆寫extraArgs自行加入。
183
+ // 邊界(2026-09-23查官方permissions文件): 此鎖只管模型可用之工具; 資料夾未受信任時-p仍會執行該專案
184
+ // .claude/settings.json之hooks與env(官方列為「Used」)。於不信任之目錄派工可於條目另加
185
+ // '--setting-sources user'(同日實測可與本鎖及skip並用); 更嚴之'--restricted'須搭配skipPermissions:false
186
+ // (restricted拒絕bypassPermissions, 同日實測帶skip即報錯)。勿帶--bare(不讀OAuth登入, 見dispatchClaude檔頭)。
187
+ let CLAUDE_READONLY = ['--tools', 'Read,Glob,Grep', '--strict-mcp-config']
188
+
189
+
172
190
  let providers = [
173
191
 
174
192
  //cli版
@@ -271,11 +289,22 @@ let providers = [
271
289
  //工作流層無害(空回覆過不了validate而自動遞補); 需要寫入能力請於條目覆寫為true
272
290
  skipPermissions: false,
273
291
  },
292
+ //claude/codex各兩條: 在前者為預設(全取遞補時先試), 在後者為較強之新模型(2026-09-23使用者指示)
274
293
  {
275
294
  id: 'claude:sonnet',
276
295
  model: 'sonnet',
277
296
  kind: 'claude',
278
- extraArgs: ['--disallowedTools', 'Write,Edit,NotebookEdit,Bash'],
297
+ extraArgs: CLAUDE_READONLY,
298
+ },
299
+ {
300
+ id: 'claude:opus-5.5',
301
+ model: 'claude-opus-5-5',
302
+ kind: 'claude',
303
+ extraArgs: CLAUDE_READONLY,
304
+ //2026-09-23新增Opus 5.5(官方2026-09-22發布, API id claude-opus-5-5): Claude Code 2.1.280實測4.9~5.1s,
305
+ //以--output-format json之modelUsage確認實際服務模型為claude-opus-5-5。
306
+ //刻意寫全名而非別名'opus': 別名當下雖同樣指向5.5(同日實測), 但日後新版Opus發布時會無聲切換,
307
+ //全名可令條目行為固定。註: 走訂閱登入態, Opus之單次耗用額度高於Sonnet, 故排在sonnet之後
279
308
  },
280
309
  {
281
310
  id: 'codex:gpt-5.6-luna',
@@ -283,6 +312,16 @@ let providers = [
283
312
  kind: 'codex',
284
313
  sandbox: 'read-only',
285
314
  },
315
+ {
316
+ id: 'codex:gpt-6-sol',
317
+ model: 'gpt-6-sol',
318
+ kind: 'codex',
319
+ sandbox: 'read-only',
320
+ //2026-09-23新增GPT-6 Sol(官方定位複雜程式與agentic工作): Codex CLI 0.156.1(穩定版)實測6.3~6.9s,
321
+ //該帳號之app-server model/list已列gpt-6-sol/gpt-6-luna/gpt-6-astra; 金絲雀實測唯讀沙箱擋下寫檔。
322
+ //openai/codex issue #47420稱「僅alpha版可用」係0.154.0使用者之回報, 0.156.1已不成立;
323
+ //若本機為較舊之codex而清單無此模型, 先執行`codex update`。推理強度沿用使用者config(實測為high)
324
+ },
286
325
 
287
326
  //api版
288
327
  {
@@ -57,7 +57,7 @@ import strFindSimilar from 'wsemi/src/strFindSimilar.mjs'
57
57
  * //全取: envVar展開為keys, 缺環境變數者列入skipped
58
58
  * let { providers, table, skipped } = resolveProviders(providersAll, { env })
59
59
  * console.log(providers.length, skipped)
60
- * // => 17 []
60
+ * // => 15 []
61
61
  *
62
62
  * //pick打錯字時, missing附拼寫提示hints(最接近之可用id)
63
63
  * let rm = resolveProviders(providersAll, { env, pick: ['poolside/laguna-s-2.1'] })
@@ -23,7 +23,7 @@ import NO_SIDE_EFFECT from './noSideEffectPrefix.mjs'
23
23
  // 與金鑰無關之失敗而「整組跳過換下一家」(不換組內金鑰——同模型換金鑰仍是
24
24
  // 同樣的產出習慣); 端點不穩而偶發空回的模型, 以maxRetries調高令同鍵重試。
25
25
  //
26
- // 【防寫檔前綴】agentic CLI對cwd隔離免疫(會自行解析專案根目錄寫檔),
26
+ // 【防寫檔前綴】agentic CLI之cwd不是隔離邊界(可用絕對路徑寫到cwd外),
27
27
  // 故預設在prompt前掛「禁止建檔」約束(實測有效); 不需要時傳promptPrefix:''關閉。
28
28
  // 措辭已移至獨立模組wkf/noSideEffectPrefix.mjs(單一來源)——不經本層、
29
29
  // 直接呼叫dispatchAiFallback的呼叫端亦應引用同一份, 措辭修正時全體同步;
@@ -6,8 +6,9 @@
6
6
  // 本套件早已修正, 形成「套件修了、消費端沒跟上」的分岔。抽成獨立模組後
7
7
  // 所有路徑引用同一來源, 措辭再修正時全體同步。
8
8
  //
9
- // 【為何需要此前綴】agentic CLI自帶檔案讀寫工具且對cwd隔離免疫(會自行解析
10
- // 專案根目錄以絕對路徑寫檔), 實測會「順手」把結果另存孤兒檔,
9
+ // 【為何需要此前綴】agentic CLI自帶檔案讀寫工具, 且cwd不是隔離邊界(可用絕對路徑
10
+ // 寫到cwd外; 另opencode曾因沿用父進程之PWD而在啟動目錄作業, 2026-09-23已於
11
+ // dispatchOpencode修正, 見該檔檔頭), 實測會「順手」把結果另存孤兒檔,
11
12
  // 甚至可能覆蓋呼叫端資料本體(殷鑑: 2026-08-10執行任務歷史.md遭AI覆寫、
12
13
  // 評比腳本繞過前綴又產生根目錄孤兒檔); 各家CLI的權限機制彼此不同(codex有sandbox、
13
14
  // claude有--disallowedTools、opencode有config.permission),
@@ -61,6 +61,7 @@ process.stdin.on('end', () => {
61
61
  OPENCODE_AUTH_CONTENT: process.env.OPENCODE_AUTH_CONTENT || '',
62
62
  OPENCODE_CONFIG_CONTENT: process.env.OPENCODE_CONFIG_CONTENT || '',
63
63
  FAKE_ENV: process.env.FAKE_ENV || '',
64
+ PWD: process.env.PWD || '',
64
65
  },
65
66
  }
66
67
  let end = () => {
@@ -1,4 +1,5 @@
1
1
  import assert from 'assert'
2
+ import path from 'path'
2
3
  import dispatchOpencode from '../src/dispatchOpencode.mjs'
3
4
  import createFakeCli from './tools/fakeCliForTest.mjs'
4
5
 
@@ -182,6 +183,26 @@ describe('dispatchOpencode', function() {
182
183
  assert.strict.deepEqual(r, rr)
183
184
  })
184
185
 
186
+ it('PWD同步為有效cwd之絕對路徑: 有給cwd(絕對或相對)即解析之且等於子進程真實cwd, 未給則為process.cwd()', async function() {
187
+ let t1 = await dispatchOpencode('abc', { exe: fake.exe, cwd: fake.fd })
188
+ let t2 = await dispatchOpencode('abc', { exe: fake.exe, cwd: path.relative(process.cwd(), fake.fd) })
189
+ let t3 = await dispatchOpencode('abc', { exe: fake.exe })
190
+ let o1 = JSON.parse(t1.stdout)
191
+ let o2 = JSON.parse(t2.stdout)
192
+ let o3 = JSON.parse(t3.stdout)
193
+ let r = [o1.env.PWD, o1.env.PWD === o1.cwd, o2.env.PWD, o3.env.PWD, o3.env.PWD === o3.cwd]
194
+ let rr = [path.resolve(fake.fd), true, path.resolve(fake.fd), process.cwd(), true]
195
+ assert.strict.deepEqual(r, rr)
196
+ })
197
+
198
+ it('呼叫端env內之PWD(含整份展開process.env之寫法)被有效cwd覆寫, 其餘變數保留', async function() {
199
+ let t = await dispatchOpencode('abc', { exe: fake.exe, cwd: fake.fd, env: { ...process.env, PWD: 'C:/somewhere/else', FAKE_ENV: 'x' } })
200
+ let o = JSON.parse(t.stdout)
201
+ let r = [o.env.PWD, o.env.FAKE_ENV]
202
+ let rr = [path.resolve(fake.fd), 'x']
203
+ assert.strict.deepEqual(r, rr)
204
+ })
205
+
185
206
  it('prompt以stdin傳入, 中文與多行皆完整保留', async function() {
186
207
  let prompt = '第一行中文\n第二行 with "quote" & <sym>\n第三行'
187
208
  let t = await dispatchOpencode(prompt, { exe: fake.exe })
@@ -89,7 +89,12 @@ describe('providers', function() {
89
89
  return !!perm && perm.edit === 'deny' && perm.bash === 'ask'
90
90
  }
91
91
  if (p.kind === 'claude') {
92
- return Array.isArray(p.extraArgs) && p.extraArgs.includes('--disallowedTools')
92
+ //白名單: 僅開放讀檔三工具且排除MCP; 黑名單(--disallowedTools)於2026-09-23實測已被
93
+ //Windows之PowerShell工具繞過而寫檔落地, 故不接受僅有黑名單之條目
94
+ let a = Array.isArray(p.extraArgs) ? p.extraArgs : []
95
+ let i = a.indexOf('--tools')
96
+ let allow = i >= 0 ? String(a[i + 1] || '').split(',').map((s) => s.trim()).sort().join(',') : ''
97
+ return allow === 'Glob,Grep,Read' && a.includes('--strict-mcp-config')
93
98
  }
94
99
  if (p.kind === 'codex') {
95
100
  return p.sandbox === 'read-only'