principles-disciple 1.42.0 → 1.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/src/commands/archive-impl.ts +5 -3
- package/src/commands/context.ts +1 -0
- package/src/commands/disable-impl.ts +1 -1
- package/src/commands/evolution-status.ts +2 -2
- package/src/commands/principle-rollback.ts +1 -1
- package/src/commands/rollback.ts +0 -1
- package/src/commands/samples.ts +1 -1
- package/src/commands/thinking-os.ts +1 -0
- package/src/commands/workflow-debug.ts +1 -1
- package/src/core/config.ts +1 -0
- package/src/core/dictionary.ts +1 -0
- package/src/core/event-log.ts +3 -3
- package/src/core/external-training-contract.ts +1 -1
- package/src/core/merge-gate-audit.ts +3 -3
- package/src/core/nocturnal-arbiter.ts +1 -1
- package/src/core/nocturnal-compliance.ts +21 -21
- package/src/core/nocturnal-executability.ts +1 -1
- package/src/core/nocturnal-reasoning-deriver.ts +4 -4
- package/src/core/nocturnal-rule-implementation-validator.ts +1 -1
- package/src/core/nocturnal-snapshot-contract.ts +1 -1
- package/src/core/pain-context-extractor.ts +2 -2
- package/src/core/path-resolver.ts +1 -0
- package/src/core/pd-task-reconciler.ts +1 -0
- package/src/core/pd-task-service.ts +1 -1
- package/src/core/pd-task-store.ts +1 -0
- package/src/core/principle-internalization/deprecated-readiness.ts +1 -1
- package/src/core/principle-internalization/principle-lifecycle-service.ts +1 -1
- package/src/core/principle-training-state.ts +2 -2
- package/src/core/principle-tree-migration.ts +1 -1
- package/src/core/replay-engine.ts +1 -0
- package/src/core/risk-calculator.ts +2 -1
- package/src/core/rule-host.ts +1 -1
- package/src/core/session-tracker.ts +1 -0
- package/src/core/shadow-observation-registry.ts +1 -1
- package/src/core/thinking-models.ts +1 -1
- package/src/core/thinking-os-parser.ts +1 -1
- package/src/core/trajectory.ts +2 -0
- package/src/hooks/bash-risk.ts +2 -2
- package/src/hooks/edit-verification.ts +3 -3
- package/src/hooks/gate.ts +8 -8
- package/src/hooks/gfi-gate.ts +2 -2
- package/src/hooks/lifecycle-routing.ts +1 -1
- package/src/hooks/pain.ts +2 -2
- package/src/hooks/progressive-trust-gate.ts +3 -3
- package/src/hooks/prompt.ts +2 -0
- package/src/hooks/thinking-checkpoint.ts +1 -1
- package/src/service/central-database.ts +3 -2
- package/src/service/central-health-service.ts +2 -1
- package/src/service/central-overview-service.ts +3 -2
- package/src/service/control-ui-query-service.ts +2 -2
- package/src/service/event-log-auditor.ts +2 -2
- package/src/service/evolution-query-service.ts +1 -1
- package/src/service/evolution-worker.ts +7 -5
- package/src/service/health-query-service.ts +11 -10
- package/src/service/monitoring-query-service.ts +4 -4
- package/src/service/nocturnal-target-selector.ts +2 -2
- package/src/service/runtime-summary-service.ts +1 -1
- package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +1 -0
- package/src/service/subagent-workflow/subagent-error-utils.ts +1 -1
- package/src/service/subagent-workflow/workflow-store.ts +3 -2
- package/src/service/workflow-watchdog.ts +1 -1
- package/src/tools/critique-prompt.ts +1 -1
- package/src/tools/model-index.ts +1 -1
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* Used for permanent cleanup of implementations that are no longer relevant.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
|
|
12
|
+
|
|
11
13
|
import { WorkspaceContext } from '../core/workspace-context.js';
|
|
12
14
|
import { refreshPrincipleLifecycle } from '../core/principle-internalization/lifecycle-refresh.js';
|
|
13
15
|
import {
|
|
@@ -52,14 +54,14 @@ export function handleArchiveImplCommand(ctx: PluginCommandContext): PluginComma
|
|
|
52
54
|
// Subcommand: list
|
|
53
55
|
if (subcommand === 'list') {
|
|
54
56
|
|
|
55
|
-
|
|
57
|
+
|
|
56
58
|
return _handleListArchivable(stateDir, isZh);
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
// Archive by ID
|
|
60
62
|
const targetId = subcommand;
|
|
61
63
|
|
|
62
|
-
|
|
64
|
+
|
|
63
65
|
return _handleArchiveImpl(workspaceDir, stateDir, targetId, isZh);
|
|
64
66
|
}
|
|
65
67
|
|
|
@@ -97,7 +99,7 @@ function _handleListArchivable(
|
|
|
97
99
|
}
|
|
98
100
|
|
|
99
101
|
|
|
100
|
-
|
|
102
|
+
|
|
101
103
|
function _handleArchiveImpl(
|
|
102
104
|
workspaceDir: string,
|
|
103
105
|
stateDir: string,
|
package/src/commands/context.ts
CHANGED
|
@@ -46,7 +46,7 @@ function formatRouteRecommendations(
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
|
|
50
50
|
function buildEnglishOutput(
|
|
51
51
|
workspaceDir: string,
|
|
52
52
|
sessionId: string | null,
|
|
@@ -99,7 +99,7 @@ function buildEnglishOutput(
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
|
|
102
|
-
|
|
102
|
+
|
|
103
103
|
function buildChineseOutput(
|
|
104
104
|
workspaceDir: string,
|
|
105
105
|
sessionId: string | null,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { WorkspaceContext } from '../core/workspace-context.js';
|
|
2
2
|
import type { PluginCommandContext } from '../openclaw-sdk.js';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
|
|
5
5
|
export function handlePrincipleRollbackCommand(ctx: PluginCommandContext): { text: string } {
|
|
6
6
|
const workspaceDir = (ctx.config?.workspaceDir as string) || process.cwd();
|
|
7
7
|
const argText = (ctx.args || '').trim();
|
package/src/commands/rollback.ts
CHANGED
package/src/commands/samples.ts
CHANGED
|
@@ -30,7 +30,7 @@ export function handleSamplesCommand(ctx: PluginCommandContext): PluginCommandRe
|
|
|
30
30
|
const normalizedDecision = decision === 'approve' ? 'approved' : 'rejected';
|
|
31
31
|
const note = noteParts.join(' ').trim();
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
let record;
|
|
35
35
|
try {
|
|
36
36
|
record = wctx.trajectory.reviewCorrectionSample(sampleId, normalizedDecision, note);
|
package/src/core/config.ts
CHANGED
package/src/core/dictionary.ts
CHANGED
package/src/core/event-log.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
|
|
2
2
|
import * as fs from 'fs';
|
|
3
3
|
import * as path from 'path';
|
|
4
4
|
import type {
|
|
@@ -221,7 +221,7 @@ export class EventLog {
|
|
|
221
221
|
}
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
-
|
|
224
|
+
|
|
225
225
|
private updateStats(entry: EventLogEntry): void {
|
|
226
226
|
let stats = this.statsCache.get(entry.date);
|
|
227
227
|
if (!stats) {
|
|
@@ -500,7 +500,7 @@ export class EventLog {
|
|
|
500
500
|
/**
|
|
501
501
|
* Aggregate empathy stats for a specific session.
|
|
502
502
|
*/
|
|
503
|
-
|
|
503
|
+
|
|
504
504
|
private aggregateSessionEmpathy(sessionId: string, result: EmpathyEventStats): void {
|
|
505
505
|
for (const entry of this.getMergedEvents()) {
|
|
506
506
|
if (entry.sessionId === sessionId && entry.type === 'pain_signal') {
|
|
@@ -405,7 +405,7 @@ export function computeConfigFingerprint(config: Partial<TrainingHyperparameters
|
|
|
405
405
|
*/
|
|
406
406
|
export function computeDatasetFingerprint(exportPath: string, sampleCount: number): string {
|
|
407
407
|
|
|
408
|
-
|
|
408
|
+
|
|
409
409
|
let contentHash: string;
|
|
410
410
|
try {
|
|
411
411
|
const content = fs.readFileSync(exportPath, 'utf-8');
|
|
@@ -342,7 +342,7 @@ function hasValidEvidenceSummary(parsed: unknown): boolean {
|
|
|
342
342
|
* Validate a single replay report file and return its category.
|
|
343
343
|
*/
|
|
344
344
|
function validateSingleReplayReport(reportPath: string): ReplayValidationCategory {
|
|
345
|
-
|
|
345
|
+
|
|
346
346
|
let rawContent: string;
|
|
347
347
|
try {
|
|
348
348
|
rawContent = fs.readFileSync(reportPath, 'utf-8');
|
|
@@ -350,7 +350,7 @@ function validateSingleReplayReport(reportPath: string): ReplayValidationCategor
|
|
|
350
350
|
return 'io_error';
|
|
351
351
|
}
|
|
352
352
|
|
|
353
|
-
|
|
353
|
+
|
|
354
354
|
let parsed: unknown;
|
|
355
355
|
try {
|
|
356
356
|
parsed = JSON.parse(rawContent);
|
|
@@ -366,7 +366,7 @@ function validateSingleReplayReport(reportPath: string): ReplayValidationCategor
|
|
|
366
366
|
return 'missing_evidence_summary';
|
|
367
367
|
}
|
|
368
368
|
|
|
369
|
-
|
|
369
|
+
|
|
370
370
|
const evidenceSummary = parsed.evidenceSummary;
|
|
371
371
|
if (parsed.overallDecision === 'pass' && evidenceSummary.totalSamples === 0) {
|
|
372
372
|
return 'unsupported_pass';
|
|
@@ -242,31 +242,31 @@ export function detectOpportunity(principleId: string, session: SessionEvents):
|
|
|
242
242
|
|
|
243
243
|
switch (principleId) {
|
|
244
244
|
case 'T-01':
|
|
245
|
-
|
|
245
|
+
|
|
246
246
|
return detectT01Opportunity(session);
|
|
247
247
|
case 'T-02':
|
|
248
|
-
|
|
248
|
+
|
|
249
249
|
return detectT02Opportunity(session);
|
|
250
250
|
case 'T-03':
|
|
251
|
-
|
|
251
|
+
|
|
252
252
|
return detectT03Opportunity(session);
|
|
253
253
|
case 'T-04':
|
|
254
|
-
|
|
254
|
+
|
|
255
255
|
return detectT04Opportunity(session);
|
|
256
256
|
case 'T-05':
|
|
257
|
-
|
|
257
|
+
|
|
258
258
|
return detectT05Opportunity(session);
|
|
259
259
|
case 'T-06':
|
|
260
|
-
|
|
260
|
+
|
|
261
261
|
return detectT06Opportunity(session);
|
|
262
262
|
case 'T-07':
|
|
263
|
-
|
|
263
|
+
|
|
264
264
|
return detectT07Opportunity(session);
|
|
265
265
|
case 'T-08':
|
|
266
|
-
|
|
266
|
+
|
|
267
267
|
return detectT08Opportunity(session);
|
|
268
268
|
case 'T-09':
|
|
269
|
-
|
|
269
|
+
|
|
270
270
|
return detectT09Opportunity(session);
|
|
271
271
|
default:
|
|
272
272
|
return { applicable: false, reason: `Unknown principle: ${principleId}` };
|
|
@@ -550,31 +550,31 @@ export function detectViolation(principleId: string, session: SessionEvents): Vi
|
|
|
550
550
|
|
|
551
551
|
switch (principleId) {
|
|
552
552
|
case 'T-01':
|
|
553
|
-
|
|
553
|
+
|
|
554
554
|
return detectT01Violation(session);
|
|
555
555
|
case 'T-02':
|
|
556
|
-
|
|
556
|
+
|
|
557
557
|
return detectT02Violation(session);
|
|
558
558
|
case 'T-03':
|
|
559
|
-
|
|
559
|
+
|
|
560
560
|
return detectT03Violation(session);
|
|
561
561
|
case 'T-04':
|
|
562
|
-
|
|
562
|
+
|
|
563
563
|
return detectT04Violation(session);
|
|
564
564
|
case 'T-05':
|
|
565
|
-
|
|
565
|
+
|
|
566
566
|
return detectT05Violation(session);
|
|
567
567
|
case 'T-06':
|
|
568
|
-
|
|
568
|
+
|
|
569
569
|
return detectT06Violation(session);
|
|
570
570
|
case 'T-07':
|
|
571
|
-
|
|
571
|
+
|
|
572
572
|
return detectT07Violation(session);
|
|
573
573
|
case 'T-08':
|
|
574
|
-
|
|
574
|
+
|
|
575
575
|
return detectT08Violation(session);
|
|
576
576
|
case 'T-09':
|
|
577
|
-
|
|
577
|
+
|
|
578
578
|
return detectT09Violation(session);
|
|
579
579
|
default:
|
|
580
580
|
console.warn(`[PD:Compliance] Unknown principle ID: ${principleId} — treating as no violation. Check for typos (P-001 vs P_001).`);
|
|
@@ -933,11 +933,11 @@ export function computeCompliance(
|
|
|
933
933
|
: 0;
|
|
934
934
|
|
|
935
935
|
// Compute violationTrend using windows
|
|
936
|
-
|
|
936
|
+
|
|
937
937
|
const violationTrend = computeViolationTrend(applicableSessions, windowSize);
|
|
938
938
|
|
|
939
939
|
// Build explanation
|
|
940
|
-
|
|
940
|
+
|
|
941
941
|
const explanation = buildExplanation(
|
|
942
942
|
principleId,
|
|
943
943
|
applicableOpportunityCount,
|
|
@@ -1009,7 +1009,7 @@ function computeViolationTrend(
|
|
|
1009
1009
|
* Builds a human-readable explanation for the compliance result.
|
|
1010
1010
|
*/
|
|
1011
1011
|
|
|
1012
|
-
|
|
1012
|
+
|
|
1013
1013
|
function buildExplanation(
|
|
1014
1014
|
principleId: string,
|
|
1015
1015
|
applicableOpportunityCount: number,
|
|
@@ -278,7 +278,7 @@ function isTooGeneric(text: string): boolean {
|
|
|
278
278
|
* @param artifact - The validated artifact from arbiter (passed = true)
|
|
279
279
|
* @returns ExecutabilityResult
|
|
280
280
|
*/
|
|
281
|
-
|
|
281
|
+
|
|
282
282
|
export function validateExecutability(artifact: {
|
|
283
283
|
badDecision: string;
|
|
284
284
|
betterDecision: string;
|
|
@@ -144,7 +144,7 @@ export function deriveReasoningChain(assistantTurns: NocturnalAssistantTurn[]):
|
|
|
144
144
|
// Without thinking tags we cannot extract a genuine reasoning trace, so
|
|
145
145
|
// we fall back to 'low' rather than misleading the downstream pipeline
|
|
146
146
|
// with activation derived from non-thinking patterns in the response text.
|
|
147
|
-
|
|
147
|
+
|
|
148
148
|
let confidenceSignal: "high" | "medium" | "low";
|
|
149
149
|
if (thinkingContent.length === 0) {
|
|
150
150
|
confidenceSignal = 'low';
|
|
@@ -213,7 +213,7 @@ export function deriveDecisionPoints(
|
|
|
213
213
|
|
|
214
214
|
// Binary search: find rightmost assistant turn with createdAt < tcTime
|
|
215
215
|
const findBeforeTurn = (tcTime: number): NocturnalAssistantTurn | undefined => {
|
|
216
|
-
|
|
216
|
+
|
|
217
217
|
let lo = 0, hi = sortedTurns.length - 1, result: NocturnalAssistantTurn | undefined;
|
|
218
218
|
while (lo <= hi) {
|
|
219
219
|
const mid = (lo + hi) >>> 1;
|
|
@@ -236,9 +236,9 @@ export function deriveDecisionPoints(
|
|
|
236
236
|
: '';
|
|
237
237
|
|
|
238
238
|
// On failure, find next assistant turn after tool call
|
|
239
|
-
|
|
239
|
+
|
|
240
240
|
let afterReflection: string | undefined;
|
|
241
|
-
|
|
241
|
+
|
|
242
242
|
let confidenceDelta: number | undefined;
|
|
243
243
|
|
|
244
244
|
if (tc.outcome === 'failure') {
|
|
@@ -85,7 +85,7 @@ function extractHelperUsage(sourceCode: string): string[] {
|
|
|
85
85
|
);
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
|
|
89
89
|
function validateMeta(meta: unknown): RuleImplementationValidationFailure[] {
|
|
90
90
|
if (!meta || typeof meta !== 'object') {
|
|
91
91
|
return [
|
|
@@ -54,7 +54,7 @@ async function safeTail(filePath: string): Promise<string[]> {
|
|
|
54
54
|
try {
|
|
55
55
|
// Check existence and stats asynchronously
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
|
|
58
58
|
let stat: fs.Stats;
|
|
59
59
|
try {
|
|
60
60
|
stat = await fsPromises.stat(filePath);
|
|
@@ -237,7 +237,7 @@ export async function extractRecentConversation(
|
|
|
237
237
|
/**
|
|
238
238
|
* Extracts failed tool call context with argument correlation.
|
|
239
239
|
*/
|
|
240
|
-
|
|
240
|
+
|
|
241
241
|
export async function extractFailedToolContext(
|
|
242
242
|
sessionId: string,
|
|
243
243
|
agentId: string,
|
|
@@ -4,7 +4,7 @@ import { reconcilePDTasks } from './pd-task-reconciler.js';
|
|
|
4
4
|
export const PDTaskService: OpenClawPluginService = {
|
|
5
5
|
id: 'principles-disciple-task-manager',
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
|
|
8
8
|
async start(ctx: OpenClawPluginServiceContext): Promise<void> {
|
|
9
9
|
const {workspaceDir} = ctx;
|
|
10
10
|
if (!workspaceDir) {
|
|
@@ -38,7 +38,7 @@ export interface PrincipleLifecycleAssessment {
|
|
|
38
38
|
routeRecommendation: InternalizationRouteRecommendation;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
|
|
42
42
|
function createValueMetrics(
|
|
43
43
|
principle: PrincipleLifecycleEvidence,
|
|
44
44
|
adherence: PrincipleAdherenceResult,
|
|
@@ -63,7 +63,7 @@ export function createDefaultPrincipleState(principleId: string): PrincipleTrain
|
|
|
63
63
|
|
|
64
64
|
export function loadStore(stateDir: string): PrincipleTrainingStore {
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
|
|
67
67
|
return ledgerTrainingStore(stateDir);
|
|
68
68
|
}
|
|
69
69
|
|
|
@@ -77,7 +77,7 @@ export function saveStore(stateDir: string, store: PrincipleTrainingStore): void
|
|
|
77
77
|
|
|
78
78
|
export async function loadStoreAsync(stateDir: string): Promise<PrincipleTrainingStore> {
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
|
|
81
81
|
return ledgerTrainingStore(stateDir);
|
|
82
82
|
}
|
|
83
83
|
|
|
@@ -50,7 +50,7 @@ function trainingStateToTreePrinciple(
|
|
|
50
50
|
text: `Principle ${principleId}`, // Minimal text, will be enriched from PRINCIPLES.md if available
|
|
51
51
|
triggerPattern: '', // Unknown from legacy data
|
|
52
52
|
action: '', // Unknown from legacy data
|
|
53
|
-
|
|
53
|
+
|
|
54
54
|
status: mapInternalizationStatusToPrincipleStatus(state.internalizationStatus),
|
|
55
55
|
priority: 'P1', // Default priority
|
|
56
56
|
scope: 'general',
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
|
|
1
2
|
/* global NodeJS */
|
|
2
3
|
import * as fs from 'fs';
|
|
3
4
|
import { isRisky } from '../utils/io.js';
|
|
@@ -95,7 +96,7 @@ export function getTargetFileLineCount(absoluteFilePath: string): number | null
|
|
|
95
96
|
* @returns Maximum allowed lines (at least minLines, at most maxLines if provided)
|
|
96
97
|
*/
|
|
97
98
|
|
|
98
|
-
|
|
99
|
+
|
|
99
100
|
export function calculatePercentageThreshold(
|
|
100
101
|
targetLineCount: number,
|
|
101
102
|
percentage: number,
|
package/src/core/rule-host.ts
CHANGED
|
@@ -202,7 +202,7 @@ let _cachedWorkspace: string | null = null;
|
|
|
202
202
|
*
|
|
203
203
|
* @param workspaceDir Optional. If provided, loads from that workspace's THINKING_OS.md.
|
|
204
204
|
*/
|
|
205
|
-
|
|
205
|
+
|
|
206
206
|
export function listThinkingModels(workspaceDir?: string): ThinkingModelDefinition[] {
|
|
207
207
|
const cacheKey = workspaceDir ?? '__global__';
|
|
208
208
|
if (_cachedDefinitions && _cachedWorkspace === cacheKey) {
|
|
@@ -45,7 +45,7 @@ export function parseThinkingOsMd(content: string): ThinkingOsDirective[] {
|
|
|
45
45
|
// Match all <directive ...> ... </directive> blocks
|
|
46
46
|
const directiveRegex = /<directive\s+([^>]*)>([\s\S]*?)<\/directive>/gi;
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
|
|
49
49
|
let _match: RegExpExecArray | null = null;
|
|
50
50
|
|
|
51
51
|
while ((_match = directiveRegex.exec(content)) !== null) {
|
package/src/core/trajectory.ts
CHANGED
package/src/hooks/bash-risk.ts
CHANGED
|
@@ -39,7 +39,7 @@ export type BashRiskLevel = 'safe' | 'dangerous' | 'normal';
|
|
|
39
39
|
* @returns The risk level: 'safe', 'dangerous', or 'normal'
|
|
40
40
|
*/
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
|
|
43
43
|
export function analyzeBashCommand(
|
|
44
44
|
command: string,
|
|
45
45
|
safePatterns: string[],
|
|
@@ -154,7 +154,7 @@ export interface DynamicThresholdConfig {
|
|
|
154
154
|
* @param config - Configuration with large_change_lines and ep_tier_multipliers
|
|
155
155
|
* @returns The adjusted threshold (minimum 0)
|
|
156
156
|
*/
|
|
157
|
-
|
|
157
|
+
|
|
158
158
|
export function calculateDynamicThreshold(
|
|
159
159
|
baseThreshold: number,
|
|
160
160
|
epTier: number,
|
|
@@ -127,7 +127,7 @@ This is enforced by P-03 (精确匹配前验证原则).`;
|
|
|
127
127
|
* This enforces P-03 at the tool layer
|
|
128
128
|
*/
|
|
129
129
|
|
|
130
|
-
|
|
130
|
+
|
|
131
131
|
export function handleEditVerification(
|
|
132
132
|
event: PluginHookBeforeToolCallEvent,
|
|
133
133
|
wctx: WorkspaceContext,
|
|
@@ -157,7 +157,7 @@ export function handleEditVerification(
|
|
|
157
157
|
|
|
158
158
|
// 2. Resolve and read file
|
|
159
159
|
|
|
160
|
-
|
|
160
|
+
|
|
161
161
|
let absolutePath: string;
|
|
162
162
|
try {
|
|
163
163
|
absolutePath = wctx.resolve(filePath);
|
|
@@ -225,7 +225,7 @@ export function handleEditVerification(
|
|
|
225
225
|
|
|
226
226
|
// 3. Read current file content with improved error handling
|
|
227
227
|
|
|
228
|
-
|
|
228
|
+
|
|
229
229
|
let currentContent: string;
|
|
230
230
|
try {
|
|
231
231
|
currentContent = fs.readFileSync(absolutePath, 'utf-8');
|
package/src/hooks/gate.ts
CHANGED
|
@@ -135,7 +135,7 @@ export function handleBeforeToolCall(
|
|
|
135
135
|
|
|
136
136
|
if (mutationMatch) {
|
|
137
137
|
|
|
138
|
-
|
|
138
|
+
|
|
139
139
|
filePath = mutationMatch[1];
|
|
140
140
|
} else {
|
|
141
141
|
const hasRiskPath = profile.risk_paths.some(rp => command.includes(rp));
|
|
@@ -169,36 +169,36 @@ export function handleBeforeToolCall(
|
|
|
169
169
|
toolName: event.toolName,
|
|
170
170
|
normalizedPath: relPath,
|
|
171
171
|
|
|
172
|
-
|
|
172
|
+
|
|
173
173
|
paramsSummary: _extractParamsSummary(event.params),
|
|
174
174
|
},
|
|
175
175
|
workspace: {
|
|
176
176
|
isRiskPath: risky,
|
|
177
177
|
|
|
178
|
-
|
|
178
|
+
|
|
179
179
|
planStatus: _getPlanStatus(ctx.workspaceDir),
|
|
180
180
|
|
|
181
|
-
|
|
181
|
+
|
|
182
182
|
hasPlanFile: _hasPlanFile(ctx.workspaceDir),
|
|
183
183
|
},
|
|
184
184
|
session: {
|
|
185
185
|
sessionId: ctx.sessionId,
|
|
186
186
|
|
|
187
|
-
|
|
187
|
+
|
|
188
188
|
currentGfi: _getCurrentGfi(ctx.sessionId),
|
|
189
189
|
|
|
190
|
-
|
|
190
|
+
|
|
191
191
|
recentThinking: _hasRecentThinking(ctx.sessionId),
|
|
192
192
|
},
|
|
193
193
|
evolution: {
|
|
194
194
|
|
|
195
|
-
|
|
195
|
+
|
|
196
196
|
epTier: _getEpTier(wctx.workspaceDir),
|
|
197
197
|
},
|
|
198
198
|
derived: {
|
|
199
199
|
estimatedLineChanges: estimateLineChanges({ toolName: event.toolName, params: event.params }),
|
|
200
200
|
|
|
201
|
-
|
|
201
|
+
|
|
202
202
|
bashRisk: _getBashRisk(event, profile),
|
|
203
203
|
},
|
|
204
204
|
};
|
package/src/hooks/gfi-gate.ts
CHANGED
|
@@ -48,7 +48,7 @@ export interface GfiGateConfig {
|
|
|
48
48
|
* Internal helper to call the shared block helper with gfi-gate source tag.
|
|
49
49
|
*/
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
|
|
52
52
|
function block(
|
|
53
53
|
wctx: WorkspaceContext,
|
|
54
54
|
filePath: string,
|
|
@@ -70,7 +70,7 @@ function block(
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
|
|
74
74
|
export function checkGfiGate(
|
|
75
75
|
event: PluginHookBeforeToolCallEvent,
|
|
76
76
|
wctx: WorkspaceContext,
|
|
@@ -66,7 +66,7 @@ export type LifecycleIntent = 'promote' | 'disable' | 'rollback' | null;
|
|
|
66
66
|
* Detect implementation lifecycle intent from user message.
|
|
67
67
|
* Returns the detected intent type or null.
|
|
68
68
|
*/
|
|
69
|
-
|
|
69
|
+
|
|
70
70
|
export function detectLifecycleIntent(message: string): LifecycleIntent {
|
|
71
71
|
// Check promote patterns
|
|
72
72
|
for (const p of PROMOTE_PATTERNS_EN) {
|
package/src/hooks/pain.ts
CHANGED
|
@@ -131,7 +131,7 @@ export function handleAfterToolCall(
|
|
|
131
131
|
|
|
132
132
|
// ── Trust Engine: Record failure ──
|
|
133
133
|
|
|
134
|
-
|
|
134
|
+
|
|
135
135
|
const errorType = extractErrorType(event.error || errorText);
|
|
136
136
|
const filePath = params.file_path || params.path || params.file;
|
|
137
137
|
const relPath = typeof filePath === 'string' ? normalizePath(filePath, effectiveWorkspaceDir) : 'unknown';
|
|
@@ -194,7 +194,7 @@ export function handleAfterToolCall(
|
|
|
194
194
|
const session = getSession(sessionId);
|
|
195
195
|
const toolFailureGfi = session?.gfiBySource?.tool_failure || 0;
|
|
196
196
|
|
|
197
|
-
|
|
197
|
+
|
|
198
198
|
let resetState: SessionState;
|
|
199
199
|
if (toolFailureGfi > 0) {
|
|
200
200
|
// Reduce tool_failure source by 50% (relief from successful tool execution)
|
|
@@ -87,7 +87,7 @@ export function buildEvolutionGateReason(
|
|
|
87
87
|
* Internal helper to call the shared block helper with progressive-trust-gate source tag.
|
|
88
88
|
*/
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+
|
|
91
91
|
function block(
|
|
92
92
|
filePath: string,
|
|
93
93
|
reason: string,
|
|
@@ -121,7 +121,7 @@ function block(
|
|
|
121
121
|
* @returns PluginHookBeforeToolCallResult to block, or undefined to allow
|
|
122
122
|
*/
|
|
123
123
|
|
|
124
|
-
|
|
124
|
+
|
|
125
125
|
export function checkProgressiveTrustGate(
|
|
126
126
|
event: PluginHookBeforeToolCallEvent,
|
|
127
127
|
wctx: WorkspaceContext,
|
|
@@ -154,7 +154,7 @@ export function checkProgressiveTrustGate(
|
|
|
154
154
|
|
|
155
155
|
const currentTier = epDecision.currentTier ?? 1;
|
|
156
156
|
|
|
157
|
-
|
|
157
|
+
|
|
158
158
|
const tierName = getTierName(currentTier);
|
|
159
159
|
|
|
160
160
|
logger.info?.(`[PD_GATE] EP Gate: Tier ${currentTier} (${tierName}), Tool: ${event.toolName}, Risk: ${risky}, Allowed: ${epDecision.allowed}`);
|
package/src/hooks/prompt.ts
CHANGED
|
@@ -41,7 +41,7 @@ export interface ThinkingCheckpointConfig {
|
|
|
41
41
|
* @returns Block result if thinking required, undefined otherwise
|
|
42
42
|
*/
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
|
|
45
45
|
export function checkThinkingCheckpoint(
|
|
46
46
|
event: PluginHookBeforeToolCallEvent,
|
|
47
47
|
config: ThinkingCheckpointConfig,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
|
|
1
2
|
import Database from 'better-sqlite3';
|
|
2
3
|
import fs from 'fs';
|
|
3
4
|
import path from 'path';
|
|
@@ -200,7 +201,7 @@ export class CentralDatabase {
|
|
|
200
201
|
/**
|
|
201
202
|
* Sync data from a single workspace into the central database
|
|
202
203
|
*/
|
|
203
|
-
|
|
204
|
+
|
|
204
205
|
syncWorkspace(workspaceName: string): number {
|
|
205
206
|
const workspace = this.workspaces.find(w => w.name === workspaceName);
|
|
206
207
|
if (!workspace) {
|
|
@@ -714,7 +715,7 @@ export class CentralDatabase {
|
|
|
714
715
|
syncEnabled: c.sync_enabled === 1,
|
|
715
716
|
}));
|
|
716
717
|
}
|
|
717
|
-
|
|
718
|
+
|
|
718
719
|
|
|
719
720
|
updateWorkspaceConfig(
|
|
720
721
|
workspaceName: string,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
1
2
|
import { getCentralDatabase } from './central-database.js';
|
|
2
3
|
import { HealthQueryService } from './health-query-service.js';
|
|
3
4
|
|
|
@@ -18,7 +19,7 @@ export interface CentralHealthResponse {
|
|
|
18
19
|
*/
|
|
19
20
|
export class CentralHealthService {
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
|
|
22
23
|
getAllWorkspaceHealth(): CentralHealthResponse {
|
|
23
24
|
const centralDb = getCentralDatabase();
|
|
24
25
|
const workspaces: WorkspaceHealthEntry[] = [];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
1
2
|
import { getCentralDatabase, type CentralDatabase } from './central-database.js';
|
|
2
3
|
import { getThinkingModelDefinitions } from '../core/thinking-models.js';
|
|
3
4
|
import type { OverviewResponse } from './control-ui-query-service.js';
|
|
@@ -24,7 +25,7 @@ export class CentralOverviewService {
|
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
|
|
27
|
-
|
|
28
|
+
|
|
28
29
|
dispose(): void {
|
|
29
30
|
// Do NOT dispose centralDb — it's a singleton shared across all requests.
|
|
30
31
|
// Individual services that open per-request connections (e.g. HealthQueryService)
|
|
@@ -62,7 +63,7 @@ export class CentralOverviewService {
|
|
|
62
63
|
|
|
63
64
|
// D-06: sampleQueue.counters from aggregated_correction_samples GROUP BY review_status
|
|
64
65
|
|
|
65
|
-
|
|
66
|
+
|
|
66
67
|
let sampleCounters: Record<string, number> = {};
|
|
67
68
|
try {
|
|
68
69
|
sampleCounters = this.centralDb.getSampleCountersByStatus();
|
|
@@ -255,7 +255,7 @@ export class ControlUiQueryService {
|
|
|
255
255
|
this.uiDb.dispose();
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
-
|
|
258
|
+
|
|
259
259
|
getOverview(days = 30): OverviewResponse {
|
|
260
260
|
const stats = this.trajectory.getDataStats();
|
|
261
261
|
const regressionRows = this.uiDb.all<{
|
|
@@ -400,7 +400,7 @@ export class ControlUiQueryService {
|
|
|
400
400
|
};
|
|
401
401
|
}
|
|
402
402
|
|
|
403
|
-
|
|
403
|
+
|
|
404
404
|
listSamples(filters: SampleListFilters = {}): SamplesResponse {
|
|
405
405
|
const page = Math.max(1, Number(filters.page ?? 1));
|
|
406
406
|
const pageSize = clampPageSize(filters.pageSize);
|
|
@@ -137,7 +137,7 @@ function countAllHooks(filePath: string): Record<string, number> {
|
|
|
137
137
|
* @param openclawDir - Base OpenClaw directory (e.g., ~/.openclaw)
|
|
138
138
|
* @param expectedToolHooks - Hook names that should appear in the primary workspace
|
|
139
139
|
*/
|
|
140
|
-
|
|
140
|
+
|
|
141
141
|
export async function auditEventLogs(
|
|
142
142
|
openclawDir: string,
|
|
143
143
|
expectedToolHooks: string[] = ['before_tool_call', 'after_tool_call'],
|
|
@@ -210,7 +210,7 @@ export async function auditEventLogs(
|
|
|
210
210
|
/**
|
|
211
211
|
* Format audit report for display.
|
|
212
212
|
*/
|
|
213
|
-
|
|
213
|
+
|
|
214
214
|
export function formatAuditReport(report: AuditReport): string {
|
|
215
215
|
const lines: string[] = [];
|
|
216
216
|
|
|
@@ -155,7 +155,7 @@ export class EvolutionQueryService {
|
|
|
155
155
|
* 注意:不关闭 trajectory,因为它是单例由 TrajectoryRegistry 管理
|
|
156
156
|
*/
|
|
157
157
|
|
|
158
|
-
|
|
158
|
+
|
|
159
159
|
dispose(): void {
|
|
160
160
|
// EvolutionQueryService 不拥有 trajectory,所以不关闭它
|
|
161
161
|
// trajectory 是由 TrajectoryRegistry 管理的单例
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
1
3
|
/* global NodeJS */
|
|
2
4
|
|
|
3
5
|
import * as fs from 'fs';
|
|
@@ -1335,15 +1337,15 @@ async function processEvolutionQueue(wctx: WorkspaceContext, logger: PluginLogge
|
|
|
1335
1337
|
|
|
1336
1338
|
try {
|
|
1337
1339
|
payload = lastEvent?.payload ?? {};
|
|
1338
|
-
|
|
1340
|
+
|
|
1339
1341
|
if ((payload as any).skipReason) {
|
|
1340
|
-
|
|
1342
|
+
|
|
1341
1343
|
detailedError += ` (skipReason: ${(payload as any).skipReason})`;
|
|
1342
1344
|
|
|
1343
1345
|
}
|
|
1344
|
-
|
|
1346
|
+
|
|
1345
1347
|
if ((payload as any).failures && Array.isArray((payload as any).failures) && (payload as any).failures.length > 0) {
|
|
1346
|
-
|
|
1348
|
+
|
|
1347
1349
|
detailedError += ` | failures: ${((payload as any).failures as string[]).slice(0, 3).join(', ')}`;
|
|
1348
1350
|
}
|
|
1349
1351
|
} catch { /* ignore parse errors */ }
|
|
@@ -1359,7 +1361,7 @@ async function processEvolutionQueue(wctx: WorkspaceContext, logger: PluginLogge
|
|
|
1359
1361
|
sleepOutcomes.push({ taskKind: 'sleep_reflection', succeeded: true });
|
|
1360
1362
|
|
|
1361
1363
|
logger?.warn?.(`[PD:EvolutionWorker] sleep_reflection task ${sleepTask.id} background runtime unavailable, using stub fallback: ${errorReason}`);
|
|
1362
|
-
|
|
1364
|
+
|
|
1363
1365
|
} else if ((payload as any).skipReason === 'no_violating_sessions') {
|
|
1364
1366
|
// #244: No meaningful violations found (thin filter) → skip without failure
|
|
1365
1367
|
sleepTask.status = 'completed';
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
1
2
|
import * as fs from 'fs';
|
|
2
3
|
import * as path from 'path';
|
|
3
4
|
import { readPainFlagData } from '../core/pain.js';
|
|
@@ -554,7 +555,7 @@ export class HealthQueryService {
|
|
|
554
555
|
const streamPath = resolvePdPath(this.workspaceDir, 'EVOLUTION_STREAM');
|
|
555
556
|
if (!fs.existsSync(streamPath)) return [];
|
|
556
557
|
|
|
557
|
-
|
|
558
|
+
|
|
558
559
|
let lines: string[];
|
|
559
560
|
try {
|
|
560
561
|
const raw = fs.readFileSync(streamPath, 'utf8').trim();
|
|
@@ -567,7 +568,7 @@ export class HealthQueryService {
|
|
|
567
568
|
const records: RecentPrincipleChange[] = [];
|
|
568
569
|
for (const line of lines) {
|
|
569
570
|
|
|
570
|
-
|
|
571
|
+
|
|
571
572
|
let event: EvolutionStreamRecord | null;
|
|
572
573
|
try {
|
|
573
574
|
event = JSON.parse(line) as EvolutionStreamRecord;
|
|
@@ -788,7 +789,7 @@ export class HealthQueryService {
|
|
|
788
789
|
|
|
789
790
|
|
|
790
791
|
|
|
791
|
-
|
|
792
|
+
|
|
792
793
|
private getEventDedupKey(entry: EventLogEntry): string {
|
|
793
794
|
const eventId = typeof entry.data?.eventId === 'string' ? entry.data.eventId : null;
|
|
794
795
|
if (eventId) {
|
|
@@ -860,7 +861,7 @@ export class HealthQueryService {
|
|
|
860
861
|
|
|
861
862
|
|
|
862
863
|
|
|
863
|
-
|
|
864
|
+
|
|
864
865
|
private resolveGateType(row: GateBlockRow): string {
|
|
865
866
|
if (typeof row.gate_type === 'string' && row.gate_type.trim().length > 0) {
|
|
866
867
|
return row.gate_type;
|
|
@@ -885,7 +886,7 @@ export class HealthQueryService {
|
|
|
885
886
|
}
|
|
886
887
|
|
|
887
888
|
|
|
888
|
-
|
|
889
|
+
|
|
889
890
|
private scoreToStatus(score: number): string {
|
|
890
891
|
if (score >= 70) return 'healthy';
|
|
891
892
|
if (score >= 40) return 'warning';
|
|
@@ -893,7 +894,7 @@ export class HealthQueryService {
|
|
|
893
894
|
}
|
|
894
895
|
|
|
895
896
|
|
|
896
|
-
|
|
897
|
+
|
|
897
898
|
private evolutionToStatus(tier: string, points: number): string {
|
|
898
899
|
const lower = tier.toLowerCase();
|
|
899
900
|
if (lower === 'forest' || lower === 'tree') return 'healthy';
|
|
@@ -902,7 +903,7 @@ export class HealthQueryService {
|
|
|
902
903
|
}
|
|
903
904
|
|
|
904
905
|
|
|
905
|
-
|
|
906
|
+
|
|
906
907
|
private safeListFiles(dirPath: string, predicate: (_name: string) => boolean): string[] {
|
|
907
908
|
if (!fs.existsSync(dirPath)) return [];
|
|
908
909
|
try {
|
|
@@ -915,7 +916,7 @@ export class HealthQueryService {
|
|
|
915
916
|
}
|
|
916
917
|
|
|
917
918
|
|
|
918
|
-
|
|
919
|
+
|
|
919
920
|
private readJsonFile<T>(filePath: string, fallback: T): T {
|
|
920
921
|
if (!fs.existsSync(filePath)) return fallback;
|
|
921
922
|
try {
|
|
@@ -926,13 +927,13 @@ export class HealthQueryService {
|
|
|
926
927
|
}
|
|
927
928
|
|
|
928
929
|
|
|
929
|
-
|
|
930
|
+
|
|
930
931
|
private asNumber(value: unknown, fallback: number): number {
|
|
931
932
|
return Number.isFinite(value) ? Number(value) : fallback;
|
|
932
933
|
}
|
|
933
934
|
|
|
934
935
|
|
|
935
|
-
|
|
936
|
+
|
|
936
937
|
private asNullableNumber(value: unknown): number | null {
|
|
937
938
|
if (Number.isFinite(value)) return Number(value);
|
|
938
939
|
if (typeof value === 'string' && value.trim().length > 0) {
|
|
@@ -36,7 +36,7 @@ export class MonitoringQueryService {
|
|
|
36
36
|
const now = Date.now();
|
|
37
37
|
const workflowsWithStuckDetection = workflows.map(wf => {
|
|
38
38
|
// Parse metadata for timeout configuration
|
|
39
|
-
|
|
39
|
+
|
|
40
40
|
const metadata = parseWorkflowMetadata(wf.metadata_json);
|
|
41
41
|
const timeoutMs = metadata.timeoutMs ?? 15 * 60 * 1000; // Default 15 minutes
|
|
42
42
|
|
|
@@ -85,10 +85,10 @@ export class MonitoringQueryService {
|
|
|
85
85
|
|
|
86
86
|
// Determine status
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
|
|
89
89
|
let status: 'pending' | 'running' | 'completed' | 'failed';
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
|
|
92
92
|
let reason: string | undefined;
|
|
93
93
|
|
|
94
94
|
if (!startEvent) {
|
|
@@ -110,7 +110,7 @@ export class MonitoringQueryService {
|
|
|
110
110
|
|
|
111
111
|
// Calculate duration if stage started and completed/failed
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
|
|
114
114
|
let duration: number | undefined;
|
|
115
115
|
if (startEvent && (completeEvent || failedEvent)) {
|
|
116
116
|
const endEvent = completeEvent || failedEvent;
|
|
@@ -288,7 +288,7 @@ export class NocturnalTargetSelector {
|
|
|
288
288
|
};
|
|
289
289
|
|
|
290
290
|
|
|
291
|
-
|
|
291
|
+
|
|
292
292
|
constructor(
|
|
293
293
|
workspaceDir: string,
|
|
294
294
|
stateDir: string,
|
|
@@ -533,7 +533,7 @@ export class NocturnalTargetSelector {
|
|
|
533
533
|
* This is a convenience wrapper for the common case.
|
|
534
534
|
*/
|
|
535
535
|
|
|
536
|
-
|
|
536
|
+
|
|
537
537
|
export function selectNocturnalTarget(
|
|
538
538
|
workspaceDir: string,
|
|
539
539
|
stateDir: string,
|
|
@@ -420,7 +420,7 @@ export class RuntimeSummaryService {
|
|
|
420
420
|
* Queue is the only authoritative execution truth source.
|
|
421
421
|
*/
|
|
422
422
|
|
|
423
|
-
|
|
423
|
+
|
|
424
424
|
private static buildDirectiveSummary(
|
|
425
425
|
queue: QueueItem[] | null,
|
|
426
426
|
directive: DirectiveFile | null,
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Callers should suppress warnings for these errors — they are not real failures.
|
|
7
7
|
*/
|
|
8
|
-
|
|
8
|
+
|
|
9
9
|
export function isExpectedSubagentError(err: unknown): boolean {
|
|
10
10
|
const msg = String(err);
|
|
11
11
|
return (
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
1
2
|
import Database from 'better-sqlite3';
|
|
2
3
|
import * as fs from 'fs';
|
|
3
4
|
import * as path from 'path';
|
|
@@ -233,7 +234,7 @@ export class WorkflowStore {
|
|
|
233
234
|
}
|
|
234
235
|
|
|
235
236
|
|
|
236
|
-
|
|
237
|
+
|
|
237
238
|
recordEvent(
|
|
238
239
|
workflowId: string,
|
|
239
240
|
eventType: string,
|
|
@@ -270,7 +271,7 @@ export class WorkflowStore {
|
|
|
270
271
|
* same idempotency_key already exists, this is a no-op (idempotent).
|
|
271
272
|
*/
|
|
272
273
|
|
|
273
|
-
|
|
274
|
+
|
|
274
275
|
recordStageOutput(
|
|
275
276
|
workflowId: string,
|
|
276
277
|
stage: 'dreamer' | 'philosopher',
|
package/src/tools/model-index.ts
CHANGED
|
@@ -62,7 +62,7 @@ export function loadModelIndex(
|
|
|
62
62
|
const customConfig = loadCustomConfig(wctx);
|
|
63
63
|
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
|
|
66
66
|
let modelsDir: string;
|
|
67
67
|
if (customConfig?.modelsDir) {
|
|
68
68
|
modelsDir = path.isAbsolute(customConfig.modelsDir)
|