@zhiman_innies/innies-codex 0.122.60 → 0.122.62

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/README.md CHANGED
@@ -439,6 +439,25 @@ innies app-server # JSON-RPC + WebSocket,供 IDE/系统
439
439
  - `qwen35_35b` 模型权重**不在本仓库**,使用知满推理服务即受其商务条款约束;客户机房私有化部署的权重交付由知满实施团队处理
440
440
  - TUI 品牌字、`Zhiman` 商标属知满科技
441
441
 
442
+ ### 5. 工具默认曝光(设计取舍)
443
+
444
+ 3 个 qwen 模型在 catalog 里 `experimental_supported_tools = []`,意味着:
445
+
446
+ - ✅ **默认开**:`shell` / `exec_command` / `apply_patch`(后者需 `features.apply_patch_freeform = true`,模板已开)
447
+ - ❌ **默认禁**:`list_dir`、`read_file` 等独立工具;模型遇到目录/文件读取需求时会自动 fallback 到 `shell ls` / `shell cat`,功能等价但 token 略多
448
+
449
+ 如确需 `list_dir` 作为独立工具暴露给模型(例如对延迟敏感的批量目录扫描场景),编辑 `~/.inniescoder/catalog.json`:
450
+
451
+ ```jsonc
452
+ {
453
+ "slug": "qwen36_27b",
454
+ // ... 其他字段
455
+ "experimental_supported_tools": ["list_dir"] // 加入想曝光的工具名
456
+ }
457
+ ```
458
+
459
+ > 这是**有意保留**的默认取舍 — 既能维持 prompt 简洁、避免 qwen 模型在工具选择上 over-think,又给需要的用户留出 catalog-level 配置开关。
460
+
442
461
  ---
443
462
 
444
463
  ## 文档
@@ -231,7 +231,8 @@
231
231
  "pro",
232
232
  "team"
233
233
  ],
234
- "supports_reasoning_summaries": true
234
+ "supports_reasoning_summaries": true,
235
+ "hidden": true
235
236
  },
236
237
  {
237
238
  "support_verbosity": true,
@@ -305,7 +306,8 @@
305
306
  "pro",
306
307
  "team"
307
308
  ],
308
- "supports_reasoning_summaries": true
309
+ "supports_reasoning_summaries": true,
310
+ "hidden": true
309
311
  },
310
312
  {
311
313
  "support_verbosity": false,
@@ -380,7 +382,8 @@
380
382
  "pro",
381
383
  "team"
382
384
  ],
383
- "supports_reasoning_summaries": true
385
+ "supports_reasoning_summaries": true,
386
+ "hidden": true
384
387
  },
385
388
  {
386
389
  "support_verbosity": false,
@@ -448,7 +451,8 @@
448
451
  "pro",
449
452
  "team"
450
453
  ],
451
- "supports_reasoning_summaries": true
454
+ "supports_reasoning_summaries": true,
455
+ "hidden": true
452
456
  },
453
457
  {
454
458
  "support_verbosity": false,
@@ -512,7 +516,8 @@
512
516
  "pro",
513
517
  "team"
514
518
  ],
515
- "supports_reasoning_summaries": true
519
+ "supports_reasoning_summaries": true,
520
+ "hidden": true
516
521
  },
517
522
  {
518
523
  "support_verbosity": true,
@@ -580,7 +585,8 @@
580
585
  "pro",
581
586
  "team"
582
587
  ],
583
- "supports_reasoning_summaries": true
588
+ "supports_reasoning_summaries": true,
589
+ "hidden": true
584
590
  },
585
591
  {
586
592
  "support_verbosity": true,
@@ -644,7 +650,8 @@
644
650
  "pro",
645
651
  "team"
646
652
  ],
647
- "supports_reasoning_summaries": true
653
+ "supports_reasoning_summaries": true,
654
+ "hidden": true
648
655
  },
649
656
  {
650
657
  "support_verbosity": false,
@@ -708,7 +715,8 @@
708
715
  "pro",
709
716
  "team"
710
717
  ],
711
- "supports_reasoning_summaries": true
718
+ "supports_reasoning_summaries": true,
719
+ "hidden": true
712
720
  },
713
721
  {
714
722
  "support_verbosity": true,
@@ -776,7 +784,8 @@
776
784
  "pro",
777
785
  "team"
778
786
  ],
779
- "supports_reasoning_summaries": true
787
+ "supports_reasoning_summaries": true,
788
+ "hidden": true
780
789
  },
781
790
  {
782
791
  "support_verbosity": true,
@@ -836,7 +845,8 @@
836
845
  "pro",
837
846
  "team"
838
847
  ],
839
- "supports_reasoning_summaries": true
848
+ "supports_reasoning_summaries": true,
849
+ "hidden": true
840
850
  },
841
851
  {
842
852
  "support_verbosity": true,
@@ -896,7 +906,8 @@
896
906
  "pro",
897
907
  "team"
898
908
  ],
899
- "supports_reasoning_summaries": true
909
+ "supports_reasoning_summaries": true,
910
+ "hidden": true
900
911
  },
901
912
  {
902
913
  "support_verbosity": false,
@@ -956,7 +967,8 @@
956
967
  "pro",
957
968
  "team"
958
969
  ],
959
- "supports_reasoning_summaries": true
970
+ "supports_reasoning_summaries": true,
971
+ "hidden": true
960
972
  },
961
973
  {
962
974
  "support_verbosity": false,
@@ -1016,7 +1028,8 @@
1016
1028
  "pro",
1017
1029
  "team"
1018
1030
  ],
1019
- "supports_reasoning_summaries": true
1031
+ "supports_reasoning_summaries": true,
1032
+ "hidden": true
1020
1033
  }
1021
1034
  ]
1022
1035
  }
@@ -30,13 +30,24 @@ const INSTALL_SUPERPOWERS_ENV = "INNIES_INSTALL_SUPERPOWERS";
30
30
  const SUPERPOWERS_MARKER_FILENAME = ".innies-superpowers.marker";
31
31
  const ZHIMAN_35B_PROVIDER_HEADER = "[model_providers.zhiman_35b]";
32
32
  const ZHIMAN_27B_PROVIDER_HEADER = "[model_providers.zhiman_27b]";
33
+ // N-BUG8 (2026-06-15): the Rust `bailian` factory only recognises the
34
+ // block header `[model_providers.bailian]`. We previously emitted
35
+ // `[model_providers.dashscope]` as a user-facing alias, but the factory
36
+ // resolved its own name (`bailian`) and silently 401'd at api.openai.com
37
+ // when the user relied on the env-var fallback path. The canonical
38
+ // header is now `bailian`; `dashscope` is kept ONLY as a migration
39
+ // source for the rename-and-rewrite logic in `normalizeInniesConfig`.
33
40
  const DASHSCOPE_PROVIDER_HEADER = "[model_providers.dashscope]";
34
- // Default `wire_api` emitted in freshly generated provider blocks. Both
35
- // the private vLLM and the DashScope public cloud only expose the
36
- // OpenAI Chat Completions endpoint (`/v1/chat/completions`); the
37
- // Responses API is not available on either, so chat_completions is
38
- // the only correct default.
39
- const DEFAULT_PROVIDER_WIRE_API = "chat_completions";
41
+ const BAILIAN_PROVIDER_HEADER = "[model_providers.bailian]";
42
+ // Default `wire_api` emitted in freshly generated provider blocks. The
43
+ // user's documented production endpoint for both the private vLLM
44
+ // (`/v1/responses`) and DashScope public cloud is the Responses API;
45
+ // vLLM 0.7 natively serves it, and DashScope's compatible-mode
46
+ // exposes both endpoints. R12 reversed the historical chat_completions
47
+ // default after E2E verification that all 4 paths
48
+ // (ChatCompletions/Responses × 1+1/multi-step) work end-to-end on
49
+ // both providers.
50
+ const DEFAULT_PROVIDER_WIRE_API = "responses";
40
51
  const RESERVED_PROVIDER_HEADERS = Object.freeze([
41
52
  "[model_providers.openai]",
42
53
  "[model_providers.ollama]",
@@ -61,6 +72,15 @@ export function resolveInniesHome() {
61
72
  }
62
73
 
63
74
  export function ensureInniesHomeDefaults(homeDir) {
75
+ // N20 (2026-06-15): self-create the home dir so the library
76
+ // contract is self-contained. Previously `innies.js:55` did the
77
+ // `fs.mkdirSync` before calling us; that meant any other caller
78
+ // (a script that `import`s this module, a unit test, a future
79
+ // sub-command) had to remember the same dance or hit ENOENT on
80
+ // the very first `writeFileSync` of catalog.json. The cost is
81
+ // one extra `mkdir` on the hot path; the benefit is the API
82
+ // matches the contract advertised in the JSDoc.
83
+ fs.mkdirSync(homeDir, { recursive: true });
64
84
  const state = loadInniesState(homeDir);
65
85
  const catalogPath = path.join(homeDir, DEFAULT_CATALOG_FILENAME);
66
86
  ensureInniesCatalog(catalogPath);
@@ -372,6 +392,60 @@ function defaultInniesConfig(catalogPath, managedDefault) {
372
392
  ].join("\n");
373
393
  }
374
394
 
395
+ // N20 (2026-06-15): detect-and-peel the FIRST-RUN warning header so
396
+ // `normalizeInniesConfig` can re-emit it at the very top, matching
397
+ // `defaultInniesConfig`'s byte ordering. Without this, init 1 (the
398
+ // fresh `defaultInniesConfig` output) places the header at the top
399
+ // and the managed lines below it; on init 2+ the managed lines are
400
+ // PREPENDED (because `stripManagedRootSettings` preserves comment
401
+ // lines verbatim) and the header drifts to position 2 — producing a
402
+ // stable-but-different md5 every other round-trip. Detecting the
403
+ // header by its leading magic line and peeling the full block off
404
+ // the unmanaged tail closes the gap: init 1 and init 2+ are now
405
+ // bytewise-equal whenever the user has not edited the header.
406
+ //
407
+ // The header is owned by the code (not the user) so re-emitting it
408
+ // unconditionally is correct; the only case where we DON'T emit it
409
+ // is when the source file does not have it (e.g. a user on a
410
+ // pre-N20 install who upgrades — their file lacks the header and we
411
+ // respect that).
412
+ const FIRST_RUN_HEADER_MAGIC = "# ⚠️ FIRST-RUN SETUP REQUIRED (N20 / 2026-06-15)";
413
+ const FIRST_RUN_HEADER_OPEN = "# ============================================================================";
414
+
415
+ function peelFirstRunHeader(unmanagedBody) {
416
+ const lines = unmanagedBody.split(/\r?\n/);
417
+ // The header always opens with `# ==========...` (line 0) and has
418
+ // the magic marker on line 1. We scan forward for the closing
419
+ // `# ==========...` (line N) which terminates the block. The
420
+ // magic-line check makes the detection robust to a user
421
+ // prepending their own comment block (which would shift the
422
+ // header's position); finding the closing delimiter makes it
423
+ // robust to future edits that add/remove filler lines.
424
+ if (lines.length < 3) {
425
+ return { header: null, body: unmanagedBody };
426
+ }
427
+ if (lines[0] !== FIRST_RUN_HEADER_OPEN) {
428
+ return { header: null, body: unmanagedBody };
429
+ }
430
+ if (!lines[1] || !lines[1].includes(FIRST_RUN_HEADER_MAGIC)) {
431
+ return { header: null, body: unmanagedBody };
432
+ }
433
+ // Find closing delimiter.
434
+ let closeIdx = -1;
435
+ for (let i = 2; i < lines.length; i++) {
436
+ if (lines[i] === FIRST_RUN_HEADER_OPEN) {
437
+ closeIdx = i;
438
+ break;
439
+ }
440
+ }
441
+ if (closeIdx < 0) {
442
+ return { header: null, body: unmanagedBody };
443
+ }
444
+ const header = lines.slice(0, closeIdx + 1).join("\n");
445
+ const body = lines.slice(closeIdx + 1).join("\n").replace(/^\n+/, "");
446
+ return { header, body };
447
+ }
448
+
375
449
  function normalizeInniesConfig(contents, catalogPath, state) {
376
450
  if (contents.trim() === "") {
377
451
  return defaultInniesConfig(catalogPath, managedDefaultModel());
@@ -382,27 +456,49 @@ function normalizeInniesConfig(contents, catalogPath, state) {
382
456
  const managedDefault = isUserSelected ? null : managedDefaultModel();
383
457
  const managedSettings = ROOT_MANAGED_SETTINGS;
384
458
  const unmanagedContents = stripManagedRootSettings(contents, managedSettings).trim();
459
+ // N20 (2026-06-15): peel the FIRST-RUN warning header off the
460
+ // unmanaged tail so we can re-emit it BEFORE the managed lines
461
+ // (matching `defaultInniesConfig`'s byte ordering). See
462
+ // `peelFirstRunHeader` for the detection rationale.
463
+ const { header: firstRunHeader, body: unmanagedBody } =
464
+ peelFirstRunHeader(unmanagedContents);
385
465
  const managedLines = isUserSelected
386
466
  ? preservedUserManagedLines(contents, catalogPath)
387
467
  : managedDefault
388
468
  ? managedDefaultLines(catalogPath, managedDefault)
389
469
  : catalogOnlyManagedLines(catalogPath);
390
- let updated = `${managedLines.join("\n")}\n`;
470
+ let updated = "";
471
+ if (firstRunHeader) {
472
+ updated = `${firstRunHeader}\n\n${managedLines.join("\n")}\n`;
473
+ } else {
474
+ updated = `${managedLines.join("\n")}\n`;
475
+ }
391
476
 
392
- if (unmanagedContents !== "") {
393
- updated = `${updated}\n${unmanagedContents}\n`;
477
+ if (unmanagedBody !== "") {
478
+ updated = `${updated}\n${unmanagedBody}\n`;
394
479
  } else {
395
480
  updated = `${updated}\n`;
396
481
  }
397
482
 
398
483
  updated = normalizeManagedProviderBlocks(stripReservedProviderBlocks(updated));
484
+ // N-BUG8 (2026-06-15): migrate the legacy `[model_providers.dashscope]`
485
+ // user-facing alias to the canonical `[model_providers.bailian]`
486
+ // header that the Rust factory recognises. Also rewrite the
487
+ // `model_provider = "dashscope"` value to `"bailian"` so the
488
+ // block lookup in the Rust resolver matches the block key.
489
+ if (updated.includes(DASHSCOPE_PROVIDER_HEADER)) {
490
+ updated = updated.replace(DASHSCOPE_PROVIDER_HEADER, BAILIAN_PROVIDER_HEADER);
491
+ }
492
+ if (/(^|\n)\s*model_provider\s*=\s*"dashscope"/.test(updated)) {
493
+ updated = updated.replace(/(^|\n)(\s*model_provider\s*=\s*)"dashscope"/, '$1$2"bailian"');
494
+ }
399
495
  if (!updated.includes(ZHIMAN_35B_PROVIDER_HEADER)) {
400
496
  updated = `${updated.trimEnd()}\n\n${defaultZhiman35bProviderBlock()}`;
401
497
  }
402
498
  if (!updated.includes(ZHIMAN_27B_PROVIDER_HEADER)) {
403
499
  updated = `${updated.trimEnd()}\n\n${defaultZhiman27bProviderBlock()}`;
404
500
  }
405
- if (!updated.includes(DASHSCOPE_PROVIDER_HEADER)) {
501
+ if (!updated.includes(BAILIAN_PROVIDER_HEADER) && !updated.includes(DASHSCOPE_PROVIDER_HEADER)) {
406
502
  updated = `${updated.trimEnd()}\n\n${defaultDashscopeProviderBlock()}`;
407
503
  }
408
504
 
@@ -590,8 +686,14 @@ function normalizeManagedProviderBlocks(contents) {
590
686
  updated = normalizeProviderBlock(updated, {
591
687
  providerHeader: ZHIMAN_27B_PROVIDER_HEADER,
592
688
  });
689
+ // N-BUG8+R12 (2026-06-15): normalise the BAILIAN block (not the
690
+ // legacy DASHSCOPE alias). After the migration rename, the dashscope
691
+ // header is gone from the file — using DASHSCOPE_PROVIDER_HEADER here
692
+ // left the `wire_api` line in the freshly-rewritten bailian block
693
+ // stale, so it kept whatever the pre-migration value was (typically
694
+ // `chat_completions`) instead of being updated to the new default.
593
695
  updated = normalizeProviderBlock(updated, {
594
- providerHeader: DASHSCOPE_PROVIDER_HEADER,
696
+ providerHeader: BAILIAN_PROVIDER_HEADER,
595
697
  });
596
698
  // Migration safety net: legacy / typo'd `env_key` names for the
597
699
  // dashscope block. If a user has `env_key = "BAILIAN_API_KEY"` (the
@@ -688,6 +790,19 @@ export function _enforceDashscopeEnvKeyForTest(contents) {
688
790
  return enforceDashscopeEnvKey(contents);
689
791
  }
690
792
 
793
+ // N20 (2026-06-15): exported for unit testing in
794
+ // scripts/test_normalize_byte_stable.cjs. Asserts that
795
+ // normalizeInniesConfig re-emits the FIRST-RUN warning header at
796
+ // the top of the file (matching defaultInniesConfig's byte
797
+ // ordering) so init 1 → init 2 produces a bytewise-equal config.
798
+ export function _normalizeInniesConfigForTest(contents, catalogPath, state) {
799
+ return normalizeInniesConfig(contents, catalogPath, state);
800
+ }
801
+
802
+ export function _peelFirstRunHeaderForTest(unmanagedBody) {
803
+ return peelFirstRunHeader(unmanagedBody);
804
+ }
805
+
691
806
  function normalizeProviderBlock(contents, provider) {
692
807
  const lines = contents.split(/\r?\n/);
693
808
  const updated = [];
@@ -835,7 +950,7 @@ function defaultDashscopeProviderBlock() {
835
950
  // `request_body_extras` to inject it if you do not want to set
836
951
  // `model_reasoning_effort` at the root.
837
952
  return [
838
- DASHSCOPE_PROVIDER_HEADER,
953
+ BAILIAN_PROVIDER_HEADER,
839
954
  'name = "bailian"',
840
955
  `# base_url = "https://dashscope.aliyuncs.com/compatible-mode/v1" # FILL IN: DashScope OpenAI-compatible endpoint — keep this default if you use the public cloud`,
841
956
  `# env_key = "DASHSCOPE_API_KEY" # FILL IN: the env var NAME holding your DashScope API key (NOT the key itself) — see file header`,
package/bin/innies.js CHANGED
@@ -49,6 +49,24 @@ if (isVersionRequest(process.argv.slice(2))) {
49
49
  process.exit(0);
50
50
  }
51
51
 
52
+ // N20 fix Phase 3 (2026-06-15): parse `--model X` from argv at the
53
+ // top level so it's available before any `await` runs. Mirrors the
54
+ // slug -> provider_key mapping in `codex-rs/exec/src/lib.rs:387-392`
55
+ // (the headless-exec ingress that `--model` actually controls). We
56
+ // need this to scope the onboarding placeholder detection to the
57
+ // provider block the user *intends* to use — without it, onboarding
58
+ // warns about every unfilled block including ones the user never
59
+ // picked, which is the G1-NEW-1/4 root cause.
60
+ function parseModelArgFromArgv(argv) {
61
+ for (let i = 0; i < argv.length - 1; i++) {
62
+ if (argv[i] === "--model" || argv[i] === "-m") {
63
+ return argv[i + 1] ?? null;
64
+ }
65
+ }
66
+ return null;
67
+ }
68
+ const MODEL_CLI_ARG = parseModelArgFromArgv(process.argv.slice(2));
69
+
52
70
  const codexHome = resolveInniesHome();
53
71
  process.env[INNIES_HOME_ENV_VAR] = codexHome;
54
72
  process.env.CODEX_HOME = codexHome;
@@ -158,10 +176,72 @@ function writeExternalRuntimeSmokeResult(runtimeVersion) {
158
176
  // warning — otherwise we'd block on readline forever. The
159
177
  // detection itself is purely synchronous and never throws.
160
178
 
161
- function detectPlaceholders(configPath) {
179
+ // N20 fix Phase 3 (2026-06-15): read the top-of-file `model_provider = "..."`
180
+ // pin (the value written by `defaultInniesConfig` in innies-config.js).
181
+ // We deliberately match the FIRST non-commented, non-section occurrence —
182
+ // the template writes `model_provider = "<DEFAULT_PROVIDER>"` at the top
183
+ // of the file (see `managedDefaultLines` and the `preservedModelProvider`
184
+ // fallback path in innies-config.js:514-525).
185
+ function resolveTopPinnedProvider(configPath) {
186
+ if (!fs.existsSync(configPath)) {
187
+ return null;
188
+ }
189
+ const lines = fs.readFileSync(configPath, "utf8").split(/\r?\n/);
190
+ for (const line of lines) {
191
+ const trimmed = line.trim();
192
+ if (trimmed.startsWith("#") || trimmed.startsWith("[")) {
193
+ continue;
194
+ }
195
+ const m = trimmed.match(/^model_provider\s*=\s*"([^"]+)"\s*$/);
196
+ if (m) {
197
+ return m[1];
198
+ }
199
+ }
200
+ return null;
201
+ }
202
+
203
+ // N20 fix Phase 3 (2026-06-15): decide which provider block the user
204
+ // is *actually* about to dispatch to. Two inputs:
205
+ // 1. `--model X` from argv (wins if present — overrides the pin,
206
+ // matches `codex-rs/exec/src/lib.rs:387-392`)
207
+ // 2. top-of-file `model_provider = "..."` pin (the user's default)
208
+ //
209
+ // We apply the same slug -> provider_key static mapping that the
210
+ // Rust `--model` resolver uses so the two layers agree. Unknown
211
+ // slugs fall through to the top-of-file pin (the trust-the-pin
212
+ // path; the user is doing something custom).
213
+ function resolveSelectedProviderKey(configPath, modelCliArg) {
214
+ const topPin = resolveTopPinnedProvider(configPath);
215
+ if (!modelCliArg) {
216
+ return topPin;
217
+ }
218
+ const slugToKey = {
219
+ qwen35_35b: "zhiman_35b",
220
+ qwen36_27b: "zhiman_27b",
221
+ "qwen3.6-27b": "dashscope",
222
+ "qwen3-6-27b": "dashscope",
223
+ };
224
+ const mapped = Object.prototype.hasOwnProperty.call(slugToKey, modelCliArg)
225
+ ? slugToKey[modelCliArg]
226
+ : null;
227
+ // If we recognize the slug, return the mapped key. Otherwise trust
228
+ // the user's top-of-file pin (they may have a custom slug that
229
+ // happens to match a block name).
230
+ return mapped ?? topPin;
231
+ }
232
+
233
+ function detectPlaceholders(configPath, selectedKey) {
162
234
  if (!fs.existsSync(configPath)) {
163
235
  return [];
164
236
  }
237
+ // N20 fix Phase 3 (2026-06-15): when we cannot determine which
238
+ // block the user is about to dispatch to, return [] (conservative
239
+ // no-op). The user can still hit the N20 path inside the binary
240
+ // if their selection happens to be the unfilled one — but we
241
+ // don't want to spam warnings for blocks they're not using.
242
+ if (selectedKey === null || selectedKey === undefined) {
243
+ return [];
244
+ }
165
245
  const lines = fs.readFileSync(configPath, "utf8").split(/\r?\n/);
166
246
  const placeholders = [];
167
247
  let currentProviderHeader = null;
@@ -179,6 +259,13 @@ function detectPlaceholders(configPath) {
179
259
  const commentedBaseUrl = line.match(/^(\s*)#\s*base_url\s*=\s*"([^"]+)"\s*(#.*)?$/);
180
260
  if (commentedBaseUrl) {
181
261
  const [, indent, value, trailingComment] = commentedBaseUrl;
262
+ // N20 fix Phase 3 (2026-06-15): only flag placeholders inside
263
+ // the block the user actually selected. This is the G1-NEW-1
264
+ // root cause: previously, ALL unfilled blocks were warned
265
+ // about, including ones the user pinned-but-isn't-using.
266
+ if (currentProviderHeader !== selectedKey) {
267
+ continue;
268
+ }
182
269
  if (Object.prototype.hasOwnProperty.call(KNOWN_BASE_URL_PLACEHOLDERS, value)) {
183
270
  placeholders.push({
184
271
  lineNo: i,
@@ -207,11 +294,15 @@ async function promptOnce(rl, question) {
207
294
 
208
295
  async function maybeRunOnboardingFlow(codexHome) {
209
296
  const configPath = path.join(codexHome, "config.toml");
210
- const placeholders = detectPlaceholders(configPath);
297
+ // N20 fix Phase 3 (2026-06-15): narrow detection to the block the
298
+ // user is actually about to dispatch to. G1-NEW-1/4 root cause.
299
+ const selectedKey = resolveSelectedProviderKey(configPath, MODEL_CLI_ARG);
300
+ const placeholders = detectPlaceholders(configPath, selectedKey);
211
301
 
212
302
  if (placeholders.length === 0) {
213
- // No unfilled placeholders user already configured (or is on a
214
- // fresh install with env vars set). Nothing to do.
303
+ // No unfilled placeholders in the selected block user already
304
+ // configured it (or is on a fresh install with env vars set for
305
+ // exactly this block). Nothing to do.
215
306
  return { prompted: false, filled: 0 };
216
307
  }
217
308
 
@@ -225,6 +316,7 @@ async function maybeRunOnboardingFlow(codexHome) {
225
316
  console.warn(
226
317
  "[innies-onboarding] 检测到 config.toml 有未填的 base_url 占位符 " +
227
318
  "(N20 fresh-install 阻断)。" +
319
+ " (已自动识别选中 provider 块: \"" + selectedKey + "\" — 只检查此块)" +
228
320
  " stdin 不是 TTY,无法交互式引导 — 继续执行(可能 N20 fail)。" +
229
321
  " 三种解决方式: (1) 设置 INNIES_SKIP_ONBOARDING=1 显式跳过;" +
230
322
  " (2) 编辑 " + configPath + " 填值;" +
@@ -242,6 +334,7 @@ async function maybeRunOnboardingFlow(codexHome) {
242
334
  console.log("[innies-onboarding] 首次运行检测 (N20 fresh-install 阻断防护)");
243
335
  console.log("-----------------------------------------------------------------------------");
244
336
  console.log(`config.toml: ${configPath}`);
337
+ console.log(`(已自动识别选中 provider 块: "${selectedKey}" — 只检查此块)`);
245
338
  console.log("");
246
339
  console.log("检测到以下 provider 块的 base_url 仍是占位符(注释状态):");
247
340
  for (const p of placeholders) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhiman_innies/innies-codex",
3
- "version": "0.122.60",
3
+ "version": "0.122.62",
4
4
  "license": "Apache-2.0",
5
5
  "bin": {
6
6
  "innies": "bin/innies.js"
@@ -23,9 +23,9 @@
23
23
  "postinstall": "node bin/innies-init.js"
24
24
  },
25
25
  "optionalDependencies": {
26
- "@zhiman_innies/innies-codex-darwin-x64": "0.122.60-darwin-x64",
27
- "@zhiman_innies/innies-codex-darwin-arm64": "0.122.60-darwin-arm64",
28
- "@zhiman_innies/innies-codex-win32-x64": "0.122.60-win32-x64",
29
- "@zhiman_innies/innies-codex-win32-arm64": "0.122.60-win32-arm64"
26
+ "@zhiman_innies/innies-codex-darwin-x64": "0.122.62-darwin-x64",
27
+ "@zhiman_innies/innies-codex-darwin-arm64": "0.122.62-darwin-arm64",
28
+ "@zhiman_innies/innies-codex-win32-x64": "0.122.62-win32-x64",
29
+ "@zhiman_innies/innies-codex-win32-arm64": "0.122.62-win32-arm64"
30
30
  }
31
31
  }