@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 +19 -0
- package/assets/innies-catalog.json +26 -13
- package/bin/innies-config.js +127 -12
- package/bin/innies.js +97 -4
- package/package.json +5 -5
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
|
}
|
package/bin/innies-config.js
CHANGED
|
@@ -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
|
-
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
|
|
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 =
|
|
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 (
|
|
393
|
-
updated = `${updated}\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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
214
|
-
// fresh install with env vars set
|
|
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.
|
|
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.
|
|
27
|
-
"@zhiman_innies/innies-codex-darwin-arm64": "0.122.
|
|
28
|
-
"@zhiman_innies/innies-codex-win32-x64": "0.122.
|
|
29
|
-
"@zhiman_innies/innies-codex-win32-arm64": "0.122.
|
|
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
|
}
|