@zhushanwen/zcode-subagent-cli 0.4.0 → 0.4.2

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.
package/dist/index.cjs CHANGED
@@ -53,7 +53,7 @@ var ZCODE_ENGINE_ID = "zcode";
53
53
  var ZCODE_ADAPTER_VERSION = "2.0.0";
54
54
  var ZCODE_CLI_DEFAULT_PATH = "/Applications/ZCode.app/Contents/Resources/glm/zcode.cjs";
55
55
  var ZCODE_V2_CONFIG_PATH_SUFFIX = [".zcode", "v2", "config.json"];
56
- var ZCODE_FALLBACK_DEFAULT_MODEL = "builtin:bigmodel-coding-plan/GLM-5.3";
56
+ var ZCODE_PERSONAL_PROVIDER_CONFIG_PATH_SUFFIX = [".zcode", "v2", "provider_config.json"];
57
57
  var ZCODE_KILL_GRACE_MS = 5e3;
58
58
  var ZCODE_ERROR_TAIL_CHARS = 2e3;
59
59
  var ZCODE_APPSERVER_REQUEST_TIMEOUT_MS = 15e3;
@@ -180,11 +180,11 @@ var ZcodePrepareError = class extends Error {
180
180
  function isRecord(v) {
181
181
  return typeof v === "object" && v !== null && !Array.isArray(v);
182
182
  }
183
- function isProviderEntry(v) {
184
- return isRecord(v);
183
+ function strArray(v) {
184
+ return Array.isArray(v) ? v.filter((m) => typeof m === "string" && m.trim() !== "") : [];
185
185
  }
186
186
  function readSourceConfig(absPath) {
187
- const empty = { providers: /* @__PURE__ */ new Map() };
187
+ const empty = { providers: /* @__PURE__ */ new Map(), order: [] };
188
188
  let raw;
189
189
  try {
190
190
  raw = fs.readFileSync(absPath, "utf8");
@@ -198,15 +198,30 @@ function readSourceConfig(absPath) {
198
198
  return empty;
199
199
  }
200
200
  if (!isRecord(parsed)) return empty;
201
- const conf = { providers: /* @__PURE__ */ new Map() };
202
- const provider = parsed["provider"];
203
- if (isRecord(provider)) {
204
- for (const [id, entry] of Object.entries(provider)) {
205
- if (isProviderEntry(entry)) conf.providers.set(id, entry);
206
- }
207
- }
201
+ const rules = parsed["config"]?.["providerConfigRules"];
202
+ const providerRules = !isRecord(rules) ? void 0 : rules["providerRules"];
203
+ const orderSrc = parsed["config"]?.["providerOrder"];
204
+ const conf = { providers: /* @__PURE__ */ new Map(), order: strArray(orderSrc) };
205
+ if (!Array.isArray(providerRules)) return conf;
206
+ collectProviderRules(providerRules, conf);
208
207
  return conf;
209
208
  }
209
+ function collectProviderRules(providerRules, conf) {
210
+ for (const rule of providerRules) {
211
+ if (!isRecord(rule)) continue;
212
+ const id = rule["providerId"];
213
+ if (typeof id !== "string" || id.trim() === "") continue;
214
+ const cfg = isRecord(rule["config"]) ? rule["config"] : {};
215
+ const access = isRecord(cfg["access"]) ? cfg["access"] : {};
216
+ const apiKey = access["apiKey"];
217
+ const personal = strArray(cfg["personalModelIds"]);
218
+ conf.providers.set(id, {
219
+ providerName: typeof rule["providerName"] === "string" ? rule["providerName"] : void 0,
220
+ apiKey: typeof apiKey === "string" && apiKey !== "" ? apiKey : void 0,
221
+ models: personal.length > 0 ? personal : strArray(cfg["modelOrder"])
222
+ });
223
+ }
224
+ }
210
225
  function modelShort(ref) {
211
226
  return ref.slice(ref.lastIndexOf("/") + 1);
212
227
  }
@@ -217,63 +232,148 @@ function splitZcodeModelRef(modelRef) {
217
232
  return { providerId: providerOf(modelRef), modelId: modelShort(modelRef) };
218
233
  }
219
234
  function hasApiKey(entry) {
220
- const key = entry.options?.apiKey;
221
- return typeof key === "string" && key !== "";
235
+ return typeof entry.apiKey === "string" && entry.apiKey !== "";
236
+ }
237
+ function defaultPersonalProviderConfigPath() {
238
+ return path2.join(os2.homedir(), ...ZCODE_PERSONAL_PROVIDER_CONFIG_PATH_SUFFIX);
222
239
  }
223
240
  function defaultV2ConfigPath() {
224
241
  return path2.join(os2.homedir(), ...ZCODE_V2_CONFIG_PATH_SUFFIX);
225
242
  }
226
- var DEFAULT_PROVIDER_ID = "builtin:bigmodel-coding-plan";
227
- function defaultProviderForShortName(merged, withKey) {
228
- if (merged.has(DEFAULT_PROVIDER_ID)) return DEFAULT_PROVIDER_ID;
243
+ var SEMVER_SEGMENTS = 3;
244
+ function locateZcodeBuiltinCatalog(sources) {
245
+ if (sources?.builtinCatalogPath !== void 0) return sources.builtinCatalogPath;
246
+ const fromEnv = process.env["ZCODE_BUILTIN_PROVIDER_CONFIG_FILE"]?.trim();
247
+ if (fromEnv !== void 0 && fromEnv !== "") return fromEnv;
248
+ try {
249
+ const providerRoot = path2.join(os2.homedir(), ".zcode", "v2", "runtime", "provider");
250
+ const prefix = `${process.platform}-`;
251
+ const platDirs = fs.readdirSync(providerRoot, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name.startsWith(prefix)).map((d) => d.name);
252
+ const byVerDesc = (a, b) => {
253
+ const pa = a.split(".");
254
+ const pb = b.split(".");
255
+ for (let i = 0; i < SEMVER_SEGMENTS; i++) {
256
+ const na = Number.parseInt(pa[i] ?? "", 10) || 0;
257
+ const nb = Number.parseInt(pb[i] ?? "", 10) || 0;
258
+ if (na !== nb) return nb - na;
259
+ }
260
+ return 0;
261
+ };
262
+ for (const plat of platDirs) {
263
+ const rtRoot = path2.join(providerRoot, plat);
264
+ let versions = [];
265
+ try {
266
+ versions = fs.readdirSync(rtRoot).filter((v) => /^\d+\.\d+/.test(v)).sort(byVerDesc);
267
+ } catch {
268
+ continue;
269
+ }
270
+ for (const ver of versions) {
271
+ const verDir = path2.join(rtRoot, ver);
272
+ let endpoints = [];
273
+ try {
274
+ endpoints = fs.readdirSync(verDir).filter((e) => e.startsWith("endpoint-")).sort();
275
+ } catch {
276
+ continue;
277
+ }
278
+ for (const ep of endpoints) {
279
+ const candidate = path2.join(verDir, ep, "zcode-builtin.json");
280
+ if (fs.existsSync(candidate)) return candidate;
281
+ }
282
+ }
283
+ }
284
+ } catch {
285
+ return void 0;
286
+ }
287
+ return void 0;
288
+ }
289
+ function resolveZcodeMinimalReasoningLevel(modelRef, sources) {
290
+ const catalogPath = locateZcodeBuiltinCatalog(sources);
291
+ if (catalogPath === void 0) return void 0;
292
+ let parsed;
293
+ try {
294
+ parsed = JSON.parse(fs.readFileSync(catalogPath, "utf8"));
295
+ } catch {
296
+ return void 0;
297
+ }
298
+ const modelId = modelShort(modelRef);
299
+ const rules = parsed["config"];
300
+ const modelRules = isRecord(rules) && isRecord(rules["modelConfigRules"]) ? rules["modelConfigRules"]["modelRules"] : void 0;
301
+ if (!Array.isArray(modelRules)) return void 0;
302
+ return lastMatchingReasoningValues(modelRules, modelId)?.[0];
303
+ }
304
+ function lastMatchingReasoningValues(modelRules, modelId) {
305
+ let values;
306
+ for (const rule of modelRules) {
307
+ if (!isRecord(rule)) continue;
308
+ const match = rule["modelMatch"];
309
+ if (typeof match !== "string") continue;
310
+ let re;
311
+ try {
312
+ re = new RegExp(match);
313
+ } catch {
314
+ continue;
315
+ }
316
+ if (!re.test(modelId)) continue;
317
+ const spec = rule["config"]?.["optionSpecs"];
318
+ const level = isRecord(spec) ? spec["reasoningLevel"] : void 0;
319
+ const vals = isRecord(level) ? strArray(level["values"]) : [];
320
+ if (vals.length > 0) values = vals;
321
+ }
322
+ return values;
323
+ }
324
+ function defaultProviderForShortName(conf, withKey) {
325
+ const ordered = conf.order.find((id) => {
326
+ const entry = conf.providers.get(id);
327
+ return entry !== void 0 && hasApiKey(entry);
328
+ });
329
+ if (ordered !== void 0) return ordered;
229
330
  return withKey[0][0];
230
331
  }
231
332
  function resolveZcodeModelRef(requested, sources) {
232
- const v2 = readSourceConfig(sources?.v2ConfigPath ?? defaultV2ConfigPath());
233
- const merged = v2.providers;
234
- const withKey = [...merged.entries()].filter(([, e]) => hasApiKey(e));
333
+ const srcPath = sources?.personalProviderConfigPath ?? defaultPersonalProviderConfigPath();
334
+ const conf = readSourceConfig(srcPath);
335
+ const withKey = [...conf.providers.entries()].filter(([, e]) => hasApiKey(e));
235
336
  const wanted = requested?.trim() || void 0;
236
- const target = wanted ?? ZCODE_FALLBACK_DEFAULT_MODEL;
237
337
  if (withKey.length === 0) {
238
- const hint = wanted ? `model="${wanted}" \u4E0D\u80FD\u6821\u9A8C` : `\u9ED8\u8BA4\u6A21\u578B "${target}" \u4E0D\u80FD\u6821\u9A8C`;
338
+ const hint = wanted ? `model="${wanted}" \u4E0D\u80FD\u6821\u9A8C` : "\u7F3A\u7701\u6A21\u578B\u89E3\u6790\u4E0D\u80FD\u6821\u9A8C";
239
339
  throw new ZcodePrepareError(
240
340
  "engine_credential_missing",
241
- `zcode \u5F15\u64CE\u627E\u4E0D\u5230\u4EFB\u4F55\u5E26 apiKey \u7684 provider\uFF08${hint}\uFF09\u3002\u5DF2\u8BFB\u6E90\uFF1A${sources?.v2ConfigPath ?? defaultV2ConfigPath()}\u3002\u6062\u590D\u6307\u5F15\uFF1A\u5148\u5728 ZCode \u684C\u9762\u7AEF\u767B\u5F55\u5E76\u914D\u7F6E provider\uFF08v2 config \u5185\u5B58\u5728\u542B apiKey \u7684\u6761\u76EE\uFF09\u540E\u91CD\u8BD5\uFF1B\u51ED\u636E\u914D\u7F6E\u8BF4\u660E\u89C1 docs/research/agent-engine-zcode.md\u3002`
341
+ `zcode \u5F15\u64CE\u627E\u4E0D\u5230\u4EFB\u4F55\u5E26 apiKey \u7684\u4E2A\u4EBA provider\uFF08${hint}\uFF09\u3002\u5DF2\u8BFB\u6E90\uFF1A${srcPath}\u3002\u6062\u590D\u6307\u5F15\uFF1A\u5728 ZCode \u684C\u9762\u7AEF\u914D\u7F6E\u81F3\u5C11\u4E00\u4E2A\u4E2A\u4EBA provider\uFF08provider_config.json \u5185\u5B58\u5728 config.access.apiKey \u975E\u7A7A\u6761\u76EE\uFF09\u540E\u91CD\u8BD5\uFF1Bplan \u5957\u9910\u65CF\uFF08builtin:/account: \u524D\u7F00\uFF09\u7ECF\u8D26\u53F7 entitlement \u95E8\u63A7\uFF0C\u5916\u90E8 spawn \u7684\u5F15\u64CE\u6CE8\u518C\u8868\u4E0D\u88C5\u8F7D\uFF0C\u4E0D\u53EF\u4F5C\u4E3A\u5F15\u64CE\u6A21\u578B\u3002`
242
342
  );
243
343
  }
244
- const provider = target.includes("/") ? providerOf(target) : defaultProviderForShortName(merged, withKey);
245
- const short = modelShort(target);
246
- const entry = merged.get(provider);
344
+ if (wanted === void 0) return "";
345
+ const provider = wanted.includes("/") ? providerOf(wanted) : defaultProviderForShortName(conf, withKey);
346
+ const short = modelShort(wanted);
347
+ const entry = conf.providers.get(provider);
247
348
  if (!entry) {
248
349
  const known = withKey.map(([id]) => id).join(", ");
249
350
  throw new ZcodePrepareError(
250
351
  "model_not_available",
251
- `\u672A\u77E5 provider "${provider}"\uFF08\u5E26\u51ED\u636E\u7684 provider: ${known}\uFF09\u3002\u6062\u590D\u6307\u5F15\uFF1A\u6539\u7528\u4E0A\u8FF0 provider \u4E4B\u4E00\uFF08\u5168\u540D provider/model\uFF09\uFF0C\u6216\u5148\u5728 ZCode \u684C\u9762\u7AEF\u914D\u7F6E\u8BE5 provider \u540E\u91CD\u8BD5\u3002`
352
+ `\u672A\u77E5 provider "${provider}"\uFF08\u5E26\u51ED\u636E\u7684\u4E2A\u4EBA provider: ${known}\uFF09\u3002\u6062\u590D\u6307\u5F15\uFF1A\u6539\u7528\u4E0A\u8FF0 provider \u4E4B\u4E00\uFF08\u5168\u540D provider/model\uFF09\uFF0C\u6216\u5728 ZCode \u684C\u9762\u7AEF\u914D\u7F6E\u8BE5 provider \u540E\u91CD\u8BD5\uFF1B\u7701\u7565 model \u53C2\u6570\u5219\u8D70\u5F15\u64CE\u7F3A\u7701\u89E3\u6790\uFF08CLI \u81EA\u8EAB\u9009\u53D6\u53EF\u7528\u6A21\u578B\uFF09\u3002`
252
353
  );
253
354
  }
254
355
  if (!hasApiKey(entry)) {
255
356
  throw new ZcodePrepareError(
256
357
  "engine_credential_missing",
257
- `provider "${provider}" \u5B58\u5728\u4F46\u672A\u914D\u7F6E apiKey\u3002\u6062\u590D\u6307\u5F15\uFF1A\u5728 ZCode \u684C\u9762\u7AEF\u4E3A\u8BE5 provider \u767B\u5F55\u914D\u7F6E\u51ED\u636E\uFF0C\u6216\u6539\u7528\u5DF2\u914D\u51ED\u636E\u7684 provider\uFF08${withKey.map(([id]) => id).join(", ")}\uFF09\u540E\u91CD\u8BD5\u3002`
358
+ `provider "${provider}" \u5B58\u5728\u4F46\u672A\u914D\u7F6E apiKey\u3002\u6062\u590D\u6307\u5F15\uFF1A\u5728 ZCode \u684C\u9762\u7AEF\u4E3A\u8BE5 provider \u914D\u7F6E\u51ED\u636E\uFF0C\u6216\u6539\u7528\u5DF2\u914D\u51ED\u636E\u7684 provider\uFF08${withKey.map(([id]) => id).join(", ")}\uFF09\u540E\u91CD\u8BD5\u3002`
258
359
  );
259
360
  }
260
- const models = Object.keys(entry.models ?? {});
361
+ const models = entry.models ?? [];
261
362
  if (models.length > 0 && !models.includes(short)) {
262
363
  throw new ZcodePrepareError(
263
364
  "model_not_available",
264
- `\u672A\u77E5\u6A21\u578B "${short}"\uFF08provider ${provider} \u4E0B\u53EF\u7528: ${models.join(", ")}\uFF09\u3002\u6062\u590D\u6307\u5F15\uFF1A\u6539\u7528\u8BE5 provider \u4E0B\u7684\u6A21\u578B\uFF0C\u6216\u5148\u5728 ZCode \u684C\u9762\u7AEF\u542F\u7528\u76EE\u6807\u6A21\u578B\u540E\u91CD\u8BD5\u3002`
365
+ `\u672A\u77E5\u6A21\u578B "${short}"\uFF08provider ${provider} \u4E0B\u53EF\u7528: ${models.join(", ")}\uFF09\u3002\u6062\u590D\u6307\u5F15\uFF1A\u6539\u7528\u8BE5 provider \u4E0B\u7684\u6A21\u578B\uFF0C\u6216\u5728 ZCode \u684C\u9762\u7AEF\u542F\u7528\u76EE\u6807\u6A21\u578B\u540E\u91CD\u8BD5\u3002`
265
366
  );
266
367
  }
267
368
  return `${provider}/${short}`;
268
369
  }
269
370
  function listZcodeModels(sources) {
270
- const v2 = readSourceConfig(sources?.v2ConfigPath ?? defaultV2ConfigPath());
371
+ const conf = readSourceConfig(sources?.personalProviderConfigPath ?? defaultPersonalProviderConfigPath());
271
372
  const out = [];
272
- for (const [pid, entry] of v2.providers) {
373
+ for (const [pid, entry] of conf.providers) {
273
374
  if (!hasApiKey(entry)) continue;
274
- const providerName = typeof entry["name"] === "string" && entry["name"].trim() !== "" ? entry["name"].trim() : void 0;
275
- const models = Object.keys(entry.models ?? {});
276
- for (const model of models) {
375
+ const providerName = entry.providerName !== void 0 && entry.providerName.trim() !== "" ? entry.providerName.trim() : void 0;
376
+ for (const model of entry.models ?? []) {
277
377
  out.push({
278
378
  id: `${pid}/${model}`,
279
379
  ...providerName !== void 0 ? { name: `${providerName} \xB7 ${model}` } : {}
@@ -301,13 +401,13 @@ const os = require('node:os');
301
401
  const CLI_PATH = process.env.ZCODE_ENG_CLI_PATH;
302
402
  const V2_PATH = process.env.ZCODE_ENG_V2_CONFIG || path.join(os.homedir(), '.zcode', 'v2', 'config.json');
303
403
  const CONFIG_PATH = path.join(os.homedir(), '.zcode', 'cli', 'config.json');
304
- // \u4E0E constants.ts ZCODE_FALLBACK_DEFAULT_MODEL \u53CC\u6E90\uFF08\u5185\u5D4C\u5B57\u7B26\u4E32\u65E0\u6CD5 import\uFF09\u2014\u2014\u6539\u90A3\u8FB9\u987B\u540C\u6B65\u6B64\u5904
305
- const FALLBACK_MODEL_MAIN = 'builtin:bigmodel-coding-plan/GLM-5.3';
306
404
 
307
405
  // \u5408\u5E76\u5F62\u6001\uFF1A\u771F\u5B9E cli config \u539F\u6837\uFF08model/plugins/mcp/subagents \u7B49 worker \u7EE7\u627F\u9762\u4E0D\u53D8\uFF09
308
406
  // + v2 \u7684 provider \u5B57\u5178\u6CE8\u5165\uFF0C\u540C id \u65F6 v2 \u6761\u76EE\u6574\u6761\u4F18\u5148\u2014\u2014GUI \u76F4\u4F20 modelConfig \u65F6
309
407
  // v2 \u5373\u6743\u5A01\u6E90\uFF08\u8FDB\u7A0B\u5916\u7B49\u4EF7\u590D\u523B\uFF09\uFF1Bcli config \u4E0D\u5728 GUI \u7BA1\u7406\u9762\u3001\u53EF\u80FD\u6B8B\u7559\u5386\u53F2\u9A8C\u8BC1
310
408
  // \u914D\u7F6E\uFF082026-08-25 \u4E8B\u6545\uFF1A\u6B8B\u7559\u65E7 key \u538B\u8FC7\u65B0\u51ED\u636E\u81F4 turn 0 401\uFF09\uFF0C\u6545\u4E0D\u53D6 real \u4F18\u5148\u3002
409
+ // model.main \u7F3A\u5931\u65F6\u53EA\u4ECE v2 \u900F\u4F20\uFF08v2 \u4E5F\u6CA1\u6709\u5219\u4E0D\u4F2A\u9020\u2014\u2014CLI \u7F3A\u7701\u89E3\u6790\u81EA\u4F1A\u843D\u5230\u6CE8\u518C\u8868
410
+ // \u53EF\u7528\u6A21\u578B\uFF1Bplan \u5BB6\u65CF id \u5DF2\u4E0D\u53EF\u7528\uFF0C\u4E0D\u518D\u515C\u5E95\u4F2A\u9020\uFF09\u3002
311
411
  // \u8FD4\u56DE null = \u65E0\u53EF\u6CE8\u5165\u51ED\u636E\uFF08v2 \u65E0 provider \u6761\u76EE\uFF09\u2014\u2014\u4E0D patch\uFF0C\u8BA9\u539F\u751F\u62A5\u9519\u900F\u51FA\u3002
312
412
  // \u8BFB\u6E90\u7ECF readOrig \u95F4\u63A5\u53D6\uFF1Apatch \u524D\u9996\u8C03 = fs.readFileSync \u672C\u4F53\uFF1Bpatch \u540E = \u539F\u51FD\u6570
313
413
  // \uFF08\u9632 patch \u540E\u7684\u9012\u5F52\u81EA\u8C03\u7528\uFF09\u3002
@@ -335,9 +435,9 @@ function injectedConfigText() {
335
435
  if (Object.keys(injectable).length === 0) return null;
336
436
  const merged = { ...real };
337
437
  merged.provider = { ...(real.provider && typeof real.provider === 'object' ? real.provider : {}), ...injectable };
338
- if (!merged.model || typeof merged.model !== 'object' || !merged.model.main) {
339
- const main = (v2.model && v2.model.main) || FALLBACK_MODEL_MAIN;
340
- merged.model = { ...(merged.model || {}), main };
438
+ if ((!merged.model || typeof merged.model !== 'object' || !merged.model.main)
439
+ && v2.model && typeof v2.model === 'object' && v2.model.main) {
440
+ merged.model = { ...(merged.model || {}), main: v2.model.main };
341
441
  }
342
442
  return JSON.stringify(merged);
343
443
  }
@@ -435,6 +535,23 @@ if (!process.env.ZCODE_BUILTIN_PROVIDER_CONFIG_FILE) {
435
535
  }
436
536
  } catch { /* provider \u76EE\u5F55\u7F3A\u5931/\u4E0D\u53EF\u8BFB \u2192 \u4E0D\u8BBE\u952E */ }
437
537
  if (located) process.env.ZCODE_BUILTIN_PROVIDER_CONFIG_FILE = located;
538
+ // endpoint origin \u5BF9\u9F50\uFF082026-09-29 \u8FC1\u79FB\u540C\u6B65\uFF09\uFF1ACLI \u6309 ZCODE_BASE_URL \u6D3E\u751F active
539
+ // \u76EE\u5F55\u8DEF\u5F84\uFF08sha256(origin) \u524D\u7F00\uFF09\uFF1BGUI \u5B89\u88C5\u7684\u76EE\u5F55\u5728 refresh \u63A7\u5236\u6587\u4EF6\u7684 endpointKey
540
+ // \u4E0B\uFF08\u5982 https://zcode.z.ai\uFF09\uFF0C\u4E0E CLI \u7F3A\u7701 origin\uFF08bigmodel.cn\uFF09\u4E0D\u540C\u2014\u2014\u4E0D\u6CE8\u5165\u5219
541
+ // active \u8DEF\u5F84\u7B97\u5230\u4E0D\u5B58\u5728\u7684\u76EE\u5F55\uFF0C\u89E6\u53D1\u65E0\u8C13\u7684\u8054\u7F51\u91CD\u88C5\u5C1D\u8BD5\u3002\u4ECE\u5DF2\u5B9A\u4F4D\u76EE\u5F55\u7684\u90BB\u4F4D\u63A7\u5236
542
+ // \u6587\u4EF6\u8BFB\u51FA origin \u6CE8\u5165\uFF08\u65E0\u786C\u7F16\u7801\uFF09\uFF1B\u663E\u5F0F env \u4F18\u5148\uFF0C\u4E0D\u8986\u76D6\u3002
543
+ if (located && !process.env.ZCODE_BASE_URL && !process.env.ZCODE_ENDPOINT_ORIGIN) {
544
+ try {
545
+ const ctlPath = path.join(path.dirname(located), 'zcode-builtin-refresh.json');
546
+ const ctlText = fs.__origReadFileSync
547
+ ? fs.__origReadFileSync.call(fs, ctlPath, 'utf8')
548
+ : fs.readFileSync(ctlPath, 'utf8');
549
+ const ctl = JSON.parse(ctlText);
550
+ if (ctl && typeof ctl.endpointKey === 'string' && ctl.endpointKey.indexOf('http') === 0) {
551
+ process.env.ZCODE_BASE_URL = ctl.endpointKey;
552
+ }
553
+ } catch { /* \u63A7\u5236\u6587\u4EF6\u7F3A\u5931/\u635F\u574F \u2192 \u4E0D\u6CE8\u5165\uFF08CLI \u7528\u7F3A\u7701 origin \u6D3E\u751F\uFF09 */ }
554
+ }
438
555
  }
439
556
 
440
557
  // \u4E0A\u6E38 provider bootstrap \u4EE5 argv[1] \u4E3A\u5165\u53E3\u951A\uFF0C\u4ECE\u8BE5\u8DEF\u5F84\u90BB\u8FD1\u5B9A\u4F4D CLI \u5185\u5EFA
@@ -2181,7 +2298,8 @@ var ZcodeEngine = class {
2181
2298
  */
2182
2299
  async attemptAppServerTurn(task, ctx, modelRef, cwd, prompt, opts = {}) {
2183
2300
  const rt = this.ensureAppServerRuntime();
2184
- const createParams = buildAppServerCreateParams(task, modelRef, cwd);
2301
+ const reasoningLevel = modelRef !== void 0 && modelRef !== "" ? this.minimalReasoningFor(modelRef) : void 0;
2302
+ const createParams = buildAppServerCreateParams(task, modelRef, cwd, reasoningLevel);
2185
2303
  let currentSessionId;
2186
2304
  let signalSessionCreated;
2187
2305
  const sessionCreated = new Promise((resolve2) => {
@@ -2222,26 +2340,52 @@ var ZcodeEngine = class {
2222
2340
  if (ctx.signal?.aborted === true) return abortedAppServerAttempt(ctx);
2223
2341
  return parsedAppServerAttempt(task, r);
2224
2342
  } catch (err) {
2225
- if (ctx.signal?.aborted === true) return abortedAppServerAttempt(ctx);
2226
- if (err instanceof TurnTimeoutError) {
2227
- const stopPath = await this.appServerAbortChain(
2228
- rt,
2229
- turn,
2230
- () => currentSessionId,
2231
- sessionCreated,
2232
- { escalateOn: "stop-outcome" }
2233
- );
2234
- return timeoutAppServerAttempt(err, currentSessionId, stopPath, { retried: opts.retried });
2235
- }
2236
- if (!isAppServerRpcError(err) && !rt.conn.alive) {
2237
- return failedAppServerAttempt(err, currentSessionId, { retried: opts.retried, transient: "conn-closed" });
2238
- }
2239
- return failedAppServerAttempt(err, currentSessionId, { retried: opts.retried });
2343
+ return await this.classifyAppServerTurnFailure(err, ctx, {
2344
+ rt,
2345
+ turn,
2346
+ currentSessionId: () => currentSessionId,
2347
+ sessionCreated,
2348
+ retried: opts.retried
2349
+ });
2240
2350
  } finally {
2241
2351
  if (currentSessionId !== void 0) rt.activeSessions.delete(currentSessionId);
2242
2352
  if (ctx.signal !== void 0) ctx.signal.removeEventListener("abort", onAbort);
2243
2353
  }
2244
2354
  }
2355
+ /**
2356
+ * [attemptAppServerTurn 拆分] 失败分流(catch 半边):
2357
+ * - signal 已 aborted → 中止终态收口;
2358
+ * - [P0-1 U2] 超时入口(D3 v1.1):turn 已被 channel 判死 reject——升级判据不能
2359
+ * 再挂在 turn 落定上(race 恒真,killChain 结构性不可达的 v1 击穿点),改以
2360
+ * stop 应答三态裁决。await 链终局(非 fire-and-forget):outcome 止损文案与
2361
+ * 重试时序(D6,u-z4)都依赖链终局信号——止损完成前不合成终态。timeout 类
2362
+ * (idle/ceiling 都算)是 D6 明文的可重试形态——结构化标记
2363
+ * (TurnTimeoutError 类型化判据,不经字符串匹配,D4 同精神);
2364
+ * - [P0-1 U4] 连接崩溃收割形态(failAllTurns 的错误,D6 第二可重试形态)判据:
2365
+ * 非 RPC error(服务端无明确应答——有应答即精确错误归类,非瞬时崩溃面)且
2366
+ * conn 不存活。时序可靠性:catch 时刻紧随 onClose 收割,连接重建仅由
2367
+ * conn.request 惰性触发——本链路中 runTurn finally 的 closeSession 对死连接
2368
+ * 短路(channel 侧 !alive 守卫)、stop 只属 abort/超时入口(前者已被
2369
+ * signal.aborted 短路、后者走上一分支)——此刻无 request 可重建,判据可靠;
2370
+ * - 其余 → 精确错误终态。
2371
+ */
2372
+ async classifyAppServerTurnFailure(err, ctx, args) {
2373
+ if (ctx.signal?.aborted === true) return abortedAppServerAttempt(ctx);
2374
+ if (err instanceof TurnTimeoutError) {
2375
+ const stopPath = await this.appServerAbortChain(
2376
+ args.rt,
2377
+ args.turn,
2378
+ args.currentSessionId,
2379
+ args.sessionCreated,
2380
+ { escalateOn: "stop-outcome" }
2381
+ );
2382
+ return timeoutAppServerAttempt(err, args.currentSessionId(), stopPath, { retried: args.retried });
2383
+ }
2384
+ if (!isAppServerRpcError(err) && !args.rt.conn.alive) {
2385
+ return failedAppServerAttempt(err, args.currentSessionId(), { retried: args.retried, transient: "conn-closed" });
2386
+ }
2387
+ return failedAppServerAttempt(err, args.currentSessionId(), { retried: args.retried });
2388
+ }
2245
2389
  /**
2246
2390
  * killChain 后等待连接 finalize 实际完成(child 置空 + onClose 广播)再宣告链终局:
2247
2391
  * shutdown resolve 于 `exit` 事件,而 finalize 挂 `close`(stdio 排空)——两者之间的
@@ -2496,10 +2640,21 @@ var ZcodeEngine = class {
2496
2640
  }
2497
2641
  for (const ev of synthesizeCoarseEvents(payload.response, payload.usage)) emit(ev);
2498
2642
  }
2499
- /** [U7] 模型可发现性:v2 桌面登录态聚合(带凭据 provider × models),失败安全返回清单本身可能为空。 */
2643
+ /** [U7] 模型可发现性:provider_config 个人 provider 聚合(注册表实况对齐源),失败安全返回清单本身可能为空。 */
2500
2644
  listModels() {
2501
2645
  return listZcodeModels(this.deps.sources);
2502
2646
  }
2647
+ /** 显式模型的最小 reasoning 档(目录 modelRules 值域;per-modelRef 记忆化——
2648
+ * 目录文件在引擎进程生命周期内视为静态,重复 run 不重读)。 */
2649
+ reasoningMemo = /* @__PURE__ */ new Map();
2650
+ minimalReasoningFor(modelRef) {
2651
+ const memoKey = `${locateZcodeBuiltinCatalog(this.deps.sources) ?? "none"}|${modelRef}`;
2652
+ const hit = this.reasoningMemo.get(memoKey);
2653
+ if (hit !== void 0 || this.reasoningMemo.has(memoKey)) return hit;
2654
+ const level = resolveZcodeMinimalReasoningLevel(modelRef, this.deps.sources);
2655
+ this.reasoningMemo.set(memoKey, level);
2656
+ return level;
2657
+ }
2503
2658
  /**
2504
2659
  * [u-h2 D2-2] 派发同步期 model 校验:委托 resolveZcodeModelRef(与 run prepare 期
2505
2660
  * 显式路径同一函数——canonicalRef 归一化、短名缺省 provider、凭据与清单校验单一
@@ -2508,11 +2663,11 @@ var ZcodeEngine = class {
2508
2663
  *
2509
2664
  * [R4/D6-②] 缺席语义:本实现是进程内形态 + 协议诊断面(宿主 cli 形态消费的是
2510
2665
  * RemoteEngine 的 manifest 本地判定,不经本方法——SDK protocol methods.ts 明示
2511
- * validateModel 为诊断面)。缺席 modelRef 时返回 ZCODE_FALLBACK_DEFAULT_MODEL
2512
- * canonical 全名作为「引擎缺省模型」的呈现值(诊断面只读不 create,不参与
2513
- * create 缺席不携带的 G3 行为链)。帧应答形态维持 {canonicalRef: string}(SDK
2514
- * port-contract 契约必填——「帧字段缺席」的 optional 对齐需放宽 SDK 契约与
2515
- * server handler 签名,非本包单方面可完成)。
2666
+ * validateModel 为诊断面)。缺席 modelRef 时返回空串 canonical(= create 帧省略
2667
+ * model 键、CLI 自身缺省解析——2026-09-29 account 体系迁移后 plan 家族兜底 id
2668
+ * 已不可用;record 留痕侧按 R4/D6-② 空串归一为「用户未指定」条件留空)。帧应答
2669
+ * 形态维持 {canonicalRef: string}(SDK port-contract 契约必填——「帧字段缺席」
2670
+ * 的 optional 对齐需放宽 SDK 契约与 server handler 签名,非本包单方面可完成)。
2516
2671
  */
2517
2672
  validateModel(modelRef) {
2518
2673
  return { canonicalRef: resolveZcodeModelRef(modelRef, this.deps.sources) };
@@ -2788,10 +2943,13 @@ function appendSchemaRetryDirective(basePrompt, validationError) {
2788
2943
  Your previous answer failed schema validation: ${validationError}
2789
2944
  Answer again. Output ONLY the JSON value conforming to the schema above \u2014 no prose, no markdown fences, no extra text.`;
2790
2945
  }
2791
- function buildAppServerCreateParams(task, modelRef, cwd) {
2946
+ function buildAppServerCreateParams(task, modelRef, cwd, reasoningLevel) {
2792
2947
  const denyTools = (task.denyTools ?? []).filter((t) => typeof t === "string" && t.trim() !== "");
2793
2948
  const thoughtLevel = task.thinkingLevel?.trim();
2794
- const model = modelRef !== void 0 && modelRef !== "" ? splitZcodeModelRef(modelRef) : void 0;
2949
+ const model = modelRef !== void 0 && modelRef !== "" ? {
2950
+ ...splitZcodeModelRef(modelRef),
2951
+ ...reasoningLevel !== void 0 && reasoningLevel !== "" ? { options: { reasoningLevel } } : {}
2952
+ } : void 0;
2795
2953
  return {
2796
2954
  workspacePath: cwd,
2797
2955
  mode: "yolo",
@@ -2937,6 +3095,7 @@ function createDefaultZcodeEngine() {
2937
3095
  var import_subagent_engine_sdk13 = require("@zhushanwen/subagent-engine-sdk");
2938
3096
 
2939
3097
  // src/server.ts
3098
+ var logger5 = (0, import_subagent_engine_sdk14.getLogger)("subagents");
2940
3099
  var REVERSE_TIMEOUT_DEFAULT_MS = 6e4;
2941
3100
  var EngineProtocolServer = class {
2942
3101
  write;
@@ -3050,22 +3209,23 @@ var EngineProtocolServer = class {
3050
3209
  ...ctx.cwd !== void 0 ? { cwd: ctx.cwd } : {}
3051
3210
  };
3052
3211
  const ctxModel = (0, import_subagent_engine_sdk13.parseCtxModel)(ctx.ctxModel);
3053
- const stream = ctx.streamMode === "stream" ? { onDelta: (delta) => {
3054
- void this.reverseRequestInternal("host/streamDelta", { runId, delta });
3055
- } } : void 0;
3212
+ const stream = ctx.streamMode === "stream" ? {
3213
+ onDelta: (delta) => {
3214
+ void this.reverseRequestInternal("host/streamDelta", { runId, delta }).catch(this.warnReverseFailure("host/streamDelta", `run ${runId}`));
3215
+ }
3216
+ } : void 0;
3056
3217
  const runCtx = {
3057
3218
  taskId: runId,
3058
3219
  signal: controller.signal,
3059
3220
  onEvent: (event) => this.emitEvent(runId, event),
3060
3221
  ...ctxModel !== void 0 ? { ctxModel } : {},
3061
3222
  ...stream !== void 0 ? { stream } : {},
3062
- ...ctx.schemaEnv !== void 0 ? { schemaEnv: ctx.schemaEnv } : {},
3063
3223
  ...ctx.engineFallback !== void 0 ? { engineFallback: ctx.engineFallback } : {},
3064
3224
  // [U6 / §3.2.6 要点 3] resume 锚点透传(宿主 → 引擎的续聊通道:zcode 锚
3065
3225
  // sessionRef {sessionId, dbPath},引擎侧 resume 读 + 新 session 注入消费)。
3066
3226
  ...params.resume !== void 0 ? { resume: params.resume } : {},
3067
3227
  onHandleReady: (partial) => {
3068
- void this.reverseRequestInternal("host/handleReady", { runId, sessionRef: partial.sessionRef });
3228
+ void this.reverseRequestInternal("host/handleReady", { runId, sessionRef: partial.sessionRef }).catch(this.warnReverseFailure("host/handleReady", `run ${runId}`));
3069
3229
  }
3070
3230
  };
3071
3231
  try {
@@ -3105,6 +3265,19 @@ var EngineProtocolServer = class {
3105
3265
  * 应答超时兜底 reject——数据面通道(host/log 等)core 10s 应答,reject 由调用方
3106
3266
  * 吞掉留痕(数据面丢失不拖死主链路)。
3107
3267
  */
3268
+ /**
3269
+ * fire-and-forget 反向请求(`void this.reverseRequestInternal(...)` 形态)的
3270
+ * rejection 兜底:60s 超时(reverseRequestInternal 计时器)或宿主 error 帧
3271
+ * (settleReverse)都会 reject——无 handler 即 unhandled rejection(Node ≥15
3272
+ * 默认崩引擎进程)。失败只降级该次通道上报,warn 留痕不断流。
3273
+ */
3274
+ warnReverseFailure(method, context) {
3275
+ return (err) => {
3276
+ logger5.warn(`[protocol] ${method} reverse request failed (${context}); continuing without it`, {
3277
+ detail: (0, import_subagent_engine_sdk4.toErrorMessage)(err)
3278
+ });
3279
+ };
3280
+ }
3108
3281
  /** 反向请求发送(公开面:main.ts 的 host/log 桥接消费;内部 run 通道同路)。 */
3109
3282
  reverseRequest(method, params) {
3110
3283
  return this.reverseRequestInternal(method, params);
@@ -3120,7 +3293,14 @@ var EngineProtocolServer = class {
3120
3293
  if (typeof timer.unref === "function") timer.unref();
3121
3294
  this.reversePending.set(id, { resolve: resolve2, reject, timer });
3122
3295
  this.reverseClock?.started(id);
3123
- this.write({ id, method, params });
3296
+ try {
3297
+ this.write({ id, method, params });
3298
+ } catch (err) {
3299
+ this.reversePending.delete(id);
3300
+ clearTimeout(timer);
3301
+ this.reverseClock?.settled(id);
3302
+ reject(new Error(`reverse request ${method} (${id}) could not be written: ${(0, import_subagent_engine_sdk4.toErrorMessage)(err)}`));
3303
+ }
3124
3304
  });
3125
3305
  }
3126
3306
  /** 反向请求应答落位(ack 两阶段:{ack:true}/{unsupported:true}/{ok:true} 均先 ack 计时面)。 */
package/dist/index.d.cts CHANGED
@@ -49,6 +49,13 @@ declare class EngineProtocolServer {
49
49
  * 应答超时兜底 reject——数据面通道(host/log 等)core 10s 应答,reject 由调用方
50
50
  * 吞掉留痕(数据面丢失不拖死主链路)。
51
51
  */
52
+ /**
53
+ * fire-and-forget 反向请求(`void this.reverseRequestInternal(...)` 形态)的
54
+ * rejection 兜底:60s 超时(reverseRequestInternal 计时器)或宿主 error 帧
55
+ * (settleReverse)都会 reject——无 handler 即 unhandled rejection(Node ≥15
56
+ * 默认崩引擎进程)。失败只降级该次通道上报,warn 留痕不断流。
57
+ */
58
+ private warnReverseFailure;
52
59
  /** 反向请求发送(公开面:main.ts 的 host/log 桥接消费;内部 run 通道同路)。 */
53
60
  reverseRequest(method: string, params: unknown): Promise<unknown>;
54
61
  private reverseRequestInternal;
@@ -57,8 +64,12 @@ declare class EngineProtocolServer {
57
64
  }
58
65
 
59
66
  interface ZcodeSourcePaths {
60
- /** 桌面登录态 config(唯一凭据源)。缺省 ~/.zcode/v2/config.json。 */
67
+ /** 个人 provider 注册表源(app-server 注册表对齐)。缺省 ~/.zcode/v2/provider_config.json。 */
68
+ personalProviderConfigPath?: string;
69
+ /** launcher 凭据注入的 v2 config 源(ZCODE_ENG_V2_CONFIG 消费——凭据供数面,与模型校验源无关)。缺省 ~/.zcode/v2/config.json。 */
61
70
  v2ConfigPath?: string;
71
+ /** 内建 provider 目录源(reasoningLevel 值域解析)。缺省按 runtime/provider 目录扫描(与 launcher 同型)。 */
72
+ builtinCatalogPath?: string;
62
73
  }
63
74
 
64
75
  /** ZcodeEngine 构造依赖(全部可注入——测试不依赖真机 CLI/真凭据)。 */
@@ -148,6 +159,24 @@ declare class ZcodeEngine implements EnginePort {
148
159
  * retried:瞬时重试轮标记——失败文案补「已自动重试一次」句(F-1/F-4)。
149
160
  */
150
161
  private attemptAppServerTurn;
162
+ /**
163
+ * [attemptAppServerTurn 拆分] 失败分流(catch 半边):
164
+ * - signal 已 aborted → 中止终态收口;
165
+ * - [P0-1 U2] 超时入口(D3 v1.1):turn 已被 channel 判死 reject——升级判据不能
166
+ * 再挂在 turn 落定上(race 恒真,killChain 结构性不可达的 v1 击穿点),改以
167
+ * stop 应答三态裁决。await 链终局(非 fire-and-forget):outcome 止损文案与
168
+ * 重试时序(D6,u-z4)都依赖链终局信号——止损完成前不合成终态。timeout 类
169
+ * (idle/ceiling 都算)是 D6 明文的可重试形态——结构化标记
170
+ * (TurnTimeoutError 类型化判据,不经字符串匹配,D4 同精神);
171
+ * - [P0-1 U4] 连接崩溃收割形态(failAllTurns 的错误,D6 第二可重试形态)判据:
172
+ * 非 RPC error(服务端无明确应答——有应答即精确错误归类,非瞬时崩溃面)且
173
+ * conn 不存活。时序可靠性:catch 时刻紧随 onClose 收割,连接重建仅由
174
+ * conn.request 惰性触发——本链路中 runTurn finally 的 closeSession 对死连接
175
+ * 短路(channel 侧 !alive 守卫)、stop 只属 abort/超时入口(前者已被
176
+ * signal.aborted 短路、后者走上一分支)——此刻无 request 可重建,判据可靠;
177
+ * - 其余 → 精确错误终态。
178
+ */
179
+ private classifyAppServerTurnFailure;
151
180
  /**
152
181
  * killChain 后等待连接 finalize 实际完成(child 置空 + onClose 广播)再宣告链终局:
153
182
  * shutdown resolve 于 `exit` 事件,而 finalize 挂 `close`(stdio 排空)——两者之间的
@@ -224,11 +253,15 @@ declare class ZcodeEngine implements EnginePort {
224
253
  private applyRunFailedOutcome;
225
254
  /** parsed 合成终态:content/sessionId/usage 落位 + schema 校验分流 + coarse 事件。 */
226
255
  private applyParsedOutcome;
227
- /** [U7] 模型可发现性:v2 桌面登录态聚合(带凭据 provider × models),失败安全返回清单本身可能为空。 */
256
+ /** [U7] 模型可发现性:provider_config 个人 provider 聚合(注册表实况对齐源),失败安全返回清单本身可能为空。 */
228
257
  listModels(): Array<{
229
258
  id: string;
230
259
  name?: string;
231
260
  }>;
261
+ /** 显式模型的最小 reasoning 档(目录 modelRules 值域;per-modelRef 记忆化——
262
+ * 目录文件在引擎进程生命周期内视为静态,重复 run 不重读)。 */
263
+ private readonly reasoningMemo;
264
+ private minimalReasoningFor;
232
265
  /**
233
266
  * [u-h2 D2-2] 派发同步期 model 校验:委托 resolveZcodeModelRef(与 run prepare 期
234
267
  * 显式路径同一函数——canonicalRef 归一化、短名缺省 provider、凭据与清单校验单一
@@ -237,11 +270,11 @@ declare class ZcodeEngine implements EnginePort {
237
270
  *
238
271
  * [R4/D6-②] 缺席语义:本实现是进程内形态 + 协议诊断面(宿主 cli 形态消费的是
239
272
  * RemoteEngine 的 manifest 本地判定,不经本方法——SDK protocol methods.ts 明示
240
- * validateModel 为诊断面)。缺席 modelRef 时返回 ZCODE_FALLBACK_DEFAULT_MODEL
241
- * canonical 全名作为「引擎缺省模型」的呈现值(诊断面只读不 create,不参与
242
- * create 缺席不携带的 G3 行为链)。帧应答形态维持 {canonicalRef: string}(SDK
243
- * port-contract 契约必填——「帧字段缺席」的 optional 对齐需放宽 SDK 契约与
244
- * server handler 签名,非本包单方面可完成)。
273
+ * validateModel 为诊断面)。缺席 modelRef 时返回空串 canonical(= create 帧省略
274
+ * model 键、CLI 自身缺省解析——2026-09-29 account 体系迁移后 plan 家族兜底 id
275
+ * 已不可用;record 留痕侧按 R4/D6-② 空串归一为「用户未指定」条件留空)。帧应答
276
+ * 形态维持 {canonicalRef: string}(SDK port-contract 契约必填——「帧字段缺席」
277
+ * 的 optional 对齐需放宽 SDK 契约与 server handler 签名,非本包单方面可完成)。
245
278
  */
246
279
  validateModel(modelRef: string | undefined): {
247
280
  canonicalRef: string;