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.
- package/dist/commands/context.js +5 -15
- package/dist/commands/evolution-status.js +29 -48
- package/dist/commands/export.js +61 -8
- package/dist/commands/nocturnal-review.d.ts +24 -0
- package/dist/commands/nocturnal-review.js +265 -0
- package/dist/commands/nocturnal-rollout.d.ts +27 -0
- package/dist/commands/nocturnal-rollout.js +671 -0
- package/dist/commands/nocturnal-train.d.ts +25 -0
- package/dist/commands/nocturnal-train.js +919 -0
- package/dist/commands/pain.js +8 -21
- package/dist/config/defaults/runtime.d.ts +40 -0
- package/dist/config/defaults/runtime.js +44 -0
- package/dist/config/errors.d.ts +84 -0
- package/dist/config/errors.js +94 -0
- package/dist/config/index.d.ts +7 -0
- package/dist/config/index.js +7 -0
- package/dist/constants/diagnostician.d.ts +0 -4
- package/dist/constants/diagnostician.js +0 -4
- package/dist/constants/tools.d.ts +2 -2
- package/dist/constants/tools.js +1 -1
- package/dist/core/adaptive-thresholds.d.ts +186 -0
- package/dist/core/adaptive-thresholds.js +300 -0
- package/dist/core/config.d.ts +2 -38
- package/dist/core/config.js +6 -61
- package/dist/core/control-ui-db.d.ts +27 -0
- package/dist/core/control-ui-db.js +18 -0
- package/dist/core/event-log.d.ts +1 -2
- package/dist/core/event-log.js +0 -3
- package/dist/core/evolution-engine.js +1 -21
- package/dist/core/evolution-reducer.d.ts +7 -1
- package/dist/core/evolution-reducer.js +56 -4
- package/dist/core/evolution-types.d.ts +61 -9
- package/dist/core/evolution-types.js +31 -9
- package/dist/core/external-training-contract.d.ts +276 -0
- package/dist/core/external-training-contract.js +269 -0
- package/dist/core/local-worker-routing.d.ts +175 -0
- package/dist/core/local-worker-routing.js +525 -0
- package/dist/core/model-deployment-registry.d.ts +218 -0
- package/dist/core/model-deployment-registry.js +503 -0
- package/dist/core/model-training-registry.d.ts +295 -0
- package/dist/core/model-training-registry.js +475 -0
- package/dist/core/nocturnal-arbiter.d.ts +159 -0
- package/dist/core/nocturnal-arbiter.js +534 -0
- package/dist/core/nocturnal-candidate-scoring.d.ts +137 -0
- package/dist/core/nocturnal-candidate-scoring.js +266 -0
- package/dist/core/nocturnal-compliance.d.ts +175 -0
- package/dist/core/nocturnal-compliance.js +824 -0
- package/dist/core/nocturnal-dataset.d.ts +224 -0
- package/dist/core/nocturnal-dataset.js +443 -0
- package/dist/core/nocturnal-executability.d.ts +85 -0
- package/dist/core/nocturnal-executability.js +331 -0
- package/dist/core/nocturnal-export.d.ts +124 -0
- package/dist/core/nocturnal-export.js +275 -0
- package/dist/core/nocturnal-paths.d.ts +124 -0
- package/dist/core/nocturnal-paths.js +214 -0
- package/dist/core/nocturnal-trajectory-extractor.d.ts +242 -0
- package/dist/core/nocturnal-trajectory-extractor.js +307 -0
- package/dist/core/nocturnal-trinity.d.ts +311 -0
- package/dist/core/nocturnal-trinity.js +880 -0
- package/dist/core/path-resolver.js +2 -1
- package/dist/core/paths.d.ts +6 -0
- package/dist/core/paths.js +6 -0
- package/dist/core/principle-training-state.d.ts +121 -0
- package/dist/core/principle-training-state.js +321 -0
- package/dist/core/promotion-gate.d.ts +238 -0
- package/dist/core/promotion-gate.js +529 -0
- package/dist/core/session-tracker.d.ts +10 -0
- package/dist/core/session-tracker.js +14 -0
- package/dist/core/shadow-observation-registry.d.ts +217 -0
- package/dist/core/shadow-observation-registry.js +308 -0
- package/dist/core/training-program.d.ts +233 -0
- package/dist/core/training-program.js +433 -0
- package/dist/core/trajectory.d.ts +155 -1
- package/dist/core/trajectory.js +292 -8
- package/dist/core/workspace-context.d.ts +0 -6
- package/dist/core/workspace-context.js +0 -12
- package/dist/hooks/bash-risk.d.ts +57 -0
- package/dist/hooks/bash-risk.js +137 -0
- package/dist/hooks/edit-verification.d.ts +62 -0
- package/dist/hooks/edit-verification.js +256 -0
- package/dist/hooks/gate-block-helper.d.ts +44 -0
- package/dist/hooks/gate-block-helper.js +119 -0
- package/dist/hooks/gate.d.ts +18 -0
- package/dist/hooks/gate.js +62 -751
- package/dist/hooks/gfi-gate.d.ts +40 -0
- package/dist/hooks/gfi-gate.js +113 -0
- package/dist/hooks/pain.js +6 -9
- package/dist/hooks/progressive-trust-gate.d.ts +51 -0
- package/dist/hooks/progressive-trust-gate.js +89 -0
- package/dist/hooks/prompt.d.ts +11 -11
- package/dist/hooks/prompt.js +167 -77
- package/dist/hooks/subagent.js +43 -6
- package/dist/hooks/thinking-checkpoint.d.ts +37 -0
- package/dist/hooks/thinking-checkpoint.js +51 -0
- package/dist/http/principles-console-route.js +13 -3
- package/dist/i18n/commands.js +8 -8
- package/dist/index.js +129 -28
- package/dist/service/central-database.js +2 -1
- package/dist/service/control-ui-query-service.d.ts +1 -1
- package/dist/service/control-ui-query-service.js +3 -3
- package/dist/service/evolution-query-service.d.ts +1 -1
- package/dist/service/evolution-query-service.js +5 -5
- package/dist/service/evolution-worker.d.ts +52 -4
- package/dist/service/evolution-worker.js +328 -16
- package/dist/service/nocturnal-runtime.d.ts +183 -0
- package/dist/service/nocturnal-runtime.js +352 -0
- package/dist/service/nocturnal-service.d.ts +163 -0
- package/dist/service/nocturnal-service.js +787 -0
- package/dist/service/nocturnal-target-selector.d.ts +145 -0
- package/dist/service/nocturnal-target-selector.js +315 -0
- package/dist/service/phase3-input-filter.d.ts +48 -12
- package/dist/service/phase3-input-filter.js +84 -18
- package/dist/service/runtime-summary-service.d.ts +34 -10
- package/dist/service/runtime-summary-service.js +87 -48
- package/dist/tools/deep-reflect.js +2 -1
- package/dist/types/event-types.d.ts +4 -10
- package/dist/types/runtime-summary.d.ts +47 -0
- package/dist/types/runtime-summary.js +1 -0
- package/dist/types.d.ts +0 -3
- package/dist/types.js +0 -2
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/templates/langs/en/skills/pd-mentor/SKILL.md +5 -5
- package/templates/langs/zh/skills/pd-mentor/SKILL.md +5 -5
- package/templates/pain_settings.json +0 -6
- package/dist/commands/trust.d.ts +0 -4
- package/dist/commands/trust.js +0 -78
- package/dist/core/trust-engine.d.ts +0 -96
- package/dist/core/trust-engine.js +0 -286
package/dist/hooks/prompt.js
CHANGED
|
@@ -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
|
|
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
|
-
*
|
|
206
|
+
* Validates model format, expects "provider/model" format
|
|
205
207
|
*/
|
|
206
208
|
function isValidModelFormat(model) {
|
|
207
|
-
//
|
|
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
|
-
*
|
|
215
|
-
*
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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.
|
|
291
|
-
// 2.
|
|
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
|
-
//
|
|
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:
|
|
384
|
+
// prependContext: Only short dynamic directives: evolutionDirective + heartbeat
|
|
383
385
|
let prependSystemContext = '';
|
|
384
386
|
let prependContext = '';
|
|
385
387
|
let appendSystemContext = '';
|
|
386
|
-
//
|
|
388
|
+
// ──── 0. Manual Pain Clearance ────
|
|
387
389
|
if (trigger === 'user' && sessionId && session && session.currentGfi >= 100) {
|
|
388
390
|
resetFriction(sessionId, workspaceDir);
|
|
389
391
|
}
|
|
390
|
-
//
|
|
391
|
-
prependSystemContext = `##
|
|
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
|
|
399
|
-
2. Principles Override
|
|
400
|
-
3. Learn from Pain
|
|
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
|
-
##
|
|
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
|
-
//
|
|
415
|
-
//
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (!
|
|
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 (
|
|
511
|
-
prependContext =
|
|
501
|
+
if (activeEvolutionTaskPrompt) {
|
|
502
|
+
prependContext = activeEvolutionTaskPrompt + prependContext;
|
|
512
503
|
}
|
|
513
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
###
|
|
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
|
-
###
|
|
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
|
-
###
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
//
|
|
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;
|
package/dist/hooks/subagent.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
169
|
+
recordEvolutionSuccess(workspaceDir, 'subagent', {
|
|
169
170
|
sessionId: ctx.sessionId,
|
|
170
|
-
|
|
171
|
-
}
|
|
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
|
-
|
|
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
|
+
}
|