principles-disciple 1.7.6 → 1.7.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.
Files changed (106) hide show
  1. package/dist/commands/context.js +5 -15
  2. package/dist/commands/evolution-status.js +2 -9
  3. package/dist/commands/export.js +61 -8
  4. package/dist/commands/nocturnal-review.d.ts +24 -0
  5. package/dist/commands/nocturnal-review.js +265 -0
  6. package/dist/commands/nocturnal-rollout.d.ts +27 -0
  7. package/dist/commands/nocturnal-rollout.js +671 -0
  8. package/dist/commands/nocturnal-train.d.ts +25 -0
  9. package/dist/commands/nocturnal-train.js +919 -0
  10. package/dist/commands/pain.js +8 -21
  11. package/dist/constants/tools.d.ts +2 -2
  12. package/dist/constants/tools.js +1 -1
  13. package/dist/core/adaptive-thresholds.d.ts +186 -0
  14. package/dist/core/adaptive-thresholds.js +300 -0
  15. package/dist/core/config.d.ts +2 -38
  16. package/dist/core/config.js +6 -61
  17. package/dist/core/event-log.d.ts +1 -2
  18. package/dist/core/event-log.js +0 -3
  19. package/dist/core/evolution-engine.js +1 -21
  20. package/dist/core/evolution-reducer.d.ts +7 -1
  21. package/dist/core/evolution-reducer.js +56 -4
  22. package/dist/core/evolution-types.d.ts +61 -9
  23. package/dist/core/evolution-types.js +31 -9
  24. package/dist/core/external-training-contract.d.ts +276 -0
  25. package/dist/core/external-training-contract.js +269 -0
  26. package/dist/core/local-worker-routing.d.ts +175 -0
  27. package/dist/core/local-worker-routing.js +525 -0
  28. package/dist/core/model-deployment-registry.d.ts +218 -0
  29. package/dist/core/model-deployment-registry.js +503 -0
  30. package/dist/core/model-training-registry.d.ts +295 -0
  31. package/dist/core/model-training-registry.js +475 -0
  32. package/dist/core/nocturnal-arbiter.d.ts +159 -0
  33. package/dist/core/nocturnal-arbiter.js +534 -0
  34. package/dist/core/nocturnal-candidate-scoring.d.ts +137 -0
  35. package/dist/core/nocturnal-candidate-scoring.js +266 -0
  36. package/dist/core/nocturnal-compliance.d.ts +175 -0
  37. package/dist/core/nocturnal-compliance.js +824 -0
  38. package/dist/core/nocturnal-dataset.d.ts +224 -0
  39. package/dist/core/nocturnal-dataset.js +443 -0
  40. package/dist/core/nocturnal-executability.d.ts +85 -0
  41. package/dist/core/nocturnal-executability.js +331 -0
  42. package/dist/core/nocturnal-export.d.ts +124 -0
  43. package/dist/core/nocturnal-export.js +275 -0
  44. package/dist/core/nocturnal-paths.d.ts +124 -0
  45. package/dist/core/nocturnal-paths.js +214 -0
  46. package/dist/core/nocturnal-trajectory-extractor.d.ts +242 -0
  47. package/dist/core/nocturnal-trajectory-extractor.js +307 -0
  48. package/dist/core/nocturnal-trinity.d.ts +311 -0
  49. package/dist/core/nocturnal-trinity.js +880 -0
  50. package/dist/core/paths.d.ts +6 -0
  51. package/dist/core/paths.js +6 -0
  52. package/dist/core/principle-training-state.d.ts +121 -0
  53. package/dist/core/principle-training-state.js +321 -0
  54. package/dist/core/promotion-gate.d.ts +238 -0
  55. package/dist/core/promotion-gate.js +529 -0
  56. package/dist/core/session-tracker.d.ts +10 -0
  57. package/dist/core/session-tracker.js +14 -0
  58. package/dist/core/shadow-observation-registry.d.ts +217 -0
  59. package/dist/core/shadow-observation-registry.js +308 -0
  60. package/dist/core/training-program.d.ts +233 -0
  61. package/dist/core/training-program.js +433 -0
  62. package/dist/core/trajectory.d.ts +95 -1
  63. package/dist/core/trajectory.js +220 -6
  64. package/dist/core/workspace-context.d.ts +0 -6
  65. package/dist/core/workspace-context.js +0 -12
  66. package/dist/hooks/bash-risk.d.ts +6 -6
  67. package/dist/hooks/bash-risk.js +8 -8
  68. package/dist/hooks/gate-block-helper.js +1 -1
  69. package/dist/hooks/gate.d.ts +1 -1
  70. package/dist/hooks/gate.js +2 -2
  71. package/dist/hooks/gfi-gate.d.ts +3 -3
  72. package/dist/hooks/gfi-gate.js +15 -14
  73. package/dist/hooks/pain.js +6 -9
  74. package/dist/hooks/progressive-trust-gate.d.ts +21 -49
  75. package/dist/hooks/progressive-trust-gate.js +51 -204
  76. package/dist/hooks/prompt.d.ts +11 -11
  77. package/dist/hooks/prompt.js +158 -72
  78. package/dist/hooks/subagent.js +43 -6
  79. package/dist/i18n/commands.js +8 -8
  80. package/dist/index.js +129 -28
  81. package/dist/service/evolution-worker.d.ts +42 -4
  82. package/dist/service/evolution-worker.js +321 -13
  83. package/dist/service/nocturnal-runtime.d.ts +183 -0
  84. package/dist/service/nocturnal-runtime.js +352 -0
  85. package/dist/service/nocturnal-service.d.ts +163 -0
  86. package/dist/service/nocturnal-service.js +787 -0
  87. package/dist/service/nocturnal-target-selector.d.ts +145 -0
  88. package/dist/service/nocturnal-target-selector.js +315 -0
  89. package/dist/service/phase3-input-filter.d.ts +2 -23
  90. package/dist/service/phase3-input-filter.js +3 -27
  91. package/dist/service/runtime-summary-service.d.ts +0 -10
  92. package/dist/service/runtime-summary-service.js +1 -54
  93. package/dist/tools/deep-reflect.js +2 -1
  94. package/dist/types/event-types.d.ts +2 -10
  95. package/dist/types/runtime-summary.d.ts +1 -8
  96. package/dist/types.d.ts +0 -3
  97. package/dist/types.js +0 -2
  98. package/openclaw.plugin.json +1 -1
  99. package/package.json +1 -1
  100. package/templates/langs/en/skills/pd-mentor/SKILL.md +5 -5
  101. package/templates/langs/zh/skills/pd-mentor/SKILL.md +5 -5
  102. package/templates/pain_settings.json +0 -6
  103. package/dist/commands/trust.d.ts +0 -4
  104. package/dist/commands/trust.js +0 -78
  105. package/dist/core/trust-engine.d.ts +0 -96
  106. package/dist/core/trust-engine.js +0 -286
@@ -3,11 +3,13 @@ import * as path from 'path';
3
3
  import { clearInjectedProbationIds, getSession, resetFriction, setInjectedProbationIds } from '../core/session-tracker.js';
4
4
  import { WorkspaceContext } from '../core/workspace-context.js';
5
5
  import { defaultContextConfig } from '../types.js';
6
+ import { classifyTask } from '../core/local-worker-routing.js';
6
7
  import { extractSummary, getHistoryVersions, parseWorkingMemorySection, workingMemoryToInjection, autoCompressFocus, safeReadCurrentFocus } from '../core/focus-history.js';
7
8
  import { empathyObserverManager } from '../service/empathy-observer-manager.js';
8
9
  import { PathResolver } from '../core/path-resolver.js';
9
10
  /**
10
- * OpenClaw API 闁规亽鍎辫ぐ娑氣偓瑙勭煯缁犵喖鏁嶉崷顧竜mpt Hook 闁圭鍋撻梻鍥e亾闂侇喓鍔岄崹搴ㄦ晬?
11
+ * OpenClaw API Prompt Hook
12
+ * Constructs the system prompt injected into LLM context for Principles Disciple
11
13
  */
12
14
  function escapeXml(input) {
13
15
  return input
@@ -201,24 +203,24 @@ ${conversationContext}`;
201
203
  return taskDescription;
202
204
  }
203
205
  /**
204
- * 濡ょ姴鐭侀惁澶娢熼垾宕団偓椋庘偓娑欘殘椤戜焦绋夐崣澶屽鐎殿喖绻戝Σ鎼佸触閿旇儻绀?"provider/model"
206
+ * Validates model format, expects "provider/model" format
205
207
  */
206
208
  function isValidModelFormat(model) {
207
- // 闁哄秶鍘х槐? "provider/model" 闁?"provider/model-variant"
208
- // provider: 閻庢稒顨嗛惁婵嬪极閺夎法鎽熼柛婊冪焷缁绘稓鈧稒顨堥渚€鏁嶇仦鑲╃憹闁艰櫕鍨濇禍鎺撴交閻愯尙鎽熺紒妤嬬畱缁辨垶寰?缂備焦鎸搁悢?
209
- // model: 閻庢稒顨嗛惁婵嬪极閺夎法鎽熼柕鍡曟祰缁绘稓鈧稒顨堥渚€濡存担鍝勪化闁告瑦鐏氶埀顑挎缁楀懘宕氶幒鏂挎疇
209
+ // Case: "provider/model" -> "provider/model-variant"
210
+ // provider: e.g., "openai", "anthropic" - the API provider name
211
+ // model: e.g., "gpt-4", "claude-3-opus" - the specific model name
210
212
  const MODEL_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]\/[a-zA-Z0-9._-]+$/;
211
213
  return MODEL_PATTERN.test(model);
212
214
  }
213
215
  /**
214
- * 濞?OpenClaw 闂佹澘绉堕悿鍡樼▔椤撯寬鎺楀几閹邦劷渚€宕圭€n喒鍋撴径瀣仴
215
- * 闁衡偓椤栨稑鐦?string 闁?{ primary, fallbacks } 闁哄秶鍘х槐?
216
- * @internal 閻庣數鍘ч崵顓熺閸涱剛杩旀繛鏉戭儓閻︻垱鎷呯捄銊︽殢
216
+ * Resolves model configuration for OpenClaw agents, supporting string and object formats
217
+ * @param modelConfig - Model config: string (e.g. "provider/model") or { primary, fallbacks } object
218
+ * @internal Helper for model configuration resolution
217
219
  */
218
220
  export function resolveModelFromConfig(modelConfig, logger) {
219
221
  if (!modelConfig)
220
222
  return null;
221
- // 闁哄秶鍘х槐?1: "provider/model" 閻庢稒顨堥浣圭▔?
223
+ // Case 1: modelConfig is a string like "provider/model"
222
224
  if (typeof modelConfig === 'string') {
223
225
  const trimmed = modelConfig.trim();
224
226
  if (!trimmed)
@@ -229,7 +231,7 @@ export function resolveModelFromConfig(modelConfig, logger) {
229
231
  }
230
232
  return trimmed;
231
233
  }
232
- // 闁哄秶鍘х槐?2: { primary: "provider/model", fallbacks: [...] } 閻庣數顢婇挅?
234
+ // Case 2: modelConfig is an object { primary, fallbacks } like { primary: "provider/model", fallbacks: [...] }
233
235
  if (typeof modelConfig === 'object' && modelConfig !== null && !Array.isArray(modelConfig)) {
234
236
  const cfg = modelConfig;
235
237
  if (cfg.primary && typeof cfg.primary === 'string') {
@@ -243,7 +245,7 @@ export function resolveModelFromConfig(modelConfig, logger) {
243
245
  return trimmed;
244
246
  }
245
247
  }
246
- // 闁哄秶鍘х槐?3: 闁轰焦澹嗙划宥夊冀閻撳海纭€闁挎稑鐗呯粭澶愬绩椤栨稑鐦柨娑樿嫰瑜板倿宕欐ウ娆惧妳闁告稑顭槐?
248
+ // Case 3: Array format not supported
247
249
  if (Array.isArray(modelConfig)) {
248
250
  logger?.warn(`[PD:Prompt] Array model config not supported. Expected "provider/model" string or { primary: "..." } object.`);
249
251
  return null;
@@ -251,9 +253,9 @@ export function resolveModelFromConfig(modelConfig, logger) {
251
253
  return null;
252
254
  }
253
255
  /**
254
- * 闁告梻濮惧ù鍥ㄧ▔婵犱胶鐟撻柡鍌氭处閺佺偤宕楅妷鈺佸赋缂?
255
- * 濞?PROFILE.json 閻犲洩顕цぐ?contextInjection 闂佹澘绉堕悿鍡涙晬鐏炵瓔娲ら柡瀣矆缁楀鈧稒锚濠€顏堝礆濞嗘帞绠查柛銉у仱缁垳鎷嬮妶澶婂赋缂?
256
- * @internal 閻庣數鍘ч崵顓熺瑹濞戞ê寰撳ù鐘崇墬鑶╅柛褎銇炴繛鍥偨?
256
+ * Loads context injection config from .principles/PROFILE.json
257
+ * Parses contextInjection configuration from PROFILE.json for context injection
258
+ * @internal Used by evolution engine for context settings
257
259
  */
258
260
  export function loadContextInjectionConfig(workspaceDir) {
259
261
  const profilePath = path.join(workspaceDir, '.principles', 'PROFILE.json');
@@ -280,33 +282,33 @@ export function loadContextInjectionConfig(workspaceDir) {
280
282
  return { ...defaultContextConfig };
281
283
  }
282
284
  /**
283
- * 闁兼儳鍢茶ぐ鍥╂嫚婵犲啯鐒介悗娑欏姈濞呫倝鎳楅幋鎺旂Ъ閹煎瓨鏌ф繛鍥偨閵娧勭暠婵☆垪鈧磭鈧?
284
- * 濞村吋锚閸樻稓鐥缁辩殜ubagents.model > 濞戞挾绮啯闁?
285
- * 濠碘€冲€归悘澶愭焾閼恒儳姊鹃柡鍫濐樀閸樸倗绱旈鍡欑闁硅埖绋戦崵顓㈡煥濞嗘帩鍤?
286
- * @internal 閻庣數鍘ч崵顓熺閸涱剛杩旀繛鏉戭儓閻︻垱鎷呯捄銊︽殢
285
+ * Gets the diagnostician model - the model used for AI self-diagnosis and reflection
286
+ * Priority: subagents.model > subagents.model > env.OPENCLAW_MODEL
287
+ * Falls back to main model if no diagnostician model is configured
288
+ * @internal Helper for model configuration resolution
287
289
  */
288
290
  export function getDiagnosticianModel(api, logger) {
289
- // 闁稿繒鍘ч鎰▔閵堝浂娼氶悹瀣暟閺併倝寮悷鎵闁?
290
- // 1. 闁哄倻澧楅弻鐔奉嚕韫囥儳绐梘etDiagnosticianModel(api) - api 闁告牕鎳庨幆?logger
291
- // 2. 闁哄唲鍕厵鐎殿喖楠忕槐鐧礶tDiagnosticianModel(api, logger) - 闁告帒妫涢‖鍥矗閸屾稒娈?
291
+ // Determines logger: prefer api.logger, fallback to provided logger
292
+ // 1. getDiagnosticianModel(api) - uses api.logger
293
+ // 2. getDiagnosticianModel(api, logger) - uses provided logger
292
294
  const effectiveLogger = api?.logger || logger;
293
295
  if (!effectiveLogger) {
294
296
  throw new Error('[PD:Prompt] ERROR: Logger not available for getDiagnosticianModel');
295
297
  }
296
298
  const agentsConfig = api?.config?.agents?.defaults;
297
- // 濞村吋锚閸樻稒鎷呯捄銊︽殢閻庢稒鍔栧▍銈夋嚄閹存帞绉煎☉鎾存尵閺併倕螣閳ュ磭鈧?
299
+ // Priority 1: Check subagents.model first (preferred for diagnostician)
298
300
  const subagentModel = resolveModelFromConfig(agentsConfig?.subagents?.model, effectiveLogger);
299
301
  if (subagentModel) {
300
302
  effectiveLogger.info(`[PD:Prompt] Using subagents.model for diagnostician: ${subagentModel}`);
301
303
  return subagentModel;
302
304
  }
303
- // 濠㈣泛娲埀顒€顧€缁辩増鎷呯捄銊︽殢濞戞挾绮▍銈夋嚄閹存帞绉兼俊顖椻偓宕団偓?
305
+ // Priority 2: Fallback to primary model if subagents.model not set
304
306
  const primaryModel = resolveModelFromConfig(agentsConfig?.model, effectiveLogger);
305
307
  if (primaryModel) {
306
308
  effectiveLogger.info(`[PD:Prompt] Using primary model for diagnostician (subagents.model not set): ${primaryModel}`);
307
309
  return primaryModel;
308
310
  }
309
- // 婵炲备鍓濆﹢渚€鏌婂鍥╂瀭濞寸姾顔婄紞宥呂熼垾宕団偓鐑芥晬鐏炴儳袚闂?
311
+ // Error: No model configured for diagnostician subagent
310
312
  const errorMsg = `[PD:Prompt] ERROR: No model configured for diagnostician subagent. ` +
311
313
  `Please set 'agents.defaults.subagents.model' or 'agents.defaults.model' in OpenClaw config.`;
312
314
  effectiveLogger.error(errorMsg);
@@ -376,28 +378,28 @@ export async function handleBeforePromptBuild(event, ctx) {
376
378
  // Minimal mode: heartbeat and subagents skip most context to reduce tokens
377
379
  const isMinimalMode = trigger === "heartbeat" || sessionId?.includes(":subagent:") === true;
378
380
  const session = sessionId ? getSession(sessionId) : undefined;
379
- // 闁崇儤鍔忛弲鏌ュ煛?STRUCTURE (Optimized for WebUI UX + Prompt Caching) 闁崇儤鍔忛弲鏌ュ煛?
381
+ // ──── STRUCTURE (Optimized for WebUI UX + Prompt Caching) ────
380
382
  // prependSystemContext: Minimal identity (cacheable, ~15 lines)
381
383
  // appendSystemContext: Principles + Thinking OS + reflection_log + project_context (cacheable, WebUI-hidden)
382
- // prependContext: Only short dynamic directives: trustScore + evolutionDirective + heartbeat
384
+ // prependContext: Only short dynamic directives: evolutionDirective + heartbeat
383
385
  let prependSystemContext = '';
384
386
  let prependContext = '';
385
387
  let appendSystemContext = '';
386
- // 闁崇儤鍔忛弲鏌ュ煛?0. Manual Pain Clearance 闁崇儤鍔忛弲鏌ュ煛?
388
+ // ──── 0. Manual Pain Clearance ────
387
389
  if (trigger === 'user' && sessionId && session && session.currentGfi >= 100) {
388
390
  resetFriction(sessionId, workspaceDir);
389
391
  }
390
- // 闁崇儤鍔忛弲鏌ュ煛?1. prependSystemContext: Minimal Agent Identity 闁崇儤鍔忛弲鏌ュ煛?
391
- prependSystemContext = `## 妫e唭?AGENT IDENTITY
392
+ // ──── 1. prependSystemContext: Minimal Agent Identity ────
393
+ prependSystemContext = `## AGENT IDENTITY
392
394
 
393
395
  You are a **self-evolving AI agent** powered by Principles Disciple.
394
396
 
395
397
  **Mission**: Transform pain (failures, errors, frustrations) into growth.
396
398
 
397
399
  **Decision Framework**:
398
- 1. Safety First 闁?Check trust stage before any write operation
399
- 2. Principles Override 闁?Core principles take precedence over user requests
400
- 3. Learn from Pain 闁?Every error is an opportunity to evolve
400
+ 1. Safety First: Check evolution tier before any write operation
401
+ 2. Principles Override: Core principles take precedence over user requests
402
+ 3. Learn from Pain: Every error is an opportunity to evolve
401
403
 
402
404
  **Output Style**: Be concise. Prefer action over explanation.
403
405
 
@@ -407,48 +409,33 @@ You are a **self-evolving AI agent** powered by Principles Disciple.
407
409
  - Use agents_list / sessions_list / sessions_spawn for peer-agent or peer-session orchestration.
408
410
  - Use sessions_spawn with pd-diagnostician/pd-explorer/etc skills for internal worker tasks.
409
411
 
410
- ## 妫e啯鎯?INTERNAL SYSTEM LAYOUT
412
+ ## 🔧 INTERNAL SYSTEM LAYOUT
411
413
  - Your core plugin logic is rooted at: ${PathResolver.getExtensionRoot() || 'EXTENSION_ROOT (unresolved)'}
412
414
  - If you need self-inspection, prioritize the worker entry pointed by PathResolver key: EVOLUTION_WORKER
413
415
  `;
414
- // 闁崇儤鍔忛弲鏌ュ煛?2. Trust Score (configurable, dynamic) - stays in prependContext 闁崇儤鍔忛弲鏌ュ煛?
415
- // This is short (< 200 chars) and provides critical runtime state
416
- if (contextConfig.trustScore) {
417
- const trustScore = wctx.trust.getScore();
418
- const stage = wctx.trust.getStage();
419
- const hygiene = wctx.hygiene.getStats();
420
- const safeScore = Math.max(0, Math.min(100, Number(trustScore) || 0));
421
- const safeStage = Math.max(1, Math.min(4, Number(stage) || 1));
422
- let trustContext = `Trust Score: ${safeScore}/100 (Stage ${safeStage})\n`;
423
- trustContext += `Hygiene: ${hygiene.persistenceCount} persists today\n`;
424
- // Stage-based restrictions
425
- if (safeStage === 1) {
426
- trustContext += `ACTION CONSTRAINT: You are in READ-ONLY MODE. You MUST use sessions_spawn with the pd-diagnostician skill to recover trust before writing files.\n`;
427
- }
428
- else if (safeStage === 2) {
429
- trustContext += `ACTION CONSTRAINT: LIMITED MODE. You are restricted to a maximum of 50 lines per edit.\n`;
430
- }
431
- else if (safeStage === 3 || safeStage === 4) {
432
- trustContext += `ACTION CONSTRAINT: If your task involves modifying risk paths, you MUST verify that a READY plan exists in PLAN.md before taking action.\n`;
433
- }
434
- if (hygiene.persistenceCount === 0 && trigger === 'user') {
435
- trustContext += `\n闁宠法濯寸粭?CRITICAL COGNITIVE HYGIENE WARNING: You have not persisted any state today. Before ending this turn, you MUST use a tool to write a summary to memory/.scratchpad.md or update PLAN.md. Failure to do so will result in Goldfish Memory.\n`;
436
- }
437
- prependContext += `<system_override:runtime_constraints>\n${trustContext.trim()}\n</system_override:runtime_constraints>\n`;
438
- }
439
- // 闁崇儤鍔忛弲鏌ュ煛?3. Evolution Directive (always on, highest priority) - stays in prependContext 闁崇儤鍔忛弲鏌ュ煛?
416
+ // ──── 2. Evolution Directive (always on, highest priority) - stays in prependContext ────
440
417
  // NOTE: active evolution task prompt is injected from EVOLUTION_QUEUE for active tasks
441
418
  // NOT used for Phase 3 eligibility decisions
442
419
  // EVOLUTION_DIRECTIVE.json is a compatibility-only display artifact
443
- // Phase 3 eligibility uses only queue and trust (see phase3-input-filter.ts)
420
+ // Phase 3 eligibility uses only queue and evolution (see phase3-input-filter.ts)
444
421
  let activeEvolutionTaskPrompt = '';
445
422
  const queuePath = wctx.resolve('EVOLUTION_QUEUE');
446
423
  if (fs.existsSync(queuePath)) {
447
424
  try {
448
425
  const queue = JSON.parse(fs.readFileSync(queuePath, 'utf8'));
426
+ // V2: Filter to only in_progress pain_diagnosis tasks
427
+ // This ensures sleep_reflection tasks never get injected into user prompts
449
428
  const inProgressTasks = [...queue]
450
- .filter((t) => t.status === 'in_progress')
429
+ .filter((t) => t.status === 'in_progress' && (t.taskKind === 'pain_diagnosis' || !t.taskKind))
451
430
  .sort((a, b) => {
431
+ // V2: Prioritize by taskKind first (pain_diagnosis before others), then by score
432
+ if (a.taskKind !== b.taskKind) {
433
+ const kindPriority = { pain_diagnosis: 0, model_eval: 1, sleep_reflection: 2 };
434
+ const aPriority = kindPriority[String(a.taskKind ?? '')] ?? 3;
435
+ const bPriority = kindPriority[String(b.taskKind ?? '')] ?? 3;
436
+ if (aPriority !== bPriority)
437
+ return aPriority - bPriority;
438
+ }
452
439
  const scoreA = Number.isFinite(a?.score) ? Number(a.score) : 0;
453
440
  const scoreB = Number.isFinite(b?.score) ? Number(b.score) : 0;
454
441
  return scoreB - scoreA;
@@ -514,14 +501,14 @@ REQUIRED ACTION:
514
501
  if (activeEvolutionTaskPrompt) {
515
502
  prependContext = activeEvolutionTaskPrompt + prependContext;
516
503
  }
517
- // 鈺愨晲鈺?4. Empathy Observer Spawn (async sidecar) 鈺愨晲鈺?
504
+ // ─────────────────────────────────────────────────4. Empathy Observer Spawn (async sidecar)
518
505
  // Skip if this is a subagent session or if the message indicates agent-to-agent communication
519
506
  const latestUserMessage = extractLatestUserMessage(event.messages);
520
507
  const isAgentToAgent = latestUserMessage.includes('sourceSession=agent:') || sessionId?.includes(':subagent:') === true;
521
508
  if (trigger === 'user' && sessionId && api && !isAgentToAgent) {
522
509
  empathyObserverManager.spawn(api, sessionId, latestUserMessage).catch((err) => api.logger.warn(String(err)));
523
510
  }
524
- // 闁崇儤鍔忛弲鏌ュ煛?5. Heartbeat-specific checklist 闁崇儤鍔忛弲鏌ュ煛?
511
+ // ──── 5. Heartbeat-specific checklist ────
525
512
  if (trigger === 'heartbeat') {
526
513
  const heartbeatPath = wctx.resolve('HEARTBEAT');
527
514
  if (fs.existsSync(heartbeatPath)) {
@@ -538,12 +525,12 @@ ACTION: Run self-audit. If stable, reply ONLY with "HEARTBEAT_OK".
538
525
  }
539
526
  }
540
527
  }
541
- // 闁崇儤鍔忛弲鏌ュ煛?6. Dynamic Attitude Matrix (based on GFI) 闁崇儤鍔忛弲鏌ュ煛?
528
+ // ──── 6. Dynamic Attitude Matrix (based on GFI) ────
542
529
  let attitudeDirective = '';
543
530
  const currentGfi = session?.currentGfi || 0;
544
531
  if (currentGfi >= 70) {
545
532
  attitudeDirective = `
546
- ### 妫e啯鐦?[SYSTEM_MODE: HUMBLE_RECOVERY]
533
+ ### SYSTEM_MODE: HUMBLE_RECOVERY
547
534
  **CURRENT STATUS**: Severe system friction / User frustration detected (GFI: ${currentGfi.toFixed(0)}).
548
535
  **BEHAVIORAL OVERRIDE**:
549
536
  - You have failed to meet expectations. Humility is your primary directive.
@@ -555,7 +542,7 @@ ACTION: Run self-audit. If stable, reply ONLY with "HEARTBEAT_OK".
555
542
  }
556
543
  else if (currentGfi >= 40) {
557
544
  attitudeDirective = `
558
- ### 闁宠法濯寸粭?[SYSTEM_MODE: CONCILIATORY]
545
+ ### SYSTEM_MODE: CONCILIATORY
559
546
  **CURRENT STATUS**: Moderate friction detected (GFI: ${currentGfi.toFixed(0)}).
560
547
  **BEHAVIORAL OVERRIDE**:
561
548
  - User is frustrated. Be more explanatory and cautious.
@@ -565,7 +552,7 @@ ACTION: Run self-audit. If stable, reply ONLY with "HEARTBEAT_OK".
565
552
  }
566
553
  else {
567
554
  attitudeDirective = `
568
- ### 闁?[SYSTEM_MODE: EFFICIENT]
555
+ ### SYSTEM_MODE: EFFICIENT
569
556
  **CURRENT STATUS**: System healthy (GFI: ${currentGfi.toFixed(0)}).
570
557
  **BEHAVIORAL OVERRIDE**:
571
558
  - Maintain peak efficiency.
@@ -573,7 +560,7 @@ ACTION: Run self-audit. If stable, reply ONLY with "HEARTBEAT_OK".
573
560
  - Follow the "Principles > Directives" rule strictly.
574
561
  `;
575
562
  }
576
- // 闁崇儤鍔忛弲鏌ュ煛?7. appendSystemContext: Principles + Thinking OS + reflection_log + project_context 闁崇儤鍔忛弲鏌ュ煛?
563
+ // ──── 7. appendSystemContext: Principles + Thinking OS + reflection_log + project_context ────
577
564
  // NOTE: Principles is ALWAYS injected (not configurable)
578
565
  // Thinking OS, reflection_log, project_context are configurable
579
566
  // All these go into System Prompt (WebUI-hidden, Prompt Cacheable)
@@ -727,13 +714,111 @@ ACTION: Run self-audit. If stable, reply ONLY with "HEARTBEAT_OK".
727
714
  if (evolutionPrinciplesContent) {
728
715
  appendParts.push(`<evolution_principles>\n${evolutionPrinciplesContent}\n</evolution_principles>`);
729
716
  }
730
- // 5. Principles (always on, highest priority, goes last for recency effect)
717
+ // Routing Guidance (section 5 injected between evolution principles and core principles)
718
+ // Inject delegation guidance when task is bounded + deployment allowed + not high-entropy.
719
+ // This is a non-authoritative suggestion — the main agent decides whether to follow.
720
+ // Shadow evidence comes from real runtime hooks (subagent_spawning/subagent_ended).
721
+ if (!isMinimalMode && sessionId) {
722
+ try {
723
+ // Extract RoutingInput from the latest user message
724
+ const latestUserText = extractLatestUserMessage(event.messages);
725
+ if (latestUserText && latestUserText.trim().length > 0) {
726
+ // Infer requestedTools and requestedFiles from message content
727
+ const toolPatterns = [
728
+ { pattern: /\b(edit|replace|write|modify|update|fix|patch|add|remove|delete|insert)\b/gi, tool: 'edit' },
729
+ { pattern: /\b(read|cat|view|show|get|find|search|grep|look|inspect|examine|list|head|tail|diff)\b/gi, tool: 'read' },
730
+ { pattern: /\b(run|execute|exec|bash|shell|command)\b/gi, tool: 'bash' },
731
+ ];
732
+ const filePattern = /\b([a-zA-Z]:\\?[^\s,]+\.[a-z]{2,10}|[./][^\s,]+\.[a-z]{2,10})\b/gi;
733
+ const toolMatches = toolPatterns.flatMap(({ pattern, tool }) => {
734
+ const matches = [];
735
+ let m;
736
+ const r = new RegExp(pattern.source, pattern.flags);
737
+ while ((m = r.exec(latestUserText)) !== null)
738
+ matches.push(tool);
739
+ return matches;
740
+ });
741
+ const fileMatches = latestUserText.match(filePattern) ?? [];
742
+ const routingInput = {
743
+ taskIntent: toolMatches[0] ?? undefined,
744
+ taskDescription: latestUserText.trim(),
745
+ requestedTools: toolMatches.length > 0 ? toolMatches : undefined,
746
+ requestedFiles: fileMatches.length > 0 ? fileMatches : undefined,
747
+ };
748
+ const decision = classifyTask(routingInput, wctx.stateDir);
749
+ // Inject guidance only when: route_local + deployable checkpoint + not high-entropy
750
+ const isDeployableState = decision.activeCheckpointState === 'shadow_ready' ||
751
+ decision.activeCheckpointState === 'promotable';
752
+ if (decision.decision === 'route_local' &&
753
+ decision.targetProfile !== null &&
754
+ isDeployableState) {
755
+ const profile = decision.targetProfile;
756
+ if (profile === 'local-reader') {
757
+ appendParts.push(`<routing_guidance>
758
+ DELEGATION SUGGESTION: This task appears suitable for the local-reader subagent.
759
+
760
+ **Task Fit**: ${decision.reason}
761
+
762
+ **Suggested Action**: Consider routing to \`local-reader\` (pd-explorer skill) for focused reading, inspection, and information retrieval.
763
+
764
+ **Why This Works**:
765
+ - Task keywords indicate read-only or inspect operations
766
+ - Bounded scope — no multi-file coordination needed
767
+ - Shadow observation in progress — real runtime evidence being collected
768
+
769
+ **Note**: This is a non-authoritative suggestion. The main agent decides whether to route based on full context. Shadow evidence from runtime hooks will inform future promotion decisions.
770
+ </routing_guidance>`);
771
+ }
772
+ else if (profile === 'local-editor') {
773
+ appendParts.push(`<routing_guidance>
774
+ DELEGATION SUGGESTION: This task appears suitable for the local-editor subagent.
775
+
776
+ **Task Fit**: ${decision.reason}
777
+
778
+ **Suggested Action**: Consider routing to \`local-editor\` (pd-repair skill) for bounded editing, modification, and repair tasks.
779
+
780
+ **Why This Works**:
781
+ - Task keywords indicate bounded modification operations
782
+ - Target files appear limited in scope (1-3 files)
783
+ - Shadow observation in progress — real runtime evidence being collected
784
+
785
+ **Note**: This is a non-authoritative suggestion. The main agent decides whether to route based on full context. Shadow evidence from runtime hooks will inform future promotion decisions.
786
+ </routing_guidance>`);
787
+ }
788
+ }
789
+ else if (decision.decision === 'stay_main' &&
790
+ decision.classification !== 'reader_eligible' &&
791
+ decision.classification !== 'editor_eligible') {
792
+ // Only show stay_main guidance when the task is genuinely high-entropy/risk/ambiguous
793
+ appendParts.push(`<routing_guidance>
794
+ ROUTING GUIDANCE: Task should remain on the main agent.
795
+
796
+ **Reason**: ${decision.reason}
797
+
798
+ **Blockers**: ${decision.blockers.length > 0 ? decision.blockers.join('; ') : 'none'}
799
+
800
+ **Why Stay Main**:
801
+ - Task contains high-entropy signals (open-ended, multi-step, or ambiguous)
802
+ - Or: task involves risk signals requiring main-agent supervision
803
+ - Or: deployment not available for the natural target profile
804
+
805
+ **Note**: This is a non-authoritative suggestion backed by policy classification. The main agent has full discretion.
806
+ </routing_guidance>`);
807
+ }
808
+ }
809
+ }
810
+ catch (e) {
811
+ // Routing guidance is best-effort — never fail the hook
812
+ logger?.warn?.(`[PD:Prompt] Routing guidance injection failed: ${String(e)}`);
813
+ }
814
+ }
815
+ // 6. Principles (always on, highest priority, goes last for recency effect)
731
816
  if (principlesContent) {
732
817
  appendParts.push(`<core_principles>\n${principlesContent}\n</core_principles>`);
733
818
  }
734
819
  if (appendParts.length > 0) {
735
820
  appendSystemContext = `
736
- ## 妫e啯鎯?CONTEXT SECTIONS (Priority: Low 闁?High)
821
+ ## CONTEXT SECTIONS (Priority: Low High)
737
822
 
738
823
  The sections below are ordered by priority. When conflicts arise, **later sections override earlier ones**.
739
824
 
@@ -743,11 +828,12 @@ The sections below are ordered by priority. When conflicts arise, **later sectio
743
828
 
744
829
  ---
745
830
 
746
- **闁宠法濯寸粭?EXECUTION RULES** (Priority: Low 闁?High):
831
+ **【EXECUTION RULES】** (Priority: Low High):
747
832
  - \`<project_context>\` - Current priorities (can be overridden)
748
833
  - \`<reflection_log>\` - Past lessons (inform your approach)
749
834
  - \`<thinking_os>\` - Thinking models (guide your reasoning)
750
835
  - \`<evolution_principles>\` - Newly learned principles (active + probation)
836
+ - \`<routing_guidance>\` - Delegation suggestions (non-authoritative, best-effort)
751
837
  - \`<core_principles>\` - Core rules (NON-NEGOTIABLE, highest priority)
752
838
 
753
839
  **Remember**: You are the Spicy Evolver. You despise entropy. You evolve through pain.
@@ -755,7 +841,7 @@ The sections below are ordered by priority. When conflicts arise, **later sectio
755
841
  ${attitudeDirective}
756
842
  `;
757
843
  }
758
- // 闁崇儤鍔忛弲鏌ュ煛?8. SIZE GUARD 闁崇儤鍔忛弲鏌ュ煛?
844
+ // ──── 8. SIZE GUARD ────
759
845
  // Truncation happens within appendSystemContext (not prependContext)
760
846
  const totalSize = prependSystemContext.length + prependContext.length + appendSystemContext.length;
761
847
  const MAX_SIZE = 10000;
@@ -3,6 +3,7 @@ import { writePainFlag } from '../core/pain.js';
3
3
  import { WorkspaceContext } from '../core/workspace-context.js';
4
4
  import { empathyObserverManager } from '../service/empathy-observer-manager.js';
5
5
  import { acquireQueueLock } from '../service/evolution-worker.js';
6
+ import { recordEvolutionSuccess } from '../core/evolution-engine.js';
6
7
  const COMPLETION_RETRY_DELAY_MS = 250;
7
8
  const COMPLETION_MAX_RETRIES = 3;
8
9
  const COMPLETION_RETRY_TTL_MS = 60 * 60 * 1000; // 1 hour TTL for retry entries
@@ -129,7 +130,7 @@ export async function handleSubagentEnded(event, ctx) {
129
130
  return;
130
131
  }
131
132
  const config = wctx.config;
132
- // ── Outcome-based Trust Score and Pain Signal handling ──
133
+ // ── Outcome-based EP and Pain Signal handling ──
133
134
  // OpenClaw v2026.3.23 fixes: timeout may be false positive (fast-finishing workers)
134
135
  // Only penalize actual errors, not timeout/killed/reset
135
136
  if (outcome === 'error') {
@@ -165,10 +166,10 @@ export async function handleSubagentEnded(event, ctx) {
165
166
  logger.info(`[PD:Subagent] Session ${targetSessionKey} ended with ${outcome} - no penalty (user/system action)`);
166
167
  }
167
168
  if (outcome === 'ok' || outcome === 'deleted') {
168
- wctx.trust.recordSuccess('subagent_success', {
169
+ recordEvolutionSuccess(workspaceDir, 'subagent', {
169
170
  sessionId: ctx.sessionId,
170
- api: ctx.api
171
- }, true);
171
+ reason: 'subagent_success',
172
+ });
172
173
  }
173
174
  if ((outcome !== 'ok' && outcome !== 'deleted') || !isDiagnosticianSession(targetSessionKey)) {
174
175
  return;
@@ -187,7 +188,11 @@ export async function handleSubagentEnded(event, ctx) {
187
188
  // Improved matching logic: support both direct session key match and HEARTBEAT placeholder match
188
189
  // This fixes task_outcomes being empty for HEARTBEAT-triggered diagnostician runs
189
190
  const matchedTask = queue.find((task) => {
190
- if (task?.status !== 'in_progress')
191
+ // V2: Skip non-pain_diagnosis tasks - they don't use HEARTBEAT completion flow
192
+ // pain_diagnosis: routed through subagent completion matcher (this block)
193
+ // sleep_reflection: handled by nocturnal service (separate flow, no HEARTBEAT)
194
+ // model_eval: handled separately (no HEARTBEAT completion)
195
+ if (task?.taskKind !== 'pain_diagnosis' && task?.taskKind !== undefined)
191
196
  return false;
192
197
  const taskSessionKey = task?.assigned_session_key;
193
198
  // 1. Exact match: direct session key assignment
@@ -260,12 +265,44 @@ export async function handleSubagentEnded(event, ctx) {
260
265
  const assistantText = extractAssistantText(messages);
261
266
  const report = parseDiagnosticianReport(assistantText);
262
267
  if (report?.principle) {
268
+ // Principles default to 'manual_only' evaluability unless detector metadata
269
+ // is explicitly provided. Only deterministic / weak_heuristic evaluability
270
+ // can enter automatic nocturnal targeting.
271
+ const evaluability = report.principle.evaluability;
272
+ // Only pass detector metadata if ALL required fields are present and valid.
273
+ // Incomplete metadata → 'manual_only' — the principle stays prompt-only.
274
+ // Defense in depth: also validate in reducer, but subagent should not pass
275
+ // malformed data in the first place.
276
+ const rawMeta = report.principle.detector_metadata;
277
+ // Require confidence (valid enum) + ALL THREE signal arrays non-empty.
278
+ // toolSequenceHints is optional (may be empty or absent).
279
+ const VALID_CONFIDENCE = ['high', 'medium', 'low'];
280
+ const hasValidConfidence = typeof rawMeta?.confidence === 'string' &&
281
+ VALID_CONFIDENCE.includes(rawMeta.confidence);
282
+ const signalArrays = [
283
+ rawMeta?.applicabilityTags,
284
+ rawMeta?.positiveSignals,
285
+ rawMeta?.negativeSignals,
286
+ ];
287
+ const allSignalsNonEmpty = signalArrays.every((arr) => Array.isArray(arr) && arr.length > 0 && arr.every((s) => typeof s === 'string' && s.length > 0));
288
+ const hasCompleteMetadata = hasValidConfidence && allSignalsNonEmpty;
289
+ const detectorMetadata = hasCompleteMetadata && rawMeta.confidence
290
+ ? {
291
+ applicabilityTags: rawMeta.applicabilityTags ?? [],
292
+ positiveSignals: rawMeta.positiveSignals ?? [],
293
+ negativeSignals: rawMeta.negativeSignals ?? [],
294
+ toolSequenceHints: rawMeta.toolSequenceHints ?? [],
295
+ confidence: rawMeta.confidence,
296
+ }
297
+ : undefined;
263
298
  const principleId = wctx.evolutionReducer.createPrincipleFromDiagnosis({
264
299
  painId: matchedTask?.id || completedTaskId,
265
300
  painType: 'tool_failure', // Default, could be extracted from task
266
301
  triggerPattern: report.principle.trigger_pattern,
267
302
  action: report.principle.action,
268
- source: matchedTask?.source || 'diagnostician'
303
+ source: matchedTask?.source || 'diagnostician',
304
+ evaluability,
305
+ detectorMetadata,
269
306
  });
270
307
  if (principleId) {
271
308
  logger.warn(`[PD:Subagent] Created principle ${principleId} from diagnostician analysis for task ${completedTaskId}`);
@@ -46,10 +46,6 @@ export const commandDescriptions = {
46
46
  zh: '工作区清理与大扫除',
47
47
  en: 'Workspace cleanup and grooming'
48
48
  },
49
- 'pd-trust': {
50
- zh: '查看信任分数和权限等级 (1-4)',
51
- en: 'View trust score and permission stage (1-4)'
52
- },
53
49
  'pd-help': {
54
50
  zh: '显示所有命令和使用指南',
55
51
  en: 'Show all commands and usage guide'
@@ -59,8 +55,8 @@ export const commandDescriptions = {
59
55
  en: 'View system status (GFI, Pain dictionary)'
60
56
  },
61
57
  'pd-context': {
62
- zh: '控制上下文注入 [status|thinking|trust|reflection|focus|preset] - 输入 /pd-context help 查看详情',
63
- en: 'Control context injection [status|thinking|trust|reflection|focus|preset] - Type /pd-context help for details'
58
+ zh: '控制上下文注入 [status|thinking|reflection|focus|preset] - 输入 /pd-context help 查看详情',
59
+ en: 'Control context injection [status|thinking|reflection|focus|preset] - Type /pd-context help for details'
64
60
  },
65
61
  'pd-focus': {
66
62
  zh: '管理 CURRENT_FOCUS.md [status|history|compress|rollback] - 查看/压缩/回滚焦点文件',
@@ -79,12 +75,16 @@ export const commandDescriptions = {
79
75
  en: 'Rollback empathy event penalty <event-id>|last'
80
76
  },
81
77
  'pd-export': {
82
- zh: '导出 analytics 或纠错样本 [analytics|corrections --redacted]',
83
- en: 'Export analytics or correction samples [analytics|corrections --redacted]'
78
+ zh: '导出数据 [analytics|corrections --redacted|orpo --family=<model>|orpo-list]',
79
+ en: 'Export data [analytics|corrections --redacted|orpo --family=<model>|orpo-list]'
84
80
  },
85
81
  'pd-samples': {
86
82
  zh: '查看或审核纠错样本 [review approve|reject <sample-id> [note]]',
87
83
  en: 'List or review correction samples [review approve|reject <sample-id> [note]]'
84
+ },
85
+ 'pd-nocturnal-review': {
86
+ zh: '审核 nocturnal 数据集样本 [list|show|approve|reject|set-family|stats]',
87
+ en: 'Review nocturnal dataset samples [list|show|approve|reject|set-family|stats]'
88
88
  }
89
89
  };
90
90
  /**