chore: stabilize Zhinian pilot delivery

This commit is contained in:
inman
2026-05-12 19:44:44 +08:00
parent 45389855e1
commit 20b5aff4ad
174 changed files with 41428 additions and 784 deletions

View File

@@ -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);
}