chore: stabilize Zhinian pilot delivery
This commit is contained in:
@@ -24,11 +24,13 @@ const NODE_MODULES = path.join(ROOT, 'node_modules');
|
||||
const YINIAN_RUNTIME_PATCH_MARKER_FILE = '.yinian-runtime-patch.json';
|
||||
const YINIAN_RUNTIME_PATCH_MARKER = {
|
||||
id: 'yinian-openclaw-runtime',
|
||||
version: '2026-05-07-desktop-fast-chat-v1',
|
||||
version: '2026-05-12-runtime-templates-selfref-v1',
|
||||
patches: [
|
||||
'bundled-npm-runner-env',
|
||||
'optional-native-clipboard-removed',
|
||||
'desktop-fast-chat-lightweight-tools',
|
||||
'runtime-reference-templates-preserved',
|
||||
'managed-openclaw-self-reference-required',
|
||||
],
|
||||
};
|
||||
|
||||
@@ -259,7 +261,6 @@ const OPENCLAW_RUNTIME_DEPS_PACKAGES = [
|
||||
'@slack/bolt',
|
||||
'@slack/web-api',
|
||||
'@tencent-connect/qqbot-connector',
|
||||
'@tloncorp/tlon-skill',
|
||||
'@twurple/api',
|
||||
'@twurple/auth',
|
||||
'@twurple/chat',
|
||||
@@ -273,6 +274,7 @@ const OPENCLAW_RUNTIME_DEPS_PACKAGES = [
|
||||
'croner',
|
||||
'discord-api-types',
|
||||
'dotenv',
|
||||
'docx',
|
||||
'express',
|
||||
'fake-indexeddb',
|
||||
'gaxios',
|
||||
@@ -299,6 +301,8 @@ const OPENCLAW_RUNTIME_DEPS_PACKAGES = [
|
||||
'opusscript',
|
||||
'pdfjs-dist',
|
||||
'playwright-core',
|
||||
'pptxgenjs',
|
||||
'react-icons',
|
||||
'semver',
|
||||
'sharp',
|
||||
'silk-wasm',
|
||||
@@ -532,6 +536,24 @@ function rmSafe(target) {
|
||||
} catch { return false; }
|
||||
}
|
||||
|
||||
function pruneOpenClawDocsButKeepRuntimeTemplates(outputDir) {
|
||||
const docsDir = path.join(outputDir, 'docs');
|
||||
const templatesDir = path.join(docsDir, 'reference', 'templates');
|
||||
if (!fs.existsSync(docsDir)) return false;
|
||||
if (!fs.existsSync(templatesDir)) {
|
||||
return rmSafe(docsDir);
|
||||
}
|
||||
|
||||
const tmpDir = path.join(outputDir, '.openclaw-runtime-templates.tmp');
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
fs.cpSync(templatesDir, tmpDir, { recursive: true, dereference: true });
|
||||
fs.rmSync(docsDir, { recursive: true, force: true });
|
||||
fs.mkdirSync(templatesDir, { recursive: true });
|
||||
fs.cpSync(tmpDir, templatesDir, { recursive: true, dereference: true });
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
return true;
|
||||
}
|
||||
|
||||
function cleanupBundle(outputDir) {
|
||||
let removedCount = 0;
|
||||
const nm = path.join(outputDir, 'node_modules');
|
||||
@@ -542,7 +564,9 @@ function cleanupBundle(outputDir) {
|
||||
if (rmSafe(path.join(outputDir, name))) removedCount++;
|
||||
}
|
||||
|
||||
// docs/ is kept — contains prompt templates and other runtime-used prompts
|
||||
// Most docs are site content, but docs/reference/templates is runtime input:
|
||||
// OpenClaw uses it when building workspace context before every chat.
|
||||
if (pruneOpenClawDocsButKeepRuntimeTemplates(outputDir)) removedCount++;
|
||||
|
||||
// --- extensions: clean junk from source, aggressively clean nested node_modules ---
|
||||
// Extension source (.ts files) are runtime entry points — must be preserved.
|
||||
@@ -551,12 +575,15 @@ function cleanupBundle(outputDir) {
|
||||
const JUNK_EXTS = new Set(['.prose', '.ignored_openclaw', '.keep']);
|
||||
const NM_REMOVE_DIRS = new Set([
|
||||
'test', 'tests', '__tests__', '.github', 'docs', 'examples', 'example',
|
||||
'__snapshots__', '__image_snapshots__', 'snapshots', 'fixtures', 'fixture',
|
||||
'bench', 'benchmark', 'benchmarks', 'screenshots',
|
||||
]);
|
||||
const NM_REMOVE_FILE_EXTS = ['.d.ts', '.d.ts.map', '.js.map', '.mjs.map', '.ts.map', '.markdown'];
|
||||
const NM_REMOVE_FILE_NAMES = new Set([
|
||||
'.DS_Store', 'README.md', 'CHANGELOG.md', 'LICENSE.md', 'CONTRIBUTING.md',
|
||||
'tsconfig.json', '.npmignore', '.eslintrc', '.prettierrc', '.editorconfig',
|
||||
]);
|
||||
const isEnvFile = (name) => name === '.env' || name.startsWith('.env.');
|
||||
|
||||
// .md files inside skills/ directories are runtime content (SKILL.md,
|
||||
// block-types.md, etc.) and must NOT be removed.
|
||||
@@ -582,7 +609,7 @@ function cleanupBundle(outputDir) {
|
||||
} else if (entry.isFile()) {
|
||||
if (insideNodeModules) {
|
||||
const name = entry.name;
|
||||
if (NM_REMOVE_FILE_NAMES.has(name) || NM_REMOVE_FILE_EXTS.some(e => name.endsWith(e))) {
|
||||
if (isEnvFile(name) || NM_REMOVE_FILE_NAMES.has(name) || NM_REMOVE_FILE_EXTS.some(e => name.endsWith(e))) {
|
||||
if (rmSafe(full)) removedCount++;
|
||||
}
|
||||
} else {
|
||||
@@ -605,12 +632,15 @@ function cleanupBundle(outputDir) {
|
||||
if (fs.existsSync(nm)) {
|
||||
const REMOVE_DIRS = new Set([
|
||||
'test', 'tests', '__tests__', '.github', 'docs', 'examples', 'example',
|
||||
'__snapshots__', '__image_snapshots__', 'snapshots', 'fixtures', 'fixture',
|
||||
'bench', 'benchmark', 'benchmarks', 'screenshots',
|
||||
]);
|
||||
const REMOVE_FILE_EXTS = ['.d.ts', '.d.ts.map', '.js.map', '.mjs.map', '.ts.map', '.markdown'];
|
||||
const REMOVE_FILE_NAMES = new Set([
|
||||
'.DS_Store', 'README.md', 'CHANGELOG.md', 'LICENSE.md', 'CONTRIBUTING.md',
|
||||
'tsconfig.json', '.npmignore', '.eslintrc', '.prettierrc', '.editorconfig',
|
||||
]);
|
||||
const isEnvFile = (name) => name === '.env' || name.startsWith('.env.');
|
||||
|
||||
function walkClean(dir) {
|
||||
let entries;
|
||||
@@ -625,7 +655,7 @@ function cleanupBundle(outputDir) {
|
||||
}
|
||||
} else if (entry.isFile()) {
|
||||
const name = entry.name;
|
||||
if (REMOVE_FILE_NAMES.has(name) || REMOVE_FILE_EXTS.some(e => name.endsWith(e))) {
|
||||
if (isEnvFile(name) || REMOVE_FILE_NAMES.has(name) || REMOVE_FILE_EXTS.some(e => name.endsWith(e))) {
|
||||
if (rmSafe(full)) removedCount++;
|
||||
}
|
||||
}
|
||||
@@ -900,6 +930,80 @@ function patchBundledRuntime(outputDir) {
|
||||
\t\t\tplatform: process$1.platform
|
||||
\t\t}) ? { shell: true } : {}
|
||||
\t});`,
|
||||
},
|
||||
{
|
||||
label: 'agents skills discovery boundary',
|
||||
target: () => findFirstFileByName(path.join(outputDir, 'dist'), /^workspace-.*\.js$/),
|
||||
search: `\tconst osHomeDir = resolveUserHomeDir();
|
||||
\tconst personalAgentsSkills = loadSkills({
|
||||
\t\tdir: osHomeDir ? path.resolve(osHomeDir, ".agents", "skills") : path.resolve(".agents", "skills"),
|
||||
\t\tsource: "agents-skills-personal"
|
||||
\t});
|
||||
\tconst projectAgentsSkills = loadSkills({
|
||||
\t\tdir: path.resolve(workspaceDir, ".agents", "skills"),
|
||||
\t\tsource: "agents-skills-project"
|
||||
\t});`,
|
||||
replace: `\tconst openClawAgentsSkillsDisabled = process.env.OPENCLAW_DISABLE_AGENTS_SKILLS === "1";
|
||||
\tconst osHomeDir = openClawAgentsSkillsDisabled ? void 0 : resolveUserHomeDir();
|
||||
\tconst personalAgentsSkills = openClawAgentsSkillsDisabled ? [] : loadSkills({
|
||||
\t\tdir: osHomeDir ? path.resolve(osHomeDir, ".agents", "skills") : path.resolve(".agents", "skills"),
|
||||
\t\tsource: "agents-skills-personal"
|
||||
\t});
|
||||
\tconst projectAgentsSkills = openClawAgentsSkillsDisabled ? [] : loadSkills({
|
||||
\t\tdir: path.resolve(workspaceDir, ".agents", "skills"),
|
||||
\t\tsource: "agents-skills-project"
|
||||
\t});`,
|
||||
},
|
||||
{
|
||||
label: 'main session restart recovery mark boundary',
|
||||
target: () => findFirstFileByName(path.join(outputDir, 'dist'), /^main-session-restart-recovery-.*\.js$/),
|
||||
search: `\t\t\tif (!interruptedSessionIds.has(entry.sessionId)) continue;
|
||||
\t\t\tentry.abortedLastRun = true;
|
||||
\t\t\tstore[sessionKey] = entry;
|
||||
\t\t\tresult.marked++;`,
|
||||
replace: `\t\t\tif (!interruptedSessionIds.has(entry.sessionId)) continue;
|
||||
\t\t\tentry.abortedLastRun = true;
|
||||
\t\t\tif (process.env.OPENCLAW_DISABLE_MAIN_SESSION_RESTART_RECOVERY === "1") {
|
||||
\t\t\t\tentry.status = "failed";
|
||||
\t\t\t\tentry.endedAt = Date.now();
|
||||
\t\t\t\tentry.updatedAt = entry.endedAt;
|
||||
\t\t\t} else {
|
||||
\t\t\t\tentry.updatedAt = Date.now();
|
||||
\t\t\t}
|
||||
\t\t\tstore[sessionKey] = entry;
|
||||
\t\t\tresult.marked++;`,
|
||||
},
|
||||
{
|
||||
label: 'main session restart recovery schedule boundary',
|
||||
target: () => findFirstFileByName(path.join(outputDir, 'dist'), /^main-session-restart-recovery-.*\.js$/),
|
||||
search: `function scheduleRestartAbortedMainSessionRecovery(params = {}) {
|
||||
\tconst initialDelay = params.delayMs ?? DEFAULT_RECOVERY_DELAY_MS;`,
|
||||
replace: `function scheduleRestartAbortedMainSessionRecovery(params = {}) {
|
||||
\tif (process.env.OPENCLAW_DISABLE_MAIN_SESSION_RESTART_RECOVERY === "1") {
|
||||
\t\tlog.info("main-session restart recovery disabled by OPENCLAW_DISABLE_MAIN_SESSION_RESTART_RECOVERY");
|
||||
\t\treturn;
|
||||
\t}
|
||||
\tconst initialDelay = params.delayMs ?? DEFAULT_RECOVERY_DELAY_MS;`,
|
||||
},
|
||||
{
|
||||
label: 'stuck session active-run recovery boundary',
|
||||
target: () => findFirstFileByName(path.join(outputDir, 'dist'), /^diagnostic-.*\.js$/),
|
||||
search: `\t\t\t\t(opts?.recoverStuckSession ?? recoverStuckSession)({
|
||||
\t\t\t\t\tsessionId: state.sessionId,
|
||||
\t\t\t\t\tsessionKey: state.sessionKey,
|
||||
\t\t\t\t\tageMs,
|
||||
\t\t\t\t\tqueueDepth: state.queueDepth
|
||||
\t\t\t\t});`,
|
||||
replace: `\t\t\t\t(opts?.recoverStuckSession ?? recoverStuckSession)({
|
||||
\t\t\t\t\tsessionId: state.sessionId,
|
||||
\t\t\t\t\tsessionKey: state.sessionKey,
|
||||
\t\t\t\t\tageMs,
|
||||
\t\t\t\t\tqueueDepth: state.queueDepth,
|
||||
\t\t\t\t\tallowActiveAbort: (() => {
|
||||
\t\t\t\t\t\tconst thresholdMs = Number(process.env.YINIAN_OPENCLAW_STUCK_ACTIVE_ABORT_MS || "900000");
|
||||
\t\t\t\t\t\treturn Number.isFinite(thresholdMs) && thresholdMs > 0 && ageMs >= thresholdMs;
|
||||
\t\t\t\t\t})()
|
||||
\t\t\t\t});`,
|
||||
},
|
||||
// Note: OpenClaw 3.31 removed the hash-suffixed agent-scope-*.js, chrome-*.js,
|
||||
// and qmd-manager-*.js files from dist/plugin-sdk/. Patches for those spawn
|
||||
@@ -1133,6 +1237,22 @@ function patchBundledRuntime(outputDir) {
|
||||
materializedCheckCount++;
|
||||
}
|
||||
|
||||
const loadRootSearch = `const packagePlan = collectBundledPluginRuntimeDeps({
|
||||
\t\t\textensionsDir,
|
||||
\t\t\t...params.config ? { config: params.config } : {},
|
||||
\t\t\tmanifestCache,
|
||||
\t\t\t...normalizePluginId ? { normalizePluginId } : {}
|
||||
\t\t});`;
|
||||
const loadRootReplace = `const packagePlan = collectBundledPluginRuntimeDeps({
|
||||
\t\t\textensionsDir,
|
||||
\t\t\t...params.config ? { config: params.config } : { selectedPluginIds: new Set([params.pluginId]) },
|
||||
\t\t\tmanifestCache,
|
||||
\t\t\t...normalizePluginId ? { normalizePluginId } : {}
|
||||
\t\t});`;
|
||||
if (next.includes(loadRootSearch)) {
|
||||
next = next.replace(loadRootSearch, loadRootReplace);
|
||||
}
|
||||
|
||||
if (next !== current) {
|
||||
fs.writeFileSync(target, next, 'utf8');
|
||||
}
|
||||
@@ -1162,6 +1282,10 @@ echo` 🧭 Wrote Yinian runtime patch marker ${YINIAN_RUNTIME_PATCH_MARKER.ver
|
||||
// 8. Verify the bundle
|
||||
const entryExists = fs.existsSync(path.join(OUTPUT, 'openclaw.mjs'));
|
||||
const distExists = fs.existsSync(path.join(OUTPUT, 'dist', 'entry.js'));
|
||||
const requiredTemplateFiles = ['AGENTS.md', 'TOOLS.md', 'HEARTBEAT.md'];
|
||||
const missingTemplateFiles = requiredTemplateFiles.filter((fileName) => (
|
||||
!fs.existsSync(path.join(OUTPUT, 'docs', 'reference', 'templates', fileName))
|
||||
));
|
||||
|
||||
echo``;
|
||||
echo`✅ Bundle complete: ${OUTPUT}`;
|
||||
@@ -1172,8 +1296,9 @@ echo` Duplicate versions skipped: ${skippedDupes}`;
|
||||
echo` Total discovered: ${collected.size}`;
|
||||
echo` openclaw.mjs: ${entryExists ? '✓' : '✗'}`;
|
||||
echo` dist/entry.js: ${distExists ? '✓' : '✗'}`;
|
||||
echo` runtime templates: ${missingTemplateFiles.length === 0 ? '✓' : `✗ missing ${missingTemplateFiles.join(', ')}`}`;
|
||||
|
||||
if (!entryExists || !distExists) {
|
||||
if (!entryExists || !distExists || missingTemplateFiles.length > 0) {
|
||||
echo`❌ Bundle verification failed!`;
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user