@baize-ai/core 0.3.6 → 0.3.8
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/CHANGELOG.md +12 -0
- package/cli/lib/__tests__/runtime-setup.test.js +21 -0
- package/cli/lib/runtime-setup.js +22 -3
- package/cli/lib/self-upgrade.js +53 -17
- package/package.json +1 -1
- package/skills/web-console/public/app.js +3 -1
- package/skills/web-console/scripts/admin-auth.js +8 -2
- package/skills/web-console/scripts/model-provider.js +25 -6
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,18 @@ All notable changes to baize-core will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.3.8] - 2026-08-26
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- web-console 重启 agent 会话按钮:无运行中会话时不再误报「没有运行中的 agent 会话」——改为触发 activity-monitor 重启(Guardian 立即拉起 agent),提示「正在拉起 agent 进程…」;PM2 不可用时由 Guardian 周期探测兜底
|
|
12
|
+
|
|
13
|
+
## [0.3.7] - 2026-08-26
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- Codex 配置链路(D31):web 控制台激活供应商现在同时写全局与项目级 `config.toml`(model + openai_base_url)——此前只写全局,baize init 回填的项目级默认 `gpt-5.5` 会压制用户配置(codex 键级合并,项目级优先)→ 大陆 DeepSeek 场景会话秒退
|
|
17
|
+
- `baize init` 的项目级 codex config 回填改为继承全局 model(无全局时才用 gpt-5.5 兜底)——升级不再覆盖用户配置
|
|
18
|
+
- `baize upgrade --self` 改为 npm registry 路径(`npm view` / `npm pack`,跟随用户 registry——大陆 npmmirror 可达);`--beta` 保留预发布通道(dist-tags/versions 选最高 prerelease);npm 返回值严格 semver 校验
|
|
19
|
+
|
|
8
20
|
## [0.3.6] - 2026-08-26
|
|
9
21
|
|
|
10
22
|
### Added
|
|
@@ -93,6 +93,27 @@ describe('renderCodexProjectConfig', () => {
|
|
|
93
93
|
assert.doesNotMatch(content, /^model = "gpt-5\.5"$/m);
|
|
94
94
|
assert.doesNotMatch(content, /^model_reasoning_effort = "medium"$/m);
|
|
95
95
|
});
|
|
96
|
+
it('backfills the global codex model when the project config has none (D31)', () => {
|
|
97
|
+
fs.mkdirSync(path.join(fakeHome, '.codex'), { recursive: true });
|
|
98
|
+
fs.writeFileSync(
|
|
99
|
+
path.join(fakeHome, '.codex', 'config.toml'),
|
|
100
|
+
'model = "deepseek-v4-flash"\nopenai_base_url = "https://api.deepseek.com"\n',
|
|
101
|
+
'utf8',
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const content = renderCodexProjectConfig('');
|
|
105
|
+
|
|
106
|
+
assert.match(content, /^model = "deepseek-v4-flash"$/m);
|
|
107
|
+
assert.doesNotMatch(content, /^model = "gpt-5\.5"$/m);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('falls back to the gpt-5.5 default when no global model exists (D31)', () => {
|
|
111
|
+
fs.rmSync(path.join(fakeHome, '.codex', 'config.toml'), { force: true });
|
|
112
|
+
|
|
113
|
+
const content = renderCodexProjectConfig('');
|
|
114
|
+
|
|
115
|
+
assert.match(content, /^model = "gpt-5\.5"$/m);
|
|
116
|
+
});
|
|
96
117
|
|
|
97
118
|
it('replaces baize-owned notice sections exactly without touching dotted siblings', () => {
|
|
98
119
|
const existing = [
|
package/cli/lib/runtime-setup.js
CHANGED
|
@@ -347,6 +347,21 @@ function isTomlSectionValue(value) {
|
|
|
347
347
|
return value && typeof value === 'object' && !Array.isArray(value);
|
|
348
348
|
}
|
|
349
349
|
|
|
350
|
+
/**
|
|
351
|
+
* Read the user's global Codex model (~/.codex/config.toml `model`), if any.
|
|
352
|
+
* Used so project-level config backfill inherits the user's provider model
|
|
353
|
+
* instead of shadowing it with a hard-coded default (D31).
|
|
354
|
+
*/
|
|
355
|
+
function readGlobalCodexModel(homeDir = os.homedir()) {
|
|
356
|
+
try {
|
|
357
|
+
const globalPath = path.join(homeDir, '.codex', 'config.toml');
|
|
358
|
+
const parsed = parseCodexToml(fs.readFileSync(globalPath, 'utf8'));
|
|
359
|
+
return typeof parsed.model === 'string' && parsed.model ? parsed.model : null;
|
|
360
|
+
} catch {
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
350
365
|
/**
|
|
351
366
|
* Render project-level .codex/config.toml with headless configuration.
|
|
352
367
|
*
|
|
@@ -359,15 +374,19 @@ function isTomlSectionValue(value) {
|
|
|
359
374
|
* @param {string} existingContent - Existing project config.toml contents (optional)
|
|
360
375
|
* @returns {string}
|
|
361
376
|
*/
|
|
362
|
-
export function renderCodexProjectConfig(existingContent = '') {
|
|
377
|
+
export function renderCodexProjectConfig(existingContent = '', opts = {}) {
|
|
363
378
|
const obj = parseCodexToml(existingContent);
|
|
364
379
|
|
|
365
380
|
// Always overwrite: these values are required for unattended Baize runtime behavior.
|
|
366
381
|
obj.check_for_update_on_startup = false;
|
|
367
382
|
obj.model_availability_nux = 'gpt-5.4';
|
|
368
383
|
|
|
369
|
-
// Backfill: default only when the user has not configured a value.
|
|
370
|
-
|
|
384
|
+
// Backfill: default only when the user has not configured a value. Inherit
|
|
385
|
+
// the global codex model when present (web-console provider activation writes
|
|
386
|
+
// it there) — a hard-coded default would shadow the user's provider model.
|
|
387
|
+
if (obj.model === undefined) {
|
|
388
|
+
obj.model = readGlobalCodexModel(opts.homeDir) || 'gpt-5.5';
|
|
389
|
+
}
|
|
371
390
|
if (obj.model_reasoning_effort === undefined) obj.model_reasoning_effort = 'medium';
|
|
372
391
|
|
|
373
392
|
obj.features = isTomlSectionValue(obj.features) ? obj.features : {};
|
package/cli/lib/self-upgrade.js
CHANGED
|
@@ -9,9 +9,9 @@ import path from 'node:path';
|
|
|
9
9
|
import os from 'node:os';
|
|
10
10
|
import { execSync, spawnSync } from 'node:child_process';
|
|
11
11
|
import { SKILLS_DIR, BAIZE_DIR, getBaizeConfig } from './config.js';
|
|
12
|
-
import {
|
|
12
|
+
import { downloadBranch } from './download.js';
|
|
13
13
|
import { generateManifest, saveMergeBaseline } from './manifest.js';
|
|
14
|
-
import { fetchRawFile,
|
|
14
|
+
import { fetchRawFile, compareSemverDesc, sanitizeError } from './github.js';
|
|
15
15
|
import { copyTree, syncTree } from './fs-utils.js';
|
|
16
16
|
import { getCommandHooks, hookScriptKey } from './hook-utils.js';
|
|
17
17
|
import { isCoreManaged } from './sync-settings-hooks.js';
|
|
@@ -67,15 +67,43 @@ function getLatestVersion({ branch, beta = false } = {}) {
|
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
// Default:
|
|
70
|
+
// Default: npm registry — follows the user's npm config (npmmirror in CN),
|
|
71
|
+
// which GitHub-based tag detection cannot reach reliably (D31).
|
|
71
72
|
try {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
// Strict semver anchor (never interpolate unvalidated registry output into
|
|
74
|
+
// a shell command or semver comparison).
|
|
75
|
+
const SEMVER_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
76
|
+
if (beta) {
|
|
77
|
+
// Prerelease channel: prefer the highest non-latest dist-tag, falling
|
|
78
|
+
// back to the highest prerelease in the full versions list.
|
|
79
|
+
const tagsOut = execSync('npm view @baize-ai/core dist-tags --json', {
|
|
80
|
+
encoding: 'utf8', timeout: 20000, stdio: 'pipe',
|
|
81
|
+
});
|
|
82
|
+
const tags = JSON.parse(tagsOut);
|
|
83
|
+
const prereleaseTags = Object.entries(tags)
|
|
84
|
+
.filter(([tag, v]) => tag !== 'latest' && typeof v === 'string' && SEMVER_RE.test(v) && v.includes('-'))
|
|
85
|
+
.map(([, v]) => v);
|
|
86
|
+
if (prereleaseTags.length > 0) {
|
|
87
|
+
return { success: true, version: prereleaseTags.sort(compareSemverDesc)[0] };
|
|
88
|
+
}
|
|
89
|
+
const versionsOut = execSync('npm view @baize-ai/core versions --json', {
|
|
90
|
+
encoding: 'utf8', timeout: 20000, stdio: 'pipe',
|
|
91
|
+
});
|
|
92
|
+
const prereleases = JSON.parse(versionsOut)
|
|
93
|
+
.filter((v) => typeof v === 'string' && SEMVER_RE.test(v) && v.includes('-'));
|
|
94
|
+
if (prereleases.length === 0) {
|
|
95
|
+
return { success: false, error: 'No prerelease versions found in npm registry' };
|
|
96
|
+
}
|
|
97
|
+
return { success: true, version: prereleases.sort(compareSemverDesc)[0] };
|
|
98
|
+
}
|
|
99
|
+
const out = execSync('npm view @baize-ai/core version', { encoding: 'utf8', timeout: 20000, stdio: 'pipe' });
|
|
100
|
+
const version = out.trim();
|
|
101
|
+
if (!SEMVER_RE.test(version)) {
|
|
102
|
+
return { success: false, error: `Unexpected npm version output: ${version}` };
|
|
75
103
|
}
|
|
76
|
-
return { success:
|
|
104
|
+
return { success: true, version };
|
|
77
105
|
} catch (err) {
|
|
78
|
-
return { success: false, error: `Cannot fetch latest version: ${sanitizeError(err.message)}` };
|
|
106
|
+
return { success: false, error: `Cannot fetch latest version from npm registry: ${sanitizeError(err.message)}` };
|
|
79
107
|
}
|
|
80
108
|
}
|
|
81
109
|
|
|
@@ -150,17 +178,25 @@ export function downloadCoreToTemp(version, branch) {
|
|
|
150
178
|
return { success: true, tempDir };
|
|
151
179
|
}
|
|
152
180
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
181
|
+
// Default: npm registry tarball (npm pack), extracted to the temp root so
|
|
182
|
+
// downstream steps see the source-tree layout (package.json at root).
|
|
183
|
+
try {
|
|
184
|
+
execSync(`npm pack @baize-ai/core@${version} --pack-destination "${tempDir}"`, {
|
|
185
|
+
encoding: 'utf8', timeout: 120000, stdio: 'pipe',
|
|
186
|
+
});
|
|
187
|
+
const tgz = fs.readdirSync(tempDir).find((f) => f.endsWith('.tgz'));
|
|
188
|
+
if (!tgz) throw new Error('npm pack produced no tarball');
|
|
189
|
+
execSync(`tar -xzf "${path.join(tempDir, tgz)}" -C "${tempDir}"`, { stdio: 'pipe' });
|
|
190
|
+
const pkgDir = path.join(tempDir, 'package');
|
|
191
|
+
for (const entry of fs.readdirSync(pkgDir)) {
|
|
192
|
+
fs.renameSync(path.join(pkgDir, entry), path.join(tempDir, entry));
|
|
160
193
|
}
|
|
194
|
+
fs.rmSync(pkgDir, { recursive: true, force: true });
|
|
195
|
+
return { success: true, tempDir };
|
|
196
|
+
} catch (err) {
|
|
197
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
198
|
+
return { success: false, error: `npm registry download failed: ${sanitizeError(err.message)}` };
|
|
161
199
|
}
|
|
162
|
-
|
|
163
|
-
return { success: true, tempDir };
|
|
164
200
|
}
|
|
165
201
|
|
|
166
202
|
// ---------------------------------------------------------------------------
|
package/package.json
CHANGED
|
@@ -986,8 +986,10 @@ async function restartSessionFromAdmin(basePath) {
|
|
|
986
986
|
const { body } = await adminFetch(basePath, '/api/admin/restart', { method: 'POST' });
|
|
987
987
|
if (body.restarted) {
|
|
988
988
|
setAdminMsg('已发送重启指令(activity-monitor 将自动拉起新会话)');
|
|
989
|
+
} else if (body.pending) {
|
|
990
|
+
setAdminMsg('正在拉起 agent 进程…(已触发 activity-monitor,稍后刷新查看)');
|
|
989
991
|
} else if (body.reason === 'no_session') {
|
|
990
|
-
setAdminMsg('
|
|
992
|
+
setAdminMsg('当前无运行中会话,请稍后刷新查看');
|
|
991
993
|
} else {
|
|
992
994
|
setAdminMsg(`重启失败:${body.reason || '未知错误'}`, true);
|
|
993
995
|
}
|
|
@@ -362,14 +362,20 @@ export async function switchRuntime(name) {
|
|
|
362
362
|
* Restart the agent session by sending /exit to the tmux session; the activity
|
|
363
363
|
* monitor relaunches it with fresh env (which merges ~/baize/.env). No-op with
|
|
364
364
|
* a clear reason when no session is running.
|
|
365
|
-
* @returns {Promise<{success: boolean, restarted: boolean, reason?: string}>}
|
|
365
|
+
* @returns {Promise<{success: boolean, restarted: boolean, pending?: boolean, reason?: string}>}
|
|
366
366
|
*/
|
|
367
367
|
export async function restartAgentSession() {
|
|
368
368
|
const session = agentSessionName();
|
|
369
369
|
try {
|
|
370
370
|
await execFileAsync('tmux', ['has-session', '-t', session], { timeout: 5000 });
|
|
371
371
|
} catch {
|
|
372
|
-
|
|
372
|
+
// No session — trigger activity-monitor to relaunch the agent promptly
|
|
373
|
+
// instead of waiting out the Guardian's backoff. PM2 unavailable → the
|
|
374
|
+
// Guardian's periodic probe will pick it up; still report as pending.
|
|
375
|
+
try {
|
|
376
|
+
await execFileAsync('pm2', ['restart', 'activity-monitor'], { timeout: 15000 });
|
|
377
|
+
} catch { /* non-fatal — Guardian probes on its own schedule */ }
|
|
378
|
+
return { success: true, restarted: false, pending: true, reason: 'no_session' };
|
|
373
379
|
}
|
|
374
380
|
try {
|
|
375
381
|
await execFileAsync('tmux', ['send-keys', '-t', session, '/exit', 'Enter'], { timeout: 5000 });
|
|
@@ -45,6 +45,13 @@ function claudeSettingsPath() {
|
|
|
45
45
|
function codexConfigPath() {
|
|
46
46
|
return path.join(process.env.HOME || '', '.codex', 'config.toml');
|
|
47
47
|
}
|
|
48
|
+
// Project-level Codex config — the file codex actually prefers (project-level
|
|
49
|
+
// keys win over global ones). Web activation must write here too, or the
|
|
50
|
+
// project-level defaults baize init backfills (e.g. model = "gpt-5.5") would
|
|
51
|
+
// shadow the user's provider model/base_url (D31).
|
|
52
|
+
function projectCodexConfigPath() {
|
|
53
|
+
return path.join(process.env.BAIZE_DIR || path.join(process.env.HOME || '', 'baize'), '.codex', 'config.toml');
|
|
54
|
+
}
|
|
48
55
|
|
|
49
56
|
function loadStore() {
|
|
50
57
|
const data = readJson(providersPath()) || {};
|
|
@@ -319,17 +326,17 @@ function applyCodexOfficial() {
|
|
|
319
326
|
}
|
|
320
327
|
|
|
321
328
|
/**
|
|
322
|
-
* Conservative top-level edit of
|
|
329
|
+
* Conservative top-level edit of a Codex config.toml: set/replace the
|
|
323
330
|
* `model` and `openai_base_url` keys, preserving every other line (comments,
|
|
324
331
|
* sections, providers). Only the TOP-LEVEL prefix (before the first section
|
|
325
332
|
* header) is touched — keys inside [table] sections are also unindented, so
|
|
326
333
|
* regex edits must never run past the first `[` header.
|
|
327
334
|
*/
|
|
328
|
-
|
|
329
|
-
const
|
|
330
|
-
fs.mkdirSync(
|
|
335
|
+
function writeCodexTomlTopLevel(targetPath, { model, openaiBaseUrl }) {
|
|
336
|
+
const dir = path.dirname(targetPath);
|
|
337
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
331
338
|
let content = '';
|
|
332
|
-
try { content = fs.readFileSync(
|
|
339
|
+
try { content = fs.readFileSync(targetPath, 'utf8'); } catch { /* new file */ }
|
|
333
340
|
|
|
334
341
|
const sectionAt = content.search(/^\[/m);
|
|
335
342
|
const head = sectionAt === -1 ? content : content.slice(0, sectionAt);
|
|
@@ -348,7 +355,19 @@ export function editCodexConfigToml({ model, openaiBaseUrl }) {
|
|
|
348
355
|
if (openaiBaseUrl) out = setTopLevel(out, 'openai_base_url', openaiBaseUrl);
|
|
349
356
|
if (openaiBaseUrl === null) out = removeTopLevel(out, 'openai_base_url');
|
|
350
357
|
const joined = tail && !out.endsWith('\n') ? `${out}\n${tail}` : `${out}${tail}`;
|
|
351
|
-
fs.writeFileSync(
|
|
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 });
|
|
352
371
|
return codexConfigPath();
|
|
353
372
|
}
|
|
354
373
|
|