principles-disciple 1.56.0 → 1.58.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.
@@ -2,7 +2,7 @@
2
2
  "id": "principles-disciple",
3
3
  "name": "Principles Disciple",
4
4
  "description": "Evolutionary programming agent framework with strategic guardrails and reflection loops.",
5
- "version": "1.56.0",
5
+ "version": "1.58.0",
6
6
  "skills": [
7
7
  "./skills"
8
8
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "principles-disciple",
3
- "version": "1.56.0",
3
+ "version": "1.58.0",
4
4
  "description": "Native OpenClaw plugin for Principles Disciple",
5
5
  "type": "module",
6
6
  "main": "./dist/bundle.js",
@@ -17,7 +17,7 @@
17
17
  * --help Show help message
18
18
  */
19
19
 
20
- import { copyFileSync, cpSync, existsSync, rmSync, readFileSync, readFileSync as readFileSyncRaw, mkdirSync, writeFileSync, readdirSync } from 'fs';
20
+ import { copyFileSync, cpSync, existsSync, lstatSync, rmSync, readFileSync, readFileSync as readFileSyncRaw, mkdirSync, writeFileSync, readdirSync } from 'fs';
21
21
  import { createHash } from 'crypto';
22
22
  import { join, dirname } from 'path';
23
23
  import { fileURLToPath } from 'url';
@@ -80,7 +80,7 @@ function parseArgs() {
80
80
  skipBuild: false,
81
81
  skipDeps: false,
82
82
  force: false,
83
- restart: false,
83
+ restart: true,
84
84
  dev: false,
85
85
  bump: false,
86
86
  help: false,
@@ -100,7 +100,8 @@ function parseArgs() {
100
100
  args.skipDeps = true;
101
101
  break;
102
102
  case '--restart':
103
- args.restart = true;
103
+ case '--no-restart':
104
+ args.restart = !arg.startsWith('--no-');
104
105
  break;
105
106
  case '--dev':
106
107
  case '-d':
@@ -145,7 +146,7 @@ Options:
145
146
  --lang <zh|en> Language for skills (default: zh)
146
147
  --skip-build Skip build step (use existing dist/)
147
148
  --skip-deps Skip dependency installation
148
- --restart Automatically restart OpenClaw gateway after installation
149
+ --restart Automatically restart OpenClaw gateway after installation (default: true, use --no-restart to skip)
149
150
  --dev, -d Developer mode: --force + --restart + --bump + clean stale backups
150
151
  --bump, -b Auto-bump patch version if there are uncommitted source changes
151
152
  --force, -f Force overwrite without prompts
@@ -610,6 +611,58 @@ function syncItem(item) {
610
611
  }
611
612
  }
612
613
 
614
+ /**
615
+ * Recursive directory copy (Windows-safe, no symlinks).
616
+ */
617
+ function copyDir(src, dest) {
618
+ mkdirSync(dest, { recursive: true });
619
+ for (const entry of readdirSync(src)) {
620
+ const srcPath = join(src, entry);
621
+ const destPath = join(dest, entry);
622
+ if (lstatSync(srcPath).isDirectory()) {
623
+ copyDir(srcPath, destPath);
624
+ } else {
625
+ copyFileSync(srcPath, destPath);
626
+ }
627
+ }
628
+ }
629
+
630
+ /**
631
+ * Inject local workspace packages (monorepo) into node_modules before npm install.
632
+ * @principles/core is a workspace package, not published to npm — we must copy it
633
+ * from the monorepo's node_modules so npm install --production doesn't 404 it.
634
+ */
635
+ function injectLocalWorkspacePackages() {
636
+ const monorepoModules = join(SOURCE_DIR, '..', '..', 'node_modules', '@principles', 'core');
637
+ const targetModules = join(INSTALL_DIR, 'node_modules', '@principles', 'core');
638
+
639
+ if (!existsSync(monorepoModules)) {
640
+ // Not in monorepo context (e.g., npm pack / CI tarball) — skip
641
+ return;
642
+ }
643
+
644
+ console.log(' 📦 Injecting local workspace packages (@principles/core)...');
645
+ mkdirSync(dirname(targetModules), { recursive: true });
646
+ // cpSync creates symlinks on Windows for symlinked dirs — use cp -rL (dereference) via exec
647
+ let injected = false;
648
+ try {
649
+ execSync(`cp -rL "${monorepoModules}" "${targetModules}"`, { stdio: 'ignore' });
650
+ injected = true;
651
+ } catch {
652
+ // Fallback: manual copy via node (Windows-compatible)
653
+ try {
654
+ copyDir(monorepoModules, targetModules);
655
+ injected = true;
656
+ } catch (copyErr) {
657
+ console.warn(' ⚠️ Failed to inject @principles/core from monorepo: ' + copyErr.message);
658
+ console.warn(' ⚠️ npm install --production may fail if @principles/core is not published');
659
+ }
660
+ }
661
+ if (injected && !existsSync(targetModules)) {
662
+ console.warn(' ⚠️ Injection reported success but target not found: ' + targetModules);
663
+ }
664
+ }
665
+
613
666
  /**
614
667
  * Install production dependencies in target.
615
668
  */
@@ -844,6 +897,7 @@ function main() {
844
897
  for (const item of SYNC_ITEMS) syncItem(item);
845
898
  syncSkills(args.lang);
846
899
 
900
+ injectLocalWorkspacePackages();
847
901
  installTargetDependencies();
848
902
 
849
903
  console.log('\n🔍 Verifying installed plugin can load native dependencies...');
@@ -260,7 +260,7 @@ export class EvolutionLogger {
260
260
  logCompleted(params: {
261
261
  traceId: string;
262
262
  taskId: string;
263
- resolution: 'marker_detected' | 'auto_completed_timeout' | 'manual' | 'late_marker_principle_created' | 'late_marker_no_principle' | 'diagnostician_timeout';
263
+ resolution: 'marker_detected' | 'auto_completed_timeout' | 'manual' | 'late_marker_principle_created' | 'late_marker_no_principle' | 'diagnostician_timeout' | 'noise_classified' | 'duplicate' | 'serverDuplicate';
264
264
  durationMs?: number;
265
265
  principlesGenerated?: number;
266
266
  }): void {
@@ -271,6 +271,8 @@ export class EvolutionLogger {
271
271
  summary = `任务 ${params.taskId} 完成,已生成 ${params.principlesGenerated || 0} 条原则`;
272
272
  } else if (params.resolution === 'auto_completed_timeout' || params.resolution === 'diagnostician_timeout' || params.resolution === 'late_marker_no_principle') {
273
273
  summary = `任务 ${params.taskId} 超时自动完成`;
274
+ } else if (params.resolution === 'noise_classified' || params.resolution === 'duplicate' || params.resolution === 'serverDuplicate') {
275
+ summary = `任务 ${params.taskId} 分类为噪音/重复,已过滤`;
274
276
  } else {
275
277
  summary = `任务 ${params.taskId} 已完成`;
276
278
  }
@@ -921,6 +921,7 @@ async function processEvolutionQueue(wctx: WorkspaceContext, logger: PluginLogge
921
921
  if (fs.existsSync(completeMarker)) {
922
922
  if (logger) logger.info(`[PD:EvolutionWorker] Task ${task.id} completed - marker file detected`);
923
923
 
924
+ let principlesGenerated = 0;
924
925
  // Create principle from the diagnostician's JSON report.
925
926
  const reportPath = path.join(wctx.stateDir, `.diagnostician_report_${task.id}.json`);
926
927
  if (fs.existsSync(reportPath)) {
@@ -1021,6 +1022,7 @@ async function processEvolutionQueue(wctx: WorkspaceContext, logger: PluginLogge
1021
1022
  });
1022
1023
  if (principleId) {
1023
1024
  logger.info(`[PD:EvolutionWorker] Created principle ${principleId} from marker fallback for task ${task.id}`);
1025
+ principlesGenerated = 1;
1024
1026
  } else {
1025
1027
  logger.warn(`[PD:EvolutionWorker] createPrincipleFromDiagnosis returned null for task ${task.id} (may be duplicate or blacklisted)`);
1026
1028
  }
@@ -1042,7 +1044,8 @@ async function processEvolutionQueue(wctx: WorkspaceContext, logger: PluginLogge
1042
1044
 
1043
1045
  task.status = 'completed';
1044
1046
  task.completed_at = new Date().toISOString();
1045
- task.resolution = 'marker_detected';
1047
+ // resolution already set by each branch (noise_classified | marker_detected)
1048
+ if (!task.resolution) task.resolution = 'marker_detected';
1046
1049
  try {
1047
1050
  fs.unlinkSync(completeMarker);
1048
1051
  } catch { /* marker may have been deleted already, not critical */ }
@@ -1063,8 +1066,9 @@ async function processEvolutionQueue(wctx: WorkspaceContext, logger: PluginLogge
1063
1066
  evoLogger.logCompleted({
1064
1067
  traceId: task.traceId || task.id,
1065
1068
  taskId: task.id,
1066
- resolution: 'marker_detected',
1069
+ resolution: task.resolution as 'marker_detected' | 'auto_completed_timeout' | 'manual' | 'late_marker_principle_created' | 'late_marker_no_principle' | 'diagnostician_timeout',
1067
1070
  durationMs,
1071
+ principlesGenerated,
1068
1072
  });
1069
1073
 
1070
1074
  // Record task completion in event stats
@@ -1078,14 +1082,14 @@ async function processEvolutionQueue(wctx: WorkspaceContext, logger: PluginLogge
1078
1082
  wctx.trajectory?.updateEvolutionTask?.(task.id, {
1079
1083
  status: 'completed',
1080
1084
  completedAt: task.completed_at,
1081
- resolution: 'marker_detected',
1085
+ resolution: task.resolution as 'marker_detected' | 'auto_completed_timeout' | 'manual' | 'late_marker_principle_created' | 'late_marker_no_principle' | 'diagnostician_timeout',
1082
1086
  });
1083
1087
 
1084
1088
  wctx.trajectory?.recordTaskOutcome({
1085
1089
  sessionId: task.assigned_session_key || 'heartbeat:diagnostician',
1086
1090
  taskId: task.id,
1087
1091
  outcome: 'ok',
1088
- summary: `Task ${task.id} completed - marker file detected.`
1092
+ summary: `Task ${task.id} completed ${principlesGenerated} principle(s) generated (${task.resolution}).`
1089
1093
  });
1090
1094
  queueChanged = true;
1091
1095
  continue;
@@ -1098,9 +1102,11 @@ async function processEvolutionQueue(wctx: WorkspaceContext, logger: PluginLogge
1098
1102
  const timeoutCompleteMarker = path.join(wctx.stateDir, `.evolution_complete_${task.id}`);
1099
1103
  const timeoutReportPath = path.join(wctx.stateDir, `.diagnostician_report_${task.id}.json`);
1100
1104
 
1105
+ let principlesGenerated = 0;
1106
+
1107
+
1101
1108
  if (fs.existsSync(timeoutCompleteMarker) && fs.existsSync(timeoutReportPath)) {
1102
1109
  if (logger) logger.info(`[PD:EvolutionWorker] Task ${task.id} timed out but marker found — creating principle anyway`);
1103
- let principleCreated = false;
1104
1110
  try {
1105
1111
  const reportData = JSON.parse(fs.readFileSync(timeoutReportPath, 'utf8'));
1106
1112
  const principle = reportData?.principle
@@ -1126,7 +1132,7 @@ async function processEvolutionQueue(wctx: WorkspaceContext, logger: PluginLogge
1126
1132
  });
1127
1133
  if (principleId) {
1128
1134
  logger.info(`[PD:EvolutionWorker] Created principle ${principleId} from late marker for task ${task.id}`);
1129
- principleCreated = true;
1135
+ principlesGenerated = 1;
1130
1136
  }
1131
1137
  }
1132
1138
  }
@@ -1139,7 +1145,7 @@ async function processEvolutionQueue(wctx: WorkspaceContext, logger: PluginLogge
1139
1145
  const lateReportPath = path.join(wctx.stateDir, `.diagnostician_report_${task.id}.json`);
1140
1146
  if (fs.existsSync(lateReportPath)) fs.unlinkSync(lateReportPath);
1141
1147
  } catch { /* report may not exist, not critical */ }
1142
- task.resolution = principleCreated ? 'late_marker_principle_created' : 'late_marker_no_principle';
1148
+ task.resolution = principlesGenerated > 0 ? 'late_marker_principle_created' : 'late_marker_no_principle';
1143
1149
  } else {
1144
1150
  if (logger) logger.info(`[PD:EvolutionWorker] Task ${task.id} auto-completed after ${timeoutMinutes} minute timeout`);
1145
1151
  // #190: Clean up diagnostician report file even on timeout (may have been written late)
@@ -1160,6 +1166,7 @@ async function processEvolutionQueue(wctx: WorkspaceContext, logger: PluginLogge
1160
1166
  taskId: task.id,
1161
1167
  resolution: task.resolution,
1162
1168
  durationMs: age,
1169
+ principlesGenerated,
1163
1170
  });
1164
1171
 
1165
1172
  // Record task completion in event stats (for timeout path too)
@@ -1180,7 +1187,7 @@ async function processEvolutionQueue(wctx: WorkspaceContext, logger: PluginLogge
1180
1187
  sessionId: task.assigned_session_key || 'heartbeat:diagnostician',
1181
1188
  taskId: task.id,
1182
1189
  outcome: 'timeout',
1183
- summary: `Task ${task.id} auto-completed after ${timeoutMinutes} minute timeout.`
1190
+ summary: `Task ${task.id} completed ${principlesGenerated} principle(s) generated (${task.resolution}).`
1184
1191
  });
1185
1192
  queueChanged = true;
1186
1193
  }