lark-remote 0.5.19 → 0.5.21

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 (136) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/bridge/approval-actions.d.ts +1 -1
  3. package/dist/bridge/approval-actions.js +1 -1
  4. package/dist/bridge/bash-run.d.ts +71 -0
  5. package/dist/bridge/bash-run.js +217 -0
  6. package/dist/bridge/compact.d.ts +155 -0
  7. package/dist/bridge/compact.js +450 -0
  8. package/dist/bridge/final-usage.js +4 -4
  9. package/dist/bridge/inbound-media.js +72 -46
  10. package/dist/bridge/index.d.ts +106 -153
  11. package/dist/bridge/index.js +400 -1000
  12. package/dist/bridge/job-watch.d.ts +68 -0
  13. package/dist/bridge/job-watch.js +146 -0
  14. package/dist/bridge/queue-manager.d.ts +3 -3
  15. package/dist/bridge/queue-manager.js +10 -29
  16. package/dist/card/approval-render.d.ts +2 -2
  17. package/dist/card/approval-render.js +141 -130
  18. package/dist/card/card-budget.js +123 -98
  19. package/dist/card/card-helpers.d.ts +3 -3
  20. package/dist/card/card-helpers.js +6 -6
  21. package/dist/card/final-answer-file.js +1 -1
  22. package/dist/card/run-renderer.d.ts +3 -3
  23. package/dist/card/run-renderer.js +114 -159
  24. package/dist/card/run-state.d.ts +1 -15
  25. package/dist/card/run-state.js +358 -292
  26. package/dist/card/tool-render.d.ts +1 -1
  27. package/dist/card/tool-render.js +21 -17
  28. package/dist/card/usage-format.d.ts +22 -7
  29. package/dist/card/usage-format.js +119 -100
  30. package/dist/cli.js +15 -3
  31. package/dist/clone.js +2 -2
  32. package/dist/common/text-width.js +36 -24
  33. package/dist/common/tool-summary.js +132 -90
  34. package/dist/config/codex-config.js +130 -106
  35. package/dist/config/index.d.ts +16 -2
  36. package/dist/config/index.js +23 -8
  37. package/dist/connector/index.js +6 -6
  38. package/dist/error-classification.js +3 -2
  39. package/dist/inbound/placeholder.d.ts +1 -1
  40. package/dist/inbound/placeholder.js +1 -1
  41. package/dist/index.js +387 -281
  42. package/dist/logger/index.js +6 -6
  43. package/dist/message-handlers.d.ts +0 -5
  44. package/dist/message-handlers.js +254 -237
  45. package/dist/model-catalog.d.ts +2 -2
  46. package/dist/model-catalog.js +3 -8
  47. package/dist/platform/identity.js +1 -1
  48. package/dist/platform/path.d.ts +1 -1
  49. package/dist/platform/path.js +1 -1
  50. package/dist/platform/sleep-blocker.js +1 -1
  51. package/dist/platform/terminator-win32.js +2 -1
  52. package/dist/router/commands/ls.d.ts +3 -0
  53. package/dist/router/commands/ls.js +256 -175
  54. package/dist/router/commands/order.js +184 -159
  55. package/dist/router/commands/resume.js +190 -152
  56. package/dist/router/commands/ws.js +219 -174
  57. package/dist/router/config/dsh.d.ts +4 -8
  58. package/dist/router/config/dsh.js +3 -10
  59. package/dist/router/config/kimi.js +1 -1
  60. package/dist/router/config/types.d.ts +1 -1
  61. package/dist/router/config-diff.js +1 -1
  62. package/dist/router/index.d.ts +56 -11
  63. package/dist/router/index.js +401 -391
  64. package/dist/runner/bash/runner.js +2 -4
  65. package/dist/runner/claude/session.d.ts +1 -1
  66. package/dist/runner/claude/session.js +6 -4
  67. package/dist/runner/codex/app-server/runner.d.ts +9 -2
  68. package/dist/runner/codex/app-server/runner.js +30 -25
  69. package/dist/runner/codex/app-server/translator.d.ts +2 -5
  70. package/dist/runner/codex/app-server/translator.js +291 -238
  71. package/dist/runner/common/acp/acp-approval.d.ts +1 -1
  72. package/dist/runner/common/acp/acp-approval.js +1 -1
  73. package/dist/runner/common/acp/base-acp-runner.d.ts +5 -2
  74. package/dist/runner/common/acp/base-acp-runner.js +26 -65
  75. package/dist/runner/common/acp/base-translator.d.ts +12 -2
  76. package/dist/runner/common/acp/base-translator.js +50 -26
  77. package/dist/runner/common/acp/protocol-types.d.ts +1 -1
  78. package/dist/runner/common/acp/protocol-types.js +2 -2
  79. package/dist/runner/common/connection-based-runner.d.ts +32 -1
  80. package/dist/runner/common/connection-based-runner.js +32 -2
  81. package/dist/runner/common/jsonl-stream.js +154 -187
  82. package/dist/runner/common/jsonrpc/client.d.ts +29 -0
  83. package/dist/runner/common/jsonrpc/client.js +43 -12
  84. package/dist/runner/common/jsonrpc/connection-manager.d.ts +2 -2
  85. package/dist/runner/common/jsonrpc/connection-manager.js +4 -4
  86. package/dist/runner/common/spawning-runner.d.ts +6 -1
  87. package/dist/runner/common/spawning-runner.js +9 -4
  88. package/dist/runner/common/turn-started.d.ts +8 -0
  89. package/dist/runner/common/turn-started.js +11 -0
  90. package/dist/runner/dsh/approval.js +84 -51
  91. package/dist/runner/dsh/catalog-probe.d.ts +17 -0
  92. package/dist/runner/dsh/catalog-probe.js +10 -0
  93. package/dist/runner/dsh/client.d.ts +7 -11
  94. package/dist/runner/dsh/client.js +356 -289
  95. package/dist/runner/dsh/exit-cancel.d.ts +2 -2
  96. package/dist/runner/dsh/exit-cancel.js +2 -2
  97. package/dist/runner/dsh/inflight.d.ts +1 -1
  98. package/dist/runner/dsh/inflight.js +1 -1
  99. package/dist/runner/dsh/mux-stream.js +28 -8
  100. package/dist/runner/dsh/runner.d.ts +44 -4
  101. package/dist/runner/dsh/runner.js +296 -199
  102. package/dist/runner/dsh/translator.d.ts +1 -31
  103. package/dist/runner/dsh/translator.js +180 -178
  104. package/dist/runner/index.d.ts +1 -1
  105. package/dist/runner/kimi/acp/runner.d.ts +1 -2
  106. package/dist/runner/kimi/acp/runner.js +82 -46
  107. package/dist/runner/kimi/acp/translator.d.ts +1 -1
  108. package/dist/runner/kimi/acp/translator.js +3 -4
  109. package/dist/runner/opencode/acp/error-monitor.js +5 -5
  110. package/dist/runner/opencode/acp/runner.d.ts +2 -2
  111. package/dist/runner/opencode/acp/runner.js +4 -4
  112. package/dist/runner/pi/rpc/client.js +4 -13
  113. package/dist/runner/pi/rpc/protocol-types.d.ts +1 -1
  114. package/dist/runner/pi/rpc/protocol-types.js +1 -1
  115. package/dist/runner/pi/rpc/runner.d.ts +3 -0
  116. package/dist/runner/pi/rpc/runner.js +27 -42
  117. package/dist/runner/pi/rpc/translator.js +61 -60
  118. package/dist/runner/types.d.ts +2 -2
  119. package/dist/runner/types.js +1 -1
  120. package/dist/session/claude/sessions.d.ts +45 -0
  121. package/dist/session/claude/sessions.js +160 -122
  122. package/dist/session/codex/rollout-reader.js +181 -182
  123. package/dist/session/common/throw-session-list-error.d.ts +10 -0
  124. package/dist/session/common/throw-session-list-error.js +16 -0
  125. package/dist/session/dsh/sessions.js +291 -235
  126. package/dist/session/kimi/sessions.d.ts +40 -8
  127. package/dist/session/kimi/sessions.js +373 -301
  128. package/dist/session/opencode/sessions.d.ts +142 -1
  129. package/dist/session/opencode/sessions.js +378 -321
  130. package/dist/session/pi/sessions.js +4 -5
  131. package/dist/session/session-store.d.ts +9 -1
  132. package/dist/session/session-store.js +99 -99
  133. package/dist/update/version-check.js +2 -1
  134. package/dist/workspace/index.d.ts +1 -1
  135. package/dist/workspace/index.js +1 -1
  136. package/package.json +1 -1
@@ -52,101 +52,143 @@ export function asRecord(input) {
52
52
  }
53
53
  return null;
54
54
  }
55
- export function summarizeToolInput(name, input) {
56
- const rec = asRecord(input);
57
- if (!rec)
55
+ // --- 字段选取原语 ---
56
+ // 模块级纯函数:调用方(`summarizeToolInput`)只负责选路,字段口径各自独立。
57
+ function pickField(rec, key, max = HEADER_SUMMARY_MAX) {
58
+ const v = rec[key];
59
+ if (typeof v !== 'string')
58
60
  return '';
59
- const pick = (key, max = HEADER_SUMMARY_MAX) => {
60
- const v = rec[key];
61
- if (typeof v !== 'string')
62
- return '';
63
- const oneLine = v.replace(/\s+/g, ' ').trim();
64
- // 码点安全截断:裸 slice 切在代理对中间会留下孤立代理项进卡片
65
- // (同族修复见 tool-render.ts 的 toolHeaderText)。
66
- return truncate(oneLine, max);
67
- };
68
- /** First non-empty of several keys(DSH 同名工具跨版本改过参数名)。 */
69
- const pickAny = (keys, max = HEADER_SUMMARY_MAX) => {
70
- for (const key of keys) {
71
- const v = pick(key, max);
72
- if (v)
73
- return v;
74
- }
61
+ const oneLine = v.replace(/\s+/g, ' ').trim();
62
+ // 码点安全截断:裸 slice 切在代理对中间会留下孤立代理项进卡片
63
+ // (同族修复见 tool-render.ts 的 toolHeaderText)。
64
+ return truncate(oneLine, max);
65
+ }
66
+ /** First non-empty of several keys(DSH 同名工具跨版本改过参数名)。 */
67
+ function pickAnyField(rec, keys, max = HEADER_SUMMARY_MAX) {
68
+ for (const key of keys) {
69
+ const v = pickField(rec, key, max);
70
+ if (v)
71
+ return v;
72
+ }
73
+ return '';
74
+ }
75
+ /** 数组参数的第一个字符串项(DSH web_search 的 queries、present 的 files)。 */
76
+ function pickFirstField(rec, key, max = HEADER_SUMMARY_MAX) {
77
+ const v = rec[key];
78
+ if (typeof v === 'string')
79
+ return pickField(rec, key, max);
80
+ if (!Array.isArray(v))
75
81
  return '';
76
- };
77
- /** 数组参数的第一个字符串项(DSH web_search 的 queries、present 的 files)。 */
78
- const pickFirst = (key, max = HEADER_SUMMARY_MAX) => {
79
- const v = rec[key];
80
- if (typeof v === 'string')
81
- return pick(key, max);
82
- if (!Array.isArray(v))
83
- return '';
84
- for (const item of v) {
85
- if (typeof item === 'string' && item.trim()) {
86
- const oneLine = item.replace(/\s+/g, ' ').trim();
87
- return truncate(oneLine, max);
88
- }
89
- if (item && typeof item === 'object') {
90
- const path = item.path;
91
- if (typeof path === 'string' && path.trim())
92
- return path;
93
- }
82
+ for (const item of v) {
83
+ if (typeof item === 'string' && item.trim()) {
84
+ const oneLine = item.replace(/\s+/g, ' ').trim();
85
+ return truncate(oneLine, max);
94
86
  }
95
- return '';
96
- };
97
- switch (toolKindOf(name)) {
98
- case 'shell':
99
- return pick('command');
100
- case 'file':
101
- return pick('file_path') || pick('path');
102
- case 'grep': {
103
- const pat = pick('pattern', 40);
104
- const p = pick('path', 30);
105
- return p ? `${pat} in ${p}` : pat;
87
+ if (item && typeof item === 'object') {
88
+ const path = item.path;
89
+ if (typeof path === 'string' && path.trim())
90
+ return path;
106
91
  }
107
- case 'glob':
108
- return pick('pattern');
109
- case 'ls':
110
- return pick('path');
111
- case 'webfetch':
112
- return pick('url');
113
- case 'websearch':
114
- case 'websearch_queries':
115
- return pickFirst('queries', 60) || pick('query', 60);
116
- default:
117
- break;
118
- }
119
- switch (name) {
120
- case 'Agent':
121
- case 'Task':
122
- case 'subagent':
123
- case 'workflow':
124
- return pick('description') || pick('subagent_type');
125
- case 'todo_write':
126
- return summarizeTodos(rec);
127
- case 'job_output':
128
- case 'job_kill':
129
- return pick('job_id');
130
- case 'job_list':
131
- return '(列出任务)';
132
- case 'present':
133
- return pickFirst('files');
134
- case 'skill':
135
- return pick('name');
136
- case 'run_code':
137
- return pick('description') || pick('language');
138
- case 'terminal_send':
139
- return pick('text');
140
- case 'ask_user_question': {
141
- const questions = rec.questions;
142
- return Array.isArray(questions) ? `${questions.length} 个问题` : '';
143
- }
144
- case 'send_message':
145
- case 'interrupt_agent':
146
- return pick('description') || pick('agent_id');
147
- default:
148
- return pickAny(['command', 'file_path', 'path', 'query', 'url']);
149
92
  }
93
+ return '';
94
+ }
95
+ function shellSummary(rec) {
96
+ return pickField(rec, 'command');
97
+ }
98
+ function fileSummary(rec) {
99
+ return pickField(rec, 'file_path') || pickField(rec, 'path');
100
+ }
101
+ function grepSummary(rec) {
102
+ const pat = pickField(rec, 'pattern', 40);
103
+ const p = pickField(rec, 'path', 30);
104
+ return p ? `${pat} in ${p}` : pat;
105
+ }
106
+ function patternSummary(rec) {
107
+ return pickField(rec, 'pattern');
108
+ }
109
+ function pathSummary(rec) {
110
+ return pickField(rec, 'path');
111
+ }
112
+ function urlSummary(rec) {
113
+ return pickField(rec, 'url');
114
+ }
115
+ function queriesSummary(rec) {
116
+ return pickFirstField(rec, 'queries', 60) || pickField(rec, 'query', 60);
117
+ }
118
+ function subagentSummary(rec) {
119
+ return pickField(rec, 'description') || pickField(rec, 'subagent_type');
120
+ }
121
+ function messageSummary(rec) {
122
+ return pickField(rec, 'description') || pickField(rec, 'agent_id');
123
+ }
124
+ function jobIdSummary(rec) {
125
+ return pickField(rec, 'job_id');
126
+ }
127
+ function jobListSummary() {
128
+ return '(列出任务)';
129
+ }
130
+ function filesSummary(rec) {
131
+ return pickFirstField(rec, 'files');
132
+ }
133
+ function nameSummary(rec) {
134
+ return pickField(rec, 'name');
135
+ }
136
+ function runCodeSummary(rec) {
137
+ return pickField(rec, 'description') || pickField(rec, 'language');
138
+ }
139
+ function textSummary(rec) {
140
+ return pickField(rec, 'text');
141
+ }
142
+ function questionCountSummary(rec) {
143
+ const questions = rec.questions;
144
+ return Array.isArray(questions) ? `${questions.length} 个问题` : '';
145
+ }
146
+ function todoSummary(rec) {
147
+ return summarizeTodos(rec);
148
+ }
149
+ const FALLBACK_FIELDS = ['command', 'file_path', 'path', 'query', 'url'];
150
+ /** 类别 → 摘要器。ToolKind 的每个成员都必须有条目。 */
151
+ const KIND_SUMMARY = new Map([
152
+ ['shell', shellSummary],
153
+ ['file', fileSummary],
154
+ ['grep', grepSummary],
155
+ ['glob', patternSummary],
156
+ ['ls', pathSummary],
157
+ ['webfetch', urlSummary],
158
+ ['websearch', queriesSummary],
159
+ ['websearch_queries', queriesSummary],
160
+ ]);
161
+ /**
162
+ * 工具名 → 摘要器。用 Map 而非对象字面量:`constructor` / `toString` 这类
163
+ * 工具名会命中 Object.prototype,查表会拿到非函数值。
164
+ */
165
+ const NAME_SUMMARY = new Map([
166
+ ['Agent', subagentSummary],
167
+ ['Task', subagentSummary],
168
+ ['subagent', subagentSummary],
169
+ ['workflow', subagentSummary],
170
+ ['todo_write', todoSummary],
171
+ ['job_output', jobIdSummary],
172
+ ['job_kill', jobIdSummary],
173
+ ['job_list', jobListSummary],
174
+ ['present', filesSummary],
175
+ ['skill', nameSummary],
176
+ ['run_code', runCodeSummary],
177
+ ['terminal_send', textSummary],
178
+ ['ask_user_question', questionCountSummary],
179
+ ['send_message', messageSummary],
180
+ ['interrupt_agent', messageSummary],
181
+ ]);
182
+ export function summarizeToolInput(name, input) {
183
+ const rec = asRecord(input);
184
+ if (!rec)
185
+ return '';
186
+ const kind = toolKindOf(name);
187
+ const byKind = kind === undefined ? undefined : KIND_SUMMARY.get(kind);
188
+ if (byKind)
189
+ return byKind(rec);
190
+ const byName = NAME_SUMMARY.get(name);
191
+ return byName ? byName(rec) : pickAnyField(rec, FALLBACK_FIELDS);
150
192
  }
151
193
  /** todo_write 摘要:完成进度 + 当前 in_progress 项主题。 */
152
194
  function summarizeTodos(rec) {
@@ -392,6 +392,123 @@ function extractCustomProvider(providers) {
392
392
  const keys = Object.keys(providers);
393
393
  return keys.find((k) => k !== 'openai' && k !== 'anthropic');
394
394
  }
395
+ /**
396
+ * 管线第 1 段(IO):读 + 解析 config.toml,不做任何语义解释。
397
+ * - missing:文件不存在;
398
+ * - parsed:TOML 解析成功(内容后续由 normalizeProviders 解释);
399
+ * - invalid:读取/解析抛错(含 TOCTOU 窗口内的文件损坏)——错误在此分类收口,
400
+ * 消费方(loadCodexConfig)据此走「解析失败」兜底,绝不向上抛异常。
401
+ */
402
+ function readCodexToml(codexHome) {
403
+ const configFile = path.join(codexHome, 'config.toml');
404
+ if (!fs.existsSync(configFile))
405
+ return { state: 'missing' };
406
+ try {
407
+ const raw = fs.readFileSync(configFile, 'utf-8');
408
+ return { state: 'parsed', parsed: TOML.parse(raw) };
409
+ }
410
+ catch {
411
+ return { state: 'invalid' };
412
+ }
413
+ }
414
+ /**
415
+ * 管线第 2 段(纯转换):把已解析的 TOML + 目录上下文归一成 provider/model 事实。
416
+ * Model filtering strategy:
417
+ * - Custom provider: model from config.toml (single model)
418
+ * - openai: all bundled models
419
+ * - anthropic: bundled models with 'claude-*' prefix
420
+ */
421
+ function normalizeProviders(parsed, ctx) {
422
+ const { catalogMode, catalogSlugs, allModels, anthropicModels } = ctx;
423
+ const modelKeyRaw = typeof parsed.model === 'string' && parsed.model.length > 0 ? parsed.model : undefined;
424
+ const currentProvider = String(parsed.model_provider ?? FALLBACK_PROVIDERS[0]);
425
+ // Extract model_providers section
426
+ const modelProviders = (parsed.model_providers ?? {});
427
+ const rawProviderNames = Object.keys(modelProviders);
428
+ if (catalogMode) {
429
+ // catalog 模式:provider 列表 = 内置 openai + [model_providers.*](对齐 codex
430
+ // merge_configured_model_providers(built_in_model_providers, cfg.model_providers),
431
+ // core/src/config/mod.rs:3655;model_provider 未声明时默认 "openai",mod.rs:3659)。
432
+ // anthropic 不是 codex 内置 provider,未显式配置时不得出现。模型列表仍只来自
433
+ // 活动目录——openai+内置 gpt-5.x 的失效路径不因 provider 合并复活。
434
+ const providerNames = rawProviderNames.includes('openai')
435
+ ? rawProviderNames
436
+ : ['openai', ...rawProviderNames];
437
+ const providerEnvKeys = { openai: 'OPENAI_API_KEY' };
438
+ for (const [name, config] of Object.entries(modelProviders)) {
439
+ if (typeof config.env_key === 'string') {
440
+ providerEnvKeys[name] = config.env_key;
441
+ }
442
+ }
443
+ // 活动目录全局:codex 运行时 provider 与模型无绑定(StaticModelsManager),
444
+ // 每个 provider 下拉都展示目录全部 slug
445
+ // 活动目录不可用(命令失败/空)时回退 [currentModel]——无目录元数据时档位/能力
446
+ // 不可信;禁止泄漏 FALLBACK_MODELS/内置目录。model 键缺失时不得虚构 'o3'。
447
+ const catalogCurrentModel = modelKeyRaw ?? catalogSlugs[0] ?? '';
448
+ const providerModelList = catalogSlugs.length > 0 ? catalogSlugs : modelKeyRaw ? [modelKeyRaw] : [];
449
+ const providerModels = {};
450
+ for (const name of providerNames) {
451
+ providerModels[name] = providerModelList;
452
+ }
453
+ const resolvedProvider = typeof parsed.model_provider === 'string' && parsed.model_provider.length > 0
454
+ ? parsed.model_provider
455
+ : 'openai';
456
+ return {
457
+ currentModel: catalogCurrentModel,
458
+ currentProvider: resolvedProvider,
459
+ providerNames,
460
+ providerEnvKeys,
461
+ providerModels,
462
+ };
463
+ }
464
+ // fallback 模式:保持原行为(openai/anthropic 内置 + bundled 模型)
465
+ const currentModel = modelKeyRaw ?? catalogSlugs[0] ?? 'o3';
466
+ const providerNames = rawProviderNames;
467
+ if (!providerNames.includes('openai')) {
468
+ providerNames.unshift('openai');
469
+ }
470
+ const customProvider = extractCustomProvider(modelProviders);
471
+ const providerEnvKeys = {};
472
+ providerEnvKeys['openai'] = 'OPENAI_API_KEY';
473
+ for (const [name, config] of Object.entries(modelProviders)) {
474
+ if (typeof config.env_key === 'string') {
475
+ providerEnvKeys[name] = config.env_key;
476
+ }
477
+ }
478
+ // anthropic 显式配置但未写 env_key 时保留默认键(恢复旧版无条件映射的
479
+ // 配置场景行为;未配置时不再虚构)
480
+ if (rawProviderNames.includes('anthropic') &&
481
+ !modelProviders['anthropic']?.env_key &&
482
+ providerEnvKeys['anthropic'] === undefined) {
483
+ providerEnvKeys['anthropic'] = 'ANTHROPIC_API_KEY';
484
+ }
485
+ const providerModels = { openai: allModels };
486
+ // anthropic 仅在用户显式配置时才存在(非 codex 内置 provider)
487
+ if (rawProviderNames.includes('anthropic')) {
488
+ providerModels['anthropic'] = anthropicModels;
489
+ }
490
+ if (customProvider) {
491
+ // Custom provider: use the model from config.toml
492
+ providerModels[customProvider] = [currentModel];
493
+ }
494
+ return {
495
+ currentModel,
496
+ currentProvider,
497
+ providerNames,
498
+ providerEnvKeys,
499
+ providerModels,
500
+ };
501
+ }
502
+ /**
503
+ * 管线第 3 段(纯装配):给 provider 事实挂上 modelOptions 查询闭包,产出
504
+ * 对外的 CodexConfigResult。
505
+ */
506
+ function applyProfiles(profile) {
507
+ return {
508
+ ...profile,
509
+ modelOptions: (provider) => buildModelOptions(profile.currentModel, profile.currentProvider, provider, profile.providerModels),
510
+ };
511
+ }
395
512
  /**
396
513
  * Read codex config.toml, extract provider and model info
397
514
  *
@@ -401,8 +518,9 @@ function extractCustomProvider(providers) {
401
518
  * - anthropic: bundled models with 'claude-*' prefix
402
519
  */
403
520
  export function loadCodexConfig(opts = {}) {
521
+ // 红线:resolveCodexHome 环境变量优先级(codexHome 参数 > $CODEX_HOME > ~/.codex)
522
+ // 只在此解析一次,下游各段共用同一 codexHome,不得重复解析。
404
523
  const codexHome = resolveCodexHome(opts.codexHome);
405
- const configFile = path.join(codexHome, 'config.toml');
406
524
  const catalogMode = isCodexCatalogMode(codexHome);
407
525
  // 统一目录来源:catalog 模式 = 活动目录(model_catalog_json 整体替换内置目录);
408
526
  // 否则 = bundled(与原行为一致)
@@ -413,111 +531,21 @@ export function loadCodexConfig(opts = {}) {
413
531
  const anthropicModels = catalogSlugs.length > 0
414
532
  ? catalogSlugs.filter((m) => m.startsWith('claude-'))
415
533
  : ANTHROPIC_FALLBACK_MODELS;
416
- if (!fs.existsSync(configFile)) {
534
+ const toml = readCodexToml(codexHome);
535
+ if (toml.state === 'missing') {
417
536
  // 无 config.toml:默认模型取目录首个可用模型(codex default_model_from_available),
418
537
  // 目录不可用时回退 'o3'(FALLBACK_MODELS 首项,非 catalog 默认)
419
538
  const currentModel = allModels[0] ?? 'o3';
420
539
  // No config file: only openai has bundled models
421
- const providerModels = {
422
- openai: allModels,
423
- };
424
- return {
540
+ return applyProfiles({
425
541
  currentModel,
426
542
  currentProvider: FALLBACK_PROVIDERS[0],
427
543
  providerNames: FALLBACK_PROVIDERS,
428
544
  providerEnvKeys: { openai: 'OPENAI_API_KEY' },
429
- providerModels,
430
- modelOptions: (provider) => buildModelOptions(currentModel, FALLBACK_PROVIDERS[0], provider, providerModels),
431
- };
432
- }
433
- try {
434
- const raw = fs.readFileSync(configFile, 'utf-8');
435
- const parsed = TOML.parse(raw);
436
- const modelKeyRaw = typeof parsed.model === 'string' && parsed.model.length > 0 ? parsed.model : undefined;
437
- const currentProvider = String(parsed.model_provider ?? FALLBACK_PROVIDERS[0]);
438
- // Extract model_providers section
439
- const modelProviders = (parsed.model_providers ?? {});
440
- const rawProviderNames = Object.keys(modelProviders);
441
- let providerNames;
442
- let providerEnvKeys;
443
- let providerModels;
444
- if (catalogMode) {
445
- // catalog 模式:provider 列表 = 内置 openai + [model_providers.*](对齐 codex
446
- // merge_configured_model_providers(built_in_model_providers, cfg.model_providers),
447
- // core/src/config/mod.rs:3655;model_provider 未声明时默认 "openai",mod.rs:3659)。
448
- // anthropic 不是 codex 内置 provider,未显式配置时不得出现。模型列表仍只来自
449
- // 活动目录——openai+内置 gpt-5.x 的失效路径不因 provider 合并复活。
450
- providerNames = rawProviderNames.includes('openai')
451
- ? rawProviderNames
452
- : ['openai', ...rawProviderNames];
453
- providerEnvKeys = { openai: 'OPENAI_API_KEY' };
454
- for (const [name, config] of Object.entries(modelProviders)) {
455
- if (typeof config.env_key === 'string') {
456
- providerEnvKeys[name] = config.env_key;
457
- }
458
- }
459
- // 活动目录全局:codex 运行时 provider 与模型无绑定(StaticModelsManager),
460
- // 每个 provider 下拉都展示目录全部 slug
461
- // 活动目录不可用(命令失败/空)时回退 [currentModel]——无目录元数据时档位/能力
462
- // 不可信;禁止泄漏 FALLBACK_MODELS/内置目录。model 键缺失时不得虚构 'o3'。
463
- const catalogCurrentModel = modelKeyRaw ?? catalogSlugs[0] ?? '';
464
- const providerModelList = catalogSlugs.length > 0 ? catalogSlugs : modelKeyRaw ? [modelKeyRaw] : [];
465
- providerModels = {};
466
- for (const name of providerNames) {
467
- providerModels[name] = providerModelList;
468
- }
469
- const resolvedProvider = typeof parsed.model_provider === 'string' && parsed.model_provider.length > 0
470
- ? parsed.model_provider
471
- : 'openai';
472
- return {
473
- currentModel: catalogCurrentModel,
474
- currentProvider: resolvedProvider,
475
- providerNames,
476
- providerEnvKeys,
477
- providerModels,
478
- modelOptions: (provider) => buildModelOptions(catalogCurrentModel, resolvedProvider, provider, providerModels),
479
- };
480
- }
481
- // fallback 模式:保持原行为(openai/anthropic 内置 + bundled 模型)
482
- const currentModel = modelKeyRaw ?? catalogSlugs[0] ?? 'o3';
483
- providerNames = rawProviderNames;
484
- if (!providerNames.includes('openai')) {
485
- providerNames.unshift('openai');
486
- }
487
- const customProvider = extractCustomProvider(modelProviders);
488
- providerEnvKeys = {};
489
- providerEnvKeys['openai'] = 'OPENAI_API_KEY';
490
- for (const [name, config] of Object.entries(modelProviders)) {
491
- if (typeof config.env_key === 'string') {
492
- providerEnvKeys[name] = config.env_key;
493
- }
494
- }
495
- // anthropic 显式配置但未写 env_key 时保留默认键(恢复旧版无条件映射的
496
- // 配置场景行为;未配置时不再虚构)
497
- if (rawProviderNames.includes('anthropic') &&
498
- !modelProviders['anthropic']?.env_key &&
499
- providerEnvKeys['anthropic'] === undefined) {
500
- providerEnvKeys['anthropic'] = 'ANTHROPIC_API_KEY';
501
- }
502
- providerModels = { openai: allModels };
503
- // anthropic 仅在用户显式配置时才存在(非 codex 内置 provider)
504
- if (rawProviderNames.includes('anthropic')) {
505
- providerModels['anthropic'] = anthropicModels;
506
- }
507
- if (customProvider) {
508
- // Custom provider: use the model from config.toml
509
- providerModels[customProvider] = [currentModel];
510
- }
511
- return {
512
- currentModel,
513
- currentProvider,
514
- providerNames,
515
- providerEnvKeys,
516
- providerModels,
517
- modelOptions: (provider) => buildModelOptions(currentModel, currentProvider, provider, providerModels),
518
- };
545
+ providerModels: { openai: allModels },
546
+ });
519
547
  }
520
- catch {
548
+ if (toml.state === 'invalid') {
521
549
  // config.toml 读取/解析失败:readModelCatalogJsonPath 对此返回
522
550
  // TOML_PARSE_FAILED_SENTINEL(isCodexCatalogMode → false),因此正常路径下
523
551
  // catalogMode 恒为 false,下面的 `if (catalogMode)` 分支只在两次读取之间
@@ -533,19 +561,15 @@ export function loadCodexConfig(opts = {}) {
533
561
  modelOptions: () => [],
534
562
  };
535
563
  }
536
- const currentModel = catalogSlugs[0] ?? 'o3';
537
- const providerModels = {
538
- openai: allModels,
539
- };
540
- return {
541
- currentModel,
564
+ return applyProfiles({
565
+ currentModel: catalogSlugs[0] ?? 'o3',
542
566
  currentProvider: FALLBACK_PROVIDERS[0],
543
567
  providerNames: FALLBACK_PROVIDERS,
544
568
  providerEnvKeys: { openai: 'OPENAI_API_KEY' },
545
- providerModels,
546
- modelOptions: (provider) => buildModelOptions(currentModel, FALLBACK_PROVIDERS[0], provider, providerModels),
547
- };
569
+ providerModels: { openai: allModels },
570
+ });
548
571
  }
572
+ return applyProfiles(normalizeProviders(toml.parsed, { catalogMode, catalogSlugs, allModels, anthropicModels }));
549
573
  }
550
574
  /**
551
575
  * Build model options for a provider.
@@ -4,7 +4,7 @@ export { getConfigDir } from './dir.js';
4
4
  export declare const CLAUDE_EFFORTS: readonly ["low", "medium", "high", "xhigh", "max"];
5
5
  /**
6
6
  * Claude 的权限模式取值:'default' 之外都是 CLI `--permission-mode` 的真实枚举
7
- * (2026-09-20 实测 claude --help:acceptEdits | auto | bypassPermissions | manual
7
+ * (claude --help:acceptEdits | auto | bypassPermissions | manual
8
8
  * | dontAsk | plan)。
9
9
  * 'default' 是本项目自造的值,不是 CLI 的取值 —— 它表示"不传 --permission-mode",
10
10
  * 由 CLI 自己决定默认模式,转换在 runner/claude/session.ts 的 spawn 参数处。
@@ -27,7 +27,7 @@ export declare const DEFAULT_APPROVAL_TIMEOUT_MS: number;
27
27
  /**
28
28
  * 入站媒体默认配置(schema 默认与 connector 兜底共用,避免双源漂移)。
29
29
  *
30
- * 单文件上限默认 100MB:50MB 对视频偏小(2026-09-15 事故就是 mp4 被丢弃)。
30
+ * 单文件上限默认 100MB:50MB 对视频偏小(mp4 会被丢弃)。
31
31
  * 注意飞书侧 `type=file` 超过 100MB 必须用 Range 分片下载,而 SDK 的
32
32
  * `downloadResourceToFile` 不分片(必报 234037),所以 100 是实际可用上限
33
33
  * (inbound-message-matrix.md §4 第 4 条)。
@@ -93,6 +93,16 @@ declare const OpencodeConfigSchema: z.ZodObject<{
93
93
  turnIdleTimeoutMinutes: z.ZodDefault<z.ZodNumber>;
94
94
  }, z.core.$strip>>;
95
95
  }, z.core.$strip>;
96
+ /**
97
+ * pi 默认配置:schema default、src/index.ts 注册兜底与 runner 构造器兜底
98
+ * 三处共用,避免三抄漂移。
99
+ */
100
+ export declare const PI_DEFAULTS: {
101
+ readonly provider: "Volcano";
102
+ readonly model: "glm-5.2";
103
+ readonly thinking: "medium";
104
+ readonly tools: "read,bash,edit,write,grep,find,ls";
105
+ };
96
106
  /** Pi-specific configuration. pi is a spawn-per-message CLI like Claude. */
97
107
  declare const PiConfigSchema: z.ZodObject<{
98
108
  provider: z.ZodDefault<z.ZodString>;
@@ -107,6 +117,10 @@ declare const PiConfigSchema: z.ZodObject<{
107
117
  }>>;
108
118
  tools: z.ZodDefault<z.ZodString>;
109
119
  }, z.core.$strip>;
120
+ /** kimi 默认模型:schema default 与 src/index.ts 注册兜底共用,避免双抄漂移。 */
121
+ export declare const KIMI_DEFAULTS: {
122
+ readonly model: "kimi-code/k3";
123
+ };
110
124
  /** Kimi-specific configuration. Pure ACP mode (persistent `kimi acp` connection). */
111
125
  declare const KimiConfigSchema: z.ZodObject<{
112
126
  model: z.ZodDefault<z.ZodString>;
@@ -4,6 +4,7 @@ import path from 'node:path';
4
4
  import YAML from 'yaml';
5
5
  import { getLogger } from '../logger/index.js';
6
6
  import { atomicWrite } from '../persistence/atomic-write.js';
7
+ import { AGENT_KINDS } from '../agent-kinds.js';
7
8
  export { getConfigDir } from './dir.js';
8
9
  // Import constants from agent config files (SSOT: Layer 1 source)
9
10
  import { KIMI_THINKING_EFFORTS } from './kimi-config.js';
@@ -11,7 +12,7 @@ import { KIMI_THINKING_EFFORTS } from './kimi-config.js';
11
12
  export const CLAUDE_EFFORTS = ['low', 'medium', 'high', 'xhigh', 'max'];
12
13
  /**
13
14
  * Claude 的权限模式取值:'default' 之外都是 CLI `--permission-mode` 的真实枚举
14
- * (2026-09-20 实测 claude --help:acceptEdits | auto | bypassPermissions | manual
15
+ * (claude --help:acceptEdits | auto | bypassPermissions | manual
15
16
  * | dontAsk | plan)。
16
17
  * 'default' 是本项目自造的值,不是 CLI 的取值 —— 它表示"不传 --permission-mode",
17
18
  * 由 CLI 自己决定默认模式,转换在 runner/claude/session.ts 的 spawn 参数处。
@@ -55,7 +56,7 @@ export const DEFAULT_APPROVAL_TIMEOUT_MS = DEFAULTS.APPROVAL_TIMEOUT_MS;
55
56
  /**
56
57
  * 入站媒体默认配置(schema 默认与 connector 兜底共用,避免双源漂移)。
57
58
  *
58
- * 单文件上限默认 100MB:50MB 对视频偏小(2026-09-15 事故就是 mp4 被丢弃)。
59
+ * 单文件上限默认 100MB:50MB 对视频偏小(mp4 会被丢弃)。
59
60
  * 注意飞书侧 `type=file` 超过 100MB 必须用 Range 分片下载,而 SDK 的
60
61
  * `downloadResourceToFile` 不分片(必报 234037),所以 100 是实际可用上限
61
62
  * (inbound-message-matrix.md §4 第 4 条)。
@@ -141,21 +142,35 @@ const OpencodeConfigSchema = z.object({
141
142
  })
142
143
  .optional(),
143
144
  });
145
+ /**
146
+ * pi 默认配置:schema default、src/index.ts 注册兜底与 runner 构造器兜底
147
+ * 三处共用,避免三抄漂移。
148
+ */
149
+ export const PI_DEFAULTS = {
150
+ provider: 'Volcano',
151
+ model: 'glm-5.2',
152
+ thinking: 'medium',
153
+ tools: 'read,bash,edit,write,grep,find,ls',
154
+ };
144
155
  /** Pi-specific configuration. pi is a spawn-per-message CLI like Claude. */
145
156
  const PiConfigSchema = z.object({
146
157
  /** LLM provider, e.g. 'Volcano', 'anthropic', 'openai'. */
147
- provider: z.string().default('Volcano'),
158
+ provider: z.string().default(PI_DEFAULTS.provider),
148
159
  /** Model ID or alias, e.g. 'glm-5.2'. */
149
- model: z.string().default('glm-5.2'),
160
+ model: z.string().default(PI_DEFAULTS.model),
150
161
  /** Thinking level: off/minimal/low/medium/high/xhigh. */
151
- thinking: z.enum(PI_THINKING_LEVELS).default('medium'),
162
+ thinking: z.enum(PI_THINKING_LEVELS).default(PI_DEFAULTS.thinking),
152
163
  /** Comma-separated tool whitelist. */
153
- tools: z.string().default('read,bash,edit,write,grep,find,ls'),
164
+ tools: z.string().default(PI_DEFAULTS.tools),
154
165
  });
166
+ /** kimi 默认模型:schema default 与 src/index.ts 注册兜底共用,避免双抄漂移。 */
167
+ export const KIMI_DEFAULTS = {
168
+ model: 'kimi-code/k3',
169
+ };
155
170
  /** Kimi-specific configuration. Pure ACP mode (persistent `kimi acp` connection). */
156
171
  const KimiConfigSchema = z.object({
157
172
  /** Model ID or alias, e.g. 'kimi-code/k3'. */
158
- model: z.string().default('kimi-code/k3'),
173
+ model: z.string().default(KIMI_DEFAULTS.model),
159
174
  /** Thinking effort,取值见 KIMI_THINKING_EFFORTS('low' | 'high' | 'max')。 */
160
175
  thinkingEffort: z.enum(KIMI_THINKING_EFFORTS).default('max'),
161
176
  /** Permission mode: 'manual' (approve each), 'auto' (engine decides), 'yolo' (allow all). */
@@ -307,7 +322,7 @@ export const AppConfigSchema = z.object({
307
322
  /** 入站媒体(图片/文件)落盘配置。 */
308
323
  inboundMedia: InboundMediaConfigSchema.default(InboundMediaConfigSchema.parse({})),
309
324
  /** Default agent for new runs. */
310
- defaultAgent: z.enum(['claude', 'codex', 'opencode', 'pi', 'kimi', 'dsh']).default('claude'),
325
+ defaultAgent: z.enum(AGENT_KINDS).default('claude'),
311
326
  /** Check for updates on lark-remote startup (default: false). */
312
327
  checkUpdateOnStartup: z.boolean().default(false),
313
328
  });