principles-disciple 1.6.0 → 1.7.1
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 +134 -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/rollback.js +9 -3
- package/dist/commands/samples.d.ts +2 -0
- package/dist/commands/samples.js +55 -0
- package/dist/commands/trust.js +64 -81
- 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 +7 -1
- package/dist/core/event-log.js +10 -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 +14 -2
- package/dist/core/session-tracker.js +75 -9
- 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 +6 -0
- package/dist/core/trust-engine.js +50 -29
- package/dist/core/workspace-context.d.ts +13 -0
- package/dist/core/workspace-context.js +50 -7
- package/dist/hooks/gate.js +171 -87
- package/dist/hooks/llm.js +119 -71
- 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 +69 -28
- package/dist/hooks/trajectory-collector.d.ts +32 -0
- package/dist/hooks/trajectory-collector.js +256 -0
- 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 +105 -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/empathy-observer-manager.d.ts +2 -0
- package/dist/service/empathy-observer-manager.js +43 -1
- package/dist/service/evolution-worker.d.ts +27 -0
- package/dist/service/evolution-worker.js +256 -41
- package/dist/service/runtime-summary-service.d.ts +79 -0
- package/dist/service/runtime-summary-service.js +319 -0
- 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 +10 -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,537 @@
|
|
|
1
|
+
import { ControlUiDatabase } from '../core/control-ui-db.js';
|
|
2
|
+
import { getThinkingModel, listThinkingModels } from '../core/thinking-models.js';
|
|
3
|
+
import { WorkspaceContext } from '../core/workspace-context.js';
|
|
4
|
+
/** Time window (in minutes) for querying principle events related to a sample */
|
|
5
|
+
const PRINCIPLE_EVENT_WINDOW_MINUTES = 10;
|
|
6
|
+
function parseJson(raw, fallback) {
|
|
7
|
+
if (!raw)
|
|
8
|
+
return fallback;
|
|
9
|
+
try {
|
|
10
|
+
return JSON.parse(raw);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return fallback;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function roundRate(numerator, denominator) {
|
|
17
|
+
if (!denominator)
|
|
18
|
+
return 0;
|
|
19
|
+
return Number((numerator / denominator).toFixed(4));
|
|
20
|
+
}
|
|
21
|
+
function clampPageSize(input) {
|
|
22
|
+
if (!Number.isFinite(input))
|
|
23
|
+
return 20;
|
|
24
|
+
return Math.min(100, Math.max(1, Number(input)));
|
|
25
|
+
}
|
|
26
|
+
function summarizeRecommendation(model) {
|
|
27
|
+
if (model.hits === 0)
|
|
28
|
+
return 'archive';
|
|
29
|
+
if (model.failureRate > model.successRate || model.correctionRate >= 0.35 || model.painRate >= 0.3) {
|
|
30
|
+
return 'rework';
|
|
31
|
+
}
|
|
32
|
+
return 'reinforce';
|
|
33
|
+
}
|
|
34
|
+
export class ControlUiQueryService {
|
|
35
|
+
workspaceDir;
|
|
36
|
+
trajectory;
|
|
37
|
+
uiDb;
|
|
38
|
+
constructor(workspaceDir) {
|
|
39
|
+
this.workspaceDir = workspaceDir;
|
|
40
|
+
this.trajectory = WorkspaceContext.fromHookContext({ workspaceDir }).trajectory;
|
|
41
|
+
this.uiDb = new ControlUiDatabase({ workspaceDir });
|
|
42
|
+
}
|
|
43
|
+
dispose() {
|
|
44
|
+
this.uiDb.dispose();
|
|
45
|
+
}
|
|
46
|
+
getOverview() {
|
|
47
|
+
const stats = this.trajectory.getDataStats();
|
|
48
|
+
const regressionRows = this.uiDb.all('SELECT tool_name, error_type, occurrences FROM v_error_clusters ORDER BY occurrences DESC LIMIT 5');
|
|
49
|
+
const failureStats = this.uiDb.get(`
|
|
50
|
+
SELECT
|
|
51
|
+
COALESCE(SUM(occurrences), 0) AS total_failures,
|
|
52
|
+
COALESCE(SUM(CASE WHEN occurrences > 1 THEN occurrences ELSE 0 END), 0) AS repeated_failures
|
|
53
|
+
FROM v_error_clusters
|
|
54
|
+
`) ?? { total_failures: 0, repeated_failures: 0 };
|
|
55
|
+
const correctionTotal = this.uiDb.get('SELECT COUNT(*) AS count FROM user_turns WHERE correction_detected = 1')?.count ?? 0;
|
|
56
|
+
const principleEventCount = this.uiDb.get('SELECT COUNT(*) AS count FROM principle_events')?.count ?? 0;
|
|
57
|
+
const sampleCounters = this.uiDb.all('SELECT review_status, total FROM v_sample_queue');
|
|
58
|
+
const samplePreview = this.uiDb.all(`
|
|
59
|
+
SELECT sample_id, session_id, quality_score, review_status, created_at
|
|
60
|
+
FROM correction_samples
|
|
61
|
+
ORDER BY created_at DESC
|
|
62
|
+
LIMIT 5
|
|
63
|
+
`);
|
|
64
|
+
const coverageRow = this.uiDb.get(`
|
|
65
|
+
SELECT
|
|
66
|
+
COUNT(DISTINCT assistant_turn_id) AS thinking_turns,
|
|
67
|
+
(SELECT COUNT(*) FROM assistant_turns) AS assistant_turns
|
|
68
|
+
FROM thinking_model_events
|
|
69
|
+
`) ?? { thinking_turns: 0, assistant_turns: 0 };
|
|
70
|
+
const effectiveCount = this.uiDb.all('SELECT events, success_windows, failure_windows, pain_windows, correction_windows FROM v_thinking_model_effectiveness')
|
|
71
|
+
.filter((row) => summarizeRecommendation({
|
|
72
|
+
hits: Number(row.events),
|
|
73
|
+
successRate: roundRate(Number(row.success_windows), Number(row.events)),
|
|
74
|
+
failureRate: roundRate(Number(row.failure_windows), Number(row.events)),
|
|
75
|
+
painRate: roundRate(Number(row.pain_windows), Number(row.events)),
|
|
76
|
+
correctionRate: roundRate(Number(row.correction_windows), Number(row.events)),
|
|
77
|
+
}) === 'reinforce').length;
|
|
78
|
+
const dailyTrend = this.uiDb.all(`
|
|
79
|
+
WITH thinking_daily AS (
|
|
80
|
+
SELECT substr(created_at, 1, 10) AS day, COUNT(DISTINCT assistant_turn_id) AS thinking_turns
|
|
81
|
+
FROM thinking_model_events
|
|
82
|
+
GROUP BY substr(created_at, 1, 10)
|
|
83
|
+
)
|
|
84
|
+
SELECT
|
|
85
|
+
dm.day AS day,
|
|
86
|
+
dm.tool_calls AS tool_calls,
|
|
87
|
+
dm.failures AS failures,
|
|
88
|
+
dm.user_corrections AS user_corrections,
|
|
89
|
+
COALESCE(td.thinking_turns, 0) AS thinking_turns
|
|
90
|
+
FROM v_daily_metrics dm
|
|
91
|
+
LEFT JOIN thinking_daily td ON td.day = dm.day
|
|
92
|
+
ORDER BY dm.day DESC
|
|
93
|
+
LIMIT 14
|
|
94
|
+
`).reverse();
|
|
95
|
+
const counters = Object.fromEntries(sampleCounters.map((row) => [row.review_status, Number(row.total)]));
|
|
96
|
+
const activeModels = this.uiDb.get('SELECT COUNT(DISTINCT model_id) AS count FROM thinking_model_events')?.count ?? 0;
|
|
97
|
+
return {
|
|
98
|
+
workspaceDir: this.workspaceDir,
|
|
99
|
+
generatedAt: new Date().toISOString(),
|
|
100
|
+
dataFreshness: stats.lastIngestAt,
|
|
101
|
+
summary: {
|
|
102
|
+
repeatErrorRate: roundRate(Number(failureStats.repeated_failures), Number(failureStats.total_failures)),
|
|
103
|
+
userCorrectionRate: roundRate(correctionTotal, stats.userTurns),
|
|
104
|
+
pendingSamples: stats.pendingSamples,
|
|
105
|
+
approvedSamples: stats.approvedSamples,
|
|
106
|
+
thinkingCoverageRate: roundRate(coverageRow.thinking_turns, coverageRow.assistant_turns),
|
|
107
|
+
painEvents: stats.painEvents,
|
|
108
|
+
principleEventCount,
|
|
109
|
+
},
|
|
110
|
+
dailyTrend: dailyTrend.map((row) => ({
|
|
111
|
+
day: row.day,
|
|
112
|
+
toolCalls: Number(row.tool_calls),
|
|
113
|
+
failures: Number(row.failures),
|
|
114
|
+
userCorrections: Number(row.user_corrections),
|
|
115
|
+
thinkingTurns: Number(row.thinking_turns),
|
|
116
|
+
})),
|
|
117
|
+
topRegressions: regressionRows.map((row) => ({
|
|
118
|
+
toolName: row.tool_name,
|
|
119
|
+
errorType: row.error_type,
|
|
120
|
+
occurrences: Number(row.occurrences),
|
|
121
|
+
})),
|
|
122
|
+
sampleQueue: {
|
|
123
|
+
counters,
|
|
124
|
+
preview: samplePreview.map((row) => ({
|
|
125
|
+
sampleId: row.sample_id,
|
|
126
|
+
sessionId: row.session_id,
|
|
127
|
+
qualityScore: Number(row.quality_score),
|
|
128
|
+
reviewStatus: row.review_status,
|
|
129
|
+
createdAt: row.created_at,
|
|
130
|
+
})),
|
|
131
|
+
},
|
|
132
|
+
thinkingSummary: {
|
|
133
|
+
activeModels,
|
|
134
|
+
dormantModels: Math.max(0, listThinkingModels().length - activeModels),
|
|
135
|
+
effectiveModels: effectiveCount,
|
|
136
|
+
coverageRate: roundRate(coverageRow.thinking_turns, coverageRow.assistant_turns),
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
listSamples(filters = {}) {
|
|
141
|
+
const page = Math.max(1, Number(filters.page ?? 1));
|
|
142
|
+
const pageSize = clampPageSize(filters.pageSize);
|
|
143
|
+
const offset = (page - 1) * pageSize;
|
|
144
|
+
const where = [];
|
|
145
|
+
const params = [];
|
|
146
|
+
if (filters.status && filters.status !== 'all') {
|
|
147
|
+
where.push('cs.review_status = ?');
|
|
148
|
+
params.push(filters.status);
|
|
149
|
+
}
|
|
150
|
+
if (Number.isFinite(filters.qualityMin)) {
|
|
151
|
+
where.push('cs.quality_score >= ?');
|
|
152
|
+
params.push(Number(filters.qualityMin));
|
|
153
|
+
}
|
|
154
|
+
if (filters.dateFrom) {
|
|
155
|
+
where.push('cs.created_at >= ?');
|
|
156
|
+
params.push(filters.dateFrom);
|
|
157
|
+
}
|
|
158
|
+
if (filters.dateTo) {
|
|
159
|
+
where.push('cs.created_at <= ?');
|
|
160
|
+
params.push(filters.dateTo);
|
|
161
|
+
}
|
|
162
|
+
if (filters.failureMode) {
|
|
163
|
+
where.push(`
|
|
164
|
+
COALESCE(
|
|
165
|
+
(
|
|
166
|
+
SELECT COALESCE(tc.error_type, tc.tool_name)
|
|
167
|
+
FROM tool_calls tc
|
|
168
|
+
WHERE tc.session_id = cs.session_id
|
|
169
|
+
AND tc.outcome = 'failure'
|
|
170
|
+
AND tc.created_at <= ut.created_at
|
|
171
|
+
ORDER BY tc.created_at DESC
|
|
172
|
+
LIMIT 1
|
|
173
|
+
),
|
|
174
|
+
'unknown'
|
|
175
|
+
) = ?
|
|
176
|
+
`);
|
|
177
|
+
params.push(filters.failureMode);
|
|
178
|
+
}
|
|
179
|
+
const whereClause = where.length > 0 ? `WHERE ${where.join(' AND ')}` : '';
|
|
180
|
+
const total = Number(this.uiDb.get(`
|
|
181
|
+
SELECT COUNT(*) AS count
|
|
182
|
+
FROM correction_samples cs
|
|
183
|
+
JOIN user_turns ut ON ut.id = cs.user_correction_turn_id
|
|
184
|
+
${whereClause}
|
|
185
|
+
`, ...params)?.count ?? 0);
|
|
186
|
+
const items = this.uiDb.all(`
|
|
187
|
+
SELECT
|
|
188
|
+
cs.sample_id,
|
|
189
|
+
cs.session_id,
|
|
190
|
+
cs.review_status,
|
|
191
|
+
cs.quality_score,
|
|
192
|
+
cs.created_at,
|
|
193
|
+
cs.updated_at,
|
|
194
|
+
cs.diff_excerpt,
|
|
195
|
+
COALESCE(
|
|
196
|
+
(
|
|
197
|
+
SELECT COALESCE(tc.error_type, tc.tool_name)
|
|
198
|
+
FROM tool_calls tc
|
|
199
|
+
WHERE tc.session_id = cs.session_id
|
|
200
|
+
AND tc.outcome = 'failure'
|
|
201
|
+
AND tc.created_at <= ut.created_at
|
|
202
|
+
ORDER BY tc.created_at DESC
|
|
203
|
+
LIMIT 1
|
|
204
|
+
),
|
|
205
|
+
'unknown'
|
|
206
|
+
) AS failure_mode,
|
|
207
|
+
(
|
|
208
|
+
SELECT COUNT(*)
|
|
209
|
+
FROM thinking_model_events tme
|
|
210
|
+
JOIN assistant_turns at2 ON at2.id = cs.bad_assistant_turn_id
|
|
211
|
+
WHERE tme.session_id = cs.session_id
|
|
212
|
+
AND tme.created_at >= at2.created_at
|
|
213
|
+
AND tme.created_at <= ut.created_at
|
|
214
|
+
) AS related_thinking_count
|
|
215
|
+
FROM correction_samples cs
|
|
216
|
+
JOIN user_turns ut ON ut.id = cs.user_correction_turn_id
|
|
217
|
+
${whereClause}
|
|
218
|
+
ORDER BY cs.created_at DESC
|
|
219
|
+
LIMIT ? OFFSET ?
|
|
220
|
+
`, ...params, pageSize, offset);
|
|
221
|
+
const counters = this.uiDb.all(`
|
|
222
|
+
SELECT review_status, COUNT(*) AS count
|
|
223
|
+
FROM correction_samples
|
|
224
|
+
GROUP BY review_status
|
|
225
|
+
`);
|
|
226
|
+
return {
|
|
227
|
+
counters: Object.fromEntries(counters.map((row) => [row.review_status, Number(row.count)])),
|
|
228
|
+
items: items.map((row) => ({
|
|
229
|
+
sampleId: row.sample_id,
|
|
230
|
+
sessionId: row.session_id,
|
|
231
|
+
reviewStatus: row.review_status,
|
|
232
|
+
qualityScore: Number(row.quality_score),
|
|
233
|
+
failureMode: row.failure_mode,
|
|
234
|
+
relatedThinkingCount: Number(row.related_thinking_count),
|
|
235
|
+
createdAt: row.created_at,
|
|
236
|
+
updatedAt: row.updated_at,
|
|
237
|
+
diffExcerpt: row.diff_excerpt,
|
|
238
|
+
})),
|
|
239
|
+
pagination: {
|
|
240
|
+
page,
|
|
241
|
+
pageSize,
|
|
242
|
+
total,
|
|
243
|
+
totalPages: total === 0 ? 0 : Math.ceil(total / pageSize),
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
getSampleDetail(sampleId) {
|
|
248
|
+
const row = this.uiDb.get(`
|
|
249
|
+
SELECT
|
|
250
|
+
cs.sample_id,
|
|
251
|
+
cs.session_id,
|
|
252
|
+
cs.review_status,
|
|
253
|
+
cs.quality_score,
|
|
254
|
+
cs.created_at,
|
|
255
|
+
cs.updated_at,
|
|
256
|
+
cs.recovery_tool_span_json,
|
|
257
|
+
cs.principle_ids_json,
|
|
258
|
+
at.id AS bad_turn_id,
|
|
259
|
+
at.raw_text AS bad_raw_text,
|
|
260
|
+
at.blob_ref AS bad_blob_ref,
|
|
261
|
+
at.sanitized_text AS bad_sanitized_text,
|
|
262
|
+
at.created_at AS bad_created_at,
|
|
263
|
+
ut.id AS user_turn_id,
|
|
264
|
+
ut.raw_text AS user_raw_text,
|
|
265
|
+
ut.blob_ref AS user_blob_ref,
|
|
266
|
+
ut.correction_cue AS user_correction_cue,
|
|
267
|
+
ut.created_at AS user_created_at
|
|
268
|
+
FROM correction_samples cs
|
|
269
|
+
JOIN assistant_turns at ON at.id = cs.bad_assistant_turn_id
|
|
270
|
+
JOIN user_turns ut ON ut.id = cs.user_correction_turn_id
|
|
271
|
+
WHERE cs.sample_id = ?
|
|
272
|
+
`, sampleId);
|
|
273
|
+
if (!row)
|
|
274
|
+
return null;
|
|
275
|
+
const reviewHistory = this.uiDb.all(`
|
|
276
|
+
SELECT review_status, note, created_at
|
|
277
|
+
FROM sample_reviews
|
|
278
|
+
WHERE sample_id = ?
|
|
279
|
+
ORDER BY created_at DESC
|
|
280
|
+
`, sampleId);
|
|
281
|
+
const relatedThinkingHits = this.uiDb.all(`
|
|
282
|
+
SELECT id, model_id, matched_pattern, scenario_json, created_at, trigger_excerpt
|
|
283
|
+
FROM thinking_model_events
|
|
284
|
+
WHERE session_id = ?
|
|
285
|
+
AND created_at >= ?
|
|
286
|
+
AND created_at <= ?
|
|
287
|
+
ORDER BY created_at DESC
|
|
288
|
+
LIMIT 20
|
|
289
|
+
`, row.session_id, row.bad_created_at, row.user_created_at);
|
|
290
|
+
const relatedPrinciples = this.uiDb.all(`
|
|
291
|
+
SELECT principle_id, event_type, created_at
|
|
292
|
+
FROM principle_events
|
|
293
|
+
WHERE created_at >= ?
|
|
294
|
+
AND created_at <= datetime(?, '+' || ? || ' minutes')
|
|
295
|
+
ORDER BY created_at DESC
|
|
296
|
+
LIMIT 20
|
|
297
|
+
`, row.bad_created_at, row.user_created_at, PRINCIPLE_EVENT_WINDOW_MINUTES);
|
|
298
|
+
const seededPrincipleIds = parseJson(row.principle_ids_json, []).map((principleId) => ({
|
|
299
|
+
principleId,
|
|
300
|
+
eventType: 'seeded_from_sample',
|
|
301
|
+
createdAt: row.created_at,
|
|
302
|
+
}));
|
|
303
|
+
return {
|
|
304
|
+
sampleId: row.sample_id,
|
|
305
|
+
sessionId: row.session_id,
|
|
306
|
+
reviewStatus: row.review_status,
|
|
307
|
+
qualityScore: Number(row.quality_score),
|
|
308
|
+
createdAt: row.created_at,
|
|
309
|
+
updatedAt: row.updated_at,
|
|
310
|
+
badAttempt: {
|
|
311
|
+
assistantTurnId: Number(row.bad_turn_id),
|
|
312
|
+
rawText: this.uiDb.restoreRawText(row.bad_raw_text, row.bad_blob_ref),
|
|
313
|
+
sanitizedText: row.bad_sanitized_text,
|
|
314
|
+
createdAt: row.bad_created_at,
|
|
315
|
+
},
|
|
316
|
+
userCorrection: {
|
|
317
|
+
userTurnId: Number(row.user_turn_id),
|
|
318
|
+
rawText: this.uiDb.restoreRawText(row.user_raw_text, row.user_blob_ref),
|
|
319
|
+
correctionCue: row.user_correction_cue,
|
|
320
|
+
createdAt: row.user_created_at,
|
|
321
|
+
},
|
|
322
|
+
recoveryToolSpan: parseJson(row.recovery_tool_span_json, []),
|
|
323
|
+
relatedPrinciples: [
|
|
324
|
+
...seededPrincipleIds,
|
|
325
|
+
...relatedPrinciples.map((item) => ({
|
|
326
|
+
principleId: item.principle_id,
|
|
327
|
+
eventType: item.event_type,
|
|
328
|
+
createdAt: item.created_at,
|
|
329
|
+
})),
|
|
330
|
+
],
|
|
331
|
+
relatedThinkingHits: relatedThinkingHits.map((item) => ({
|
|
332
|
+
id: Number(item.id),
|
|
333
|
+
modelId: item.model_id,
|
|
334
|
+
modelName: getThinkingModel(item.model_id)?.name ?? item.model_id,
|
|
335
|
+
matchedPattern: item.matched_pattern,
|
|
336
|
+
scenarios: parseJson(item.scenario_json, []),
|
|
337
|
+
createdAt: item.created_at,
|
|
338
|
+
triggerExcerpt: item.trigger_excerpt,
|
|
339
|
+
})),
|
|
340
|
+
reviewHistory: reviewHistory.map((item) => ({
|
|
341
|
+
reviewStatus: item.review_status,
|
|
342
|
+
note: item.note,
|
|
343
|
+
createdAt: item.created_at,
|
|
344
|
+
})),
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
reviewSample(sampleId, decision, note) {
|
|
348
|
+
return this.trajectory.reviewCorrectionSample(sampleId, decision, note);
|
|
349
|
+
}
|
|
350
|
+
exportCorrections(mode) {
|
|
351
|
+
return this.trajectory.exportCorrections({ mode, approvedOnly: true });
|
|
352
|
+
}
|
|
353
|
+
getThinkingOverview() {
|
|
354
|
+
const topModels = this.loadThinkingModelSummaries();
|
|
355
|
+
const knownModels = listThinkingModels();
|
|
356
|
+
const activeIds = new Set(topModels.filter((model) => model.hits > 0).map((model) => model.modelId));
|
|
357
|
+
const dormantModels = knownModels
|
|
358
|
+
.filter((model) => !activeIds.has(model.id))
|
|
359
|
+
.map((model) => ({
|
|
360
|
+
modelId: model.id,
|
|
361
|
+
name: model.name,
|
|
362
|
+
description: model.description,
|
|
363
|
+
}));
|
|
364
|
+
const coverageRow = this.uiDb.get(`
|
|
365
|
+
SELECT
|
|
366
|
+
COUNT(DISTINCT assistant_turn_id) AS thinking_turns,
|
|
367
|
+
(SELECT COUNT(*) FROM assistant_turns) AS assistant_turns
|
|
368
|
+
FROM thinking_model_events
|
|
369
|
+
`) ?? { thinking_turns: 0, assistant_turns: 0 };
|
|
370
|
+
const coverageTrend = this.uiDb.all(`
|
|
371
|
+
WITH assistant_daily AS (
|
|
372
|
+
SELECT substr(created_at, 1, 10) AS day, COUNT(*) AS assistant_turns
|
|
373
|
+
FROM assistant_turns
|
|
374
|
+
GROUP BY substr(created_at, 1, 10)
|
|
375
|
+
),
|
|
376
|
+
thinking_daily AS (
|
|
377
|
+
SELECT substr(created_at, 1, 10) AS day, COUNT(DISTINCT assistant_turn_id) AS thinking_turns
|
|
378
|
+
FROM thinking_model_events
|
|
379
|
+
GROUP BY substr(created_at, 1, 10)
|
|
380
|
+
)
|
|
381
|
+
SELECT
|
|
382
|
+
assistant_daily.day AS day,
|
|
383
|
+
assistant_daily.assistant_turns AS assistant_turns,
|
|
384
|
+
COALESCE(thinking_daily.thinking_turns, 0) AS thinking_turns
|
|
385
|
+
FROM assistant_daily
|
|
386
|
+
LEFT JOIN thinking_daily ON thinking_daily.day = assistant_daily.day
|
|
387
|
+
ORDER BY assistant_daily.day ASC
|
|
388
|
+
`);
|
|
389
|
+
const scenarioMatrix = this.uiDb.all('SELECT model_id, scenario, hits FROM v_thinking_model_scenarios ORDER BY hits DESC, model_id ASC');
|
|
390
|
+
return {
|
|
391
|
+
summary: {
|
|
392
|
+
totalModels: knownModels.length,
|
|
393
|
+
activeModels: activeIds.size,
|
|
394
|
+
dormantModels: dormantModels.length,
|
|
395
|
+
effectiveModels: topModels.filter((model) => model.recommendation === 'reinforce').length,
|
|
396
|
+
coverageRate: roundRate(coverageRow.thinking_turns, coverageRow.assistant_turns),
|
|
397
|
+
},
|
|
398
|
+
topModels,
|
|
399
|
+
dormantModels,
|
|
400
|
+
effectiveModels: topModels.filter((model) => model.recommendation === 'reinforce'),
|
|
401
|
+
scenarioMatrix: scenarioMatrix.map((row) => ({
|
|
402
|
+
modelId: row.model_id,
|
|
403
|
+
modelName: getThinkingModel(row.model_id)?.name ?? row.model_id,
|
|
404
|
+
scenario: row.scenario,
|
|
405
|
+
hits: Number(row.hits),
|
|
406
|
+
})),
|
|
407
|
+
coverageTrend: coverageTrend.map((row) => ({
|
|
408
|
+
day: row.day,
|
|
409
|
+
assistantTurns: Number(row.assistant_turns),
|
|
410
|
+
thinkingTurns: Number(row.thinking_turns),
|
|
411
|
+
coverageRate: roundRate(Number(row.thinking_turns), Number(row.assistant_turns)),
|
|
412
|
+
})),
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
getThinkingModelDetail(modelId) {
|
|
416
|
+
if (!getThinkingModel(modelId)) {
|
|
417
|
+
return null;
|
|
418
|
+
}
|
|
419
|
+
const summary = this.loadThinkingModelSummaries().find((item) => item.modelId === modelId) ?? {
|
|
420
|
+
modelId,
|
|
421
|
+
name: getThinkingModel(modelId)?.name ?? modelId,
|
|
422
|
+
description: getThinkingModel(modelId)?.description ?? 'Unknown thinking model.',
|
|
423
|
+
hits: 0,
|
|
424
|
+
coverageRate: 0,
|
|
425
|
+
successRate: 0,
|
|
426
|
+
failureRate: 0,
|
|
427
|
+
painRate: 0,
|
|
428
|
+
correctionRate: 0,
|
|
429
|
+
correctionSampleRate: 0,
|
|
430
|
+
commonScenarios: [],
|
|
431
|
+
recommendation: 'archive',
|
|
432
|
+
};
|
|
433
|
+
const usageTrend = this.uiDb.all(`
|
|
434
|
+
SELECT day, hits
|
|
435
|
+
FROM v_thinking_model_daily_trend
|
|
436
|
+
WHERE model_id = ?
|
|
437
|
+
ORDER BY day ASC
|
|
438
|
+
`, modelId);
|
|
439
|
+
const scenarioDistribution = this.uiDb.all(`
|
|
440
|
+
SELECT scenario, hits
|
|
441
|
+
FROM v_thinking_model_scenarios
|
|
442
|
+
WHERE model_id = ?
|
|
443
|
+
ORDER BY hits DESC, scenario ASC
|
|
444
|
+
`, modelId);
|
|
445
|
+
const effect = this.uiDb.get('SELECT * FROM v_thinking_model_effectiveness WHERE model_id = ?', modelId) ?? {
|
|
446
|
+
events: 0,
|
|
447
|
+
success_windows: 0,
|
|
448
|
+
failure_windows: 0,
|
|
449
|
+
pain_windows: 0,
|
|
450
|
+
correction_windows: 0,
|
|
451
|
+
correction_sample_windows: 0,
|
|
452
|
+
};
|
|
453
|
+
const recentEvents = this.uiDb.all(`
|
|
454
|
+
SELECT id, created_at, matched_pattern, scenario_json, trigger_excerpt,
|
|
455
|
+
tool_context_json, pain_context_json, principle_context_json
|
|
456
|
+
FROM thinking_model_events
|
|
457
|
+
WHERE model_id = ?
|
|
458
|
+
ORDER BY created_at DESC
|
|
459
|
+
LIMIT 20
|
|
460
|
+
`, modelId);
|
|
461
|
+
return {
|
|
462
|
+
modelMeta: {
|
|
463
|
+
modelId: summary.modelId,
|
|
464
|
+
name: summary.name,
|
|
465
|
+
description: summary.description,
|
|
466
|
+
hits: summary.hits,
|
|
467
|
+
coverageRate: summary.coverageRate,
|
|
468
|
+
recommendation: summary.recommendation,
|
|
469
|
+
},
|
|
470
|
+
usageTrend: usageTrend.map((row) => ({
|
|
471
|
+
day: row.day,
|
|
472
|
+
hits: Number(row.hits),
|
|
473
|
+
})),
|
|
474
|
+
scenarioDistribution: scenarioDistribution.map((row) => ({
|
|
475
|
+
scenario: row.scenario,
|
|
476
|
+
hits: Number(row.hits),
|
|
477
|
+
})),
|
|
478
|
+
outcomeStats: {
|
|
479
|
+
events: Number(effect.events),
|
|
480
|
+
successRate: roundRate(Number(effect.success_windows), Number(effect.events)),
|
|
481
|
+
failureRate: roundRate(Number(effect.failure_windows), Number(effect.events)),
|
|
482
|
+
painRate: roundRate(Number(effect.pain_windows), Number(effect.events)),
|
|
483
|
+
correctionRate: roundRate(Number(effect.correction_windows), Number(effect.events)),
|
|
484
|
+
correctionSampleRate: roundRate(Number(effect.correction_sample_windows), Number(effect.events)),
|
|
485
|
+
},
|
|
486
|
+
recentEvents: recentEvents.map((row) => ({
|
|
487
|
+
id: Number(row.id),
|
|
488
|
+
createdAt: row.created_at,
|
|
489
|
+
matchedPattern: row.matched_pattern,
|
|
490
|
+
scenarios: parseJson(row.scenario_json, []),
|
|
491
|
+
triggerExcerpt: row.trigger_excerpt,
|
|
492
|
+
toolContext: parseJson(row.tool_context_json, []),
|
|
493
|
+
painContext: parseJson(row.pain_context_json, []),
|
|
494
|
+
principleContext: parseJson(row.principle_context_json, []),
|
|
495
|
+
})),
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
loadThinkingModelSummaries() {
|
|
499
|
+
const knownModels = listThinkingModels();
|
|
500
|
+
const usageRows = new Map(this.uiDb.all('SELECT model_id, hits, coverage_rate FROM v_thinking_model_usage').map((row) => [row.model_id, row]));
|
|
501
|
+
const effectRows = new Map(this.uiDb.all('SELECT * FROM v_thinking_model_effectiveness').map((row) => [row.model_id, row]));
|
|
502
|
+
const scenarioRows = this.uiDb.all('SELECT model_id, scenario, hits FROM v_thinking_model_scenarios ORDER BY hits DESC');
|
|
503
|
+
return knownModels.map((model) => {
|
|
504
|
+
const usage = usageRows.get(model.id);
|
|
505
|
+
const effect = effectRows.get(model.id);
|
|
506
|
+
const events = Number(effect?.events ?? usage?.hits ?? 0);
|
|
507
|
+
const successRate = roundRate(Number(effect?.success_windows ?? 0), events);
|
|
508
|
+
const failureRate = roundRate(Number(effect?.failure_windows ?? 0), events);
|
|
509
|
+
const painRate = roundRate(Number(effect?.pain_windows ?? 0), events);
|
|
510
|
+
const correctionRate = roundRate(Number(effect?.correction_windows ?? 0), events);
|
|
511
|
+
const correctionSampleRate = roundRate(Number(effect?.correction_sample_windows ?? 0), events);
|
|
512
|
+
return {
|
|
513
|
+
modelId: model.id,
|
|
514
|
+
name: model.name,
|
|
515
|
+
description: model.description,
|
|
516
|
+
hits: Number(usage?.hits ?? 0),
|
|
517
|
+
coverageRate: Number(usage?.coverage_rate ?? 0),
|
|
518
|
+
successRate,
|
|
519
|
+
failureRate,
|
|
520
|
+
painRate,
|
|
521
|
+
correctionRate,
|
|
522
|
+
correctionSampleRate,
|
|
523
|
+
commonScenarios: scenarioRows
|
|
524
|
+
.filter((row) => row.model_id === model.id)
|
|
525
|
+
.slice(0, 3)
|
|
526
|
+
.map((row) => row.scenario),
|
|
527
|
+
recommendation: summarizeRecommendation({
|
|
528
|
+
hits: Number(usage?.hits ?? 0),
|
|
529
|
+
successRate,
|
|
530
|
+
failureRate,
|
|
531
|
+
painRate,
|
|
532
|
+
correctionRate,
|
|
533
|
+
}),
|
|
534
|
+
};
|
|
535
|
+
}).sort((left, right) => right.hits - left.hits || left.modelId.localeCompare(right.modelId));
|
|
536
|
+
}
|
|
537
|
+
}
|
|
@@ -38,5 +38,7 @@ export declare class EmpathyObserverManager {
|
|
|
38
38
|
private parseJsonPayload;
|
|
39
39
|
private extractAssistantText;
|
|
40
40
|
private scoreFromSeverity;
|
|
41
|
+
private normalizeSeverity;
|
|
42
|
+
private normalizeConfidence;
|
|
41
43
|
}
|
|
42
44
|
export declare const empathyObserverManager: EmpathyObserverManager;
|
|
@@ -71,7 +71,37 @@ export class EmpathyObserverManager {
|
|
|
71
71
|
if (parsed?.damageDetected && sessionId) {
|
|
72
72
|
const wctx = WorkspaceContext.fromHookContext({ workspaceDir });
|
|
73
73
|
const score = this.scoreFromSeverity(parsed.severity, wctx.config);
|
|
74
|
-
trackFriction(sessionId, score, `observer_empathy_${parsed.severity || 'mild'}`, workspaceDir);
|
|
74
|
+
trackFriction(sessionId, score, `observer_empathy_${parsed.severity || 'mild'}`, workspaceDir, { source: 'user_empathy' });
|
|
75
|
+
const eventId = `emp_obs_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`;
|
|
76
|
+
wctx.eventLog.recordPainSignal(sessionId, {
|
|
77
|
+
score,
|
|
78
|
+
source: 'user_empathy',
|
|
79
|
+
reason: parsed.reason || 'Empathy observer detected likely user frustration.',
|
|
80
|
+
isRisky: false,
|
|
81
|
+
origin: 'system_infer',
|
|
82
|
+
severity: this.normalizeSeverity(parsed.severity),
|
|
83
|
+
confidence: this.normalizeConfidence(parsed.confidence),
|
|
84
|
+
detection_mode: 'structured',
|
|
85
|
+
deduped: false,
|
|
86
|
+
trigger_text_excerpt: rawText.substring(0, 120),
|
|
87
|
+
raw_score: score,
|
|
88
|
+
calibrated_score: score,
|
|
89
|
+
eventId,
|
|
90
|
+
});
|
|
91
|
+
try {
|
|
92
|
+
wctx.trajectory?.recordPainEvent?.({
|
|
93
|
+
sessionId,
|
|
94
|
+
source: 'user_empathy',
|
|
95
|
+
score,
|
|
96
|
+
reason: parsed.reason || 'Empathy observer detected likely user frustration.',
|
|
97
|
+
severity: this.normalizeSeverity(parsed.severity),
|
|
98
|
+
origin: 'system_infer',
|
|
99
|
+
confidence: this.normalizeConfidence(parsed.confidence),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
api.logger.warn(`[PD:EmpathyObserver] Failed to persist observer pain event for ${sessionId}: ${String(error)}`);
|
|
104
|
+
}
|
|
75
105
|
api.logger.info(`[PD:EmpathyObserver] Applied GFI +${score} for ${sessionId}`);
|
|
76
106
|
}
|
|
77
107
|
}
|
|
@@ -143,5 +173,17 @@ export class EmpathyObserverManager {
|
|
|
143
173
|
return Number(config.get('empathy_engine.penalties.moderate') ?? 25);
|
|
144
174
|
return Number(config.get('empathy_engine.penalties.mild') ?? 10);
|
|
145
175
|
}
|
|
176
|
+
normalizeSeverity(severity) {
|
|
177
|
+
if (severity === 'severe')
|
|
178
|
+
return 'severe';
|
|
179
|
+
if (severity === 'moderate')
|
|
180
|
+
return 'moderate';
|
|
181
|
+
return 'mild';
|
|
182
|
+
}
|
|
183
|
+
normalizeConfidence(value) {
|
|
184
|
+
if (!Number.isFinite(value))
|
|
185
|
+
return 1;
|
|
186
|
+
return Math.max(0, Math.min(1, Number(value)));
|
|
187
|
+
}
|
|
146
188
|
}
|
|
147
189
|
export const empathyObserverManager = EmpathyObserverManager.getInstance();
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { OpenClawPluginServiceContext, OpenClawPluginApi } from '../openclaw-sdk.js';
|
|
2
|
+
import { WorkspaceContext } from '../core/workspace-context.js';
|
|
2
3
|
export interface EvolutionQueueItem {
|
|
3
4
|
id: string;
|
|
4
5
|
task?: string;
|
|
@@ -9,6 +10,32 @@ export interface EvolutionQueueItem {
|
|
|
9
10
|
trigger_text_preview?: string;
|
|
10
11
|
status: 'pending' | 'in_progress' | 'completed';
|
|
11
12
|
}
|
|
13
|
+
export declare const EVOLUTION_QUEUE_LOCK_SUFFIX = ".lock";
|
|
14
|
+
export declare const PAIN_CANDIDATES_LOCK_SUFFIX = ".candidates.lock";
|
|
15
|
+
export declare const LOCK_MAX_RETRIES = 50;
|
|
16
|
+
export declare const LOCK_RETRY_DELAY_MS = 50;
|
|
17
|
+
export declare const LOCK_STALE_MS = 30000;
|
|
18
|
+
export declare function createEvolutionTaskId(source: string, score: number, preview: string, reason: string, now: number): string;
|
|
19
|
+
export declare function shouldTrackPainCandidate(text: string): boolean;
|
|
20
|
+
export declare function createPainCandidateFingerprint(text: string): string;
|
|
21
|
+
export declare function summarizePainCandidateSample(text: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* Acquire an exclusive file lock for the given resource.
|
|
24
|
+
* Returns a release function. Uses 'wx' flag for atomic exclusive create.
|
|
25
|
+
* Detects stale locks by checking PID and mtime.
|
|
26
|
+
*/
|
|
27
|
+
export declare function acquireQueueLock(lockPath: string, logger: any): (() => void) | null;
|
|
28
|
+
export declare function hasRecentDuplicateTask(queue: EvolutionQueueItem[], source: string, preview: string, now: number, reason?: string): boolean;
|
|
29
|
+
export declare function hasEquivalentPromotedRule(dictionary: {
|
|
30
|
+
getAllRules(): Record<string, {
|
|
31
|
+
type: string;
|
|
32
|
+
phrases?: string[];
|
|
33
|
+
pattern?: string;
|
|
34
|
+
status: string;
|
|
35
|
+
}>;
|
|
36
|
+
}, phrase: string): boolean;
|
|
37
|
+
export declare function trackPainCandidate(text: string, wctx: WorkspaceContext): void;
|
|
38
|
+
export declare function processPromotion(wctx: WorkspaceContext, logger: any, eventLog: any): void;
|
|
12
39
|
export interface ExtendedEvolutionWorkerService {
|
|
13
40
|
id: string;
|
|
14
41
|
api: OpenClawPluginApi | null;
|