@baize-ai/core 0.3.15 → 0.3.17
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/.dockerignore +1 -0
- package/CHANGELOG.md +29 -0
- package/Dockerfile +10 -1
- package/cli/commands/doctor.js +60 -0
- package/cli/commands/init.js +104 -42
- package/cli/commands/runtime.js +9 -1
- package/cli/lib/__tests__/init-base-url.test.js +55 -13
- package/cli/lib/__tests__/runtime-base-url.test.js +3 -1
- package/cli/lib/__tests__/runtime-launch.test.js +4 -2
- package/cli/lib/__tests__/runtime-setup.test.js +53 -6
- package/cli/lib/__tests__/tmux-env.test.js +19 -5
- package/cli/lib/claude-eval.js +2 -1
- package/cli/lib/codex-hooks.js +12 -0
- package/cli/lib/path-bins.js +54 -0
- package/cli/lib/runtime/claude.js +4 -2
- package/cli/lib/runtime/codex.js +19 -0
- package/cli/lib/runtime/tmux-env.js +5 -4
- package/cli/lib/runtime-setup.js +157 -13
- package/docker/entrypoint.sh +4 -2
- package/docs/ops-runbook.md +161 -0
- package/package.json +2 -2
- package/scripts/docker-publish.sh +11 -5
- package/scripts/pack-release.sh +34 -26
- package/skills/activity-monitor/scripts/__tests__/guardian.test.js +51 -0
- package/skills/activity-monitor/scripts/adapters/runtime-components.js +27 -0
- package/skills/activity-monitor/scripts/guardian.js +45 -1
- package/skills/activity-monitor/scripts/monitor-orchestrator.js +8 -1
- package/skills/activity-monitor/scripts/upgrade-check.js +3 -1
- package/skills/web-console/public/app.js +20 -75
- package/skills/web-console/scripts/model-provider.js +97 -59
- package/skills/web-console/scripts/server.js +6 -24
- package/templates/pm2/ecosystem.config.cjs +5 -4
- package/test/model-provider.test.js +150 -40
- package/test/web-console-routes.test.js +11 -13
|
@@ -1170,7 +1170,18 @@ async function loadProviders(basePath) {
|
|
|
1170
1170
|
const names = { official: 'Anthropic 官方', 'official-openai': 'OpenAI 官方' };
|
|
1171
1171
|
const prov = (body.providers || []).find((x) => x.id === body.active);
|
|
1172
1172
|
const label = prov?.name || names[body.active] || body.active || 'official';
|
|
1173
|
-
|
|
1173
|
+
const codexSlug = body.codexProviderKey || prov?.codex?.providerKey || null;
|
|
1174
|
+
const codexKind = prov?.codex?.kind || null;
|
|
1175
|
+
// D51 review P3: surface the kind so openai-official (no block) vs
|
|
1176
|
+
// responses-compatible (provider block) are distinguishable at a glance.
|
|
1177
|
+
const codexTag = codexKind === 'responses-compatible' && codexSlug
|
|
1178
|
+
? `(codex: ${codexSlug} · responses)`
|
|
1179
|
+
: codexKind === 'openai-official'
|
|
1180
|
+
? '(codex: OpenAI 官方)'
|
|
1181
|
+
: codexSlug
|
|
1182
|
+
? `(codex: ${codexSlug})`
|
|
1183
|
+
: '';
|
|
1184
|
+
activeEl.textContent = `当前激活:${label}${codexTag}`;
|
|
1174
1185
|
}
|
|
1175
1186
|
const list = document.getElementById('provider-list');
|
|
1176
1187
|
if (!list) return;
|
|
@@ -1249,7 +1260,8 @@ async function activateProviderFromAdmin(basePath, id) {
|
|
|
1249
1260
|
const restartNote = body.restart?.restarted ? ',会话已重启' : (body.restart?.reason === 'no_session' ? '(无运行中会话)' : '');
|
|
1250
1261
|
const warnNote = body.warnings?.length ? `
|
|
1251
1262
|
警告:${body.warnings.join('\n')}` : '';
|
|
1252
|
-
|
|
1263
|
+
const slugNote = body.codexProviderKey ? `(codex: ${body.codexProviderKey})` : '';
|
|
1264
|
+
setAdminMsg(`已激活 ${body.active}${slugNote}${restartNote}${warnNote}`);
|
|
1253
1265
|
} else {
|
|
1254
1266
|
setAdminMsg(`激活失败:${body.error || '未知错误'}`, true);
|
|
1255
1267
|
}
|
|
@@ -1863,13 +1875,11 @@ async function openAgentCardEditor(basePath) {
|
|
|
1863
1875
|
let catalogue;
|
|
1864
1876
|
let card;
|
|
1865
1877
|
let authz;
|
|
1866
|
-
let peerIds;
|
|
1867
1878
|
try {
|
|
1868
|
-
const [catRes, cardRes, authzRes
|
|
1879
|
+
const [catRes, cardRes, authzRes] = await Promise.all([
|
|
1869
1880
|
adminFetch(basePath, '/api/admin/a2a/card/skills'),
|
|
1870
1881
|
adminFetch(basePath, '/api/admin/a2a/card'),
|
|
1871
1882
|
adminFetch(basePath, '/api/admin/a2a/authz'),
|
|
1872
|
-
adminFetch(basePath, '/api/admin/a2a/peers'),
|
|
1873
1883
|
]);
|
|
1874
1884
|
if (catRes.status !== 200) throw new Error(catRes.body.error || 'skills 列表加载失败');
|
|
1875
1885
|
if (cardRes.status !== 200) throw new Error(cardRes.body.error || '名片加载失败');
|
|
@@ -1877,9 +1887,6 @@ async function openAgentCardEditor(basePath) {
|
|
|
1877
1887
|
catalogue = catRes.body.skills || [];
|
|
1878
1888
|
card = cardRes.body;
|
|
1879
1889
|
authz = authzRes.body;
|
|
1880
|
-
peerIds = peersRes.status === 200 && Array.isArray(peersRes.body.peers)
|
|
1881
|
-
? peersRes.body.peers.map((p) => p.agentId).filter(Boolean)
|
|
1882
|
-
: [];
|
|
1883
1890
|
} catch (err) {
|
|
1884
1891
|
setAdminMsg(`❌ 无法打开名片编辑器:${err.message}`, true);
|
|
1885
1892
|
return;
|
|
@@ -1917,14 +1924,7 @@ async function openAgentCardEditor(basePath) {
|
|
|
1917
1924
|
<label class="authz-mode-option"><input type="radio" name="authz-mode" value="allowlist"> 白名单</label>
|
|
1918
1925
|
</div>
|
|
1919
1926
|
<p class="conn-block-desc" id="authz-mode-desc"></p>
|
|
1920
|
-
<
|
|
1921
|
-
<div class="authz-tags" id="authz-tags" aria-live="polite"></div>
|
|
1922
|
-
<div class="authz-add-row">
|
|
1923
|
-
<input type="text" id="authz-peer-input" class="agent-card-input" placeholder="输入 peer agent_id 后添加" autocomplete="off" spellcheck="false">
|
|
1924
|
-
<button type="button" class="btn btn-outline" id="authz-peer-add">添加</button>
|
|
1925
|
-
</div>
|
|
1926
|
-
<div class="agent-card-field-label">从 peer 列表选择</div>
|
|
1927
|
-
<select id="authz-peer-select" class="agent-card-input" aria-label="从 peer 列表选择"></select>
|
|
1927
|
+
<p class="conn-block-desc" style="color:#8c8c8c">允许 / 阻止名单由 admin 控制台统一管控(组织架构 · 调用白名单),此处仅切换本 agent 的授权模式。</p>
|
|
1928
1928
|
</div>
|
|
1929
1929
|
</div>
|
|
1930
1930
|
<div class="agent-card-modal-foot">
|
|
@@ -1943,18 +1943,10 @@ async function openAgentCardEditor(basePath) {
|
|
|
1943
1943
|
nameInput.value = card.name || '';
|
|
1944
1944
|
descInput.value = card.description || '';
|
|
1945
1945
|
|
|
1946
|
-
// ── 调用授权 tab state (
|
|
1947
|
-
const allowSet = new Set(Array.isArray(authz.allow) ? authz.allow : []);
|
|
1948
|
-
const blockSet = new Set(Array.isArray(authz.block) ? authz.block : []);
|
|
1946
|
+
// ── 调用授权 tab state (D52: local mode only; lists are admin-managed) ──
|
|
1949
1947
|
let authzMode = authz.mode === 'allowlist' ? 'allowlist' : 'open';
|
|
1950
|
-
const activeAuthzList = () => (authzMode === 'allowlist' ? allowSet : blockSet);
|
|
1951
1948
|
|
|
1952
1949
|
const authzModeDesc = overlay.querySelector('#authz-mode-desc');
|
|
1953
|
-
const authzListTitle = overlay.querySelector('#authz-list-title');
|
|
1954
|
-
const authzCount = overlay.querySelector('#authz-count');
|
|
1955
|
-
const authzTagsEl = overlay.querySelector('#authz-tags');
|
|
1956
|
-
const authzPeerInput = overlay.querySelector('#authz-peer-input');
|
|
1957
|
-
const authzPeerSelect = overlay.querySelector('#authz-peer-select');
|
|
1958
1950
|
|
|
1959
1951
|
const renderAuthz = () => {
|
|
1960
1952
|
overlay.querySelectorAll('input[name="authz-mode"]').forEach((r) => {
|
|
@@ -1962,19 +1954,8 @@ async function openAgentCardEditor(basePath) {
|
|
|
1962
1954
|
});
|
|
1963
1955
|
const isAllowlist = authzMode === 'allowlist';
|
|
1964
1956
|
authzModeDesc.textContent = isAllowlist
|
|
1965
|
-
? '
|
|
1966
|
-
: '接受所有已批准 agent
|
|
1967
|
-
authzListTitle.textContent = isAllowlist ? '允许列表' : '阻止列表';
|
|
1968
|
-
const list = [...activeAuthzList()];
|
|
1969
|
-
authzCount.textContent = `${list.length} 个`;
|
|
1970
|
-
authzTagsEl.innerHTML = list.length
|
|
1971
|
-
? list.map((id) => `<span class="authz-tag">${escapeHtml(id)}<button type="button" class="authz-tag-x" aria-label="移除 ${escapeHtml(id)}" data-id="${escapeHtml(id)}">×</button></span>`).join('')
|
|
1972
|
-
: '<span class="cred-state">(空)</span>';
|
|
1973
|
-
const current = new Set(activeAuthzList());
|
|
1974
|
-
const available = peerIds.filter((id) => !current.has(id));
|
|
1975
|
-
authzPeerSelect.innerHTML = `<option value="">${available.length ? '选择 peer 添加到列表…' : '列表已包含所有已知 peer'}</option>`
|
|
1976
|
-
+ available.map((id) => `<option value="${escapeHtml(id)}">${escapeHtml(id)}</option>`).join('');
|
|
1977
|
-
authzPeerSelect.disabled = available.length === 0;
|
|
1957
|
+
? '仅允许白名单中的 agent 调用本 agent(白名单由 admin 控制台配置;空名单 = 无人可调用)。'
|
|
1958
|
+
: '接受所有已批准 agent(除 admin 控制台配置的阻止名单)。';
|
|
1978
1959
|
};
|
|
1979
1960
|
|
|
1980
1961
|
overlay.querySelectorAll('input[name="authz-mode"]').forEach((r) => {
|
|
@@ -1986,42 +1967,6 @@ async function openAgentCardEditor(basePath) {
|
|
|
1986
1967
|
});
|
|
1987
1968
|
});
|
|
1988
1969
|
|
|
1989
|
-
const addAuthzPeer = () => {
|
|
1990
|
-
const value = authzPeerInput.value.trim();
|
|
1991
|
-
if (!value) {
|
|
1992
|
-
setAdminMsg('请输入 peer agent_id', true);
|
|
1993
|
-
return;
|
|
1994
|
-
}
|
|
1995
|
-
const list = activeAuthzList();
|
|
1996
|
-
if (list.has(value)) {
|
|
1997
|
-
setAdminMsg(`⚠️ ${value} 已在${authzMode === 'allowlist' ? '允许' : '阻止'}列表中`, true);
|
|
1998
|
-
return;
|
|
1999
|
-
}
|
|
2000
|
-
list.add(value);
|
|
2001
|
-
authzPeerInput.value = '';
|
|
2002
|
-
renderAuthz();
|
|
2003
|
-
};
|
|
2004
|
-
authzPeerInput.addEventListener('keydown', (e) => {
|
|
2005
|
-
if (e.key === 'Enter') {
|
|
2006
|
-
e.preventDefault();
|
|
2007
|
-
addAuthzPeer();
|
|
2008
|
-
}
|
|
2009
|
-
});
|
|
2010
|
-
overlay.querySelector('#authz-peer-add').addEventListener('click', addAuthzPeer);
|
|
2011
|
-
authzPeerSelect.addEventListener('change', () => {
|
|
2012
|
-
const value = authzPeerSelect.value;
|
|
2013
|
-
if (!value) return;
|
|
2014
|
-
activeAuthzList().add(value);
|
|
2015
|
-
renderAuthz();
|
|
2016
|
-
});
|
|
2017
|
-
authzTagsEl.addEventListener('click', (e) => {
|
|
2018
|
-
const btn = e.target.closest('.authz-tag-x');
|
|
2019
|
-
if (btn) {
|
|
2020
|
-
activeAuthzList().delete(btn.dataset.id);
|
|
2021
|
-
renderAuthz();
|
|
2022
|
-
}
|
|
2023
|
-
});
|
|
2024
|
-
|
|
2025
1970
|
// ── Tab switching ──
|
|
2026
1971
|
const tabButtons = overlay.querySelectorAll('.agent-card-tab');
|
|
2027
1972
|
const cardTabEl = overlay.querySelector('#agent-card-tab-card');
|
|
@@ -2123,7 +2068,7 @@ async function openAgentCardEditor(basePath) {
|
|
|
2123
2068
|
adminFetch(basePath, '/api/admin/a2a/authz', {
|
|
2124
2069
|
method: 'PUT',
|
|
2125
2070
|
headers: { 'Content-Type': 'application/json' },
|
|
2126
|
-
body: JSON.stringify({ mode: authzMode
|
|
2071
|
+
body: JSON.stringify({ mode: authzMode }),
|
|
2127
2072
|
}),
|
|
2128
2073
|
]);
|
|
2129
2074
|
if (cardRes.status === 401 || authzRes.status === 401) {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* A provider entry carries per-runtime endpoints/models:
|
|
6
6
|
* cc: { baseUrl, authToken, model, smallModel } → ANTHROPIC_* env
|
|
7
|
-
* codex: { baseUrl, apiKey, model }
|
|
7
|
+
* codex: { kind, providerKey, baseUrl, apiKey, model } → dedicated provider block in config.toml
|
|
8
8
|
*
|
|
9
9
|
* Activation writes the same storage surfaces init uses (~/baize/.env +
|
|
10
10
|
* ~/.claude/settings.json env + ~/.codex/auth.json + ~/.codex/config.toml)
|
|
@@ -31,6 +31,13 @@ import {
|
|
|
31
31
|
restartAgentSession,
|
|
32
32
|
} from './admin-auth.js';
|
|
33
33
|
|
|
34
|
+
// D51: Codex provider-block writers shared with the CLI (same package tree).
|
|
35
|
+
import {
|
|
36
|
+
applyCodexProviderBlock,
|
|
37
|
+
codexProviderKeyForId,
|
|
38
|
+
restoreCodexOfficialBlock,
|
|
39
|
+
} from '../../../cli/lib/runtime-setup.js';
|
|
40
|
+
|
|
34
41
|
// ── Config store (~/.baize/providers.json) ───────────────────────────────────
|
|
35
42
|
|
|
36
43
|
function providersPath() {
|
|
@@ -53,13 +60,49 @@ function projectCodexConfigPath() {
|
|
|
53
60
|
return path.join(process.env.BAIZE_DIR || path.join(process.env.HOME || '', 'baize'), '.codex', 'config.toml');
|
|
54
61
|
}
|
|
55
62
|
|
|
63
|
+
// Codex-side kind enum (D51 — DeepSeek remote-compaction fix):
|
|
64
|
+
// openai-official use the built-in OpenAI provider (no model_provider block)
|
|
65
|
+
// responses-compatible generic /responses endpoint → dedicated [model_providers.<slug>] block
|
|
66
|
+
// chat-only legacy /chat wire only — Codex removed the Chat wire
|
|
67
|
+
const CODEX_KINDS = new Set(['openai-official', 'responses-compatible', 'chat-only']);
|
|
68
|
+
|
|
69
|
+
// Home/project roots, resolved from the same env vars every other path in
|
|
70
|
+
// this module uses. Passed explicitly to the runtime-setup writers because
|
|
71
|
+
// their os.homedir()/BAIZE_DIR fallbacks don't follow test env overrides
|
|
72
|
+
// (jest sandboxes os.homedir()).
|
|
73
|
+
function homeRoot() {
|
|
74
|
+
return process.env.HOME || '';
|
|
75
|
+
}
|
|
76
|
+
function baizeRoot() {
|
|
77
|
+
return process.env.BAIZE_DIR || path.join(process.env.HOME || '', 'baize');
|
|
78
|
+
}
|
|
79
|
+
|
|
56
80
|
function loadStore() {
|
|
57
81
|
const data = readJson(providersPath()) || {};
|
|
58
|
-
|
|
82
|
+
const store = {
|
|
59
83
|
active: typeof data.active === 'string' ? data.active : 'official',
|
|
60
84
|
providers: Array.isArray(data.providers) ? data.providers : [],
|
|
61
85
|
stash: data.stash && typeof data.stash === 'object' ? data.stash : null,
|
|
62
86
|
};
|
|
87
|
+
// Lazy D51 migration: entries saved before kind/providerKey existed get
|
|
88
|
+
// backfilled from the entry shape (baseUrl → responses-compatible) and the
|
|
89
|
+
// store is re-persisted so slugs stay stable from the first read onward.
|
|
90
|
+
let migrated = false;
|
|
91
|
+
for (const p of store.providers) {
|
|
92
|
+
const codex = p.codex && typeof p.codex === 'object' ? p.codex : {};
|
|
93
|
+
if (!codex.kind) {
|
|
94
|
+
codex.kind = codex.baseUrl ? 'responses-compatible' : 'openai-official';
|
|
95
|
+
p.codex = codex;
|
|
96
|
+
migrated = true;
|
|
97
|
+
}
|
|
98
|
+
if (!codex.providerKey && p.id) {
|
|
99
|
+
codex.providerKey = codexProviderKeyForId(p.id);
|
|
100
|
+
p.codex = codex;
|
|
101
|
+
migrated = true;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (migrated) saveStore(store);
|
|
105
|
+
return store;
|
|
63
106
|
}
|
|
64
107
|
|
|
65
108
|
function saveStore(store) {
|
|
@@ -91,6 +134,7 @@ function validProviderInput(data) {
|
|
|
91
134
|
const urlRe = /^https?:\/\/.+/;
|
|
92
135
|
if (cc.baseUrl && !urlRe.test(String(cc.baseUrl))) return 'cc.baseUrl must start with http(s)://';
|
|
93
136
|
if (codex.baseUrl && !urlRe.test(String(codex.baseUrl))) return 'codex.baseUrl must start with http(s)://';
|
|
137
|
+
if (codex.kind && !CODEX_KINDS.has(String(codex.kind))) return `codex.kind must be one of: ${[...CODEX_KINDS].join(', ')}`;
|
|
94
138
|
return null;
|
|
95
139
|
}
|
|
96
140
|
|
|
@@ -106,6 +150,8 @@ function sanitizeProvider(p) {
|
|
|
106
150
|
authConfigured: Boolean(p.cc?.authToken),
|
|
107
151
|
},
|
|
108
152
|
codex: {
|
|
153
|
+
kind: p.codex?.kind || null,
|
|
154
|
+
providerKey: p.codex?.providerKey || null,
|
|
109
155
|
baseUrl: p.codex?.baseUrl || null,
|
|
110
156
|
model: p.codex?.model || null,
|
|
111
157
|
apiConfigured: Boolean(p.codex?.apiKey),
|
|
@@ -131,8 +177,15 @@ export async function saveProvider(input) {
|
|
|
131
177
|
const cc = input.cc || {};
|
|
132
178
|
const codex = input.codex || {};
|
|
133
179
|
const exists = input.id && store.providers.some((p) => p.id === input.id);
|
|
180
|
+
// D51: keep the pre-existing kind/providerKey on updates (slug is generated
|
|
181
|
+
// once and never regenerated — a rename must not orphan the TOML block).
|
|
182
|
+
const prevCodex = exists ? store.providers.find((p) => p.id === input.id).codex || {} : {};
|
|
183
|
+
const kind = codex.kind
|
|
184
|
+
? String(codex.kind)
|
|
185
|
+
: (prevCodex.kind || (codex.baseUrl ? 'responses-compatible' : 'openai-official'));
|
|
186
|
+
const id = exists ? input.id : genId(name, store);
|
|
134
187
|
const entry = {
|
|
135
|
-
id
|
|
188
|
+
id,
|
|
136
189
|
name,
|
|
137
190
|
cc: {
|
|
138
191
|
baseUrl: cc.baseUrl ? String(cc.baseUrl).trim() : undefined,
|
|
@@ -141,6 +194,8 @@ export async function saveProvider(input) {
|
|
|
141
194
|
smallModel: cc.smallModel ? String(cc.smallModel).trim() : undefined,
|
|
142
195
|
},
|
|
143
196
|
codex: {
|
|
197
|
+
kind,
|
|
198
|
+
providerKey: prevCodex.providerKey || codexProviderKeyForId(id),
|
|
144
199
|
baseUrl: codex.baseUrl ? String(codex.baseUrl).trim() : undefined,
|
|
145
200
|
apiKey: codex.apiKey ? String(codex.apiKey).trim() : undefined,
|
|
146
201
|
model: codex.model ? String(codex.model).trim() : undefined,
|
|
@@ -165,9 +220,12 @@ function readRuntime() {
|
|
|
165
220
|
|
|
166
221
|
function genId(name, store) {
|
|
167
222
|
const base = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'provider';
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
|
|
223
|
+
// Reserved ids never land in the store: the built-in restore targets
|
|
224
|
+
// ('official' / 'official-openai') plus Codex built-in provider keys that a
|
|
225
|
+
// user-named provider must not shadow (D51 — 'openai' would make the
|
|
226
|
+
// generated provider block collide with the built-in OpenAI entry).
|
|
227
|
+
const reserved = new Set(['official', 'official-openai', 'openai', 'azure', 'amazon-bedrock', 'amazon-bedrock-runtime', 'ollama', 'lmstudio', 'gpt-oss']);
|
|
228
|
+
let id = reserved.has(base) ? `${base}-custom` : base;
|
|
171
229
|
let n = 2;
|
|
172
230
|
while (store.providers.some((p) => p.id === id)) id = `${base}-${n++}`;
|
|
173
231
|
return id;
|
|
@@ -189,7 +247,8 @@ const CC_EXTERNAL_KEYS = ['ANTHROPIC_AUTH_TOKEN', 'ANTHROPIC_BASE_URL', 'ANTHROP
|
|
|
189
247
|
|
|
190
248
|
/**
|
|
191
249
|
* Activate a provider or built-in official restore target.
|
|
192
|
-
* @returns {Promise<{success: boolean, active: string, warnings: string[],
|
|
250
|
+
* @returns {Promise<{success: boolean, active: string, warnings: string[],
|
|
251
|
+
* restart?: object, codexProviderKey?: string|null, error?: string}>}
|
|
193
252
|
*/
|
|
194
253
|
export async function activateProvider(id, deps = {}) {
|
|
195
254
|
const store = loadStore();
|
|
@@ -206,6 +265,14 @@ export async function activateProvider(id, deps = {}) {
|
|
|
206
265
|
// current runtime's side is meaningless (e.g. a cc-only provider while the
|
|
207
266
|
// Codex runtime is active). Official restores and dual-side providers pass.
|
|
208
267
|
if (provider) {
|
|
268
|
+
// D51: chat-only endpoints have no Codex wiring at all (the Chat wire was
|
|
269
|
+
// removed from Codex) — block activation before anything is written.
|
|
270
|
+
if (provider.codex?.kind === 'chat-only') {
|
|
271
|
+
return {
|
|
272
|
+
success: false,
|
|
273
|
+
error: '该供应商仅支持 Chat 接口,Codex 已移除 Chat wire,请改用 Claude 运行时',
|
|
274
|
+
};
|
|
275
|
+
}
|
|
209
276
|
const runtime = readRuntime();
|
|
210
277
|
const hasCc = Boolean(provider.cc?.authToken || provider.cc?.baseUrl || provider.cc?.model || provider.cc?.smallModel);
|
|
211
278
|
const hasCodex = Boolean(provider.codex?.baseUrl || provider.codex?.apiKey || provider.codex?.model);
|
|
@@ -233,10 +300,11 @@ export async function activateProvider(id, deps = {}) {
|
|
|
233
300
|
applyClaudeExternal(cc, store);
|
|
234
301
|
}
|
|
235
302
|
|
|
303
|
+
let codexProviderKey = null;
|
|
236
304
|
if (isOfficialOpenai) {
|
|
237
305
|
applyCodexOfficial();
|
|
238
306
|
} else if (provider?.codex) {
|
|
239
|
-
applyCodexExternal(provider.codex);
|
|
307
|
+
codexProviderKey = applyCodexExternal(provider.codex) || null;
|
|
240
308
|
}
|
|
241
309
|
|
|
242
310
|
// Non-blocking reachability probe for configured base URLs (informational).
|
|
@@ -251,7 +319,7 @@ export async function activateProvider(id, deps = {}) {
|
|
|
251
319
|
store.active = id;
|
|
252
320
|
saveStore(store);
|
|
253
321
|
const restart = await (deps.restart ?? restartAgentSession)();
|
|
254
|
-
return { success: true, active: id, warnings, restart };
|
|
322
|
+
return { success: true, active: id, warnings, codexProviderKey, restart };
|
|
255
323
|
}
|
|
256
324
|
|
|
257
325
|
// Claude Code: external provider application
|
|
@@ -308,67 +376,37 @@ function applyOfficialClaude(store) {
|
|
|
308
376
|
updateSettingsEnv(updates, CC_EXTERNAL_KEYS);
|
|
309
377
|
}
|
|
310
378
|
|
|
311
|
-
// Codex: external provider application
|
|
379
|
+
// Codex: external provider application (D51 — dedicated provider block).
|
|
380
|
+
// Returns the active codex provider slug, or null when nothing was written.
|
|
312
381
|
function applyCodexExternal(codex) {
|
|
313
382
|
let env = readEnv(envFile());
|
|
383
|
+
// Legacy override kept for health-probe/status consumers that still read it.
|
|
314
384
|
if (codex.baseUrl) env = upsertEnv(env, 'OPENAI_BASE_URL', codex.baseUrl, 'OpenAI-compatible endpoint (web console)');
|
|
315
385
|
writeEnvFile(env);
|
|
316
386
|
if (codex.apiKey) writeCodexAuth(codex.apiKey);
|
|
317
|
-
|
|
387
|
+
if (codex.kind === 'openai-official') {
|
|
388
|
+
// Official endpoint: keep the built-in OpenAI provider (remote compaction
|
|
389
|
+
// works there) — just drop any leftover custom block/override.
|
|
390
|
+
restoreCodexOfficialBlock({ homeDir: homeRoot(), projectDir: baizeRoot() });
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
applyCodexProviderBlock({
|
|
394
|
+
providerKey: codex.providerKey || codexProviderKeyForId('provider'),
|
|
395
|
+
baseUrl: codex.baseUrl,
|
|
396
|
+
token: codex.apiKey,
|
|
397
|
+
model: codex.model,
|
|
398
|
+
homeDir: homeRoot(),
|
|
399
|
+
projectDir: baizeRoot(),
|
|
400
|
+
});
|
|
401
|
+
return codex.providerKey || null;
|
|
318
402
|
}
|
|
319
403
|
|
|
320
|
-
// Codex: restore official (
|
|
404
|
+
// Codex: restore official (drop provider block + overrides, reset model; auth untouched)
|
|
321
405
|
function applyCodexOfficial() {
|
|
322
406
|
let env = readEnv(envFile());
|
|
323
407
|
env = removeEnvKey(env, 'OPENAI_BASE_URL');
|
|
324
408
|
writeEnvFile(env);
|
|
325
|
-
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
/**
|
|
329
|
-
* Conservative top-level edit of a Codex config.toml: set/replace the
|
|
330
|
-
* `model` and `openai_base_url` keys, preserving every other line (comments,
|
|
331
|
-
* sections, providers). Only the TOP-LEVEL prefix (before the first section
|
|
332
|
-
* header) is touched — keys inside [table] sections are also unindented, so
|
|
333
|
-
* regex edits must never run past the first `[` header.
|
|
334
|
-
*/
|
|
335
|
-
function writeCodexTomlTopLevel(targetPath, { model, openaiBaseUrl }) {
|
|
336
|
-
const dir = path.dirname(targetPath);
|
|
337
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
338
|
-
let content = '';
|
|
339
|
-
try { content = fs.readFileSync(targetPath, 'utf8'); } catch { /* new file */ }
|
|
340
|
-
|
|
341
|
-
const sectionAt = content.search(/^\[/m);
|
|
342
|
-
const head = sectionAt === -1 ? content : content.slice(0, sectionAt);
|
|
343
|
-
const tail = sectionAt === -1 ? '' : content.slice(sectionAt);
|
|
344
|
-
|
|
345
|
-
const setTopLevel = (text, key, value) => {
|
|
346
|
-
const lineRe = new RegExp(`^${key}\\s*=.*$`, 'm');
|
|
347
|
-
const line = `${key} = ${JSON.stringify(value)}`;
|
|
348
|
-
if (lineRe.test(text)) return text.replace(lineRe, line);
|
|
349
|
-
return `${text.trimEnd()}\n${line}\n`;
|
|
350
|
-
};
|
|
351
|
-
const removeTopLevel = (text, key) => text.replace(new RegExp(`^${key}\\s*=.*\\n?`, 'm'), '');
|
|
352
|
-
|
|
353
|
-
let out = head;
|
|
354
|
-
if (model) out = setTopLevel(out, 'model', model);
|
|
355
|
-
if (openaiBaseUrl) out = setTopLevel(out, 'openai_base_url', openaiBaseUrl);
|
|
356
|
-
if (openaiBaseUrl === null) out = removeTopLevel(out, 'openai_base_url');
|
|
357
|
-
const joined = tail && !out.endsWith('\n') ? `${out}\n${tail}` : `${out}${tail}`;
|
|
358
|
-
fs.writeFileSync(targetPath, joined, 'utf8');
|
|
359
|
-
return targetPath;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
/**
|
|
363
|
-
* Apply a provider's Codex model/base_url to BOTH the global config.toml and
|
|
364
|
-
* the baize project-level config.toml. codex prefers project-level keys, so
|
|
365
|
-
* writing only the global file let baize init's backfilled defaults (model =
|
|
366
|
-
* "gpt-5.5") shadow the provider config (D31).
|
|
367
|
-
*/
|
|
368
|
-
export function editCodexConfigToml({ model, openaiBaseUrl }) {
|
|
369
|
-
writeCodexTomlTopLevel(codexConfigPath(), { model, openaiBaseUrl });
|
|
370
|
-
writeCodexTomlTopLevel(projectCodexConfigPath(), { model, openaiBaseUrl });
|
|
371
|
-
return codexConfigPath();
|
|
409
|
+
restoreCodexOfficialBlock({ homeDir: homeRoot(), projectDir: baizeRoot() });
|
|
372
410
|
}
|
|
373
411
|
|
|
374
412
|
// Reachability probe: any <500 status means the endpoint answers.
|
|
@@ -1558,23 +1558,6 @@ function readAuthz() {
|
|
|
1558
1558
|
}
|
|
1559
1559
|
}
|
|
1560
1560
|
|
|
1561
|
-
// Validates an allow/block list; returns { list } or { error }.
|
|
1562
|
-
function validateAuthzList(value, name) {
|
|
1563
|
-
if (!Array.isArray(value)) return { error: `${name} 必须是数组` };
|
|
1564
|
-
const seen = new Set();
|
|
1565
|
-
const list = [];
|
|
1566
|
-
for (const item of value) {
|
|
1567
|
-
if (typeof item !== 'string' || item.trim() === '') {
|
|
1568
|
-
return { error: `${name} 包含空项(必须是非空字符串)` };
|
|
1569
|
-
}
|
|
1570
|
-
const v = item.trim();
|
|
1571
|
-
if (seen.has(v)) return { error: `${name} 包含重复的 agent_id:${v}` };
|
|
1572
|
-
seen.add(v);
|
|
1573
|
-
list.push(v);
|
|
1574
|
-
}
|
|
1575
|
-
return { list };
|
|
1576
|
-
}
|
|
1577
|
-
|
|
1578
1561
|
// Current authz policy (config.json authz; contract defaults when unconfigured)
|
|
1579
1562
|
app.get('/api/admin/a2a/authz', (req, res) => {
|
|
1580
1563
|
try {
|
|
@@ -1584,8 +1567,11 @@ app.get('/api/admin/a2a/authz', (req, res) => {
|
|
|
1584
1567
|
}
|
|
1585
1568
|
});
|
|
1586
1569
|
|
|
1587
|
-
// Save the authz policy —
|
|
1588
|
-
//
|
|
1570
|
+
// Save the authz policy — D52 governance: the LOCAL agent may only flip the
|
|
1571
|
+
// mode (open/allowlist); allow/block lists are admin-managed (admin-workspace
|
|
1572
|
+
// pushes the cluster policy; local lists here would be dead weight / bypass
|
|
1573
|
+
// confusion). Any allow/block sent is ignored and forced to [] — the UI hides
|
|
1574
|
+
// list editing, and a direct API call cannot reintroduce local lists.
|
|
1589
1575
|
app.put('/api/admin/a2a/authz', (req, res) => {
|
|
1590
1576
|
try {
|
|
1591
1577
|
const body = req.body || {};
|
|
@@ -1593,11 +1579,7 @@ app.put('/api/admin/a2a/authz', (req, res) => {
|
|
|
1593
1579
|
if (mode !== 'open' && mode !== 'allowlist') {
|
|
1594
1580
|
return res.status(400).json({ success: false, error: 'mode 必须是 open 或 allowlist' });
|
|
1595
1581
|
}
|
|
1596
|
-
const
|
|
1597
|
-
if (allow.error) return res.status(400).json({ success: false, error: allow.error });
|
|
1598
|
-
const block = body.block === undefined ? { list: [] } : validateAuthzList(body.block, 'block');
|
|
1599
|
-
if (block.error) return res.status(400).json({ success: false, error: block.error });
|
|
1600
|
-
const authz = { mode, allow: allow.list, block: block.list };
|
|
1582
|
+
const authz = { mode, allow: [], block: [] };
|
|
1601
1583
|
const file = a2aConfigPath();
|
|
1602
1584
|
let cfg = {};
|
|
1603
1585
|
try {
|
|
@@ -28,11 +28,12 @@ function readEnvValue(key, defaultValue = '') {
|
|
|
28
28
|
return defaultValue;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
// Build PATH: Claude locations + user's full shell PATH +
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
31
|
+
// Build PATH: npm global bin + Claude locations + user's full shell PATH +
|
|
32
|
+
// PM2's own PATH. D54: ~/.npm-global/bin is added EXPLICITLY (independent of
|
|
33
|
+
// SYSTEM_PATH / process.env.PATH) — codex/baize live there; a polluted login
|
|
34
|
+
// shell PATH must never hide them from PM2 services.
|
|
35
35
|
const ENHANCED_PATH = [...new Set([
|
|
36
|
+
path.join(HOME, '.npm-global', 'bin'),
|
|
36
37
|
path.join(HOME, '.local', 'bin'),
|
|
37
38
|
path.join(HOME, '.claude', 'bin'),
|
|
38
39
|
...(readEnvValue('SYSTEM_PATH') || '').split(':').filter(Boolean),
|