principles-disciple 1.6.0 → 1.7.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/context.js +7 -3
- package/dist/commands/evolution-status.d.ts +4 -0
- package/dist/commands/evolution-status.js +138 -0
- package/dist/commands/export.d.ts +2 -0
- package/dist/commands/export.js +45 -0
- package/dist/commands/focus.js +9 -6
- package/dist/commands/pain.js +8 -0
- package/dist/commands/principle-rollback.d.ts +4 -0
- package/dist/commands/principle-rollback.js +22 -0
- package/dist/commands/samples.d.ts +2 -0
- package/dist/commands/samples.js +55 -0
- package/dist/core/config.d.ts +5 -0
- package/dist/core/control-ui-db.d.ts +68 -0
- package/dist/core/control-ui-db.js +274 -0
- package/dist/core/detection-funnel.d.ts +1 -1
- package/dist/core/detection-funnel.js +4 -0
- package/dist/core/dictionary.d.ts +2 -0
- package/dist/core/dictionary.js +13 -0
- package/dist/core/event-log.d.ts +2 -1
- package/dist/core/event-log.js +3 -0
- package/dist/core/evolution-engine.d.ts +5 -5
- package/dist/core/evolution-engine.js +18 -18
- package/dist/core/evolution-migration.d.ts +5 -0
- package/dist/core/evolution-migration.js +65 -0
- package/dist/core/evolution-reducer.d.ts +69 -0
- package/dist/core/evolution-reducer.js +369 -0
- package/dist/core/evolution-types.d.ts +103 -0
- package/dist/core/path-resolver.js +75 -36
- package/dist/core/paths.d.ts +7 -8
- package/dist/core/paths.js +48 -40
- package/dist/core/profile.js +1 -1
- package/dist/core/session-tracker.d.ts +4 -0
- package/dist/core/session-tracker.js +15 -0
- package/dist/core/thinking-models.d.ts +38 -0
- package/dist/core/thinking-models.js +170 -0
- package/dist/core/trajectory.d.ts +184 -0
- package/dist/core/trajectory.js +817 -0
- package/dist/core/trust-engine.d.ts +2 -0
- package/dist/core/trust-engine.js +30 -4
- package/dist/core/workspace-context.d.ts +13 -0
- package/dist/core/workspace-context.js +50 -7
- package/dist/hooks/gate.js +117 -48
- package/dist/hooks/llm.js +114 -69
- package/dist/hooks/pain.js +105 -5
- package/dist/hooks/prompt.d.ts +11 -14
- package/dist/hooks/prompt.js +283 -57
- package/dist/hooks/subagent.js +27 -1
- package/dist/http/principles-console-route.d.ts +2 -0
- package/dist/http/principles-console-route.js +257 -0
- package/dist/i18n/commands.js +16 -0
- package/dist/index.js +83 -4
- package/dist/service/control-ui-query-service.d.ts +217 -0
- package/dist/service/control-ui-query-service.js +537 -0
- package/dist/service/evolution-worker.d.ts +9 -0
- package/dist/service/evolution-worker.js +152 -22
- package/dist/service/trajectory-service.d.ts +2 -0
- package/dist/service/trajectory-service.js +15 -0
- package/dist/tools/agent-spawn.d.ts +27 -6
- package/dist/tools/agent-spawn.js +339 -87
- package/dist/tools/deep-reflect.d.ts +27 -7
- package/dist/tools/deep-reflect.js +210 -121
- package/dist/types/event-types.d.ts +9 -2
- package/dist/types.d.ts +10 -0
- package/dist/types.js +5 -0
- package/openclaw.plugin.json +43 -11
- package/package.json +14 -4
- package/templates/langs/zh/skills/pd-daily/SKILL.md +97 -13
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { withLock } from '../utils/file-lock.js';
|
|
5
|
+
import { resolvePdPath } from './paths.js';
|
|
6
|
+
const DEFAULT_BUSY_TIMEOUT_MS = 5000;
|
|
7
|
+
function safeJson(value) {
|
|
8
|
+
return JSON.stringify(value ?? []);
|
|
9
|
+
}
|
|
10
|
+
export class ControlUiDatabase {
|
|
11
|
+
workspaceDir;
|
|
12
|
+
dbPath;
|
|
13
|
+
blobDir;
|
|
14
|
+
db;
|
|
15
|
+
constructor(opts) {
|
|
16
|
+
this.workspaceDir = path.resolve(opts.workspaceDir);
|
|
17
|
+
this.dbPath = resolvePdPath(this.workspaceDir, 'TRAJECTORY_DB');
|
|
18
|
+
this.blobDir = resolvePdPath(this.workspaceDir, 'TRAJECTORY_BLOBS_DIR');
|
|
19
|
+
fs.mkdirSync(path.dirname(this.dbPath), { recursive: true });
|
|
20
|
+
fs.mkdirSync(this.blobDir, { recursive: true });
|
|
21
|
+
this.db = new Database(this.dbPath);
|
|
22
|
+
this.db.pragma('journal_mode = WAL');
|
|
23
|
+
this.db.pragma('foreign_keys = ON');
|
|
24
|
+
this.db.pragma('synchronous = NORMAL');
|
|
25
|
+
this.db.pragma(`busy_timeout = ${Math.max(0, opts.busyTimeoutMs ?? DEFAULT_BUSY_TIMEOUT_MS)}`);
|
|
26
|
+
this.initSchema();
|
|
27
|
+
}
|
|
28
|
+
dispose() {
|
|
29
|
+
this.db.close();
|
|
30
|
+
}
|
|
31
|
+
recordThinkingModelEvent(input) {
|
|
32
|
+
return this.withWrite(() => {
|
|
33
|
+
const result = this.db.prepare(`
|
|
34
|
+
INSERT INTO thinking_model_events (
|
|
35
|
+
session_id, run_id, assistant_turn_id, model_id, matched_pattern, scenario_json,
|
|
36
|
+
tool_context_json, pain_context_json, principle_context_json, trigger_excerpt, created_at
|
|
37
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
38
|
+
`).run(input.sessionId, input.runId, input.assistantTurnId, input.modelId, input.matchedPattern, safeJson(input.scenarioJson), safeJson(input.toolContextJson), safeJson(input.painContextJson), safeJson(input.principleContextJson), input.triggerExcerpt, input.createdAt);
|
|
39
|
+
return Number(result.lastInsertRowid);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
getRecentThinkingContext(sessionId, beforeCreatedAt, limit = 5) {
|
|
43
|
+
return {
|
|
44
|
+
toolCalls: this.all(`
|
|
45
|
+
SELECT id, tool_name, outcome, error_type, error_message, created_at
|
|
46
|
+
FROM tool_calls
|
|
47
|
+
WHERE session_id = ? AND created_at <= ?
|
|
48
|
+
ORDER BY created_at DESC
|
|
49
|
+
LIMIT ?
|
|
50
|
+
`, sessionId, beforeCreatedAt, limit).map((row) => ({
|
|
51
|
+
id: Number(row.id),
|
|
52
|
+
toolName: String(row.tool_name),
|
|
53
|
+
outcome: row.outcome,
|
|
54
|
+
errorType: row.error_type,
|
|
55
|
+
errorMessage: row.error_message,
|
|
56
|
+
createdAt: String(row.created_at),
|
|
57
|
+
})),
|
|
58
|
+
painEvents: this.all(`
|
|
59
|
+
SELECT id, source, score, reason, created_at
|
|
60
|
+
FROM pain_events
|
|
61
|
+
WHERE session_id = ? AND created_at <= ?
|
|
62
|
+
ORDER BY created_at DESC
|
|
63
|
+
LIMIT ?
|
|
64
|
+
`, sessionId, beforeCreatedAt, limit).map((row) => ({
|
|
65
|
+
id: Number(row.id),
|
|
66
|
+
source: String(row.source),
|
|
67
|
+
score: Number(row.score),
|
|
68
|
+
reason: row.reason,
|
|
69
|
+
createdAt: String(row.created_at),
|
|
70
|
+
})),
|
|
71
|
+
gateBlocks: this.all(`
|
|
72
|
+
SELECT id, tool_name, reason, file_path, created_at
|
|
73
|
+
FROM gate_blocks
|
|
74
|
+
WHERE session_id = ? AND created_at <= ?
|
|
75
|
+
ORDER BY created_at DESC
|
|
76
|
+
LIMIT ?
|
|
77
|
+
`, sessionId, beforeCreatedAt, limit).map((row) => ({
|
|
78
|
+
id: Number(row.id),
|
|
79
|
+
toolName: String(row.tool_name),
|
|
80
|
+
reason: String(row.reason),
|
|
81
|
+
filePath: row.file_path,
|
|
82
|
+
createdAt: String(row.created_at),
|
|
83
|
+
})),
|
|
84
|
+
userCorrections: this.all(`
|
|
85
|
+
SELECT id, correction_cue, raw_excerpt, created_at
|
|
86
|
+
FROM user_turns
|
|
87
|
+
WHERE session_id = ? AND correction_detected = 1 AND created_at <= ?
|
|
88
|
+
ORDER BY created_at DESC
|
|
89
|
+
LIMIT ?
|
|
90
|
+
`, sessionId, beforeCreatedAt, limit).map((row) => ({
|
|
91
|
+
id: Number(row.id),
|
|
92
|
+
correctionCue: row.correction_cue,
|
|
93
|
+
rawExcerpt: row.raw_excerpt,
|
|
94
|
+
createdAt: String(row.created_at),
|
|
95
|
+
})),
|
|
96
|
+
principleEvents: this.all(`
|
|
97
|
+
SELECT id, principle_id, event_type, created_at
|
|
98
|
+
FROM principle_events
|
|
99
|
+
WHERE created_at <= ?
|
|
100
|
+
ORDER BY created_at DESC
|
|
101
|
+
LIMIT ?
|
|
102
|
+
`, beforeCreatedAt, limit).map((row) => ({
|
|
103
|
+
id: Number(row.id),
|
|
104
|
+
principleId: row.principle_id,
|
|
105
|
+
eventType: String(row.event_type),
|
|
106
|
+
createdAt: String(row.created_at),
|
|
107
|
+
})),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
all(sql, ...params) {
|
|
111
|
+
return this.db.prepare(sql).all(...params);
|
|
112
|
+
}
|
|
113
|
+
get(sql, ...params) {
|
|
114
|
+
return this.db.prepare(sql).get(...params);
|
|
115
|
+
}
|
|
116
|
+
restoreRawText(inlineText, blobRef) {
|
|
117
|
+
if (inlineText)
|
|
118
|
+
return inlineText;
|
|
119
|
+
if (!blobRef)
|
|
120
|
+
return '';
|
|
121
|
+
const fullPath = path.join(this.blobDir, blobRef);
|
|
122
|
+
return fs.existsSync(fullPath) ? fs.readFileSync(fullPath, 'utf8') : '';
|
|
123
|
+
}
|
|
124
|
+
initSchema() {
|
|
125
|
+
this.db.exec(`
|
|
126
|
+
CREATE TABLE IF NOT EXISTS thinking_model_events (
|
|
127
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
128
|
+
session_id TEXT NOT NULL,
|
|
129
|
+
run_id TEXT NOT NULL,
|
|
130
|
+
assistant_turn_id INTEGER NOT NULL,
|
|
131
|
+
model_id TEXT NOT NULL,
|
|
132
|
+
matched_pattern TEXT NOT NULL,
|
|
133
|
+
scenario_json TEXT NOT NULL,
|
|
134
|
+
tool_context_json TEXT NOT NULL,
|
|
135
|
+
pain_context_json TEXT NOT NULL,
|
|
136
|
+
principle_context_json TEXT NOT NULL,
|
|
137
|
+
trigger_excerpt TEXT NOT NULL,
|
|
138
|
+
created_at TEXT NOT NULL
|
|
139
|
+
);
|
|
140
|
+
CREATE INDEX IF NOT EXISTS idx_thinking_model_events_model_created
|
|
141
|
+
ON thinking_model_events(model_id, created_at);
|
|
142
|
+
CREATE INDEX IF NOT EXISTS idx_thinking_model_events_session_created
|
|
143
|
+
ON thinking_model_events(session_id, created_at);
|
|
144
|
+
CREATE INDEX IF NOT EXISTS idx_thinking_model_events_assistant_turn
|
|
145
|
+
ON thinking_model_events(assistant_turn_id);
|
|
146
|
+
CREATE INDEX IF NOT EXISTS idx_thinking_model_events_run_id
|
|
147
|
+
ON thinking_model_events(run_id);
|
|
148
|
+
|
|
149
|
+
DROP VIEW IF EXISTS v_thinking_model_usage;
|
|
150
|
+
CREATE VIEW v_thinking_model_usage AS
|
|
151
|
+
WITH totals AS (
|
|
152
|
+
SELECT COUNT(*) AS assistant_turns FROM assistant_turns
|
|
153
|
+
),
|
|
154
|
+
usage_rows AS (
|
|
155
|
+
SELECT
|
|
156
|
+
model_id,
|
|
157
|
+
COUNT(*) AS hits,
|
|
158
|
+
COUNT(DISTINCT session_id) AS distinct_sessions,
|
|
159
|
+
COUNT(DISTINCT assistant_turn_id) AS distinct_turns
|
|
160
|
+
FROM thinking_model_events
|
|
161
|
+
GROUP BY model_id
|
|
162
|
+
)
|
|
163
|
+
SELECT
|
|
164
|
+
usage_rows.model_id AS model_id,
|
|
165
|
+
usage_rows.hits AS hits,
|
|
166
|
+
usage_rows.distinct_sessions AS distinct_sessions,
|
|
167
|
+
usage_rows.distinct_turns AS distinct_turns,
|
|
168
|
+
CASE
|
|
169
|
+
WHEN totals.assistant_turns = 0 THEN 0
|
|
170
|
+
ELSE ROUND(CAST(usage_rows.distinct_turns AS REAL) / CAST(totals.assistant_turns AS REAL), 4)
|
|
171
|
+
END AS coverage_rate
|
|
172
|
+
FROM usage_rows, totals
|
|
173
|
+
ORDER BY usage_rows.hits DESC, usage_rows.model_id ASC;
|
|
174
|
+
|
|
175
|
+
DROP VIEW IF EXISTS v_thinking_model_effectiveness;
|
|
176
|
+
CREATE VIEW v_thinking_model_effectiveness AS
|
|
177
|
+
WITH event_windows AS (
|
|
178
|
+
SELECT
|
|
179
|
+
e.id,
|
|
180
|
+
e.session_id,
|
|
181
|
+
e.model_id,
|
|
182
|
+
e.created_at,
|
|
183
|
+
(
|
|
184
|
+
SELECT MIN(a.created_at)
|
|
185
|
+
FROM assistant_turns a
|
|
186
|
+
WHERE a.session_id = e.session_id AND a.created_at > e.created_at
|
|
187
|
+
) AS next_assistant_at,
|
|
188
|
+
datetime(e.created_at, '+10 minutes') AS max_window_end
|
|
189
|
+
FROM thinking_model_events e
|
|
190
|
+
),
|
|
191
|
+
bounded_windows AS (
|
|
192
|
+
SELECT
|
|
193
|
+
id,
|
|
194
|
+
session_id,
|
|
195
|
+
model_id,
|
|
196
|
+
created_at,
|
|
197
|
+
CASE
|
|
198
|
+
WHEN next_assistant_at IS NULL THEN max_window_end
|
|
199
|
+
WHEN next_assistant_at < max_window_end THEN next_assistant_at
|
|
200
|
+
ELSE max_window_end
|
|
201
|
+
END AS window_end
|
|
202
|
+
FROM event_windows
|
|
203
|
+
)
|
|
204
|
+
SELECT
|
|
205
|
+
b.model_id AS model_id,
|
|
206
|
+
COUNT(*) AS events,
|
|
207
|
+
SUM(CASE WHEN EXISTS (
|
|
208
|
+
SELECT 1 FROM tool_calls t
|
|
209
|
+
WHERE t.session_id = b.session_id
|
|
210
|
+
AND t.created_at > b.created_at
|
|
211
|
+
AND t.created_at <= b.window_end
|
|
212
|
+
AND t.outcome = 'success'
|
|
213
|
+
) THEN 1 ELSE 0 END) AS success_windows,
|
|
214
|
+
SUM(CASE WHEN EXISTS (
|
|
215
|
+
SELECT 1 FROM tool_calls t
|
|
216
|
+
WHERE t.session_id = b.session_id
|
|
217
|
+
AND t.created_at > b.created_at
|
|
218
|
+
AND t.created_at <= b.window_end
|
|
219
|
+
AND t.outcome = 'failure'
|
|
220
|
+
) THEN 1 ELSE 0 END) AS failure_windows,
|
|
221
|
+
SUM(CASE WHEN EXISTS (
|
|
222
|
+
SELECT 1 FROM pain_events p
|
|
223
|
+
WHERE p.session_id = b.session_id
|
|
224
|
+
AND p.created_at > b.created_at
|
|
225
|
+
AND p.created_at <= b.window_end
|
|
226
|
+
) THEN 1 ELSE 0 END) AS pain_windows,
|
|
227
|
+
SUM(CASE WHEN EXISTS (
|
|
228
|
+
SELECT 1 FROM user_turns u
|
|
229
|
+
WHERE u.session_id = b.session_id
|
|
230
|
+
AND u.created_at > b.created_at
|
|
231
|
+
AND u.created_at <= b.window_end
|
|
232
|
+
AND u.correction_detected = 1
|
|
233
|
+
) THEN 1 ELSE 0 END) AS correction_windows,
|
|
234
|
+
SUM(CASE WHEN EXISTS (
|
|
235
|
+
SELECT 1 FROM correction_samples c
|
|
236
|
+
WHERE c.session_id = b.session_id
|
|
237
|
+
AND c.created_at > b.created_at
|
|
238
|
+
AND c.created_at <= b.window_end
|
|
239
|
+
) THEN 1 ELSE 0 END) AS correction_sample_windows
|
|
240
|
+
FROM bounded_windows b
|
|
241
|
+
GROUP BY b.model_id
|
|
242
|
+
ORDER BY events DESC, model_id ASC;
|
|
243
|
+
|
|
244
|
+
DROP VIEW IF EXISTS v_thinking_model_scenarios;
|
|
245
|
+
CREATE VIEW v_thinking_model_scenarios AS
|
|
246
|
+
SELECT
|
|
247
|
+
e.model_id AS model_id,
|
|
248
|
+
CAST(j.value AS TEXT) AS scenario,
|
|
249
|
+
COUNT(*) AS hits
|
|
250
|
+
FROM thinking_model_events e
|
|
251
|
+
JOIN json_each(
|
|
252
|
+
CASE
|
|
253
|
+
WHEN json_valid(e.scenario_json) THEN e.scenario_json
|
|
254
|
+
ELSE '[]'
|
|
255
|
+
END
|
|
256
|
+
) AS j
|
|
257
|
+
GROUP BY e.model_id, CAST(j.value AS TEXT)
|
|
258
|
+
ORDER BY hits DESC, scenario ASC;
|
|
259
|
+
|
|
260
|
+
DROP VIEW IF EXISTS v_thinking_model_daily_trend;
|
|
261
|
+
CREATE VIEW v_thinking_model_daily_trend AS
|
|
262
|
+
SELECT
|
|
263
|
+
substr(created_at, 1, 10) AS day,
|
|
264
|
+
model_id,
|
|
265
|
+
COUNT(*) AS hits
|
|
266
|
+
FROM thinking_model_events
|
|
267
|
+
GROUP BY substr(created_at, 1, 10), model_id
|
|
268
|
+
ORDER BY day ASC, model_id ASC;
|
|
269
|
+
`);
|
|
270
|
+
}
|
|
271
|
+
withWrite(fn) {
|
|
272
|
+
return withLock(this.dbPath, fn, { lockSuffix: '.trajectory.lock', lockStaleMs: 30000 });
|
|
273
|
+
}
|
|
274
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash } from 'crypto';
|
|
2
|
+
import { shouldIgnorePainProtocolText } from './dictionary.js';
|
|
2
3
|
/**
|
|
3
4
|
* A simple LRU Cache implementation using Map.
|
|
4
5
|
*/
|
|
@@ -46,6 +47,9 @@ export class DetectionFunnel {
|
|
|
46
47
|
* Detects pain in the given text using L1 (Exact), L2 (Cache), and L3 (Async).
|
|
47
48
|
*/
|
|
48
49
|
detect(text) {
|
|
50
|
+
if (shouldIgnorePainProtocolText(text)) {
|
|
51
|
+
return { detected: false, source: 'l1_exact' };
|
|
52
|
+
}
|
|
49
53
|
// --- Layer 1: Exact Match (Sync) ---
|
|
50
54
|
const exactMatch = this.dictionary.match(text);
|
|
51
55
|
if (exactMatch) {
|
|
@@ -10,6 +10,8 @@ export interface PainRule {
|
|
|
10
10
|
export interface PainDictionaryData {
|
|
11
11
|
rules: Record<string, PainRule>;
|
|
12
12
|
}
|
|
13
|
+
export declare const PAIN_PROTOCOL_TOKENS: readonly ["[EVOLUTION_ACK]", "HEARTBEAT_OK", "HEARTBEAT_CHECK"];
|
|
14
|
+
export declare function shouldIgnorePainProtocolText(text: string): boolean;
|
|
13
15
|
export declare class PainDictionary {
|
|
14
16
|
private stateDir;
|
|
15
17
|
private data;
|
package/dist/core/dictionary.js
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
|
+
export const PAIN_PROTOCOL_TOKENS = [
|
|
4
|
+
'[EVOLUTION_ACK]',
|
|
5
|
+
'HEARTBEAT_OK',
|
|
6
|
+
'HEARTBEAT_CHECK',
|
|
7
|
+
];
|
|
8
|
+
export function shouldIgnorePainProtocolText(text) {
|
|
9
|
+
const normalized = text.trim();
|
|
10
|
+
if (!normalized)
|
|
11
|
+
return false;
|
|
12
|
+
return PAIN_PROTOCOL_TOKENS.some((token) => normalized === token || normalized.startsWith(`${token} `) || normalized.includes(token));
|
|
13
|
+
}
|
|
3
14
|
const DEFAULT_RULES = {
|
|
4
15
|
'P_CONFUSION_ZH': {
|
|
5
16
|
type: 'regex',
|
|
@@ -84,6 +95,8 @@ export class PainDictionary {
|
|
|
84
95
|
}
|
|
85
96
|
}
|
|
86
97
|
match(text) {
|
|
98
|
+
if (shouldIgnorePainProtocolText(text))
|
|
99
|
+
return undefined;
|
|
87
100
|
let bestMatch = undefined;
|
|
88
101
|
for (const [id, rule] of Object.entries(this.data.rules)) {
|
|
89
102
|
if (rule.status !== 'active')
|
package/dist/core/event-log.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DailyStats, EmpathyEventStats, ToolCallEventData, PainSignalEventData, RuleMatchEventData, RulePromotionEventData, HookExecutionEventData, GateBlockEventData, PlanApprovalEventData, EvolutionTaskEventData, DeepReflectionEventData, TrustChangeEventData, EmpathyRollbackEventData } from '../types/event-types.js';
|
|
1
|
+
import type { DailyStats, EmpathyEventStats, ToolCallEventData, PainSignalEventData, RuleMatchEventData, RulePromotionEventData, HookExecutionEventData, GateBlockEventData, GateBypassEventData, PlanApprovalEventData, EvolutionTaskEventData, DeepReflectionEventData, TrustChangeEventData, EmpathyRollbackEventData } from '../types/event-types.js';
|
|
2
2
|
import type { PluginLogger } from '../openclaw-sdk.js';
|
|
3
3
|
/**
|
|
4
4
|
* EventLog - Structured event logging with daily statistics aggregation.
|
|
@@ -19,6 +19,7 @@ export declare class EventLog {
|
|
|
19
19
|
recordRulePromotion(data: RulePromotionEventData): void;
|
|
20
20
|
recordHookExecution(data: HookExecutionEventData): void;
|
|
21
21
|
recordGateBlock(sessionId: string | undefined, data: GateBlockEventData): void;
|
|
22
|
+
recordGateBypass(sessionId: string | undefined, data: GateBypassEventData): void;
|
|
22
23
|
recordPlanApproval(sessionId: string | undefined, data: PlanApprovalEventData): void;
|
|
23
24
|
recordEvolutionTask(data: EvolutionTaskEventData): void;
|
|
24
25
|
recordDeepReflection(sessionId: string | undefined, data: DeepReflectionEventData): void;
|
package/dist/core/event-log.js
CHANGED
|
@@ -44,6 +44,9 @@ export class EventLog {
|
|
|
44
44
|
recordGateBlock(sessionId, data) {
|
|
45
45
|
this.record('gate_block', 'blocked', sessionId, data);
|
|
46
46
|
}
|
|
47
|
+
recordGateBypass(sessionId, data) {
|
|
48
|
+
this.record('gate_bypass', 'bypassed', sessionId, data);
|
|
49
|
+
}
|
|
47
50
|
recordPlanApproval(sessionId, data) {
|
|
48
51
|
this.record('plan_approval', 'approved', sessionId, data);
|
|
49
52
|
}
|
|
@@ -81,13 +81,13 @@ export declare class EvolutionEngine {
|
|
|
81
81
|
private isLockStale;
|
|
82
82
|
/** 持久化评分卡(含锁保护) */
|
|
83
83
|
private saveScorecard;
|
|
84
|
-
/**
|
|
85
|
-
private
|
|
86
|
-
private
|
|
84
|
+
/** Per-instance retry queue (P0 fix: was static, causing cross-instance race) */
|
|
85
|
+
private retryQueue;
|
|
86
|
+
private retryTimer;
|
|
87
87
|
/** 调度重试保存 */
|
|
88
|
-
private
|
|
88
|
+
private scheduleRetrySave;
|
|
89
89
|
/** 处理重试队列 */
|
|
90
|
-
private
|
|
90
|
+
private processRetryQueue;
|
|
91
91
|
/** 无锁快速保存(用于重试) */
|
|
92
92
|
private saveScorecardImmediate;
|
|
93
93
|
private generateId;
|
|
@@ -30,10 +30,10 @@ const EXPLORATORY_TOOLS = new Set([
|
|
|
30
30
|
const CONSTRUCTIVE_TOOLS = new Set([
|
|
31
31
|
'write', 'write_file', 'edit', 'edit_file', 'replace', 'apply_patch',
|
|
32
32
|
'insert', 'patch', 'delete_file', 'move_file', 'run_shell_command',
|
|
33
|
-
'
|
|
33
|
+
'pd_run_worker', 'sessions_spawn',
|
|
34
34
|
]);
|
|
35
35
|
// 高风险工具:需要 allowRiskPath 权限
|
|
36
|
-
// 注意:
|
|
36
|
+
// 注意:pd_run_worker 和 sessions_spawn 已从高风险中移出,它们由 allowSubagentSpawn 单独控制
|
|
37
37
|
const HIGH_RISK_TOOLS = new Set([
|
|
38
38
|
'run_shell_command', 'delete_file', 'move_file',
|
|
39
39
|
]);
|
|
@@ -202,7 +202,7 @@ export class EvolutionEngine {
|
|
|
202
202
|
};
|
|
203
203
|
}
|
|
204
204
|
// 子智能体检查
|
|
205
|
-
if ((context.toolName === '
|
|
205
|
+
if ((context.toolName === 'pd_run_worker' || context.toolName === 'sessions_spawn') && !perms.allowSubagentSpawn) {
|
|
206
206
|
return {
|
|
207
207
|
allowed: false,
|
|
208
208
|
reason: `Tier ${this.scorecard.currentTier} (${tierDef.name}) 未解锁子智能体权限`,
|
|
@@ -464,29 +464,29 @@ export class EvolutionEngine {
|
|
|
464
464
|
release();
|
|
465
465
|
}
|
|
466
466
|
}
|
|
467
|
-
/**
|
|
468
|
-
|
|
469
|
-
|
|
467
|
+
/** Per-instance retry queue (P0 fix: was static, causing cross-instance race) */
|
|
468
|
+
retryQueue = [];
|
|
469
|
+
retryTimer = null;
|
|
470
470
|
/** 调度重试保存 */
|
|
471
|
-
|
|
471
|
+
scheduleRetrySave() {
|
|
472
472
|
// 每个引擎只保留最新数据
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
//
|
|
476
|
-
if (!
|
|
477
|
-
|
|
478
|
-
|
|
473
|
+
this.retryQueue = this.retryQueue.filter(item => item.engine !== this);
|
|
474
|
+
this.retryQueue.push({ engine: this, data: { ...this.scorecard } });
|
|
475
|
+
// 启动重试定时器(每个实例独立)
|
|
476
|
+
if (!this.retryTimer) {
|
|
477
|
+
this.retryTimer = setTimeout(() => {
|
|
478
|
+
this.processRetryQueue();
|
|
479
479
|
}, 1000);
|
|
480
480
|
}
|
|
481
481
|
}
|
|
482
482
|
/** 处理重试队列 */
|
|
483
|
-
|
|
484
|
-
|
|
483
|
+
processRetryQueue() {
|
|
484
|
+
this.retryTimer = null;
|
|
485
485
|
const latestByEngine = new Map();
|
|
486
|
-
for (const item of
|
|
486
|
+
for (const item of this.retryQueue) {
|
|
487
487
|
latestByEngine.set(item.engine, item.data);
|
|
488
488
|
}
|
|
489
|
-
|
|
489
|
+
this.retryQueue = [];
|
|
490
490
|
for (const [engine, data] of latestByEngine) {
|
|
491
491
|
try {
|
|
492
492
|
engine.saveScorecardImmediate(data);
|
|
@@ -494,7 +494,7 @@ export class EvolutionEngine {
|
|
|
494
494
|
}
|
|
495
495
|
catch (e) {
|
|
496
496
|
console.error(`[Evolution] Retry save failed: ${String(e)}`);
|
|
497
|
-
|
|
497
|
+
engine.scheduleRetrySave(); // 每个引擎独立重试
|
|
498
498
|
}
|
|
499
499
|
}
|
|
500
500
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { stableContentHash } from './evolution-reducer.js';
|
|
4
|
+
import { SystemLogger } from './system-logger.js';
|
|
5
|
+
function appendEvent(streamPath, event) {
|
|
6
|
+
fs.appendFileSync(streamPath, `${JSON.stringify(event)}\n`, 'utf8');
|
|
7
|
+
}
|
|
8
|
+
function loadImportedHashes(streamPath, workspaceDir) {
|
|
9
|
+
if (!fs.existsSync(streamPath))
|
|
10
|
+
return new Set();
|
|
11
|
+
const raw = fs.readFileSync(streamPath, 'utf8').trim();
|
|
12
|
+
if (!raw)
|
|
13
|
+
return new Set();
|
|
14
|
+
const hashes = new Set();
|
|
15
|
+
for (const line of raw.split('\n')) {
|
|
16
|
+
try {
|
|
17
|
+
const event = JSON.parse(line);
|
|
18
|
+
if (event.type !== 'legacy_import')
|
|
19
|
+
continue;
|
|
20
|
+
const hash = event.data.contentHash;
|
|
21
|
+
if (typeof hash === 'string')
|
|
22
|
+
hashes.add(hash);
|
|
23
|
+
}
|
|
24
|
+
catch (e) {
|
|
25
|
+
SystemLogger.log(workspaceDir, 'MIGRATION_WARN', `skip malformed line: ${String(e)}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return hashes;
|
|
29
|
+
}
|
|
30
|
+
export function migrateLegacyEvolutionData(workspaceDir) {
|
|
31
|
+
const streamPath = path.join(workspaceDir, 'memory', 'evolution.jsonl');
|
|
32
|
+
fs.mkdirSync(path.dirname(streamPath), { recursive: true });
|
|
33
|
+
const candidates = [
|
|
34
|
+
path.join(workspaceDir, 'memory', 'ISSUE_LOG.md'),
|
|
35
|
+
path.join(workspaceDir, 'memory', 'DECISIONS.md'),
|
|
36
|
+
path.join(workspaceDir, '.principles', 'PRINCIPLES.md'),
|
|
37
|
+
];
|
|
38
|
+
const existingHashes = loadImportedHashes(streamPath, workspaceDir);
|
|
39
|
+
let importedEvents = 0;
|
|
40
|
+
for (const sourceFile of candidates) {
|
|
41
|
+
if (!fs.existsSync(sourceFile)) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const content = fs.readFileSync(sourceFile, 'utf8').trim();
|
|
45
|
+
if (!content) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const contentHash = stableContentHash(`${sourceFile}:${content}`);
|
|
49
|
+
if (existingHashes.has(contentHash)) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
appendEvent(streamPath, {
|
|
53
|
+
ts: new Date().toISOString(),
|
|
54
|
+
type: 'legacy_import',
|
|
55
|
+
data: {
|
|
56
|
+
sourceFile: path.relative(workspaceDir, sourceFile),
|
|
57
|
+
content,
|
|
58
|
+
contentHash,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
importedEvents += 1;
|
|
62
|
+
existingHashes.add(contentHash);
|
|
63
|
+
}
|
|
64
|
+
return { importedEvents, streamPath };
|
|
65
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { EvolutionLoopEvent, Principle } from './evolution-types.js';
|
|
2
|
+
export interface EvolutionReducer {
|
|
3
|
+
emit(event: EvolutionLoopEvent): void;
|
|
4
|
+
emitSync(event: EvolutionLoopEvent): void;
|
|
5
|
+
getEventLog(): EvolutionLoopEvent[];
|
|
6
|
+
getCandidatePrinciples(): Principle[];
|
|
7
|
+
getProbationPrinciples(): Principle[];
|
|
8
|
+
getActivePrinciples(): Principle[];
|
|
9
|
+
getPrincipleById(id: string): Principle | null;
|
|
10
|
+
promote(principleId: string, reason?: string): void;
|
|
11
|
+
deprecate(principleId: string, reason: string): void;
|
|
12
|
+
rollbackPrinciple(principleId: string, reason: string): void;
|
|
13
|
+
recordProbationFeedback(principleId: string, success: boolean): void;
|
|
14
|
+
getStats(): {
|
|
15
|
+
candidateCount: number;
|
|
16
|
+
probationCount: number;
|
|
17
|
+
activeCount: number;
|
|
18
|
+
deprecatedCount: number;
|
|
19
|
+
lastPromotedAt: string | null;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export declare class EvolutionReducerImpl implements EvolutionReducer {
|
|
23
|
+
private readonly streamPath;
|
|
24
|
+
private readonly lockTargetPath;
|
|
25
|
+
private readonly blacklistPath;
|
|
26
|
+
private readonly workspaceDir;
|
|
27
|
+
private readonly memoryEvents;
|
|
28
|
+
private readonly principles;
|
|
29
|
+
private readonly failureStreak;
|
|
30
|
+
private lastPromotedAt;
|
|
31
|
+
private isReplaying;
|
|
32
|
+
constructor(opts: {
|
|
33
|
+
workspaceDir: string;
|
|
34
|
+
});
|
|
35
|
+
emit(event: EvolutionLoopEvent): void;
|
|
36
|
+
emitSync(event: EvolutionLoopEvent): void;
|
|
37
|
+
getEventLog(): EvolutionLoopEvent[];
|
|
38
|
+
getCandidatePrinciples(): Principle[];
|
|
39
|
+
getProbationPrinciples(): Principle[];
|
|
40
|
+
getActivePrinciples(): Principle[];
|
|
41
|
+
getPrincipleById(id: string): Principle | null;
|
|
42
|
+
promote(principleId: string, reason?: string): void;
|
|
43
|
+
deprecate(principleId: string, reason: string): void;
|
|
44
|
+
rollbackPrinciple(principleId: string, reason: string): void;
|
|
45
|
+
recordProbationFeedback(principleId: string, success: boolean): void;
|
|
46
|
+
getStats(): {
|
|
47
|
+
candidateCount: number;
|
|
48
|
+
probationCount: number;
|
|
49
|
+
activeCount: number;
|
|
50
|
+
deprecatedCount: number;
|
|
51
|
+
lastPromotedAt: string | null;
|
|
52
|
+
};
|
|
53
|
+
private ensureDirs;
|
|
54
|
+
private loadFromStream;
|
|
55
|
+
private applyEvent;
|
|
56
|
+
private onCandidateCreated;
|
|
57
|
+
private onPrinciplePromoted;
|
|
58
|
+
private onPrincipleDeprecated;
|
|
59
|
+
private onPrincipleRolledBack;
|
|
60
|
+
private onPainDetected;
|
|
61
|
+
private updateFailureStreakFromPain;
|
|
62
|
+
private nextPrincipleId;
|
|
63
|
+
private getByStatus;
|
|
64
|
+
private sweepExpiredProbation;
|
|
65
|
+
private persistBlacklist;
|
|
66
|
+
private loadBlacklist;
|
|
67
|
+
private isBlacklisted;
|
|
68
|
+
}
|
|
69
|
+
export declare function stableContentHash(input: string): string;
|