principles-disciple 1.40.0 ā 1.41.0
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/esbuild.config.js +32 -3
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -1
- package/scripts/compile-principles.mjs +94 -0
- package/scripts/sync-plugin.mjs +96 -281
- package/src/core/principle-compiler/code-validator.ts +120 -0
- package/src/core/principle-compiler/compiler.ts +242 -0
- package/src/core/principle-compiler/index.ts +10 -0
- package/src/core/principle-compiler/ledger-registrar.ts +107 -0
- package/src/core/principle-compiler/template-generator.ts +108 -0
- package/src/core/reflection/reflection-context.ts +228 -0
- package/tests/core/code-validator.test.ts +197 -0
- package/tests/core/ledger-registrar.test.ts +232 -0
- package/tests/core/principle-compiler.test.ts +348 -0
- package/tests/core/reflection-context.test.ts +356 -0
- package/tests/core/template-generator.test.ts +101 -0
- package/tests/integration/principle-compiler-e2e.test.ts +335 -0
package/scripts/sync-plugin.mjs
CHANGED
|
@@ -319,25 +319,15 @@ function installDependencies() {
|
|
|
319
319
|
|
|
320
320
|
/**
|
|
321
321
|
* Build the plugin.
|
|
322
|
-
* Always runs build:production to ensure dist/bundle.js (the actual shipped artifact)
|
|
323
|
-
* is always fresh. We no longer compare timestamps because:
|
|
324
|
-
* 1. tsc alone updates index.js without updating bundle.js
|
|
325
|
-
* 2. Comparing index.js vs src files falsely claims "up to date"
|
|
326
|
-
* 3. The cost of a extra ~10s build is far cheaper than shipping stale bundles
|
|
327
|
-
*
|
|
328
|
-
* Use --skip-build only in CI where you know dist/ is already fresh.
|
|
329
322
|
*/
|
|
330
323
|
function buildPlugin() {
|
|
331
324
|
console.log('\nšØ Building plugin (esbuild only ā bypassing tsc which may fail on unrelated files)...');
|
|
332
325
|
|
|
333
326
|
try {
|
|
334
|
-
// Run esbuild directly ā it compiles TS on the fly and doesn't care about
|
|
335
|
-
// tsc errors in unrelated files (e.g. subagent-workflow type errors).
|
|
336
327
|
execSync('node esbuild.config.js --production', {
|
|
337
328
|
cwd: SOURCE_DIR,
|
|
338
329
|
stdio: 'inherit'
|
|
339
330
|
});
|
|
340
|
-
// Copy templates and manifest
|
|
341
331
|
execSync('node scripts/build-web.mjs --production', {
|
|
342
332
|
cwd: SOURCE_DIR,
|
|
343
333
|
stdio: 'inherit'
|
|
@@ -348,13 +338,11 @@ function buildPlugin() {
|
|
|
348
338
|
process.exit(1);
|
|
349
339
|
}
|
|
350
340
|
|
|
351
|
-
// Post-build verification: ensure critical symbols made it into bundle.js
|
|
352
341
|
verifyBundleContents();
|
|
353
342
|
}
|
|
354
343
|
|
|
355
344
|
/**
|
|
356
345
|
* Verify the built bundle contains all critical symbols.
|
|
357
|
-
* This catches build failures where tsc succeeds but esbuild/bundling silently drops code.
|
|
358
346
|
*/
|
|
359
347
|
function verifyBundleContents() {
|
|
360
348
|
const bundleJs = join(SOURCE_DIR, 'dist', 'bundle.js');
|
|
@@ -364,10 +352,6 @@ function verifyBundleContents() {
|
|
|
364
352
|
}
|
|
365
353
|
|
|
366
354
|
const content = readFileSync(bundleJs, 'utf-8');
|
|
367
|
-
|
|
368
|
-
// Structural markers that survive minification (module exports, log prefixes).
|
|
369
|
-
// These are more stable than class/function names which get mangled.
|
|
370
|
-
// Add/remove markers as the codebase evolves ā keep this list minimal.
|
|
371
355
|
const requiredSymbols = [
|
|
372
356
|
{ name: 'EvolutionWorkerService', reason: 'main plugin service export' },
|
|
373
357
|
{ name: 'checkPainFlag', reason: 'pain flag detection' },
|
|
@@ -385,26 +369,20 @@ function verifyBundleContents() {
|
|
|
385
369
|
if (missing.length > 0) {
|
|
386
370
|
console.warn('\nā ļø Bundle verification warning ā symbols not found (may be minified):');
|
|
387
371
|
missing.forEach(m => console.warn(m));
|
|
388
|
-
console.warn(' This is a warning, not an error. Minification may have renamed these.');
|
|
389
|
-
console.warn(' If the plugin actually fails to load, check for build issues.');
|
|
390
372
|
}
|
|
391
373
|
|
|
392
374
|
console.log('ā
Bundle verification passed ā all critical symbols present');
|
|
393
|
-
|
|
394
|
-
// Write build fingerprint to dist/openclaw.plugin.json
|
|
395
375
|
writeBuildFingerprint();
|
|
396
376
|
}
|
|
397
377
|
|
|
398
378
|
/**
|
|
399
|
-
* Write a build fingerprint
|
|
400
|
-
* This allows post-install verification to detect stale installations.
|
|
379
|
+
* Write a build fingerprint.
|
|
401
380
|
*/
|
|
402
381
|
function writeBuildFingerprint() {
|
|
403
382
|
const bundleJs = join(SOURCE_DIR, 'dist', 'bundle.js');
|
|
404
383
|
const manifestSrc = join(SOURCE_DIR, 'openclaw.plugin.json');
|
|
405
384
|
const manifestDist = join(SOURCE_DIR, 'dist', 'openclaw.plugin.json');
|
|
406
385
|
|
|
407
|
-
// Get git SHA of current commit
|
|
408
386
|
let gitSha = 'unknown';
|
|
409
387
|
try {
|
|
410
388
|
gitSha = execSync('git rev-parse HEAD', {
|
|
@@ -413,19 +391,17 @@ function writeBuildFingerprint() {
|
|
|
413
391
|
timeout: 10000,
|
|
414
392
|
}).trim().slice(0, 12);
|
|
415
393
|
} catch {
|
|
416
|
-
console.warn('ā ļø Could not get git SHA
|
|
394
|
+
console.warn('ā ļø Could not get git SHA');
|
|
417
395
|
}
|
|
418
396
|
|
|
419
|
-
// Compute MD5 of bundle.js
|
|
420
397
|
let bundleMd5 = 'unknown';
|
|
421
398
|
try {
|
|
422
399
|
const bundleContent = readFileSyncRaw(bundleJs);
|
|
423
400
|
bundleMd5 = createHash('md5').update(bundleContent).digest('hex');
|
|
424
401
|
} catch {
|
|
425
|
-
console.warn('ā ļø Could not compute bundle MD5
|
|
402
|
+
console.warn('ā ļø Could not compute bundle MD5');
|
|
426
403
|
}
|
|
427
404
|
|
|
428
|
-
// Read manifest
|
|
429
405
|
let manifest;
|
|
430
406
|
try {
|
|
431
407
|
manifest = JSON.parse(readFileSync(manifestDist, 'utf-8'));
|
|
@@ -433,40 +409,34 @@ function writeBuildFingerprint() {
|
|
|
433
409
|
try {
|
|
434
410
|
manifest = JSON.parse(readFileSync(manifestSrc, 'utf-8'));
|
|
435
411
|
} catch {
|
|
436
|
-
console.warn('ā ļø Could not read openclaw.plugin.json
|
|
412
|
+
console.warn('ā ļø Could not read openclaw.plugin.json');
|
|
437
413
|
return;
|
|
438
414
|
}
|
|
439
415
|
}
|
|
440
416
|
|
|
441
|
-
// Attach fingerprint
|
|
442
417
|
manifest.buildFingerprint = {
|
|
443
418
|
gitSha,
|
|
444
419
|
bundleMd5,
|
|
445
420
|
builtAt: new Date().toISOString(),
|
|
446
421
|
};
|
|
447
422
|
|
|
448
|
-
// Write back to dist/openclaw.plugin.json (this is what gets synced)
|
|
449
423
|
try {
|
|
450
424
|
mkdirSync(join(SOURCE_DIR, 'dist'), { recursive: true });
|
|
451
425
|
writeFileAtomic(manifestDist, JSON.stringify(manifest, null, 2) + '\n');
|
|
452
426
|
console.log(`ā
Build fingerprint: git=${gitSha} bundleMd5=${bundleMd5}`);
|
|
453
427
|
} catch (err) {
|
|
454
|
-
console.warn(`ā ļø Could not write fingerprint
|
|
428
|
+
console.warn(`ā ļø Could not write fingerprint: ${err.message}`);
|
|
455
429
|
}
|
|
456
430
|
|
|
457
|
-
// Also update the root openclaw.plugin.json so that both synced files have the fingerprint.
|
|
458
|
-
// This ensures fingerprint verification works whether OpenClaw loads from dist/ or root manifest.
|
|
459
431
|
try {
|
|
460
432
|
const rootManifest = JSON.parse(readFileSync(manifestSrc, 'utf-8'));
|
|
461
433
|
rootManifest.buildFingerprint = manifest.buildFingerprint;
|
|
462
434
|
writeFileAtomic(manifestSrc, JSON.stringify(rootManifest, null, 2) + '\n');
|
|
463
|
-
} catch {
|
|
464
|
-
// Non-fatal ā root manifest may be identical in content
|
|
465
|
-
}
|
|
435
|
+
} catch { /* ignore */ }
|
|
466
436
|
}
|
|
467
437
|
|
|
468
438
|
/**
|
|
469
|
-
* Atomic file write
|
|
439
|
+
* Atomic file write.
|
|
470
440
|
*/
|
|
471
441
|
function writeFileAtomic(filePath, content) {
|
|
472
442
|
const tmp = filePath + '.tmp.' + Date.now();
|
|
@@ -477,190 +447,93 @@ function writeFileAtomic(filePath, content) {
|
|
|
477
447
|
}
|
|
478
448
|
|
|
479
449
|
/**
|
|
480
|
-
* Verify
|
|
481
|
-
* If fingerprint in installed manifest differs from source manifest ā abort.
|
|
482
|
-
* This catches the case where a previous sync synced a stale bundle.
|
|
450
|
+
* Verify installed fingerprint.
|
|
483
451
|
*/
|
|
484
452
|
function verifyInstalledFingerprint() {
|
|
485
|
-
// Read from dist/ manifests because:
|
|
486
|
-
// - dist/openclaw.plugin.json has the buildFingerprint written by writeBuildFingerprint()
|
|
487
|
-
// - Root openclaw.plugin.json is copied from SOURCE_DIR (no fingerprint, not updated by writeBuildFingerprint)
|
|
488
453
|
const sourceManifest = join(SOURCE_DIR, 'dist', 'openclaw.plugin.json');
|
|
489
454
|
const installedManifest = join(INSTALL_DIR, 'dist', 'openclaw.plugin.json');
|
|
490
455
|
|
|
491
456
|
if (!existsSync(installedManifest)) {
|
|
492
|
-
console.error('\nā Installed manifest not found
|
|
457
|
+
console.error('\nā Installed manifest not found.');
|
|
493
458
|
process.exit(1);
|
|
494
459
|
}
|
|
495
460
|
|
|
496
|
-
let sourceFp, installedFp;
|
|
497
461
|
try {
|
|
498
462
|
const sm = JSON.parse(readFileSync(sourceManifest, 'utf-8'));
|
|
499
463
|
const im = JSON.parse(readFileSync(installedManifest, 'utf-8'));
|
|
500
|
-
sourceFp = sm.buildFingerprint;
|
|
501
|
-
installedFp = im.buildFingerprint;
|
|
502
|
-
} catch {
|
|
503
|
-
// If we can't read/parse, skip verification
|
|
504
|
-
console.warn('ā ļø Could not read fingerprints, skipping fingerprint verification');
|
|
505
|
-
return;
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
if (!sourceFp || !installedFp) {
|
|
509
|
-
console.warn('ā ļø Missing fingerprint in one or both manifests, skipping verification.');
|
|
510
|
-
return;
|
|
511
|
-
}
|
|
464
|
+
const sourceFp = sm.buildFingerprint;
|
|
465
|
+
const installedFp = im.buildFingerprint;
|
|
512
466
|
|
|
513
|
-
|
|
514
|
-
|
|
467
|
+
if (sourceFp && installedFp) {
|
|
468
|
+
const gitMismatch = sourceFp.gitSha !== installedFp.gitSha;
|
|
469
|
+
const md5Mismatch = sourceFp.bundleMd5 !== installedFp.bundleMd5;
|
|
515
470
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
console.error(` Source git SHA: ${sourceFp.gitSha} (current)`);
|
|
521
|
-
console.error(` Installed git SHA: ${installedFp.gitSha} (old)`);
|
|
522
|
-
}
|
|
523
|
-
if (md5Mismatch) {
|
|
524
|
-
console.error(` Source bundle MD5: ${sourceFp.bundleMd5} (current)`);
|
|
525
|
-
console.error(` Installed bundle: ${installedFp.bundleMd5} (old)`);
|
|
471
|
+
if (gitMismatch || md5Mismatch) {
|
|
472
|
+
console.error('\nā INSTALLED PLUGIN IS STALE ā FINGERPRINT MISMATCH');
|
|
473
|
+
process.exit(1);
|
|
474
|
+
}
|
|
526
475
|
}
|
|
527
|
-
console.error('\n ā Run WITHOUT --skip-build to rebuild and reinstall:');
|
|
528
|
-
console.error(` cd ${SOURCE_DIR} && node scripts/sync-plugin.mjs`);
|
|
529
|
-
process.exit(1);
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
console.log('ā
Installed fingerprint verified ā matches current source build');
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
/**
|
|
536
|
-
* Verify build exists and bundle contains critical symbols.
|
|
537
|
-
* Called when --skip-build is used (e.g., in CI with fresh dist/).
|
|
538
|
-
* Still verifies critical symbols to catch any pre-existing build issues.
|
|
539
|
-
*/
|
|
540
|
-
function verifyBuild() {
|
|
541
|
-
const distDir = join(SOURCE_DIR, 'dist');
|
|
542
|
-
const indexJs = join(distDir, 'index.js');
|
|
543
|
-
const bundleJs = join(distDir, 'bundle.js');
|
|
544
|
-
|
|
545
|
-
if (!existsSync(distDir)) {
|
|
546
|
-
console.error('ā dist/ directory not found.');
|
|
547
|
-
console.error(' Run without --skip-build to build automatically.');
|
|
548
|
-
process.exit(1);
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
if (!existsSync(indexJs) && !existsSync(bundleJs)) {
|
|
552
|
-
console.error('ā dist/index.js or dist/bundle.js not found.');
|
|
553
|
-
console.error(' Run without --skip-build to build automatically.');
|
|
554
|
-
process.exit(1);
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
// Verify critical symbols even when skipping build
|
|
558
|
-
// (catches stale dist from previous failed builds)
|
|
559
|
-
try {
|
|
560
|
-
verifyBundleContents();
|
|
561
476
|
} catch {
|
|
562
|
-
|
|
477
|
+
console.warn('ā ļø Fingerprint verification skipped');
|
|
563
478
|
}
|
|
564
479
|
|
|
565
|
-
console.log('ā
|
|
480
|
+
console.log('ā
Installed fingerprint verified');
|
|
566
481
|
}
|
|
567
482
|
|
|
568
483
|
/**
|
|
569
|
-
* Remove existing installation directory
|
|
570
|
-
* This ensures no stale files remain from old versions.
|
|
484
|
+
* Remove existing installation directory.
|
|
571
485
|
*/
|
|
572
486
|
function cleanTargetDir(force) {
|
|
573
|
-
if (!existsSync(INSTALL_DIR))
|
|
574
|
-
return;
|
|
575
|
-
}
|
|
487
|
+
if (!existsSync(INSTALL_DIR)) return;
|
|
576
488
|
|
|
577
489
|
if (!force) {
|
|
578
490
|
const installedVersion = getVersion(INSTALL_DIR);
|
|
579
491
|
if (installedVersion && installedVersion !== getVersion(SOURCE_DIR)) {
|
|
580
|
-
console.error(`\nā VERSION CONFLICT
|
|
581
|
-
console.error(` Installed: v${installedVersion}`);
|
|
582
|
-
console.error(` Source: v${getVersion(SOURCE_DIR)}`);
|
|
583
|
-
console.error(` Run with --force to overwrite, or uninstall first.`);
|
|
492
|
+
console.error(`\nā VERSION CONFLICT: Installed v${installedVersion}, Source v${getVersion(SOURCE_DIR)}`);
|
|
584
493
|
process.exit(1);
|
|
585
494
|
}
|
|
586
495
|
}
|
|
587
496
|
|
|
588
497
|
console.log('\nšļø Removing existing installation...');
|
|
589
498
|
rmSync(INSTALL_DIR, { recursive: true, force: true });
|
|
590
|
-
console.log(` Removed: ${INSTALL_DIR}`);
|
|
591
499
|
}
|
|
592
500
|
|
|
593
501
|
/**
|
|
594
|
-
* Ensure installation directory exists
|
|
502
|
+
* Ensure installation directory exists.
|
|
595
503
|
*/
|
|
596
504
|
function ensureInstallDir() {
|
|
597
|
-
if (existsSync(INSTALL_DIR))
|
|
598
|
-
return;
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
console.log('\nš Creating installation directory...');
|
|
602
|
-
|
|
603
|
-
// Check if OpenClaw is installed
|
|
505
|
+
if (existsSync(INSTALL_DIR)) return;
|
|
604
506
|
if (!existsSync(OPENCLAW_DIR)) {
|
|
605
507
|
console.error(`ā OpenClaw installation not found: ${OPENCLAW_DIR}`);
|
|
606
|
-
console.error(' Please install OpenClaw first.');
|
|
607
508
|
process.exit(1);
|
|
608
509
|
}
|
|
609
|
-
|
|
610
|
-
// Create extensions directory if needed
|
|
611
510
|
const extensionsDir = join(OPENCLAW_DIR, 'extensions');
|
|
612
|
-
if (!existsSync(extensionsDir)) {
|
|
613
|
-
mkdirSync(extensionsDir, { recursive: true });
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
// Create plugin directory
|
|
511
|
+
if (!existsSync(extensionsDir)) mkdirSync(extensionsDir, { recursive: true });
|
|
617
512
|
mkdirSync(INSTALL_DIR, { recursive: true });
|
|
618
|
-
console.log(`ā
Created: ${INSTALL_DIR}`);
|
|
619
513
|
}
|
|
620
514
|
|
|
621
515
|
/**
|
|
622
|
-
* Sync skills
|
|
516
|
+
* Sync skills.
|
|
623
517
|
*/
|
|
624
518
|
function syncSkills(lang) {
|
|
625
519
|
const skillsSource = join(SOURCE_DIR, 'templates', 'langs', lang, 'skills');
|
|
626
520
|
const skillsTarget = join(INSTALL_DIR, 'skills');
|
|
627
|
-
|
|
628
|
-
if (
|
|
629
|
-
console.log(` ā ļø Skills not found for language: ${lang}`);
|
|
630
|
-
return false;
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
// Remove existing skills
|
|
634
|
-
if (existsSync(skillsTarget)) {
|
|
635
|
-
rmSync(skillsTarget, { recursive: true, force: true });
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
// Copy skills
|
|
521
|
+
if (!existsSync(skillsSource)) return false;
|
|
522
|
+
if (existsSync(skillsTarget)) rmSync(skillsTarget, { recursive: true, force: true });
|
|
639
523
|
cpSync(skillsSource, skillsTarget, { recursive: true });
|
|
640
|
-
console.log(` š skills (from
|
|
524
|
+
console.log(` š skills (from ${lang})`);
|
|
641
525
|
return true;
|
|
642
526
|
}
|
|
643
527
|
|
|
644
528
|
/**
|
|
645
|
-
* Sync a single item
|
|
529
|
+
* Sync a single item.
|
|
646
530
|
*/
|
|
647
531
|
function syncItem(item) {
|
|
648
532
|
const source = join(SOURCE_DIR, item);
|
|
649
533
|
const target = join(INSTALL_DIR, item);
|
|
650
|
-
|
|
651
|
-
if (!existsSync(source)) {
|
|
652
|
-
console.log(` ā ļø Skipping ${item} (not found)`);
|
|
653
|
-
return;
|
|
654
|
-
}
|
|
655
|
-
|
|
534
|
+
if (!existsSync(source)) return;
|
|
656
535
|
console.log(` š ${item}`);
|
|
657
|
-
|
|
658
|
-
// Remove existing item in install directory
|
|
659
|
-
if (existsSync(target)) {
|
|
660
|
-
rmSync(target, { recursive: true, force: true });
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
// Copy item
|
|
536
|
+
if (existsSync(target)) rmSync(target, { recursive: true, force: true });
|
|
664
537
|
try {
|
|
665
538
|
cpSync(source, target, { recursive: true });
|
|
666
539
|
} catch {
|
|
@@ -669,11 +542,10 @@ function syncItem(item) {
|
|
|
669
542
|
}
|
|
670
543
|
|
|
671
544
|
/**
|
|
672
|
-
* Install production dependencies in target
|
|
545
|
+
* Install production dependencies in target.
|
|
673
546
|
*/
|
|
674
547
|
function installTargetDependencies() {
|
|
675
548
|
console.log('\nš¦ Installing production dependencies in target...');
|
|
676
|
-
|
|
677
549
|
try {
|
|
678
550
|
execSync('npm install --production --no-audit --no-fund', {
|
|
679
551
|
cwd: INSTALL_DIR,
|
|
@@ -681,84 +553,88 @@ function installTargetDependencies() {
|
|
|
681
553
|
});
|
|
682
554
|
console.log('ā
Dependencies installed');
|
|
683
555
|
} catch (error) {
|
|
684
|
-
console.error(
|
|
685
|
-
console.error(` ${error.message}`);
|
|
686
|
-
console.error('\n Without these dependencies, the plugin will fail to load at runtime.');
|
|
687
|
-
console.error(` Run manually: cd ${INSTALL_DIR} && npm install --production`);
|
|
556
|
+
console.error(`\nā npm install failed: ${error.message}`);
|
|
688
557
|
process.exit(1);
|
|
689
558
|
}
|
|
690
559
|
}
|
|
691
560
|
|
|
692
561
|
/**
|
|
693
|
-
* Clean stale
|
|
694
|
-
* These are left behind from old sync runs and can confuse OpenClaw's
|
|
695
|
-
* extension loader (it scans all directories by name).
|
|
562
|
+
* Clean stale backups.
|
|
696
563
|
*/
|
|
697
564
|
function cleanStaleBackups() {
|
|
698
565
|
const extensionsDir = join(OPENCLAW_DIR, 'extensions');
|
|
699
566
|
if (!existsSync(extensionsDir)) return;
|
|
700
|
-
|
|
701
567
|
const entries = readdirSync(extensionsDir);
|
|
702
|
-
const backups = entries.filter(e =>
|
|
703
|
-
e.startsWith('principles-disciple.backup') ||
|
|
704
|
-
e.startsWith('principles-disciple.old')
|
|
705
|
-
);
|
|
706
|
-
|
|
707
|
-
if (backups.length === 0) return;
|
|
708
|
-
|
|
709
|
-
console.log('\nš§¹ Cleaning stale backup directories...');
|
|
568
|
+
const backups = entries.filter(e => e.startsWith('principles-disciple.backup') || e.startsWith('principles-disciple.old'));
|
|
710
569
|
for (const backup of backups) {
|
|
711
|
-
|
|
712
|
-
rmSync(path, { recursive: true, force: true });
|
|
570
|
+
rmSync(join(extensionsDir, backup), { recursive: true, force: true });
|
|
713
571
|
console.log(` Removed: ${backup}`);
|
|
714
572
|
}
|
|
715
573
|
}
|
|
716
574
|
|
|
717
575
|
/**
|
|
718
|
-
* Restart OpenClaw Gateway
|
|
576
|
+
* Restart OpenClaw Gateway.
|
|
719
577
|
*/
|
|
720
578
|
function restartGateway() {
|
|
721
579
|
console.log('\nš Restarting OpenClaw Gateway...');
|
|
722
580
|
try {
|
|
723
|
-
// Try systemd first (most common deployment)
|
|
724
581
|
try {
|
|
725
582
|
execSync('systemctl --user is-active openclaw-gateway.service', { stdio: 'pipe' });
|
|
726
|
-
console.log('
|
|
583
|
+
console.log(' Restarting via systemctl...');
|
|
727
584
|
execSync('systemctl --user restart openclaw-gateway.service', { stdio: 'inherit' });
|
|
728
585
|
console.log('ā
Gateway restarted via systemctl.');
|
|
729
|
-
|
|
586
|
+
|
|
587
|
+
console.log(' Waiting for Gateway to initialize and load PD plugin (8s)...');
|
|
730
588
|
setTimeout(() => {
|
|
731
589
|
try {
|
|
732
590
|
const status = execSync('systemctl --user is-active openclaw-gateway.service', { encoding: 'utf-8' }).trim();
|
|
733
591
|
if (status === 'active') {
|
|
734
592
|
console.log('ā
Gateway is running.');
|
|
593
|
+
const logs = execSync('journalctl --user -u openclaw-gateway.service --since "10 seconds ago"', { encoding: 'utf-8' });
|
|
594
|
+
if (logs.includes('Principles Disciple Plugin registered')) {
|
|
595
|
+
console.log('ā
SUCCESS: Principles Disciple plugin registered successfully!');
|
|
596
|
+
} else if (logs.includes('failed to load') || logs.includes('Error: Cannot find module')) {
|
|
597
|
+
console.error('\nā CRITICAL: Gateway is running but PD plugin FAILED to load!');
|
|
598
|
+
process.exit(1);
|
|
599
|
+
} else {
|
|
600
|
+
console.warn('ā ļø Gateway started but PD registration not confirmed in recent logs.');
|
|
601
|
+
}
|
|
735
602
|
} else {
|
|
736
|
-
console.error(`ā Gateway status: ${status}
|
|
603
|
+
console.error(`ā Gateway status: ${status}.`);
|
|
604
|
+
process.exit(1);
|
|
737
605
|
}
|
|
738
|
-
} catch {
|
|
739
|
-
|
|
606
|
+
} catch (e) {
|
|
607
|
+
console.warn(`ā ļø Post-restart verification skipped: ${e.message}`);
|
|
608
|
+
}
|
|
609
|
+
}, 8000);
|
|
740
610
|
return;
|
|
741
|
-
} catch {
|
|
742
|
-
// Not a systemd service ā fall through to manual restart
|
|
743
|
-
}
|
|
611
|
+
} catch { /* ignore */ }
|
|
744
612
|
|
|
745
|
-
// Manual restart: find and kill existing gateway processes
|
|
746
613
|
const pids = execSync('pgrep -f "openclaw-gateway|openclaw gateway"', { encoding: 'utf-8' }).trim();
|
|
747
614
|
if (pids) {
|
|
748
|
-
console.log(`
|
|
615
|
+
console.log(` Terminating existing gateway process(es)...`);
|
|
749
616
|
execSync(`echo "${pids}" | xargs kill -TERM 2>/dev/null || true`);
|
|
750
617
|
execSync('sleep 3');
|
|
751
618
|
}
|
|
752
619
|
|
|
753
|
-
// Start new gateway
|
|
754
620
|
const logPath = '/tmp/openclaw-auto-restart.log';
|
|
755
621
|
console.log(` Starting new gateway (logs: ${logPath})...`);
|
|
756
622
|
execSync(`nohup openclaw gateway --force > ${logPath} 2>&1 &`, { stdio: 'ignore' });
|
|
757
623
|
console.log('ā
Gateway restart triggered.');
|
|
758
|
-
|
|
624
|
+
|
|
625
|
+
setTimeout(() => {
|
|
626
|
+
if (existsSync(logPath)) {
|
|
627
|
+
const logs = readFileSync(logPath, 'utf-8');
|
|
628
|
+
if (logs.includes('Principles Disciple Plugin registered')) {
|
|
629
|
+
console.log('ā
SUCCESS: Principles Disciple plugin registered successfully (manual restart)!');
|
|
630
|
+
} else if (logs.includes('failed to load')) {
|
|
631
|
+
console.error('\nā CRITICAL: Manual restart triggered but PD plugin FAILED to load!');
|
|
632
|
+
process.exit(1);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}, 8000);
|
|
759
636
|
} catch (error) {
|
|
760
637
|
console.error(`\nā Failed to restart gateway: ${error.message}`);
|
|
761
|
-
console.error(' Manual restart: systemctl --user restart openclaw-gateway.service');
|
|
762
638
|
process.exit(1);
|
|
763
639
|
}
|
|
764
640
|
}
|
|
@@ -768,7 +644,6 @@ function restartGateway() {
|
|
|
768
644
|
*/
|
|
769
645
|
function main() {
|
|
770
646
|
const args = parseArgs();
|
|
771
|
-
|
|
772
647
|
if (args.help) {
|
|
773
648
|
showHelp();
|
|
774
649
|
process.exit(0);
|
|
@@ -778,66 +653,36 @@ function main() {
|
|
|
778
653
|
console.log('ā Principles Disciple Plugin Installer ā');
|
|
779
654
|
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n');
|
|
780
655
|
|
|
781
|
-
|
|
782
|
-
if (args.
|
|
783
|
-
console.log('š ļø DEV MODE: force + restart + bump + stale backup cleanup\n');
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
// Auto-bump version if requested
|
|
787
|
-
if (args.bump) {
|
|
788
|
-
autoBumpVersion(SOURCE_DIR);
|
|
789
|
-
}
|
|
656
|
+
if (args.dev) console.log('š ļø DEV MODE: force + restart + bump + stale backup cleanup\n');
|
|
657
|
+
if (args.bump) autoBumpVersion(SOURCE_DIR);
|
|
790
658
|
|
|
791
|
-
// Get source version (after potential bump)
|
|
792
659
|
const sourceVersion = getVersion(SOURCE_DIR);
|
|
793
660
|
if (!sourceVersion) {
|
|
794
|
-
console.error('ā Cannot determine source version.
|
|
661
|
+
console.error('ā Cannot determine source version.');
|
|
795
662
|
process.exit(1);
|
|
796
663
|
}
|
|
797
664
|
console.log(`š Plugin version: v${sourceVersion}`);
|
|
798
665
|
console.log(`š Language: ${args.lang}`);
|
|
799
666
|
|
|
800
|
-
// Step 1: Check prerequisites
|
|
801
667
|
console.log('\nš Checking prerequisites...');
|
|
802
668
|
checkPrerequisites();
|
|
803
669
|
|
|
804
|
-
|
|
805
|
-
if (!args.skipDeps) {
|
|
806
|
-
installDependencies();
|
|
807
|
-
}
|
|
670
|
+
if (!args.skipDeps) installDependencies();
|
|
808
671
|
|
|
809
|
-
// Step 3: ALWAYS rebuild ā esbuild is fast (~2s) and compiles TS directly.
|
|
810
|
-
// dist/ .js files from tsc may be stale when tsc has errors in other files.
|
|
811
|
-
// We always rebuild to guarantee the synced code matches current source.
|
|
812
672
|
buildPlugin();
|
|
813
|
-
|
|
814
|
-
// Step 4: Clean existing installation (must happen after build so we know what's current)
|
|
815
673
|
cleanTargetDir(args.force);
|
|
816
|
-
|
|
817
|
-
// Step 5: Ensure installation directory exists
|
|
818
674
|
ensureInstallDir();
|
|
819
675
|
|
|
820
|
-
// Step 6: Sync files
|
|
821
676
|
console.log('\nš¦ Syncing files to OpenClaw...');
|
|
822
|
-
for (const item of SYNC_ITEMS)
|
|
823
|
-
syncItem(item);
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
// Step 7: Sync skills
|
|
677
|
+
for (const item of SYNC_ITEMS) syncItem(item);
|
|
827
678
|
syncSkills(args.lang);
|
|
828
679
|
|
|
829
|
-
// Step 8: Install production dependencies in target (ALWAYS ā cleanTargetDir wiped node_modules)
|
|
830
|
-
// --skip-deps only applies to SOURCE directory deps, not the installed plugin.
|
|
831
680
|
installTargetDependencies();
|
|
832
681
|
|
|
833
|
-
// Step 9: Verify installed bundle can load its native dependencies
|
|
834
682
|
console.log('\nš Verifying installed plugin can load native dependencies...');
|
|
835
683
|
try {
|
|
836
|
-
execSync(`node -e "require('better-sqlite3')"`, {
|
|
837
|
-
|
|
838
|
-
stdio: 'pipe'
|
|
839
|
-
});
|
|
840
|
-
console.log('ā
Native dependencies verified (better-sqlite3 loads correctly)');
|
|
684
|
+
execSync(`node -e "require('better-sqlite3')"`, { cwd: INSTALL_DIR, stdio: 'pipe' });
|
|
685
|
+
console.log('ā
Native dependencies verified');
|
|
841
686
|
} catch (error) {
|
|
842
687
|
console.warn('\nā ļø Native module better-sqlite3 failed to load. Attempting automatic rebuild...');
|
|
843
688
|
try {
|
|
@@ -846,87 +691,57 @@ function main() {
|
|
|
846
691
|
console.log('ā
Rebuild successful!');
|
|
847
692
|
} catch (rebuildErr) {
|
|
848
693
|
console.error('\nā CRITICAL: better-sqlite3 rebuild failed!');
|
|
849
|
-
console.error(' OpenClaw will likely fail to load this plugin.');
|
|
850
|
-
console.error(` Fix: cd ${INSTALL_DIR} && npm install --build-from-source better-sqlite3`);
|
|
851
694
|
process.exit(1);
|
|
852
695
|
}
|
|
853
696
|
}
|
|
854
697
|
|
|
855
|
-
// Step 10: AUTOMATED PRINCIPLE BOOTSTRAP (The "Neural Link")
|
|
856
|
-
// Sync PRINCIPLES.md changes to the active ledger immediately.
|
|
857
698
|
const bootstrapScript = join(SOURCE_DIR, 'scripts', 'bootstrap-rules.mjs');
|
|
858
699
|
if (existsSync(bootstrapScript)) {
|
|
859
700
|
console.log('\nš§ Synchronizing principles to active rules (Bootstrap)...');
|
|
860
701
|
try {
|
|
861
|
-
// Target the main workspace state dir by default
|
|
862
702
|
const targetStateDir = join(process.env.HOME, '.openclaw', 'workspace-main', '.state');
|
|
863
703
|
if (existsSync(targetStateDir)) {
|
|
864
|
-
execSync(`STATE_DIR=${targetStateDir} BOOTSTRAP_LIMIT=100 node scripts/bootstrap-rules.mjs`, {
|
|
865
|
-
|
|
866
|
-
stdio: 'inherit'
|
|
867
|
-
});
|
|
868
|
-
console.log('ā
Principles synchronized to active enforcement rules.');
|
|
869
|
-
} else {
|
|
870
|
-
console.warn('ā ļø Target state directory not found, skipping rule bootstrap.');
|
|
704
|
+
execSync(`STATE_DIR=${targetStateDir} BOOTSTRAP_LIMIT=100 node scripts/bootstrap-rules.mjs`, { cwd: SOURCE_DIR, stdio: 'inherit' });
|
|
705
|
+
console.log('ā
Principles synchronized.');
|
|
871
706
|
}
|
|
872
707
|
} catch (e) {
|
|
873
708
|
console.warn(`ā ļø Principle synchronization failed: ${e.message}`);
|
|
874
709
|
}
|
|
875
710
|
}
|
|
876
711
|
|
|
877
|
-
|
|
712
|
+
const compileScript = join(SOURCE_DIR, 'scripts', 'compile-principles.mjs');
|
|
713
|
+
if (existsSync(compileScript)) {
|
|
714
|
+
console.log('\nāļø Compiling pain-derived principles into rules...');
|
|
715
|
+
try {
|
|
716
|
+
const targetWorkspaceDir = join(process.env.HOME, '.openclaw', 'workspace-main');
|
|
717
|
+
if (existsSync(targetWorkspaceDir)) {
|
|
718
|
+
execSync(`node scripts/compile-principles.mjs ${targetWorkspaceDir}`, { cwd: SOURCE_DIR, stdio: 'inherit' });
|
|
719
|
+
console.log('ā
Principle compilation complete.');
|
|
720
|
+
}
|
|
721
|
+
} catch (e) {
|
|
722
|
+
console.warn(`ā ļø Principle compilation failed: ${e.message}`);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
878
726
|
const installedVersion = getVersion(INSTALL_DIR);
|
|
879
727
|
if (installedVersion !== sourceVersion) {
|
|
880
|
-
console.error(
|
|
881
|
-
console.error(` Expected: v${sourceVersion}`);
|
|
882
|
-
console.error(` Got: v${installedVersion}`);
|
|
728
|
+
console.error(`\nā VERSION MISMATCH: Expected v${sourceVersion}, Got v${installedVersion}`);
|
|
883
729
|
process.exit(1);
|
|
884
730
|
}
|
|
885
731
|
|
|
886
|
-
// Step 12: Verify installed fingerprint matches current source
|
|
887
732
|
verifyInstalledFingerprint();
|
|
733
|
+
if (args.dev || args.restart) cleanStaleBackups();
|
|
888
734
|
|
|
889
|
-
// Step 13: Clean stale backup directories (dev mode or explicit restart)
|
|
890
|
-
if (args.dev || args.restart) {
|
|
891
|
-
cleanStaleBackups();
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
// Step 14: Signal for reload
|
|
895
735
|
try {
|
|
896
736
|
const reloadSignal = join(OPENCLAW_DIR, '.plugin_reload_signal');
|
|
897
737
|
writeFileSync(reloadSignal, new Date().toISOString(), 'utf-8');
|
|
898
738
|
console.log(`\nš Reload signal sent to ${reloadSignal}`);
|
|
899
739
|
} catch { /* ignore */ }
|
|
900
740
|
|
|
901
|
-
// Build fingerprint info for report
|
|
902
|
-
let fpReport = '';
|
|
903
|
-
try {
|
|
904
|
-
const sourceManifest = JSON.parse(readFileSync(join(SOURCE_DIR, 'dist', 'openclaw.plugin.json'), 'utf-8'));
|
|
905
|
-
const fp = sourceManifest.buildFingerprint;
|
|
906
|
-
if (fp) {
|
|
907
|
-
fpReport = `\n Build: ${fp.gitSha} / MD5 ${fp.bundleMd5.slice(0, 8)}ā¦`;
|
|
908
|
-
}
|
|
909
|
-
} catch { /* ignore */ }
|
|
910
|
-
|
|
911
|
-
// Success!
|
|
912
741
|
console.log('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
913
742
|
console.log('ā ā
Installation Complete ā');
|
|
914
743
|
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
915
|
-
console.log(`\n Version: v${sourceVersion}${fpReport}`);
|
|
916
|
-
console.log(` Language: ${args.lang}`);
|
|
917
|
-
console.log(` Source: ${SOURCE_DIR}`);
|
|
918
|
-
console.log(` Target: ${INSTALL_DIR}`);
|
|
919
744
|
|
|
920
|
-
// Handle automatic restart if requested
|
|
921
|
-
if (args.restart) {
|
|
922
|
-
restartGateway();
|
|
923
|
-
} else {
|
|
924
|
-
console.log('\nš” Restart OpenClaw Gateway to load the new version.');
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
main();
|
|
929
|
-
atic restart if requested
|
|
930
745
|
if (args.restart) {
|
|
931
746
|
restartGateway();
|
|
932
747
|
} else {
|