principles-disciple 1.7.5 → 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 (129) hide show
  1. package/dist/commands/context.js +5 -15
  2. package/dist/commands/evolution-status.js +29 -48
  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/config/defaults/runtime.d.ts +40 -0
  12. package/dist/config/defaults/runtime.js +44 -0
  13. package/dist/config/errors.d.ts +84 -0
  14. package/dist/config/errors.js +94 -0
  15. package/dist/config/index.d.ts +7 -0
  16. package/dist/config/index.js +7 -0
  17. package/dist/constants/diagnostician.d.ts +0 -4
  18. package/dist/constants/diagnostician.js +0 -4
  19. package/dist/constants/tools.d.ts +2 -2
  20. package/dist/constants/tools.js +1 -1
  21. package/dist/core/adaptive-thresholds.d.ts +186 -0
  22. package/dist/core/adaptive-thresholds.js +300 -0
  23. package/dist/core/config.d.ts +2 -38
  24. package/dist/core/config.js +6 -61
  25. package/dist/core/control-ui-db.d.ts +27 -0
  26. package/dist/core/control-ui-db.js +18 -0
  27. package/dist/core/event-log.d.ts +1 -2
  28. package/dist/core/event-log.js +0 -3
  29. package/dist/core/evolution-engine.js +1 -21
  30. package/dist/core/evolution-reducer.d.ts +7 -1
  31. package/dist/core/evolution-reducer.js +56 -4
  32. package/dist/core/evolution-types.d.ts +61 -9
  33. package/dist/core/evolution-types.js +31 -9
  34. package/dist/core/external-training-contract.d.ts +276 -0
  35. package/dist/core/external-training-contract.js +269 -0
  36. package/dist/core/local-worker-routing.d.ts +175 -0
  37. package/dist/core/local-worker-routing.js +525 -0
  38. package/dist/core/model-deployment-registry.d.ts +218 -0
  39. package/dist/core/model-deployment-registry.js +503 -0
  40. package/dist/core/model-training-registry.d.ts +295 -0
  41. package/dist/core/model-training-registry.js +475 -0
  42. package/dist/core/nocturnal-arbiter.d.ts +159 -0
  43. package/dist/core/nocturnal-arbiter.js +534 -0
  44. package/dist/core/nocturnal-candidate-scoring.d.ts +137 -0
  45. package/dist/core/nocturnal-candidate-scoring.js +266 -0
  46. package/dist/core/nocturnal-compliance.d.ts +175 -0
  47. package/dist/core/nocturnal-compliance.js +824 -0
  48. package/dist/core/nocturnal-dataset.d.ts +224 -0
  49. package/dist/core/nocturnal-dataset.js +443 -0
  50. package/dist/core/nocturnal-executability.d.ts +85 -0
  51. package/dist/core/nocturnal-executability.js +331 -0
  52. package/dist/core/nocturnal-export.d.ts +124 -0
  53. package/dist/core/nocturnal-export.js +275 -0
  54. package/dist/core/nocturnal-paths.d.ts +124 -0
  55. package/dist/core/nocturnal-paths.js +214 -0
  56. package/dist/core/nocturnal-trajectory-extractor.d.ts +242 -0
  57. package/dist/core/nocturnal-trajectory-extractor.js +307 -0
  58. package/dist/core/nocturnal-trinity.d.ts +311 -0
  59. package/dist/core/nocturnal-trinity.js +880 -0
  60. package/dist/core/path-resolver.js +2 -1
  61. package/dist/core/paths.d.ts +6 -0
  62. package/dist/core/paths.js +6 -0
  63. package/dist/core/principle-training-state.d.ts +121 -0
  64. package/dist/core/principle-training-state.js +321 -0
  65. package/dist/core/promotion-gate.d.ts +238 -0
  66. package/dist/core/promotion-gate.js +529 -0
  67. package/dist/core/session-tracker.d.ts +10 -0
  68. package/dist/core/session-tracker.js +14 -0
  69. package/dist/core/shadow-observation-registry.d.ts +217 -0
  70. package/dist/core/shadow-observation-registry.js +308 -0
  71. package/dist/core/training-program.d.ts +233 -0
  72. package/dist/core/training-program.js +433 -0
  73. package/dist/core/trajectory.d.ts +155 -1
  74. package/dist/core/trajectory.js +292 -8
  75. package/dist/core/workspace-context.d.ts +0 -6
  76. package/dist/core/workspace-context.js +0 -12
  77. package/dist/hooks/bash-risk.d.ts +57 -0
  78. package/dist/hooks/bash-risk.js +137 -0
  79. package/dist/hooks/edit-verification.d.ts +62 -0
  80. package/dist/hooks/edit-verification.js +256 -0
  81. package/dist/hooks/gate-block-helper.d.ts +44 -0
  82. package/dist/hooks/gate-block-helper.js +119 -0
  83. package/dist/hooks/gate.d.ts +18 -0
  84. package/dist/hooks/gate.js +62 -751
  85. package/dist/hooks/gfi-gate.d.ts +40 -0
  86. package/dist/hooks/gfi-gate.js +113 -0
  87. package/dist/hooks/pain.js +6 -9
  88. package/dist/hooks/progressive-trust-gate.d.ts +51 -0
  89. package/dist/hooks/progressive-trust-gate.js +89 -0
  90. package/dist/hooks/prompt.d.ts +11 -11
  91. package/dist/hooks/prompt.js +167 -77
  92. package/dist/hooks/subagent.js +43 -6
  93. package/dist/hooks/thinking-checkpoint.d.ts +37 -0
  94. package/dist/hooks/thinking-checkpoint.js +51 -0
  95. package/dist/http/principles-console-route.js +13 -3
  96. package/dist/i18n/commands.js +8 -8
  97. package/dist/index.js +129 -28
  98. package/dist/service/central-database.js +2 -1
  99. package/dist/service/control-ui-query-service.d.ts +1 -1
  100. package/dist/service/control-ui-query-service.js +3 -3
  101. package/dist/service/evolution-query-service.d.ts +1 -1
  102. package/dist/service/evolution-query-service.js +5 -5
  103. package/dist/service/evolution-worker.d.ts +52 -4
  104. package/dist/service/evolution-worker.js +328 -16
  105. package/dist/service/nocturnal-runtime.d.ts +183 -0
  106. package/dist/service/nocturnal-runtime.js +352 -0
  107. package/dist/service/nocturnal-service.d.ts +163 -0
  108. package/dist/service/nocturnal-service.js +787 -0
  109. package/dist/service/nocturnal-target-selector.d.ts +145 -0
  110. package/dist/service/nocturnal-target-selector.js +315 -0
  111. package/dist/service/phase3-input-filter.d.ts +48 -12
  112. package/dist/service/phase3-input-filter.js +84 -18
  113. package/dist/service/runtime-summary-service.d.ts +34 -10
  114. package/dist/service/runtime-summary-service.js +87 -48
  115. package/dist/tools/deep-reflect.js +2 -1
  116. package/dist/types/event-types.d.ts +4 -10
  117. package/dist/types/runtime-summary.d.ts +47 -0
  118. package/dist/types/runtime-summary.js +1 -0
  119. package/dist/types.d.ts +0 -3
  120. package/dist/types.js +0 -2
  121. package/openclaw.plugin.json +1 -1
  122. package/package.json +1 -1
  123. package/templates/langs/en/skills/pd-mentor/SKILL.md +5 -5
  124. package/templates/langs/zh/skills/pd-mentor/SKILL.md +5 -5
  125. package/templates/pain_settings.json +0 -6
  126. package/dist/commands/trust.d.ts +0 -4
  127. package/dist/commands/trust.js +0 -78
  128. package/dist/core/trust-engine.d.ts +0 -96
  129. 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,44 +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 闁崇儤鍔忛弲鏌ュ煛?
440
- let evolutionDirective = '';
416
+ // ──── 2. Evolution Directive (always on, highest priority) - stays in prependContext ────
417
+ // NOTE: active evolution task prompt is injected from EVOLUTION_QUEUE for active tasks
418
+ // NOT used for Phase 3 eligibility decisions
419
+ // EVOLUTION_DIRECTIVE.json is a compatibility-only display artifact
420
+ // Phase 3 eligibility uses only queue and evolution (see phase3-input-filter.ts)
421
+ let activeEvolutionTaskPrompt = '';
441
422
  const queuePath = wctx.resolve('EVOLUTION_QUEUE');
442
423
  if (fs.existsSync(queuePath)) {
443
424
  try {
444
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
445
428
  const inProgressTasks = [...queue]
446
- .filter((t) => t.status === 'in_progress')
429
+ .filter((t) => t.status === 'in_progress' && (t.taskKind === 'pain_diagnosis' || !t.taskKind))
447
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
+ }
448
439
  const scoreA = Number.isFinite(a?.score) ? Number(a.score) : 0;
449
440
  const scoreB = Number.isFinite(b?.score) ? Number(b.score) : 0;
450
441
  return scoreB - scoreA;
@@ -457,7 +448,7 @@ You are a **self-evolving AI agent** powered by Principles Disciple.
457
448
  const escapedTask = JSON.stringify(resolvedTask);
458
449
  logger?.info(`[PD:Prompt] Injecting EVOLUTION TASK for: ${inProgressTask.id}`);
459
450
  if (trigger === 'user') {
460
- evolutionDirective = `<evolution_task priority="high">
451
+ activeEvolutionTaskPrompt = `<evolution_task priority="high">
461
452
  TASK: ${escapedTask}
462
453
 
463
454
  REQUIRED ACTION (两阶段回复):
@@ -487,7 +478,7 @@ IMPORTANT:
487
478
  </evolution_task>\n`;
488
479
  }
489
480
  else {
490
- evolutionDirective = `<evolution_task priority="critical">
481
+ activeEvolutionTaskPrompt = `<evolution_task priority="critical">
491
482
  TASK: ${escapedTask}
492
483
 
493
484
  REQUIRED ACTION:
@@ -498,7 +489,7 @@ REQUIRED ACTION:
498
489
  }
499
490
  break;
500
491
  }
501
- if (!evolutionDirective && inProgressTasks.length > 0) {
492
+ if (!activeEvolutionTaskPrompt && inProgressTasks.length > 0) {
502
493
  logger?.warn('[PD:Prompt] Skipping evolution task injection because task payload is invalid.');
503
494
  }
504
495
  }
@@ -507,17 +498,17 @@ REQUIRED ACTION:
507
498
  }
508
499
  }
509
500
  // Inject queue-derived evolution task at the front of prependContext
510
- if (evolutionDirective) {
511
- prependContext = evolutionDirective + prependContext;
501
+ if (activeEvolutionTaskPrompt) {
502
+ prependContext = activeEvolutionTaskPrompt + prependContext;
512
503
  }
513
- // 鈺愨晲鈺?4. Empathy Observer Spawn (async sidecar) 鈺愨晲鈺?
504
+ // ─────────────────────────────────────────────────4. Empathy Observer Spawn (async sidecar)
514
505
  // Skip if this is a subagent session or if the message indicates agent-to-agent communication
515
506
  const latestUserMessage = extractLatestUserMessage(event.messages);
516
507
  const isAgentToAgent = latestUserMessage.includes('sourceSession=agent:') || sessionId?.includes(':subagent:') === true;
517
508
  if (trigger === 'user' && sessionId && api && !isAgentToAgent) {
518
509
  empathyObserverManager.spawn(api, sessionId, latestUserMessage).catch((err) => api.logger.warn(String(err)));
519
510
  }
520
- // 闁崇儤鍔忛弲鏌ュ煛?5. Heartbeat-specific checklist 闁崇儤鍔忛弲鏌ュ煛?
511
+ // ──── 5. Heartbeat-specific checklist ────
521
512
  if (trigger === 'heartbeat') {
522
513
  const heartbeatPath = wctx.resolve('HEARTBEAT');
523
514
  if (fs.existsSync(heartbeatPath)) {
@@ -534,12 +525,12 @@ ACTION: Run self-audit. If stable, reply ONLY with "HEARTBEAT_OK".
534
525
  }
535
526
  }
536
527
  }
537
- // 闁崇儤鍔忛弲鏌ュ煛?6. Dynamic Attitude Matrix (based on GFI) 闁崇儤鍔忛弲鏌ュ煛?
528
+ // ──── 6. Dynamic Attitude Matrix (based on GFI) ────
538
529
  let attitudeDirective = '';
539
530
  const currentGfi = session?.currentGfi || 0;
540
531
  if (currentGfi >= 70) {
541
532
  attitudeDirective = `
542
- ### 妫e啯鐦?[SYSTEM_MODE: HUMBLE_RECOVERY]
533
+ ### SYSTEM_MODE: HUMBLE_RECOVERY
543
534
  **CURRENT STATUS**: Severe system friction / User frustration detected (GFI: ${currentGfi.toFixed(0)}).
544
535
  **BEHAVIORAL OVERRIDE**:
545
536
  - You have failed to meet expectations. Humility is your primary directive.
@@ -551,7 +542,7 @@ ACTION: Run self-audit. If stable, reply ONLY with "HEARTBEAT_OK".
551
542
  }
552
543
  else if (currentGfi >= 40) {
553
544
  attitudeDirective = `
554
- ### 闁宠法濯寸粭?[SYSTEM_MODE: CONCILIATORY]
545
+ ### SYSTEM_MODE: CONCILIATORY
555
546
  **CURRENT STATUS**: Moderate friction detected (GFI: ${currentGfi.toFixed(0)}).
556
547
  **BEHAVIORAL OVERRIDE**:
557
548
  - User is frustrated. Be more explanatory and cautious.
@@ -561,7 +552,7 @@ ACTION: Run self-audit. If stable, reply ONLY with "HEARTBEAT_OK".
561
552
  }
562
553
  else {
563
554
  attitudeDirective = `
564
- ### 闁?[SYSTEM_MODE: EFFICIENT]
555
+ ### SYSTEM_MODE: EFFICIENT
565
556
  **CURRENT STATUS**: System healthy (GFI: ${currentGfi.toFixed(0)}).
566
557
  **BEHAVIORAL OVERRIDE**:
567
558
  - Maintain peak efficiency.
@@ -569,7 +560,7 @@ ACTION: Run self-audit. If stable, reply ONLY with "HEARTBEAT_OK".
569
560
  - Follow the "Principles > Directives" rule strictly.
570
561
  `;
571
562
  }
572
- // 闁崇儤鍔忛弲鏌ュ煛?7. appendSystemContext: Principles + Thinking OS + reflection_log + project_context 闁崇儤鍔忛弲鏌ュ煛?
563
+ // ──── 7. appendSystemContext: Principles + Thinking OS + reflection_log + project_context ────
573
564
  // NOTE: Principles is ALWAYS injected (not configurable)
574
565
  // Thinking OS, reflection_log, project_context are configurable
575
566
  // All these go into System Prompt (WebUI-hidden, Prompt Cacheable)
@@ -723,13 +714,111 @@ ACTION: Run self-audit. If stable, reply ONLY with "HEARTBEAT_OK".
723
714
  if (evolutionPrinciplesContent) {
724
715
  appendParts.push(`<evolution_principles>\n${evolutionPrinciplesContent}\n</evolution_principles>`);
725
716
  }
726
- // 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)
727
816
  if (principlesContent) {
728
817
  appendParts.push(`<core_principles>\n${principlesContent}\n</core_principles>`);
729
818
  }
730
819
  if (appendParts.length > 0) {
731
820
  appendSystemContext = `
732
- ## 妫e啯鎯?CONTEXT SECTIONS (Priority: Low 闁?High)
821
+ ## CONTEXT SECTIONS (Priority: Low High)
733
822
 
734
823
  The sections below are ordered by priority. When conflicts arise, **later sections override earlier ones**.
735
824
 
@@ -739,11 +828,12 @@ The sections below are ordered by priority. When conflicts arise, **later sectio
739
828
 
740
829
  ---
741
830
 
742
- **闁宠法濯寸粭?EXECUTION RULES** (Priority: Low 闁?High):
831
+ **【EXECUTION RULES】** (Priority: Low High):
743
832
  - \`<project_context>\` - Current priorities (can be overridden)
744
833
  - \`<reflection_log>\` - Past lessons (inform your approach)
745
834
  - \`<thinking_os>\` - Thinking models (guide your reasoning)
746
835
  - \`<evolution_principles>\` - Newly learned principles (active + probation)
836
+ - \`<routing_guidance>\` - Delegation suggestions (non-authoritative, best-effort)
747
837
  - \`<core_principles>\` - Core rules (NON-NEGOTIABLE, highest priority)
748
838
 
749
839
  **Remember**: You are the Spicy Evolver. You despise entropy. You evolve through pain.
@@ -751,7 +841,7 @@ The sections below are ordered by priority. When conflicts arise, **later sectio
751
841
  ${attitudeDirective}
752
842
  `;
753
843
  }
754
- // 闁崇儤鍔忛弲鏌ュ煛?8. SIZE GUARD 闁崇儤鍔忛弲鏌ュ煛?
844
+ // ──── 8. SIZE GUARD ────
755
845
  // Truncation happens within appendSystemContext (not prependContext)
756
846
  const totalSize = prependSystemContext.length + prependContext.length + appendSystemContext.length;
757
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}`);
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Thinking Checkpoint Module
3
+ *
4
+ * Enforces P-10 deep reflection requirement for high-risk tool operations.
5
+ *
6
+ * **Responsibilities:**
7
+ * - Check if high-risk tools have recent deep thinking (T-01 through T-10)
8
+ * - Block high-risk operations without preceding deep reflection
9
+ * - Configurable time window for thinking validity (default 5 minutes)
10
+ * - Provide clear guidance on required action (deep_reflect tool usage)
11
+ *
12
+ * **Configuration:**
13
+ * - Thinking checkpoint settings from profile.thinking_checkpoint
14
+ * - Window duration for thinking validity
15
+ * - High-risk tool list
16
+ */
17
+ import type { PluginHookBeforeToolCallEvent, PluginHookBeforeToolCallResult } from '../openclaw-sdk.js';
18
+ export interface ThinkingCheckpointConfig {
19
+ enabled?: boolean;
20
+ window_ms?: number;
21
+ high_risk_tools?: string[];
22
+ }
23
+ /**
24
+ * Checks if a tool call requires a recent deep thinking checkpoint.
25
+ *
26
+ * This enforces P-10 (Thinking OS Checkpoint) - high-risk operations must
27
+ * be preceded by deep reflection within the configured time window.
28
+ *
29
+ * @param event - The before_tool_call event
30
+ * @param config - Thinking checkpoint configuration from profile
31
+ * @param sessionId - Current session ID
32
+ * @param logger - Optional logger for info messages
33
+ * @returns Block result if thinking required, undefined otherwise
34
+ */
35
+ export declare function checkThinkingCheckpoint(event: PluginHookBeforeToolCallEvent, config: ThinkingCheckpointConfig, sessionId: string | undefined, logger?: {
36
+ info?: (message: string) => void;
37
+ }): PluginHookBeforeToolCallResult | undefined;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Thinking Checkpoint Module
3
+ *
4
+ * Enforces P-10 deep reflection requirement for high-risk tool operations.
5
+ *
6
+ * **Responsibilities:**
7
+ * - Check if high-risk tools have recent deep thinking (T-01 through T-10)
8
+ * - Block high-risk operations without preceding deep reflection
9
+ * - Configurable time window for thinking validity (default 5 minutes)
10
+ * - Provide clear guidance on required action (deep_reflect tool usage)
11
+ *
12
+ * **Configuration:**
13
+ * - Thinking checkpoint settings from profile.thinking_checkpoint
14
+ * - Window duration for thinking validity
15
+ * - High-risk tool list
16
+ */
17
+ import { hasRecentThinking } from '../core/session-tracker.js';
18
+ import { THINKING_CHECKPOINT_WINDOW_MS, THINKING_CHECKPOINT_DEFAULT_HIGH_RISK_TOOLS } from '../config/index.js';
19
+ /**
20
+ * Checks if a tool call requires a recent deep thinking checkpoint.
21
+ *
22
+ * This enforces P-10 (Thinking OS Checkpoint) - high-risk operations must
23
+ * be preceded by deep reflection within the configured time window.
24
+ *
25
+ * @param event - The before_tool_call event
26
+ * @param config - Thinking checkpoint configuration from profile
27
+ * @param sessionId - Current session ID
28
+ * @param logger - Optional logger for info messages
29
+ * @returns Block result if thinking required, undefined otherwise
30
+ */
31
+ export function checkThinkingCheckpoint(event, config, sessionId, logger) {
32
+ const enabled = config.enabled ?? false;
33
+ const windowMs = config.window_ms ?? THINKING_CHECKPOINT_WINDOW_MS;
34
+ const highRiskTools = config.high_risk_tools ?? [...THINKING_CHECKPOINT_DEFAULT_HIGH_RISK_TOOLS];
35
+ if (!enabled || !sessionId) {
36
+ return undefined;
37
+ }
38
+ const isHighRisk = highRiskTools.includes(event.toolName);
39
+ if (!isHighRisk) {
40
+ return undefined;
41
+ }
42
+ const hasThinking = hasRecentThinking(sessionId, windowMs);
43
+ if (!hasThinking) {
44
+ logger?.info?.(`[PD:THINKING_GATE] High-risk tool "${event.toolName}" called without recent deep thinking`);
45
+ return {
46
+ block: true,
47
+ blockReason: `[Thinking OS Checkpoint] 高风险操作 "${event.toolName}" 需要先进行深度思考。\n\n请先使用 deep_reflect 工具分析当前情况,然后再尝试此操作。\n\n这是强制性检查点,目的是确保决策质量。\n\n提示:调用 deep_reflect 后,${Math.round(windowMs / 60000)}分钟内的操作将自动放行。\n\n可在PROFILE.json中设置 thinking_checkpoint.enabled: false 来禁用此检查。`,
48
+ };
49
+ }
50
+ return undefined;
51
+ }