principles-disciple 1.154.0 → 1.155.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/core/control-ui-db.d.ts +11 -0
- package/dist/core/control-ui-db.js +40 -16
- package/dist/core/trajectory.d.ts +16 -0
- package/dist/core/trajectory.js +332 -254
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -0
- package/dist/service/subagent-workflow/workflow-store.d.ts +14 -0
- package/dist/service/subagent-workflow/workflow-store.js +91 -2
- package/openclaw.plugin.json +1 -1
- package/package.json +7 -3
|
@@ -102,5 +102,16 @@ export declare class ControlUiDatabase {
|
|
|
102
102
|
run(sql: string, ...params: unknown[]): void;
|
|
103
103
|
restoreRawText(inlineText?: string | null, blobRef?: string | null): string;
|
|
104
104
|
private initSchema;
|
|
105
|
+
/**
|
|
106
|
+
* Create or replace a view, tolerating missing dependency tables.
|
|
107
|
+
*
|
|
108
|
+
* Views in ControlUiDatabase depend on tables created by TrajectoryDatabase
|
|
109
|
+
* (assistant_turns, tool_calls, pain_events, user_turns, correction_samples).
|
|
110
|
+
* If ControlUiDatabase is constructed before TrajectoryDatabase has created
|
|
111
|
+
* those tables, CREATE VIEW would throw. We DROP+CREATE with try/catch so
|
|
112
|
+
* missing-dependency views are skipped with a warning (rc-9: surfaced via
|
|
113
|
+
* console.info) rather than aborting schema initialization.
|
|
114
|
+
*/
|
|
115
|
+
private createViewSafely;
|
|
105
116
|
private withWrite;
|
|
106
117
|
}
|
|
@@ -180,9 +180,15 @@ export class ControlUiDatabase {
|
|
|
180
180
|
ON thinking_model_events(assistant_turn_id);
|
|
181
181
|
CREATE INDEX IF NOT EXISTS idx_thinking_model_events_run_id
|
|
182
182
|
ON thinking_model_events(run_id);
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
183
|
+
`);
|
|
184
|
+
// Views depend on tables owned by TrajectoryDatabase (assistant_turns, tool_calls,
|
|
185
|
+
// pain_events, user_turns, correction_samples). If ControlUiDatabase is constructed
|
|
186
|
+
// before TrajectoryDatabase has run initSchema (e.g. llm_output is the first hook
|
|
187
|
+
// triggered on a fresh workspace), CREATE VIEW would throw and break the hook.
|
|
188
|
+
// Wrap each view creation in try/catch so missing-dependency views are skipped with
|
|
189
|
+
// a warning rather than aborting schema initialization (rc-9: no silent fallback —
|
|
190
|
+
// failures are surfaced via console.info).
|
|
191
|
+
this.createViewSafely('v_thinking_model_usage', `
|
|
186
192
|
WITH totals AS (
|
|
187
193
|
SELECT COUNT(*) AS assistant_turns FROM assistant_turns
|
|
188
194
|
),
|
|
@@ -205,10 +211,9 @@ export class ControlUiDatabase {
|
|
|
205
211
|
ELSE ROUND(CAST(usage_rows.distinct_turns AS REAL) / CAST(totals.assistant_turns AS REAL), 4)
|
|
206
212
|
END AS coverage_rate
|
|
207
213
|
FROM usage_rows, totals
|
|
208
|
-
ORDER BY usage_rows.hits DESC, usage_rows.model_id ASC
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
CREATE VIEW v_thinking_model_effectiveness AS
|
|
214
|
+
ORDER BY usage_rows.hits DESC, usage_rows.model_id ASC
|
|
215
|
+
`);
|
|
216
|
+
this.createViewSafely('v_thinking_model_effectiveness', `
|
|
212
217
|
WITH event_windows AS (
|
|
213
218
|
SELECT
|
|
214
219
|
e.id,
|
|
@@ -274,10 +279,9 @@ export class ControlUiDatabase {
|
|
|
274
279
|
) THEN 1 ELSE 0 END) AS correction_sample_windows
|
|
275
280
|
FROM bounded_windows b
|
|
276
281
|
GROUP BY b.model_id
|
|
277
|
-
ORDER BY events DESC, model_id ASC
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
CREATE VIEW v_thinking_model_scenarios AS
|
|
282
|
+
ORDER BY events DESC, model_id ASC
|
|
283
|
+
`);
|
|
284
|
+
this.createViewSafely('v_thinking_model_scenarios', `
|
|
281
285
|
SELECT
|
|
282
286
|
e.model_id AS model_id,
|
|
283
287
|
CAST(j.value AS TEXT) AS scenario,
|
|
@@ -290,19 +294,39 @@ export class ControlUiDatabase {
|
|
|
290
294
|
END
|
|
291
295
|
) AS j
|
|
292
296
|
GROUP BY e.model_id, CAST(j.value AS TEXT)
|
|
293
|
-
ORDER BY hits DESC, scenario ASC
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
CREATE VIEW v_thinking_model_daily_trend AS
|
|
297
|
+
ORDER BY hits DESC, scenario ASC
|
|
298
|
+
`);
|
|
299
|
+
this.createViewSafely('v_thinking_model_daily_trend', `
|
|
297
300
|
SELECT
|
|
298
301
|
substr(created_at, 1, 10) AS day,
|
|
299
302
|
model_id,
|
|
300
303
|
COUNT(*) AS hits
|
|
301
304
|
FROM thinking_model_events
|
|
302
305
|
GROUP BY substr(created_at, 1, 10), model_id
|
|
303
|
-
ORDER BY day ASC, model_id ASC
|
|
306
|
+
ORDER BY day ASC, model_id ASC
|
|
304
307
|
`);
|
|
305
308
|
}
|
|
309
|
+
/**
|
|
310
|
+
* Create or replace a view, tolerating missing dependency tables.
|
|
311
|
+
*
|
|
312
|
+
* Views in ControlUiDatabase depend on tables created by TrajectoryDatabase
|
|
313
|
+
* (assistant_turns, tool_calls, pain_events, user_turns, correction_samples).
|
|
314
|
+
* If ControlUiDatabase is constructed before TrajectoryDatabase has created
|
|
315
|
+
* those tables, CREATE VIEW would throw. We DROP+CREATE with try/catch so
|
|
316
|
+
* missing-dependency views are skipped with a warning (rc-9: surfaced via
|
|
317
|
+
* console.info) rather than aborting schema initialization.
|
|
318
|
+
*/
|
|
319
|
+
createViewSafely(viewName, viewBody) {
|
|
320
|
+
try {
|
|
321
|
+
this.db.exec(`DROP VIEW IF EXISTS ${viewName};`);
|
|
322
|
+
this.db.exec(`CREATE VIEW ${viewName} AS ${viewBody};`);
|
|
323
|
+
}
|
|
324
|
+
catch (err) {
|
|
325
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
326
|
+
// rc-9: surface the failure — do not silently swallow
|
|
327
|
+
console.info(`[PD:ControlUiDatabase] view ${viewName} creation skipped: ${message}. It will be created on next TrajectoryDatabase initSchema().`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
306
330
|
withWrite(fn) {
|
|
307
331
|
return withLock(this.dbPath, fn, { lockSuffix: '.trajectory.lock', lockStaleMs: 30000 });
|
|
308
332
|
}
|
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
import type { CorrectionSampleReviewStatus, CorrectionExportMode, TrajectoryDataStats, TrajectoryAssistantTurnInput, TrajectoryUserTurnInput, TrajectoryToolCallInput, TrajectoryPainEventInput, TrajectoryGateBlockInput, TrajectoryTrustChangeInput, TrajectoryPrincipleEventInput, TrajectoryTaskOutcomeInput, TrajectorySessionInput, EvolutionTaskInput, EvolutionEventInput, EvolutionTaskRecord, EvolutionEventRecord, EvolutionTaskFilters, AssistantTurnRecord, CorrectionSampleRecord, TrajectoryExportResult, TrajectoryDatabaseOptions } from './trajectory-types.js';
|
|
2
2
|
export type { CorrectionSampleReviewStatus, CorrectionExportMode, TrajectoryDataStats, TrajectoryAssistantTurnInput, TrajectoryUserTurnInput, TrajectoryToolCallInput, TrajectoryPainEventInput, TrajectoryGateBlockInput, DailyMetricRow, TrajectoryTrustChangeInput, TrajectoryPrincipleEventInput, TrajectoryTaskOutcomeInput, TrajectorySessionInput, TaskKind, TaskPriority, EvolutionTaskInput, EvolutionEventInput, EvolutionTaskRecord, EvolutionEventRecord, EvolutionTaskFilters, AssistantTurnRecord, CorrectionSampleRecord, TrajectoryExportResult, TrajectoryDatabaseOptions, } from './trajectory-types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Initialize trajectory.db schema at the given workspace directory.
|
|
5
|
+
*
|
|
6
|
+
* Opens trajectory.db in write mode, applies the full schema (tables + indexes + views +
|
|
7
|
+
* migrations), then closes the DB. Does NOT run importLegacyArtifacts() or
|
|
8
|
+
* pruneUnreferencedBlobs() — those are runtime side-effects of TrajectoryDatabase
|
|
9
|
+
* construction and are not needed for pure initialization.
|
|
10
|
+
*
|
|
11
|
+
* Idempotent: safe to call on an existing DB; all CREATE statements use IF NOT EXISTS.
|
|
12
|
+
*
|
|
13
|
+
* @returns list of created/verified table names and any warnings
|
|
14
|
+
*/
|
|
15
|
+
export declare function initTrajectorySchema(workspaceDir: string): {
|
|
16
|
+
tables: string[];
|
|
17
|
+
warnings: string[];
|
|
18
|
+
};
|
|
3
19
|
export declare class TrajectoryDatabase {
|
|
4
20
|
private readonly workspaceDir;
|
|
5
21
|
private readonly stateDir;
|
package/dist/core/trajectory.js
CHANGED
|
@@ -19,6 +19,332 @@ const DEFAULT_INLINE_THRESHOLD = 16 * 1024;
|
|
|
19
19
|
const DEFAULT_BUSY_TIMEOUT_MS = 5000;
|
|
20
20
|
const DEFAULT_ORPHAN_BLOB_GRACE_DAYS = 7;
|
|
21
21
|
const SCHEMA_VERSION = 1;
|
|
22
|
+
/**
|
|
23
|
+
* Type guard: narrows `object` to `{ name: unknown }` without `as` casts.
|
|
24
|
+
* Used for PRAGMA table_info rows (rc-2-no-as-bypass).
|
|
25
|
+
*/
|
|
26
|
+
function hasNameField(row) {
|
|
27
|
+
return Object.hasOwn(row, 'name');
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Apply the full trajectory.db schema (tables + indexes + views + migrations) to an open
|
|
31
|
+
* Database handle. Used by TrajectoryDatabase.initSchema() and exported via
|
|
32
|
+
* initTrajectorySchema() for external callers (e.g. `pd runtime init`).
|
|
33
|
+
*
|
|
34
|
+
* DDL is the single source of truth for trajectory.db schema. pain-signal-observability.ts
|
|
35
|
+
* in principles-core also duplicates a subset of this DDL (acknowledged duplication); when
|
|
36
|
+
* updating this function, audit ensureTrajectorySchema() in core for parallel updates.
|
|
37
|
+
*
|
|
38
|
+
* Idempotent: all CREATE statements use IF NOT EXISTS; ALTER columns use try/catch on
|
|
39
|
+
* "duplicate column name" to skip existing columns.
|
|
40
|
+
*/
|
|
41
|
+
function applyTrajectorySchema(db) {
|
|
42
|
+
const warnings = [];
|
|
43
|
+
const tables = [
|
|
44
|
+
'schema_version', 'ingest_checkpoint', 'sessions', 'assistant_turns',
|
|
45
|
+
'user_turns', 'tool_calls', 'pain_events', 'gate_blocks', 'trust_changes',
|
|
46
|
+
'principle_events', 'task_outcomes', 'correction_samples', 'sample_reviews',
|
|
47
|
+
'exports_audit', 'evolution_tasks', 'evolution_events',
|
|
48
|
+
];
|
|
49
|
+
db.exec(`
|
|
50
|
+
CREATE TABLE IF NOT EXISTS schema_version (version INTEGER NOT NULL);
|
|
51
|
+
CREATE TABLE IF NOT EXISTS ingest_checkpoint (
|
|
52
|
+
source_key TEXT PRIMARY KEY,
|
|
53
|
+
imported_at TEXT NOT NULL
|
|
54
|
+
);
|
|
55
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
56
|
+
session_id TEXT PRIMARY KEY,
|
|
57
|
+
started_at TEXT NOT NULL,
|
|
58
|
+
updated_at TEXT NOT NULL
|
|
59
|
+
);
|
|
60
|
+
CREATE TABLE IF NOT EXISTS assistant_turns (
|
|
61
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
62
|
+
session_id TEXT NOT NULL,
|
|
63
|
+
run_id TEXT NOT NULL,
|
|
64
|
+
provider TEXT NOT NULL,
|
|
65
|
+
model TEXT NOT NULL,
|
|
66
|
+
raw_text TEXT,
|
|
67
|
+
sanitized_text TEXT NOT NULL,
|
|
68
|
+
usage_json TEXT NOT NULL,
|
|
69
|
+
empathy_signal_json TEXT NOT NULL,
|
|
70
|
+
blob_ref TEXT,
|
|
71
|
+
raw_excerpt TEXT,
|
|
72
|
+
created_at TEXT NOT NULL
|
|
73
|
+
);
|
|
74
|
+
CREATE TABLE IF NOT EXISTS user_turns (
|
|
75
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
76
|
+
session_id TEXT NOT NULL,
|
|
77
|
+
turn_index INTEGER NOT NULL,
|
|
78
|
+
raw_text TEXT,
|
|
79
|
+
blob_ref TEXT,
|
|
80
|
+
raw_excerpt TEXT,
|
|
81
|
+
correction_detected INTEGER NOT NULL DEFAULT 0,
|
|
82
|
+
correction_cue TEXT,
|
|
83
|
+
references_assistant_turn_id INTEGER,
|
|
84
|
+
created_at TEXT NOT NULL
|
|
85
|
+
);
|
|
86
|
+
CREATE TABLE IF NOT EXISTS tool_calls (
|
|
87
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
88
|
+
session_id TEXT NOT NULL,
|
|
89
|
+
tool_name TEXT NOT NULL,
|
|
90
|
+
outcome TEXT NOT NULL,
|
|
91
|
+
duration_ms INTEGER,
|
|
92
|
+
exit_code INTEGER,
|
|
93
|
+
error_type TEXT,
|
|
94
|
+
error_message TEXT,
|
|
95
|
+
gfi_before REAL,
|
|
96
|
+
gfi_after REAL,
|
|
97
|
+
params_json TEXT NOT NULL,
|
|
98
|
+
created_at TEXT NOT NULL
|
|
99
|
+
);
|
|
100
|
+
CREATE TABLE IF NOT EXISTS pain_events (
|
|
101
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
102
|
+
session_id TEXT NOT NULL,
|
|
103
|
+
source TEXT NOT NULL,
|
|
104
|
+
score REAL NOT NULL,
|
|
105
|
+
reason TEXT,
|
|
106
|
+
severity TEXT,
|
|
107
|
+
origin TEXT,
|
|
108
|
+
confidence REAL,
|
|
109
|
+
text TEXT,
|
|
110
|
+
created_at TEXT NOT NULL
|
|
111
|
+
);
|
|
112
|
+
CREATE TABLE IF NOT EXISTS gate_blocks (
|
|
113
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
114
|
+
session_id TEXT,
|
|
115
|
+
tool_name TEXT NOT NULL,
|
|
116
|
+
file_path TEXT,
|
|
117
|
+
reason TEXT NOT NULL,
|
|
118
|
+
plan_status TEXT,
|
|
119
|
+
created_at TEXT NOT NULL
|
|
120
|
+
);
|
|
121
|
+
CREATE TABLE IF NOT EXISTS trust_changes (
|
|
122
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
123
|
+
session_id TEXT,
|
|
124
|
+
previous_score REAL NOT NULL,
|
|
125
|
+
new_score REAL NOT NULL,
|
|
126
|
+
delta REAL NOT NULL,
|
|
127
|
+
reason TEXT NOT NULL,
|
|
128
|
+
created_at TEXT NOT NULL
|
|
129
|
+
);
|
|
130
|
+
CREATE TABLE IF NOT EXISTS principle_events (
|
|
131
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
132
|
+
principle_id TEXT,
|
|
133
|
+
event_type TEXT NOT NULL,
|
|
134
|
+
payload_json TEXT NOT NULL,
|
|
135
|
+
created_at TEXT NOT NULL
|
|
136
|
+
);
|
|
137
|
+
CREATE TABLE IF NOT EXISTS task_outcomes (
|
|
138
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
139
|
+
session_id TEXT NOT NULL,
|
|
140
|
+
task_id TEXT,
|
|
141
|
+
outcome TEXT NOT NULL,
|
|
142
|
+
summary TEXT,
|
|
143
|
+
principle_ids_json TEXT NOT NULL,
|
|
144
|
+
created_at TEXT NOT NULL
|
|
145
|
+
);
|
|
146
|
+
CREATE TABLE IF NOT EXISTS correction_samples (
|
|
147
|
+
sample_id TEXT PRIMARY KEY,
|
|
148
|
+
session_id TEXT NOT NULL,
|
|
149
|
+
bad_assistant_turn_id INTEGER NOT NULL,
|
|
150
|
+
user_correction_turn_id INTEGER NOT NULL,
|
|
151
|
+
recovery_tool_span_json TEXT NOT NULL,
|
|
152
|
+
diff_excerpt TEXT NOT NULL,
|
|
153
|
+
principle_ids_json TEXT NOT NULL,
|
|
154
|
+
quality_score REAL NOT NULL,
|
|
155
|
+
review_status TEXT NOT NULL,
|
|
156
|
+
export_mode TEXT NOT NULL,
|
|
157
|
+
created_at TEXT NOT NULL,
|
|
158
|
+
updated_at TEXT NOT NULL
|
|
159
|
+
);
|
|
160
|
+
CREATE TABLE IF NOT EXISTS sample_reviews (
|
|
161
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
162
|
+
sample_id TEXT NOT NULL,
|
|
163
|
+
review_status TEXT NOT NULL,
|
|
164
|
+
note TEXT,
|
|
165
|
+
created_at TEXT NOT NULL
|
|
166
|
+
);
|
|
167
|
+
CREATE TABLE IF NOT EXISTS exports_audit (
|
|
168
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
169
|
+
export_kind TEXT NOT NULL,
|
|
170
|
+
mode TEXT NOT NULL,
|
|
171
|
+
approved_only INTEGER NOT NULL,
|
|
172
|
+
file_path TEXT NOT NULL,
|
|
173
|
+
row_count INTEGER NOT NULL,
|
|
174
|
+
created_at TEXT NOT NULL
|
|
175
|
+
);
|
|
176
|
+
CREATE TABLE IF NOT EXISTS evolution_tasks (
|
|
177
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
178
|
+
task_id TEXT UNIQUE NOT NULL,
|
|
179
|
+
trace_id TEXT NOT NULL,
|
|
180
|
+
source TEXT NOT NULL,
|
|
181
|
+
reason TEXT,
|
|
182
|
+
score INTEGER DEFAULT 0,
|
|
183
|
+
status TEXT DEFAULT 'pending',
|
|
184
|
+
enqueued_at TEXT,
|
|
185
|
+
started_at TEXT,
|
|
186
|
+
completed_at TEXT,
|
|
187
|
+
resolution TEXT,
|
|
188
|
+
created_at TEXT NOT NULL,
|
|
189
|
+
updated_at TEXT NOT NULL
|
|
190
|
+
);
|
|
191
|
+
CREATE TABLE IF NOT EXISTS evolution_events (
|
|
192
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
193
|
+
trace_id TEXT NOT NULL,
|
|
194
|
+
task_id TEXT,
|
|
195
|
+
stage TEXT NOT NULL,
|
|
196
|
+
level TEXT DEFAULT 'info',
|
|
197
|
+
message TEXT NOT NULL,
|
|
198
|
+
summary TEXT,
|
|
199
|
+
metadata_json TEXT,
|
|
200
|
+
created_at TEXT NOT NULL
|
|
201
|
+
);
|
|
202
|
+
`);
|
|
203
|
+
// Migration: Add text column to pain_events if it doesn't exist (MEM-01)
|
|
204
|
+
// SQLite doesn't support IF NOT EXISTS for ADD COLUMN, so we use try/catch
|
|
205
|
+
try {
|
|
206
|
+
db.exec(`ALTER TABLE pain_events ADD COLUMN text TEXT`);
|
|
207
|
+
}
|
|
208
|
+
catch (err) {
|
|
209
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
210
|
+
if (!message.includes('duplicate column name') && !message.includes('no column named')) {
|
|
211
|
+
// Re-throw unexpected errors — silently swallowing migration failures is dangerous
|
|
212
|
+
throw err;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// PRI-406: Add canonical_pain_id and runtime_task_id columns to pain_events
|
|
216
|
+
for (const col of [
|
|
217
|
+
{ name: 'canonical_pain_id', type: 'TEXT' },
|
|
218
|
+
{ name: 'runtime_task_id', type: 'TEXT' },
|
|
219
|
+
]) {
|
|
220
|
+
try {
|
|
221
|
+
db.exec(`ALTER TABLE pain_events ADD COLUMN ${col.name} ${col.type}`);
|
|
222
|
+
}
|
|
223
|
+
catch (err) {
|
|
224
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
225
|
+
if (!message.includes('duplicate column name') && !message.includes('no column named')) {
|
|
226
|
+
throw err;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
// Trajectory enhancement: add stop_reason, thinking_blocks_count, result_preview
|
|
231
|
+
const trajectoryEnhancementColumns = [
|
|
232
|
+
{ table: 'assistant_turns', name: 'stop_reason', type: 'TEXT' },
|
|
233
|
+
{ table: 'assistant_turns', name: 'thinking_blocks_count', type: 'INTEGER' },
|
|
234
|
+
{ table: 'tool_calls', name: 'result_preview', type: 'TEXT' },
|
|
235
|
+
];
|
|
236
|
+
for (const col of trajectoryEnhancementColumns) {
|
|
237
|
+
try {
|
|
238
|
+
db.exec(`ALTER TABLE ${col.table} ADD COLUMN ${col.name} ${col.type}`);
|
|
239
|
+
}
|
|
240
|
+
catch (err) {
|
|
241
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
242
|
+
if (!message.includes('duplicate column name') && !message.includes('no column named')) {
|
|
243
|
+
throw err;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
// PRI-406: Partial unique index on canonical_pain_id (non-null only) for dedup
|
|
248
|
+
db.exec(`
|
|
249
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_pain_events_canonical_pain_id
|
|
250
|
+
ON pain_events(canonical_pain_id)
|
|
251
|
+
WHERE canonical_pain_id IS NOT NULL
|
|
252
|
+
`);
|
|
253
|
+
// V2 migration: Add V2 columns to evolution_tasks if they don't exist
|
|
254
|
+
const v2Columns = [
|
|
255
|
+
{ name: 'task_kind', type: 'TEXT' },
|
|
256
|
+
{ name: 'priority', type: 'TEXT' },
|
|
257
|
+
{ name: 'retry_count', type: 'INTEGER' },
|
|
258
|
+
{ name: 'max_retries', type: 'INTEGER' },
|
|
259
|
+
{ name: 'last_error', type: 'TEXT' },
|
|
260
|
+
{ name: 'result_ref', type: 'TEXT' },
|
|
261
|
+
];
|
|
262
|
+
for (const col of v2Columns) {
|
|
263
|
+
const rows = db.prepare(`PRAGMA table_info(evolution_tasks)`).all();
|
|
264
|
+
const exists = rows.some((row) => {
|
|
265
|
+
if (typeof row !== 'object' || row === null)
|
|
266
|
+
return false;
|
|
267
|
+
// Use type guard predicate to narrow without `as` (rc-2-no-as-bypass)
|
|
268
|
+
return hasNameField(row) && row.name === col.name;
|
|
269
|
+
});
|
|
270
|
+
if (!exists) {
|
|
271
|
+
db.exec(`ALTER TABLE evolution_tasks ADD COLUMN ${col.name} ${col.type}`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
db.exec(`
|
|
275
|
+
CREATE VIEW IF NOT EXISTS v_error_clusters AS
|
|
276
|
+
SELECT tool_name, COALESCE(error_type, 'unknown') AS error_type, COUNT(*) AS occurrences
|
|
277
|
+
FROM tool_calls
|
|
278
|
+
WHERE outcome = 'failure'
|
|
279
|
+
GROUP BY tool_name, COALESCE(error_type, 'unknown')
|
|
280
|
+
ORDER BY occurrences DESC;
|
|
281
|
+
CREATE VIEW IF NOT EXISTS v_principle_effectiveness AS
|
|
282
|
+
SELECT event_type, COUNT(*) AS total
|
|
283
|
+
FROM principle_events
|
|
284
|
+
GROUP BY event_type
|
|
285
|
+
ORDER BY total DESC;
|
|
286
|
+
CREATE VIEW IF NOT EXISTS v_sample_queue AS
|
|
287
|
+
SELECT review_status, COUNT(*) AS total
|
|
288
|
+
FROM correction_samples
|
|
289
|
+
GROUP BY review_status;
|
|
290
|
+
CREATE INDEX IF NOT EXISTS idx_assistant_turns_session_id ON assistant_turns(session_id);
|
|
291
|
+
CREATE INDEX IF NOT EXISTS idx_assistant_turns_created_at ON assistant_turns(created_at);
|
|
292
|
+
CREATE INDEX IF NOT EXISTS idx_assistant_turns_provider_model ON assistant_turns(provider, model);
|
|
293
|
+
CREATE INDEX IF NOT EXISTS idx_user_turns_session_id ON user_turns(session_id);
|
|
294
|
+
CREATE INDEX IF NOT EXISTS idx_tool_calls_session_id ON tool_calls(session_id);
|
|
295
|
+
CREATE INDEX IF NOT EXISTS idx_tool_calls_created_at ON tool_calls(created_at);
|
|
296
|
+
CREATE INDEX IF NOT EXISTS idx_pain_events_session_id ON pain_events(session_id);
|
|
297
|
+
CREATE INDEX IF NOT EXISTS idx_correction_samples_review_status ON correction_samples(review_status);
|
|
298
|
+
CREATE INDEX IF NOT EXISTS idx_evolution_tasks_trace_id ON evolution_tasks(trace_id);
|
|
299
|
+
CREATE INDEX IF NOT EXISTS idx_evolution_tasks_status ON evolution_tasks(status);
|
|
300
|
+
CREATE INDEX IF NOT EXISTS idx_evolution_tasks_created_at ON evolution_tasks(created_at);
|
|
301
|
+
CREATE INDEX IF NOT EXISTS idx_evolution_events_trace_id ON evolution_events(trace_id);
|
|
302
|
+
CREATE INDEX IF NOT EXISTS idx_evolution_events_created_at ON evolution_events(created_at);
|
|
303
|
+
`);
|
|
304
|
+
return { tables, warnings };
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Initialize trajectory.db schema at the given workspace directory.
|
|
308
|
+
*
|
|
309
|
+
* Opens trajectory.db in write mode, applies the full schema (tables + indexes + views +
|
|
310
|
+
* migrations), then closes the DB. Does NOT run importLegacyArtifacts() or
|
|
311
|
+
* pruneUnreferencedBlobs() — those are runtime side-effects of TrajectoryDatabase
|
|
312
|
+
* construction and are not needed for pure initialization.
|
|
313
|
+
*
|
|
314
|
+
* Idempotent: safe to call on an existing DB; all CREATE statements use IF NOT EXISTS.
|
|
315
|
+
*
|
|
316
|
+
* @returns list of created/verified table names and any warnings
|
|
317
|
+
*/
|
|
318
|
+
export function initTrajectorySchema(workspaceDir) {
|
|
319
|
+
const resolvedDir = path.resolve(workspaceDir);
|
|
320
|
+
const stateDir = resolvePdPath(resolvedDir, 'STATE_DIR');
|
|
321
|
+
const blobDir = resolvePdPath(resolvedDir, 'TRAJECTORY_BLOBS_DIR');
|
|
322
|
+
const exportDir = resolvePdPath(resolvedDir, 'EXPORTS_DIR');
|
|
323
|
+
const dbPath = resolvePdPath(resolvedDir, 'TRAJECTORY_DB');
|
|
324
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
325
|
+
fs.mkdirSync(blobDir, { recursive: true });
|
|
326
|
+
fs.mkdirSync(exportDir, { recursive: true });
|
|
327
|
+
const db = new Database(dbPath);
|
|
328
|
+
try {
|
|
329
|
+
db.pragma('journal_mode = WAL');
|
|
330
|
+
db.pragma('foreign_keys = ON');
|
|
331
|
+
db.pragma('synchronous = NORMAL');
|
|
332
|
+
db.pragma(`busy_timeout = ${DEFAULT_BUSY_TIMEOUT_MS}`);
|
|
333
|
+
const result = applyTrajectorySchema(db);
|
|
334
|
+
// Record schema version (same logic as TrajectoryDatabase.initSchema)
|
|
335
|
+
const row = db.prepare('SELECT version FROM schema_version LIMIT 1').get();
|
|
336
|
+
if (!row) {
|
|
337
|
+
db.prepare('INSERT INTO schema_version (version) VALUES (?)').run(SCHEMA_VERSION);
|
|
338
|
+
}
|
|
339
|
+
else if (row.version !== SCHEMA_VERSION) {
|
|
340
|
+
db.prepare('UPDATE schema_version SET version = ?').run(SCHEMA_VERSION);
|
|
341
|
+
}
|
|
342
|
+
return result;
|
|
343
|
+
}
|
|
344
|
+
finally {
|
|
345
|
+
db.close();
|
|
346
|
+
}
|
|
347
|
+
}
|
|
22
348
|
function nowIso() {
|
|
23
349
|
return new Date().toISOString();
|
|
24
350
|
}
|
|
@@ -862,261 +1188,13 @@ export class TrajectoryDatabase {
|
|
|
862
1188
|
return this.pruneUnreferencedBlobs();
|
|
863
1189
|
}
|
|
864
1190
|
initSchema() {
|
|
865
|
-
this.db
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
CREATE TABLE IF NOT EXISTS sessions (
|
|
872
|
-
session_id TEXT PRIMARY KEY,
|
|
873
|
-
started_at TEXT NOT NULL,
|
|
874
|
-
updated_at TEXT NOT NULL
|
|
875
|
-
);
|
|
876
|
-
CREATE TABLE IF NOT EXISTS assistant_turns (
|
|
877
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
878
|
-
session_id TEXT NOT NULL,
|
|
879
|
-
run_id TEXT NOT NULL,
|
|
880
|
-
provider TEXT NOT NULL,
|
|
881
|
-
model TEXT NOT NULL,
|
|
882
|
-
raw_text TEXT,
|
|
883
|
-
sanitized_text TEXT NOT NULL,
|
|
884
|
-
usage_json TEXT NOT NULL,
|
|
885
|
-
empathy_signal_json TEXT NOT NULL,
|
|
886
|
-
blob_ref TEXT,
|
|
887
|
-
raw_excerpt TEXT,
|
|
888
|
-
created_at TEXT NOT NULL
|
|
889
|
-
);
|
|
890
|
-
CREATE TABLE IF NOT EXISTS user_turns (
|
|
891
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
892
|
-
session_id TEXT NOT NULL,
|
|
893
|
-
turn_index INTEGER NOT NULL,
|
|
894
|
-
raw_text TEXT,
|
|
895
|
-
blob_ref TEXT,
|
|
896
|
-
raw_excerpt TEXT,
|
|
897
|
-
correction_detected INTEGER NOT NULL DEFAULT 0,
|
|
898
|
-
correction_cue TEXT,
|
|
899
|
-
references_assistant_turn_id INTEGER,
|
|
900
|
-
created_at TEXT NOT NULL
|
|
901
|
-
);
|
|
902
|
-
CREATE TABLE IF NOT EXISTS tool_calls (
|
|
903
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
904
|
-
session_id TEXT NOT NULL,
|
|
905
|
-
tool_name TEXT NOT NULL,
|
|
906
|
-
outcome TEXT NOT NULL,
|
|
907
|
-
duration_ms INTEGER,
|
|
908
|
-
exit_code INTEGER,
|
|
909
|
-
error_type TEXT,
|
|
910
|
-
error_message TEXT,
|
|
911
|
-
gfi_before REAL,
|
|
912
|
-
gfi_after REAL,
|
|
913
|
-
params_json TEXT NOT NULL,
|
|
914
|
-
created_at TEXT NOT NULL
|
|
915
|
-
);
|
|
916
|
-
CREATE TABLE IF NOT EXISTS pain_events (
|
|
917
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
918
|
-
session_id TEXT NOT NULL,
|
|
919
|
-
source TEXT NOT NULL,
|
|
920
|
-
score REAL NOT NULL,
|
|
921
|
-
reason TEXT,
|
|
922
|
-
severity TEXT,
|
|
923
|
-
origin TEXT,
|
|
924
|
-
confidence REAL,
|
|
925
|
-
text TEXT,
|
|
926
|
-
created_at TEXT NOT NULL
|
|
927
|
-
);
|
|
928
|
-
CREATE TABLE IF NOT EXISTS gate_blocks (
|
|
929
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
930
|
-
session_id TEXT,
|
|
931
|
-
tool_name TEXT NOT NULL,
|
|
932
|
-
file_path TEXT,
|
|
933
|
-
reason TEXT NOT NULL,
|
|
934
|
-
plan_status TEXT,
|
|
935
|
-
created_at TEXT NOT NULL
|
|
936
|
-
);
|
|
937
|
-
CREATE TABLE IF NOT EXISTS trust_changes (
|
|
938
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
939
|
-
session_id TEXT,
|
|
940
|
-
previous_score REAL NOT NULL,
|
|
941
|
-
new_score REAL NOT NULL,
|
|
942
|
-
delta REAL NOT NULL,
|
|
943
|
-
reason TEXT NOT NULL,
|
|
944
|
-
created_at TEXT NOT NULL
|
|
945
|
-
);
|
|
946
|
-
CREATE TABLE IF NOT EXISTS principle_events (
|
|
947
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
948
|
-
principle_id TEXT,
|
|
949
|
-
event_type TEXT NOT NULL,
|
|
950
|
-
payload_json TEXT NOT NULL,
|
|
951
|
-
created_at TEXT NOT NULL
|
|
952
|
-
);
|
|
953
|
-
CREATE TABLE IF NOT EXISTS task_outcomes (
|
|
954
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
955
|
-
session_id TEXT NOT NULL,
|
|
956
|
-
task_id TEXT,
|
|
957
|
-
outcome TEXT NOT NULL,
|
|
958
|
-
summary TEXT,
|
|
959
|
-
principle_ids_json TEXT NOT NULL,
|
|
960
|
-
created_at TEXT NOT NULL
|
|
961
|
-
);
|
|
962
|
-
CREATE TABLE IF NOT EXISTS correction_samples (
|
|
963
|
-
sample_id TEXT PRIMARY KEY,
|
|
964
|
-
session_id TEXT NOT NULL,
|
|
965
|
-
bad_assistant_turn_id INTEGER NOT NULL,
|
|
966
|
-
user_correction_turn_id INTEGER NOT NULL,
|
|
967
|
-
recovery_tool_span_json TEXT NOT NULL,
|
|
968
|
-
diff_excerpt TEXT NOT NULL,
|
|
969
|
-
principle_ids_json TEXT NOT NULL,
|
|
970
|
-
quality_score REAL NOT NULL,
|
|
971
|
-
review_status TEXT NOT NULL,
|
|
972
|
-
export_mode TEXT NOT NULL,
|
|
973
|
-
created_at TEXT NOT NULL,
|
|
974
|
-
updated_at TEXT NOT NULL
|
|
975
|
-
);
|
|
976
|
-
CREATE TABLE IF NOT EXISTS sample_reviews (
|
|
977
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
978
|
-
sample_id TEXT NOT NULL,
|
|
979
|
-
review_status TEXT NOT NULL,
|
|
980
|
-
note TEXT,
|
|
981
|
-
created_at TEXT NOT NULL
|
|
982
|
-
);
|
|
983
|
-
CREATE TABLE IF NOT EXISTS exports_audit (
|
|
984
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
985
|
-
export_kind TEXT NOT NULL,
|
|
986
|
-
mode TEXT NOT NULL,
|
|
987
|
-
approved_only INTEGER NOT NULL,
|
|
988
|
-
file_path TEXT NOT NULL,
|
|
989
|
-
row_count INTEGER NOT NULL,
|
|
990
|
-
created_at TEXT NOT NULL
|
|
991
|
-
);
|
|
992
|
-
CREATE TABLE IF NOT EXISTS evolution_tasks (
|
|
993
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
994
|
-
task_id TEXT UNIQUE NOT NULL,
|
|
995
|
-
trace_id TEXT NOT NULL,
|
|
996
|
-
source TEXT NOT NULL,
|
|
997
|
-
reason TEXT,
|
|
998
|
-
score INTEGER DEFAULT 0,
|
|
999
|
-
status TEXT DEFAULT 'pending',
|
|
1000
|
-
enqueued_at TEXT,
|
|
1001
|
-
started_at TEXT,
|
|
1002
|
-
completed_at TEXT,
|
|
1003
|
-
resolution TEXT,
|
|
1004
|
-
created_at TEXT NOT NULL,
|
|
1005
|
-
updated_at TEXT NOT NULL
|
|
1006
|
-
);
|
|
1007
|
-
CREATE TABLE IF NOT EXISTS evolution_events (
|
|
1008
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1009
|
-
trace_id TEXT NOT NULL,
|
|
1010
|
-
task_id TEXT,
|
|
1011
|
-
stage TEXT NOT NULL,
|
|
1012
|
-
level TEXT DEFAULT 'info',
|
|
1013
|
-
message TEXT NOT NULL,
|
|
1014
|
-
summary TEXT,
|
|
1015
|
-
metadata_json TEXT,
|
|
1016
|
-
created_at TEXT NOT NULL
|
|
1017
|
-
);
|
|
1018
|
-
`);
|
|
1019
|
-
// Migration: Add text column to pain_events if it doesn't exist (MEM-01)
|
|
1020
|
-
// SQLite doesn't support IF NOT EXISTS for ADD COLUMN, so we use try/catch
|
|
1021
|
-
try {
|
|
1022
|
-
this.db.exec(`ALTER TABLE pain_events ADD COLUMN text TEXT`);
|
|
1023
|
-
}
|
|
1024
|
-
catch (err) {
|
|
1025
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1026
|
-
if (!message.includes('duplicate column name') && !message.includes('no column named')) {
|
|
1027
|
-
// Re-throw unexpected errors — silently swallowing migration failures is dangerous
|
|
1028
|
-
throw err;
|
|
1029
|
-
}
|
|
1191
|
+
const result = applyTrajectorySchema(this.db);
|
|
1192
|
+
// Best-effort: surface any warnings from schema initialization (rc-9: no silent fallback).
|
|
1193
|
+
// Warnings include failed migrations or view creation issues; surfaced via console.info
|
|
1194
|
+
// because TrajectoryDatabase does not have a warnings buffer at this layer.
|
|
1195
|
+
for (const w of result.warnings) {
|
|
1196
|
+
console.info(`[PD:TrajectoryDatabase] schema init warning: ${w}`);
|
|
1030
1197
|
}
|
|
1031
|
-
// PRI-406: Add canonical_pain_id and runtime_task_id columns to pain_events
|
|
1032
|
-
for (const col of [
|
|
1033
|
-
{ name: 'canonical_pain_id', type: 'TEXT' },
|
|
1034
|
-
{ name: 'runtime_task_id', type: 'TEXT' },
|
|
1035
|
-
]) {
|
|
1036
|
-
try {
|
|
1037
|
-
this.db.exec(`ALTER TABLE pain_events ADD COLUMN ${col.name} ${col.type}`);
|
|
1038
|
-
}
|
|
1039
|
-
catch (err) {
|
|
1040
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1041
|
-
if (!message.includes('duplicate column name') && !message.includes('no column named')) {
|
|
1042
|
-
throw err;
|
|
1043
|
-
}
|
|
1044
|
-
}
|
|
1045
|
-
}
|
|
1046
|
-
// Trajectory enhancement: add stop_reason, thinking_blocks_count, result_preview
|
|
1047
|
-
const trajectoryEnhancementColumns = [
|
|
1048
|
-
{ table: 'assistant_turns', name: 'stop_reason', type: 'TEXT' },
|
|
1049
|
-
{ table: 'assistant_turns', name: 'thinking_blocks_count', type: 'INTEGER' },
|
|
1050
|
-
{ table: 'tool_calls', name: 'result_preview', type: 'TEXT' },
|
|
1051
|
-
];
|
|
1052
|
-
for (const col of trajectoryEnhancementColumns) {
|
|
1053
|
-
try {
|
|
1054
|
-
this.db.exec(`ALTER TABLE ${col.table} ADD COLUMN ${col.name} ${col.type}`);
|
|
1055
|
-
}
|
|
1056
|
-
catch (err) {
|
|
1057
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1058
|
-
if (!message.includes('duplicate column name') && !message.includes('no column named')) {
|
|
1059
|
-
throw err;
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1062
|
-
}
|
|
1063
|
-
// PRI-406: Partial unique index on canonical_pain_id (non-null only) for dedup
|
|
1064
|
-
this.db.exec(`
|
|
1065
|
-
CREATE UNIQUE INDEX IF NOT EXISTS idx_pain_events_canonical_pain_id
|
|
1066
|
-
ON pain_events(canonical_pain_id)
|
|
1067
|
-
WHERE canonical_pain_id IS NOT NULL
|
|
1068
|
-
`);
|
|
1069
|
-
// pain_events_fts FTS5 virtual table creation removed (PRI-451 Wave 1):
|
|
1070
|
-
// the only reader (searchPainEvents) was dead code. Existing DBs keep the
|
|
1071
|
-
// orphan table harmlessly (CREATE was IF NOT EXISTS); new DBs skip it.
|
|
1072
|
-
// V2 migration: Add V2 columns to evolution_tasks if they don't exist
|
|
1073
|
-
// SQLite does not support IF NOT EXISTS for ADD COLUMN, so we must check manually
|
|
1074
|
-
// before each ALTER to avoid "duplicate column name" errors on existing DBs
|
|
1075
|
-
const v2Columns = [
|
|
1076
|
-
{ name: 'task_kind', type: 'TEXT' },
|
|
1077
|
-
{ name: 'priority', type: 'TEXT' },
|
|
1078
|
-
{ name: 'retry_count', type: 'INTEGER' },
|
|
1079
|
-
{ name: 'max_retries', type: 'INTEGER' },
|
|
1080
|
-
{ name: 'last_error', type: 'TEXT' },
|
|
1081
|
-
{ name: 'result_ref', type: 'TEXT' },
|
|
1082
|
-
];
|
|
1083
|
-
for (const col of v2Columns) {
|
|
1084
|
-
const exists = this.db.prepare(`PRAGMA table_info(evolution_tasks)`).all()
|
|
1085
|
-
.some((row) => row.name === col.name);
|
|
1086
|
-
if (!exists) {
|
|
1087
|
-
this.db.exec(`ALTER TABLE evolution_tasks ADD COLUMN ${col.name} ${col.type}`);
|
|
1088
|
-
}
|
|
1089
|
-
}
|
|
1090
|
-
this.db.exec(`
|
|
1091
|
-
CREATE VIEW IF NOT EXISTS v_error_clusters AS
|
|
1092
|
-
SELECT tool_name, COALESCE(error_type, 'unknown') AS error_type, COUNT(*) AS occurrences
|
|
1093
|
-
FROM tool_calls
|
|
1094
|
-
WHERE outcome = 'failure'
|
|
1095
|
-
GROUP BY tool_name, COALESCE(error_type, 'unknown')
|
|
1096
|
-
ORDER BY occurrences DESC;
|
|
1097
|
-
CREATE VIEW IF NOT EXISTS v_principle_effectiveness AS
|
|
1098
|
-
SELECT event_type, COUNT(*) AS total
|
|
1099
|
-
FROM principle_events
|
|
1100
|
-
GROUP BY event_type
|
|
1101
|
-
ORDER BY total DESC;
|
|
1102
|
-
CREATE VIEW IF NOT EXISTS v_sample_queue AS
|
|
1103
|
-
SELECT review_status, COUNT(*) AS total
|
|
1104
|
-
FROM correction_samples
|
|
1105
|
-
GROUP BY review_status;
|
|
1106
|
-
CREATE INDEX IF NOT EXISTS idx_assistant_turns_session_id ON assistant_turns(session_id);
|
|
1107
|
-
CREATE INDEX IF NOT EXISTS idx_assistant_turns_created_at ON assistant_turns(created_at);
|
|
1108
|
-
CREATE INDEX IF NOT EXISTS idx_assistant_turns_provider_model ON assistant_turns(provider, model);
|
|
1109
|
-
CREATE INDEX IF NOT EXISTS idx_user_turns_session_id ON user_turns(session_id);
|
|
1110
|
-
CREATE INDEX IF NOT EXISTS idx_tool_calls_session_id ON tool_calls(session_id);
|
|
1111
|
-
CREATE INDEX IF NOT EXISTS idx_tool_calls_created_at ON tool_calls(created_at);
|
|
1112
|
-
CREATE INDEX IF NOT EXISTS idx_pain_events_session_id ON pain_events(session_id);
|
|
1113
|
-
CREATE INDEX IF NOT EXISTS idx_correction_samples_review_status ON correction_samples(review_status);
|
|
1114
|
-
CREATE INDEX IF NOT EXISTS idx_evolution_tasks_trace_id ON evolution_tasks(trace_id);
|
|
1115
|
-
CREATE INDEX IF NOT EXISTS idx_evolution_tasks_status ON evolution_tasks(status);
|
|
1116
|
-
CREATE INDEX IF NOT EXISTS idx_evolution_tasks_created_at ON evolution_tasks(created_at);
|
|
1117
|
-
CREATE INDEX IF NOT EXISTS idx_evolution_events_trace_id ON evolution_events(trace_id);
|
|
1118
|
-
CREATE INDEX IF NOT EXISTS idx_evolution_events_created_at ON evolution_events(created_at);
|
|
1119
|
-
`);
|
|
1120
1198
|
const row = this.db.prepare('SELECT version FROM schema_version LIMIT 1').get();
|
|
1121
1199
|
this.migrateSchema(row?.version);
|
|
1122
1200
|
if (!row) {
|
package/dist/index.d.ts
CHANGED
|
@@ -47,4 +47,6 @@ declare const plugin: {
|
|
|
47
47
|
};
|
|
48
48
|
export { PrincipleTreeLedgerAdapter } from '@principles/core/runtime-v2';
|
|
49
49
|
export { loadFeatureFlagFromWorkspace, isRecord };
|
|
50
|
+
export { initTrajectorySchema } from './core/trajectory.js';
|
|
51
|
+
export { initWorkflowSchema } from './service/subagent-workflow/workflow-store.js';
|
|
50
52
|
export default plugin;
|
package/dist/index.js
CHANGED
|
@@ -747,4 +747,9 @@ const plugin = {
|
|
|
747
747
|
export { PrincipleTreeLedgerAdapter } from '@principles/core/runtime-v2';
|
|
748
748
|
/* istanbul ignore next — test exports for evolution worker gate */
|
|
749
749
|
export { loadFeatureFlagFromWorkspace, isRecord };
|
|
750
|
+
// Schema initialization exports for `pd runtime init` (unified DB init).
|
|
751
|
+
// These functions open the DB in write mode, apply the full schema, and close the DB.
|
|
752
|
+
// They do NOT run runtime side-effects (importLegacyArtifacts, pruneUnreferencedBlobs).
|
|
753
|
+
export { initTrajectorySchema } from './core/trajectory.js';
|
|
754
|
+
export { initWorkflowSchema } from './service/subagent-workflow/workflow-store.js';
|
|
750
755
|
export default plugin;
|
|
@@ -28,3 +28,17 @@ export declare class WorkflowStore {
|
|
|
28
28
|
recordDuration(workflowId: string, durationMs: number): void;
|
|
29
29
|
getCompletionDurations(workflowType: string, limit?: number): number[];
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Initialize subagent_workflows.db schema at the given workspace directory.
|
|
33
|
+
*
|
|
34
|
+
* Opens the DB in write mode, applies the full schema (tables + indexes + migrations),
|
|
35
|
+
* then closes the DB. Used by `pd runtime init` for unified DB initialization.
|
|
36
|
+
*
|
|
37
|
+
* Idempotent: safe to call on an existing DB; all CREATE statements use IF NOT EXISTS.
|
|
38
|
+
*
|
|
39
|
+
* @returns list of created/verified table names and any warnings
|
|
40
|
+
*/
|
|
41
|
+
export declare function initWorkflowSchema(workspaceDir: string): {
|
|
42
|
+
tables: string[];
|
|
43
|
+
warnings: string[];
|
|
44
|
+
};
|
|
@@ -78,8 +78,12 @@ export class WorkflowStore {
|
|
|
78
78
|
this.db.exec('ALTER TABLE subagent_workflows ADD COLUMN duration_ms INTEGER');
|
|
79
79
|
console.info(`[PD:WorkflowStore] Schema migration v${fromVersion} → v${toVersion}: added duration_ms column`);
|
|
80
80
|
}
|
|
81
|
-
catch {
|
|
82
|
-
void 0
|
|
81
|
+
catch (err) {
|
|
82
|
+
// rc-9: surface failure reason instead of silent void 0
|
|
83
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
84
|
+
if (!message.includes('duplicate column name')) {
|
|
85
|
+
console.info(`[PD:WorkflowStore] Schema migration v${fromVersion} → v${toVersion} failed: ${message}`);
|
|
86
|
+
}
|
|
83
87
|
}
|
|
84
88
|
}
|
|
85
89
|
}
|
|
@@ -207,3 +211,88 @@ export class WorkflowStore {
|
|
|
207
211
|
return rows.map(r => r.duration_ms);
|
|
208
212
|
}
|
|
209
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* Initialize subagent_workflows.db schema at the given workspace directory.
|
|
216
|
+
*
|
|
217
|
+
* Opens the DB in write mode, applies the full schema (tables + indexes + migrations),
|
|
218
|
+
* then closes the DB. Used by `pd runtime init` for unified DB initialization.
|
|
219
|
+
*
|
|
220
|
+
* Idempotent: safe to call on an existing DB; all CREATE statements use IF NOT EXISTS.
|
|
221
|
+
*
|
|
222
|
+
* @returns list of created/verified table names and any warnings
|
|
223
|
+
*/
|
|
224
|
+
export function initWorkflowSchema(workspaceDir) {
|
|
225
|
+
const resolvedDir = path.resolve(workspaceDir);
|
|
226
|
+
const stateDir = path.join(resolvedDir, '.state');
|
|
227
|
+
const dbPath = path.join(stateDir, 'subagent_workflows.db');
|
|
228
|
+
const warnings = [];
|
|
229
|
+
const tables = ['schema_version', 'subagent_workflows', 'subagent_workflow_events'];
|
|
230
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
231
|
+
const db = new Database(dbPath);
|
|
232
|
+
try {
|
|
233
|
+
db.pragma('journal_mode = WAL');
|
|
234
|
+
db.pragma('foreign_keys = ON');
|
|
235
|
+
db.pragma('synchronous = NORMAL');
|
|
236
|
+
db.pragma(`busy_timeout = ${DEFAULT_BUSY_TIMEOUT_MS}`);
|
|
237
|
+
db.exec(`
|
|
238
|
+
CREATE TABLE IF NOT EXISTS schema_version (
|
|
239
|
+
version INTEGER NOT NULL
|
|
240
|
+
);
|
|
241
|
+
CREATE TABLE IF NOT EXISTS subagent_workflows (
|
|
242
|
+
workflow_id TEXT PRIMARY KEY,
|
|
243
|
+
workflow_type TEXT NOT NULL,
|
|
244
|
+
transport TEXT NOT NULL,
|
|
245
|
+
parent_session_id TEXT NOT NULL,
|
|
246
|
+
child_session_key TEXT NOT NULL,
|
|
247
|
+
run_id TEXT,
|
|
248
|
+
state TEXT NOT NULL DEFAULT 'pending',
|
|
249
|
+
cleanup_state TEXT NOT NULL DEFAULT 'none',
|
|
250
|
+
created_at INTEGER NOT NULL,
|
|
251
|
+
updated_at INTEGER NOT NULL,
|
|
252
|
+
last_observed_at INTEGER,
|
|
253
|
+
duration_ms INTEGER,
|
|
254
|
+
metadata_json TEXT NOT NULL DEFAULT '{}'
|
|
255
|
+
);
|
|
256
|
+
CREATE TABLE IF NOT EXISTS subagent_workflow_events (
|
|
257
|
+
workflow_id TEXT NOT NULL,
|
|
258
|
+
event_type TEXT NOT NULL,
|
|
259
|
+
from_state TEXT,
|
|
260
|
+
to_state TEXT NOT NULL,
|
|
261
|
+
reason TEXT NOT NULL,
|
|
262
|
+
payload_json TEXT NOT NULL DEFAULT '{}',
|
|
263
|
+
created_at INTEGER NOT NULL,
|
|
264
|
+
FOREIGN KEY (workflow_id) REFERENCES subagent_workflows(workflow_id) ON DELETE CASCADE
|
|
265
|
+
);
|
|
266
|
+
CREATE INDEX IF NOT EXISTS idx_workflows_parent_session ON subagent_workflows(parent_session_id);
|
|
267
|
+
CREATE INDEX IF NOT EXISTS idx_workflows_child_session ON subagent_workflows(child_session_key);
|
|
268
|
+
CREATE INDEX IF NOT EXISTS idx_workflows_state ON subagent_workflows(state);
|
|
269
|
+
CREATE INDEX IF NOT EXISTS idx_workflows_type ON subagent_workflows(workflow_type);
|
|
270
|
+
CREATE INDEX IF NOT EXISTS idx_events_workflow ON subagent_workflow_events(workflow_id);
|
|
271
|
+
`);
|
|
272
|
+
// Run migrations to bring schema up to SCHEMA_VERSION
|
|
273
|
+
const row = db.prepare('SELECT version FROM schema_version LIMIT 1').get();
|
|
274
|
+
const currentVersion = row?.version ?? 0;
|
|
275
|
+
if (currentVersion === 0) {
|
|
276
|
+
db.prepare('INSERT INTO schema_version (version) VALUES (?)').run(SCHEMA_VERSION);
|
|
277
|
+
}
|
|
278
|
+
else if (currentVersion < SCHEMA_VERSION) {
|
|
279
|
+
// Inline migration logic (mirrors WorkflowStore.runMigrations)
|
|
280
|
+
if (currentVersion < 2 && SCHEMA_VERSION >= 2) {
|
|
281
|
+
try {
|
|
282
|
+
db.exec('ALTER TABLE subagent_workflows ADD COLUMN duration_ms INTEGER');
|
|
283
|
+
}
|
|
284
|
+
catch (err) {
|
|
285
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
286
|
+
if (!message.includes('duplicate column name')) {
|
|
287
|
+
warnings.push(`migration v${currentVersion}→v${SCHEMA_VERSION} duration_ms failed: ${message}`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
db.prepare('UPDATE schema_version SET version = ?').run(SCHEMA_VERSION);
|
|
292
|
+
}
|
|
293
|
+
return { tables, warnings };
|
|
294
|
+
}
|
|
295
|
+
finally {
|
|
296
|
+
db.close();
|
|
297
|
+
}
|
|
298
|
+
}
|
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.155.0",
|
|
6
6
|
"activation": {
|
|
7
7
|
"onCapabilities": [
|
|
8
8
|
"hook"
|
package/package.json
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "principles-disciple",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.155.0",
|
|
4
4
|
"description": "Native OpenClaw plugin for Principles Disciple",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./dist/
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
7
8
|
"exports": {
|
|
8
|
-
".":
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
9
13
|
},
|
|
10
14
|
"repository": {
|
|
11
15
|
"type": "git",
|