principles-disciple 1.144.0 → 1.145.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/dist/commands/evolution-status.js +0 -2
- package/dist/core/pain.d.ts +0 -62
- package/dist/core/pain.js +0 -143
- package/dist/service/runtime-summary-service.d.ts +0 -2
- package/dist/service/runtime-summary-service.js +0 -4
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/scripts/validate-live-path.ts +0 -18
|
@@ -38,7 +38,6 @@ function buildEnglishOutput(workspaceDir, sessionId, warnings, stats, summary, r
|
|
|
38
38
|
'Control Plane',
|
|
39
39
|
`- Session GFI: current ${formatNumber(summary.gfi.current)}, peak ${formatNumber(summary.gfi.peak)} (${summary.gfi.dataQuality})`,
|
|
40
40
|
`- GFI Sources: ${formatSources(summary.gfi.sources)}`,
|
|
41
|
-
`- Pain Flag: ${summary.pain.activeFlag ? 'active' : 'inactive'}${summary.pain.activeFlagSource ? ` (${summary.pain.activeFlagSource})` : ''}`,
|
|
42
41
|
`- Last Pain Signal: ${summary.pain.lastSignal ? `${summary.pain.lastSignal.source}${summary.pain.lastSignal.reason ? ` - ${summary.pain.lastSignal.reason}` : ''}` : '--'}`,
|
|
43
42
|
`- Gate Events: blocks ${formatNumber(summary.gate.recentBlocks)}, bypasses ${formatNumber(summary.gate.recentBypasses)} (${summary.gate.dataQuality})`,
|
|
44
43
|
'',
|
|
@@ -97,7 +96,6 @@ function buildChineseOutput(workspaceDir, sessionId, warnings, stats, summary, r
|
|
|
97
96
|
'控制面',
|
|
98
97
|
`- 会话 GFI: 当前 ${formatNumber(summary.gfi.current)},峰值 ${formatNumber(summary.gfi.peak)} (${summary.gfi.dataQuality})`,
|
|
99
98
|
`- GFI 来源: ${formatSources(summary.gfi.sources)}`,
|
|
100
|
-
`- Pain Flag: ${summary.pain.activeFlag ? 'active' : 'inactive'}${summary.pain.activeFlagSource ? ` (${summary.pain.activeFlagSource})` : ''}`,
|
|
101
99
|
`- 最近 Pain 信号: ${summary.pain.lastSignal ? `${summary.pain.lastSignal.source}${summary.pain.lastSignal.reason ? ` - ${summary.pain.lastSignal.reason}` : ''}` : '--'}`,
|
|
102
100
|
`- Gate 事件: block ${formatNumber(summary.gate.recentBlocks)},bypass ${formatNumber(summary.gate.recentBypasses)} (${summary.gate.dataQuality})`,
|
|
103
101
|
'',
|
package/dist/core/pain.d.ts
CHANGED
|
@@ -1,67 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Required fields — every pain flag MUST have these.
|
|
3
|
-
*/
|
|
4
|
-
export interface PainFlagData {
|
|
5
|
-
/** What triggered this pain signal (e.g., tool_failure, human_intervention, intercept_extraction) */
|
|
6
|
-
source: string;
|
|
7
|
-
/** Pain score 0-100 */
|
|
8
|
-
score: string;
|
|
9
|
-
/** ISO 8601 timestamp */
|
|
10
|
-
time: string;
|
|
11
|
-
/** Human-readable reason / error description */
|
|
12
|
-
reason: string;
|
|
13
|
-
/** Session ID — identifies which conversation this happened in */
|
|
14
|
-
session_id: string;
|
|
15
|
-
/** Agent ID — identifies which agent (main, builder, diagnostician, etc.) */
|
|
16
|
-
agent_id: string;
|
|
17
|
-
/** Whether this involves risky operation ('true' / 'false') */
|
|
18
|
-
is_risky: string;
|
|
19
|
-
/** Correlation trace ID (for linking events across the pipeline) */
|
|
20
|
-
trace_id: string;
|
|
21
|
-
/** Preview of the text that triggered this pain */
|
|
22
|
-
trigger_text_preview: string;
|
|
23
|
-
/** Trajectory pain_events row ID (set by recordPainEvent) */
|
|
24
|
-
pain_event_id?: string;
|
|
25
|
-
}
|
|
26
|
-
export interface PainFlagContractResult {
|
|
27
|
-
status: 'missing' | 'valid' | 'invalid';
|
|
28
|
-
format: 'missing' | 'empty' | 'kv' | 'json' | 'invalid_json';
|
|
29
|
-
data: Record<string, string>;
|
|
30
|
-
missingFields: string[];
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Builds legacy pain flag data for compatibility tests and historical readers.
|
|
34
|
-
* Do not use this to create new Runtime V2 diagnosis requests.
|
|
35
|
-
*/
|
|
36
|
-
export declare function buildPainFlag(input: {
|
|
37
|
-
source: string;
|
|
38
|
-
score: string;
|
|
39
|
-
time?: string;
|
|
40
|
-
reason: string;
|
|
41
|
-
session_id?: string;
|
|
42
|
-
agent_id?: string;
|
|
43
|
-
is_risky?: boolean;
|
|
44
|
-
trace_id?: string;
|
|
45
|
-
trigger_text_preview?: string;
|
|
46
|
-
pain_event_id?: string;
|
|
47
|
-
}): PainFlagData;
|
|
48
|
-
/**
|
|
49
|
-
* Validates a pain flag read from disk.
|
|
50
|
-
* Returns list of missing required fields — empty string means all present.
|
|
51
|
-
*/
|
|
52
|
-
export declare function validatePainFlag(data: Record<string, string>): string[];
|
|
53
1
|
export declare function computePainScore(rc: number, isSpiral: boolean, missingTestCommand: boolean, softScore: number, projectDir?: string): number;
|
|
54
2
|
export declare function painSeverityLabel(painScore: number, isSpiral?: boolean, projectDir?: string): string;
|
|
55
|
-
/**
|
|
56
|
-
* Reads and validates the legacy pain flag file.
|
|
57
|
-
*
|
|
58
|
-
* - If file doesn't exist → returns {}
|
|
59
|
-
* - If file is JSON format → converts to KV in memory only
|
|
60
|
-
* - If file is KV format → validates required fields, logs warning if missing
|
|
61
|
-
* - If file has unknown fields → silently ignores them (forward-compatible)
|
|
62
|
-
*/
|
|
63
|
-
export declare function readPainFlagData(projectDir: string): Record<string, string>;
|
|
64
|
-
export declare function readPainFlagContract(projectDir: string): PainFlagContractResult;
|
|
65
3
|
/**
|
|
66
4
|
* Track principle value metrics when a pain signal is written.
|
|
67
5
|
* This is observation-only — it does NOT affect the pain flag write flow.
|
package/dist/core/pain.js
CHANGED
|
@@ -1,44 +1,5 @@
|
|
|
1
|
-
import * as fs from 'fs';
|
|
2
|
-
import { parseKvLines } from '../utils/io.js';
|
|
3
1
|
import { resolvePdPath } from './paths.js';
|
|
4
2
|
import { ConfigService } from './config-service.js';
|
|
5
|
-
import { SystemLogger } from './system-logger.js';
|
|
6
|
-
/**
|
|
7
|
-
* Builds legacy pain flag data for compatibility tests and historical readers.
|
|
8
|
-
* Do not use this to create new Runtime V2 diagnosis requests.
|
|
9
|
-
*/
|
|
10
|
-
export function buildPainFlag(input) {
|
|
11
|
-
// Omit optional fields when not provided — prevents writing empty lines to disk
|
|
12
|
-
// which causes agent confusion (SKILL.md vs reality drift)
|
|
13
|
-
return {
|
|
14
|
-
source: input.source,
|
|
15
|
-
score: input.score,
|
|
16
|
-
time: input.time || new Date().toISOString(),
|
|
17
|
-
reason: input.reason,
|
|
18
|
-
session_id: input.session_id ?? '',
|
|
19
|
-
agent_id: input.agent_id ?? '',
|
|
20
|
-
is_risky: input.is_risky ? 'true' : 'false',
|
|
21
|
-
trace_id: input.trace_id ?? '',
|
|
22
|
-
trigger_text_preview: input.trigger_text_preview ?? '',
|
|
23
|
-
pain_event_id: input.pain_event_id,
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Validates a pain flag read from disk.
|
|
28
|
-
* Returns list of missing required fields — empty string means all present.
|
|
29
|
-
*/
|
|
30
|
-
export function validatePainFlag(data) {
|
|
31
|
-
const missing = [];
|
|
32
|
-
// Only source/score/time/reason are truly required — session_id/agent_id
|
|
33
|
-
// may be empty in automated contexts (heartbeat, background workers)
|
|
34
|
-
const required = ['source', 'score', 'time', 'reason'];
|
|
35
|
-
for (const field of required) {
|
|
36
|
-
if (!data[field] || data[field].trim() === '') {
|
|
37
|
-
missing.push(field);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
return missing;
|
|
41
|
-
}
|
|
42
3
|
export function computePainScore(rc, isSpiral, missingTestCommand, softScore, projectDir) {
|
|
43
4
|
let score = Math.max(0, softScore || 0);
|
|
44
5
|
const stateDir = projectDir ? resolvePdPath(projectDir, 'STATE_DIR') : undefined;
|
|
@@ -83,110 +44,6 @@ export function painSeverityLabel(painScore, isSpiral = false, projectDir) {
|
|
|
83
44
|
return "info";
|
|
84
45
|
}
|
|
85
46
|
}
|
|
86
|
-
/**
|
|
87
|
-
* Converts a JSON pain flag object to KV format.
|
|
88
|
-
*/
|
|
89
|
-
function convertJsonToKv(json) {
|
|
90
|
-
const kvData = {};
|
|
91
|
-
const fieldMap = {
|
|
92
|
-
source: 'source',
|
|
93
|
-
score: 'score',
|
|
94
|
-
time: 'time',
|
|
95
|
-
timestamp: 'time',
|
|
96
|
-
reason: 'reason',
|
|
97
|
-
session_id: 'session_id',
|
|
98
|
-
sessionId: 'session_id',
|
|
99
|
-
agent_id: 'agent_id',
|
|
100
|
-
agentId: 'agent_id',
|
|
101
|
-
is_risky: 'is_risky',
|
|
102
|
-
isRisky: 'is_risky',
|
|
103
|
-
severity: 'severity',
|
|
104
|
-
painId: 'pain_id',
|
|
105
|
-
};
|
|
106
|
-
for (const [jsonKey, kvKey] of Object.entries(fieldMap)) {
|
|
107
|
-
if (json[jsonKey] !== undefined) {
|
|
108
|
-
kvData[kvKey] = String(json[jsonKey]);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
for (const [key, value] of Object.entries(json)) {
|
|
112
|
-
if (fieldMap[key] === undefined && value !== undefined && value !== null) {
|
|
113
|
-
kvData[key] = String(value);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
return kvData;
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* Reads and validates the legacy pain flag file.
|
|
120
|
-
*
|
|
121
|
-
* - If file doesn't exist → returns {}
|
|
122
|
-
* - If file is JSON format → converts to KV in memory only
|
|
123
|
-
* - If file is KV format → validates required fields, logs warning if missing
|
|
124
|
-
* - If file has unknown fields → silently ignores them (forward-compatible)
|
|
125
|
-
*/
|
|
126
|
-
export function readPainFlagData(projectDir) {
|
|
127
|
-
const painFlagPath = resolvePdPath(projectDir, 'PAIN_FLAG');
|
|
128
|
-
try {
|
|
129
|
-
if (!fs.existsSync(painFlagPath)) {
|
|
130
|
-
return {};
|
|
131
|
-
}
|
|
132
|
-
const content = fs.readFileSync(painFlagPath, "utf-8").trim();
|
|
133
|
-
if (!content) {
|
|
134
|
-
return {};
|
|
135
|
-
}
|
|
136
|
-
// Detect JSON format. Legacy compatibility only: parse in memory and do not
|
|
137
|
-
// rewrite .pain_flag, because Runtime V2 must not create or repair this file.
|
|
138
|
-
if (content.startsWith('{')) {
|
|
139
|
-
let json;
|
|
140
|
-
try {
|
|
141
|
-
json = JSON.parse(content);
|
|
142
|
-
}
|
|
143
|
-
catch {
|
|
144
|
-
SystemLogger.log(projectDir, 'PAIN_FLAG_CORRUPT', 'Pain flag file contains invalid JSON');
|
|
145
|
-
return {};
|
|
146
|
-
}
|
|
147
|
-
const kvData = convertJsonToKv(json);
|
|
148
|
-
SystemLogger.log(projectDir, 'PAIN_FLAG_LEGACY_JSON_READ', `Read legacy JSON pain flag in memory (${Object.keys(json).length} fields)`);
|
|
149
|
-
return kvData;
|
|
150
|
-
}
|
|
151
|
-
// KV format — parse and validate
|
|
152
|
-
const data = parseKvLines(content);
|
|
153
|
-
const missing = validatePainFlag(data);
|
|
154
|
-
if (missing.length > 0) {
|
|
155
|
-
SystemLogger.log(projectDir, 'PAIN_FLAG_INCOMPLETE', `Pain flag missing required fields: ${missing.join(', ')}`);
|
|
156
|
-
}
|
|
157
|
-
return data;
|
|
158
|
-
}
|
|
159
|
-
catch (e) {
|
|
160
|
-
SystemLogger.log(projectDir, 'PAIN_FLAG_READ_ERROR', `Failed to read pain flag: ${String(e)}`);
|
|
161
|
-
return {};
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
export function readPainFlagContract(projectDir) {
|
|
165
|
-
const data = readPainFlagData(projectDir);
|
|
166
|
-
if (Object.keys(data).length === 0) {
|
|
167
|
-
const painFlagPath = resolvePdPath(projectDir, 'PAIN_FLAG');
|
|
168
|
-
if (!fs.existsSync(painFlagPath)) {
|
|
169
|
-
return { status: 'missing', format: 'missing', data: {}, missingFields: [] };
|
|
170
|
-
}
|
|
171
|
-
const raw = fs.readFileSync(painFlagPath, 'utf-8').trim();
|
|
172
|
-
if (!raw) {
|
|
173
|
-
return { status: 'missing', format: 'empty', data: {}, missingFields: [] };
|
|
174
|
-
}
|
|
175
|
-
return {
|
|
176
|
-
status: 'invalid',
|
|
177
|
-
format: raw.startsWith('{') ? 'invalid_json' : 'kv',
|
|
178
|
-
data: {},
|
|
179
|
-
missingFields: ['unparseable'],
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
const missing = validatePainFlag(data);
|
|
183
|
-
return {
|
|
184
|
-
status: missing.length > 0 ? 'invalid' : 'valid',
|
|
185
|
-
format: 'kv',
|
|
186
|
-
data,
|
|
187
|
-
missingFields: missing,
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
47
|
/**
|
|
191
48
|
* Track principle value metrics when a pain signal is written.
|
|
192
49
|
* This is observation-only — it does NOT affect the pain flag write flow.
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import Database from 'better-sqlite3';
|
|
3
3
|
import * as path from 'path';
|
|
4
|
-
import { readPainFlagData } from '../core/pain.js';
|
|
5
4
|
import { listSessions } from '../core/session-tracker.js';
|
|
6
5
|
import { WorkspaceContext } from '../core/workspace-context.js';
|
|
7
6
|
import { evaluatePhase3Inputs } from './phase3-input-filter.js';
|
|
@@ -130,7 +129,6 @@ export class RuntimeSummaryService {
|
|
|
130
129
|
const directive = this.readJsonFile(wctx.resolve('EVOLUTION_DIRECTIVE'), warnings, false);
|
|
131
130
|
const queueStats = this.buildQueueStats(queue);
|
|
132
131
|
const directiveSummary = this.buildDirectiveSummary(queue, directive, generatedAt, warnings);
|
|
133
|
-
const painFlag = readPainFlagData(workspaceDir);
|
|
134
132
|
const painCandidates = this.readJsonFile(wctx.resolve('PAIN_CANDIDATES'), warnings, false);
|
|
135
133
|
const phase3Inputs = evaluatePhase3Inputs(queue ?? []);
|
|
136
134
|
const lastPainSignal = this.findLastPainSignal(events, selectedSessionId);
|
|
@@ -239,8 +237,6 @@ export class RuntimeSummaryService {
|
|
|
239
237
|
eligibilitySource: 'runtime_truth',
|
|
240
238
|
},
|
|
241
239
|
pain: {
|
|
242
|
-
activeFlag: Object.keys(painFlag).length > 0,
|
|
243
|
-
activeFlagSource: painFlag.source || null,
|
|
244
240
|
candidates: painCandidates?.candidates && typeof painCandidates.candidates === 'object'
|
|
245
241
|
? Object.keys(painCandidates.candidates).length
|
|
246
242
|
: null,
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "principles-disciple",
|
|
3
3
|
"name": "Principles Disciple",
|
|
4
4
|
"description": "Evolutionary programming agent framework with strategic guardrails and reflection loops.",
|
|
5
|
-
"version": "1.
|
|
5
|
+
"version": "1.145.0",
|
|
6
6
|
"activation": {
|
|
7
7
|
"onCapabilities": [
|
|
8
8
|
"hook"
|
package/package.json
CHANGED
|
@@ -37,7 +37,6 @@ const STATE_DIR = path.join(WORKSPACE_DIR, '.state');
|
|
|
37
37
|
const QUEUE_PATH = path.join(STATE_DIR, 'EVOLUTION_QUEUE');
|
|
38
38
|
const LEDGER_PATH = path.join(STATE_DIR, 'principle_training_state.json');
|
|
39
39
|
const DB_PATH = path.join(STATE_DIR, 'subagent_workflows.db');
|
|
40
|
-
const PAIN_FLAG_PATH = path.join(STATE_DIR, '.pain_flag');
|
|
41
40
|
const SAMPLES_DIR = path.join(STATE_DIR, 'nocturnal', 'samples');
|
|
42
41
|
|
|
43
42
|
// ─── Helpers ─────────────────────────────────────────────────────────────
|
|
@@ -310,11 +309,6 @@ async function main() {
|
|
|
310
309
|
logStep('BASELINE', 'Capturing current state before validation');
|
|
311
310
|
const queueBefore = safeReadJson(QUEUE_PATH) as QueueItem[] | null;
|
|
312
311
|
logData('EVOLUTION_QUEUE (before)', queueBefore?.length ?? 0);
|
|
313
|
-
if (fs.existsSync(PAIN_FLAG_PATH)) {
|
|
314
|
-
logData('.pain_flag', 'EXISTS — ' + fs.readFileSync(PAIN_FLAG_PATH, 'utf8').slice(0, 100));
|
|
315
|
-
} else {
|
|
316
|
-
logData('.pain_flag', 'not present');
|
|
317
|
-
}
|
|
318
312
|
if (fs.existsSync(SAMPLES_DIR)) {
|
|
319
313
|
const samplesBefore = fs.readdirSync(SAMPLES_DIR).length;
|
|
320
314
|
logData('nocturnal/samples/', `${samplesBefore} files`);
|
|
@@ -432,18 +426,6 @@ async function main() {
|
|
|
432
426
|
}
|
|
433
427
|
}
|
|
434
428
|
|
|
435
|
-
// Check pain_flag cleanup
|
|
436
|
-
if (fs.existsSync(PAIN_FLAG_PATH)) {
|
|
437
|
-
const flagContent = fs.readFileSync(PAIN_FLAG_PATH, 'utf8');
|
|
438
|
-
if (flagContent.includes('[object Object]')) {
|
|
439
|
-
logStep('⚠️ WARNING', 'pain_flag is corrupted ([object Object])');
|
|
440
|
-
} else {
|
|
441
|
-
logData('.pain_flag (after)', `still exists, ${flagContent.length} bytes`);
|
|
442
|
-
}
|
|
443
|
-
} else {
|
|
444
|
-
logData('.pain_flag (after)', 'cleaned up (file removed)');
|
|
445
|
-
}
|
|
446
|
-
|
|
447
429
|
if (result.resolution === 'MISSING' || result.resolution === 'expired') {
|
|
448
430
|
console.error('FAIL: resolution not explicit');
|
|
449
431
|
process.exit(1);
|