@devflow-tools/database 0.17.1 → 0.17.3
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/CHANGELOG.md +35 -0
- package/__tests__/database.retrieval-ledger.test.ts +15 -0
- package/__tests__/database.task-runtime.test.ts +73 -0
- package/__tests__/database.work-queue.test.ts +34 -0
- package/dist/database.d.ts +46 -0
- package/dist/database.js +374 -41
- package/dist/index.d.ts +4 -2
- package/dist/index.js +7 -1
- package/dist/node-sqlite.d.ts +1 -1
- package/dist/node-sqlite.js +8 -4
- package/dist/retrieval-ledger.d.ts +8 -1
- package/dist/task-runtime.d.ts +64 -0
- package/dist/task-runtime.js +106 -0
- package/dist/work-queue.d.ts +19 -2
- package/package.json +1 -1
- package/src/database.ts +413 -44
- package/src/index.ts +18 -0
- package/src/node-sqlite.ts +8 -4
- package/src/retrieval-ledger.ts +9 -2
- package/src/task-runtime.ts +169 -0
- package/src/work-queue.ts +21 -1
package/dist/database.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.DevFlowDatabase = void 0;
|
|
4
4
|
exports.getGlobalDevFlowDbPath = getGlobalDevFlowDbPath;
|
|
5
5
|
exports.openGlobalDevFlowDatabase = openGlobalDevFlowDatabase;
|
|
6
|
+
exports.openGlobalDevFlowReadOnlyDatabase = openGlobalDevFlowReadOnlyDatabase;
|
|
6
7
|
const node_sqlite_1 = require("./node-sqlite");
|
|
7
8
|
const path_1 = require("path");
|
|
8
9
|
const fs_1 = require("fs");
|
|
@@ -14,6 +15,7 @@ const retrieval_sessions_1 = require("./retrieval-sessions");
|
|
|
14
15
|
const learning_candidates_1 = require("./learning-candidates");
|
|
15
16
|
const workflow_workers_1 = require("./workflow-workers");
|
|
16
17
|
const task_semantic_control_1 = require("./task-semantic-control");
|
|
18
|
+
const task_runtime_1 = require("./task-runtime");
|
|
17
19
|
const CONTEXT_REQUIRED_SKILLS = new Set([
|
|
18
20
|
'react', 'vue', 'nest', 'nextjs', 'graphql', 'typescript',
|
|
19
21
|
]);
|
|
@@ -62,28 +64,59 @@ function openGlobalDevFlowDatabase(home = (0, os_1.homedir)(), options) {
|
|
|
62
64
|
return new DevFlowDatabase(home, {
|
|
63
65
|
dbPath: getGlobalDevFlowDbPath(home),
|
|
64
66
|
busyTimeoutMs: options?.busyTimeoutMs,
|
|
67
|
+
readonly: options?.readonly,
|
|
65
68
|
});
|
|
66
69
|
}
|
|
70
|
+
function openGlobalDevFlowReadOnlyDatabase(home = (0, os_1.homedir)(), options) {
|
|
71
|
+
return openGlobalDevFlowDatabase(home, { ...options, readonly: true });
|
|
72
|
+
}
|
|
73
|
+
// Schema setup is process-owned. Hook daemons open short-lived connections for
|
|
74
|
+
// bounded transactions, but replaying the full idempotent DDL on every open is
|
|
75
|
+
// still expensive and serializes concurrent host requests. An inode identity
|
|
76
|
+
// keeps the cache safe when a test, repair, or user replaces the database file.
|
|
77
|
+
const initializedDatabaseFiles = new Map();
|
|
78
|
+
function getDatabaseIdentity(path) {
|
|
79
|
+
try {
|
|
80
|
+
const stats = (0, fs_1.statSync)(path);
|
|
81
|
+
return { dev: stats.dev, ino: stats.ino };
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
67
87
|
class DevFlowDatabase {
|
|
68
88
|
constructor(projectRoot, opts) {
|
|
69
89
|
let dbPath;
|
|
70
90
|
if (opts?.dbPath) {
|
|
71
91
|
const dir = (0, path_1.dirname)(opts.dbPath);
|
|
72
|
-
if (!(0, fs_1.existsSync)(dir))
|
|
92
|
+
if (!(0, fs_1.existsSync)(dir) && !opts.readonly)
|
|
73
93
|
(0, fs_1.mkdirSync)(dir, { recursive: true });
|
|
74
94
|
dbPath = opts.dbPath;
|
|
75
95
|
}
|
|
76
96
|
else {
|
|
77
97
|
const devflowDir = (0, path_1.join)(projectRoot, '.devflow');
|
|
78
|
-
if (!(0, fs_1.existsSync)(devflowDir))
|
|
98
|
+
if (!(0, fs_1.existsSync)(devflowDir) && !opts?.readonly)
|
|
79
99
|
(0, fs_1.mkdirSync)(devflowDir, { recursive: true });
|
|
80
100
|
dbPath = (0, path_1.join)(devflowDir, 'devflow.db');
|
|
81
101
|
}
|
|
82
|
-
|
|
83
|
-
|
|
102
|
+
if (opts?.readonly && !(0, fs_1.existsSync)(dbPath))
|
|
103
|
+
throw new Error(`DEVFLOW_DATABASE_NOT_FOUND:${dbPath}`);
|
|
104
|
+
this.db = new node_sqlite_1.NodeSqliteDatabase(dbPath, opts?.busyTimeoutMs, opts?.readonly === true);
|
|
84
105
|
this.db.exec(`PRAGMA busy_timeout = ${Math.max(0, Math.floor(opts?.busyTimeoutMs ?? 5000))}`);
|
|
106
|
+
if (opts?.readonly)
|
|
107
|
+
return;
|
|
85
108
|
this.db.exec('PRAGMA foreign_keys = OFF');
|
|
86
|
-
|
|
109
|
+
const databaseIdentity = getDatabaseIdentity(dbPath);
|
|
110
|
+
const initializedIdentity = initializedDatabaseFiles.get(dbPath);
|
|
111
|
+
const schemaInitialized = databaseIdentity !== null
|
|
112
|
+
&& initializedIdentity?.dev === databaseIdentity.dev
|
|
113
|
+
&& initializedIdentity.ino === databaseIdentity.ino;
|
|
114
|
+
if (!schemaInitialized) {
|
|
115
|
+
this.initializeSchema();
|
|
116
|
+
const currentIdentity = getDatabaseIdentity(dbPath);
|
|
117
|
+
if (currentIdentity)
|
|
118
|
+
initializedDatabaseFiles.set(dbPath, currentIdentity);
|
|
119
|
+
}
|
|
87
120
|
}
|
|
88
121
|
initializeSchema() {
|
|
89
122
|
this.db.exec(`
|
|
@@ -324,6 +357,9 @@ class DevFlowDatabase {
|
|
|
324
357
|
source_id TEXT NOT NULL, stage TEXT NOT NULL, rank INTEGER, raw_score REAL,
|
|
325
358
|
normalized_score REAL, final_score REAL, applicability TEXT NOT NULL DEFAULT '[]',
|
|
326
359
|
reason TEXT, tool_evidence TEXT NOT NULL DEFAULT '[]', verification_receipt TEXT,
|
|
360
|
+
schema_version TEXT NOT NULL DEFAULT 'retrieval-ledger-event.v1', task_spec_hash TEXT,
|
|
361
|
+
actor TEXT, source_version TEXT, source_content_hash TEXT,
|
|
362
|
+
evidence_ids TEXT NOT NULL DEFAULT '[]', reason_code TEXT,
|
|
327
363
|
payload TEXT NOT NULL DEFAULT '{}', created_at INTEGER NOT NULL
|
|
328
364
|
);
|
|
329
365
|
CREATE INDEX IF NOT EXISTS idx_retrieval_ledger_identity ON devflow_retrieval_ledger(
|
|
@@ -428,6 +464,40 @@ class DevFlowDatabase {
|
|
|
428
464
|
CREATE INDEX IF NOT EXISTS idx_terminal_transition_turn
|
|
429
465
|
ON devflow_terminal_transitions(project_root, session_id, turn_id, sequence);
|
|
430
466
|
|
|
467
|
+
CREATE TABLE IF NOT EXISTS devflow_task_runtime_events (
|
|
468
|
+
event_id TEXT PRIMARY KEY,
|
|
469
|
+
schema_version TEXT NOT NULL,
|
|
470
|
+
producer TEXT NOT NULL,
|
|
471
|
+
producer_version TEXT NOT NULL,
|
|
472
|
+
project_root TEXT NOT NULL,
|
|
473
|
+
project_id TEXT NOT NULL,
|
|
474
|
+
host_id TEXT NOT NULL,
|
|
475
|
+
session_id TEXT NOT NULL,
|
|
476
|
+
turn_id TEXT NOT NULL,
|
|
477
|
+
request_id TEXT NOT NULL,
|
|
478
|
+
execution_id TEXT,
|
|
479
|
+
task_spec_hash TEXT NOT NULL,
|
|
480
|
+
sequence INTEGER NOT NULL CHECK(sequence > 0),
|
|
481
|
+
kind TEXT NOT NULL,
|
|
482
|
+
payload_json TEXT NOT NULL DEFAULT '{}',
|
|
483
|
+
created_at INTEGER NOT NULL,
|
|
484
|
+
UNIQUE(project_root, session_id, turn_id, sequence)
|
|
485
|
+
);
|
|
486
|
+
CREATE INDEX IF NOT EXISTS idx_task_runtime_event_identity
|
|
487
|
+
ON devflow_task_runtime_events(project_root, session_id, turn_id, sequence);
|
|
488
|
+
|
|
489
|
+
CREATE TABLE IF NOT EXISTS devflow_task_runtime_snapshots (
|
|
490
|
+
project_root TEXT NOT NULL,
|
|
491
|
+
session_id TEXT NOT NULL,
|
|
492
|
+
turn_id TEXT NOT NULL,
|
|
493
|
+
last_event_sequence INTEGER NOT NULL,
|
|
494
|
+
schema_version TEXT NOT NULL,
|
|
495
|
+
snapshot_json TEXT NOT NULL,
|
|
496
|
+
snapshot_hash TEXT NOT NULL,
|
|
497
|
+
updated_at INTEGER NOT NULL,
|
|
498
|
+
PRIMARY KEY(project_root, session_id, turn_id)
|
|
499
|
+
);
|
|
500
|
+
|
|
431
501
|
CREATE TABLE IF NOT EXISTS devflow_transcript_checkpoints (
|
|
432
502
|
id TEXT PRIMARY KEY,
|
|
433
503
|
project_root TEXT NOT NULL,
|
|
@@ -581,9 +651,11 @@ class DevFlowDatabase {
|
|
|
581
651
|
project_root TEXT NOT NULL,
|
|
582
652
|
session_id TEXT,
|
|
583
653
|
turn_id TEXT,
|
|
654
|
+
source_hash TEXT,
|
|
655
|
+
task_spec_hash TEXT,
|
|
584
656
|
payload TEXT NOT NULL,
|
|
585
657
|
state TEXT NOT NULL DEFAULT 'pending'
|
|
586
|
-
CHECK(state IN ('pending', 'leased', 'completed', 'failed', 'dead_letter')),
|
|
658
|
+
CHECK(state IN ('pending', 'leased', 'completed', 'failed', 'dead_letter', 'cancelled')),
|
|
587
659
|
attempts INTEGER NOT NULL DEFAULT 0 CHECK(attempts >= 0),
|
|
588
660
|
max_attempts INTEGER NOT NULL DEFAULT 5 CHECK(max_attempts > 0),
|
|
589
661
|
lease_owner TEXT,
|
|
@@ -593,7 +665,11 @@ class DevFlowDatabase {
|
|
|
593
665
|
error_message TEXT,
|
|
594
666
|
created_at INTEGER NOT NULL,
|
|
595
667
|
updated_at INTEGER NOT NULL,
|
|
596
|
-
completed_at INTEGER
|
|
668
|
+
completed_at INTEGER,
|
|
669
|
+
cancelled_at INTEGER,
|
|
670
|
+
cancellation_reason TEXT,
|
|
671
|
+
replay_of_id TEXT,
|
|
672
|
+
replay_count INTEGER NOT NULL DEFAULT 0
|
|
597
673
|
);
|
|
598
674
|
|
|
599
675
|
CREATE INDEX IF NOT EXISTS idx_work_items_ready
|
|
@@ -603,6 +679,16 @@ class DevFlowDatabase {
|
|
|
603
679
|
CREATE INDEX IF NOT EXISTS idx_work_items_completed
|
|
604
680
|
ON devflow_work_items(project_root, completed_at DESC);
|
|
605
681
|
|
|
682
|
+
CREATE TABLE IF NOT EXISTS devflow_project_mutation_leases (
|
|
683
|
+
project_root TEXT NOT NULL,
|
|
684
|
+
mutation_kind TEXT NOT NULL,
|
|
685
|
+
owner TEXT NOT NULL,
|
|
686
|
+
lease_expires_at INTEGER NOT NULL,
|
|
687
|
+
source_hash TEXT,
|
|
688
|
+
updated_at INTEGER NOT NULL,
|
|
689
|
+
PRIMARY KEY(project_root, mutation_kind)
|
|
690
|
+
);
|
|
691
|
+
|
|
606
692
|
CREATE TABLE IF NOT EXISTS devflow_session_closures (
|
|
607
693
|
session_id TEXT NOT NULL,
|
|
608
694
|
project_root TEXT NOT NULL,
|
|
@@ -946,6 +1032,58 @@ class DevFlowDatabase {
|
|
|
946
1032
|
this.db.exec('ALTER TABLE devflow_context_receipts ADD COLUMN request_id TEXT');
|
|
947
1033
|
}
|
|
948
1034
|
catch { }
|
|
1035
|
+
try {
|
|
1036
|
+
this.db.exec("ALTER TABLE devflow_retrieval_ledger ADD COLUMN schema_version TEXT NOT NULL DEFAULT 'retrieval-ledger-event.v1'");
|
|
1037
|
+
}
|
|
1038
|
+
catch { }
|
|
1039
|
+
try {
|
|
1040
|
+
this.db.exec('ALTER TABLE devflow_retrieval_ledger ADD COLUMN task_spec_hash TEXT');
|
|
1041
|
+
}
|
|
1042
|
+
catch { }
|
|
1043
|
+
try {
|
|
1044
|
+
this.db.exec('ALTER TABLE devflow_retrieval_ledger ADD COLUMN actor TEXT');
|
|
1045
|
+
}
|
|
1046
|
+
catch { }
|
|
1047
|
+
try {
|
|
1048
|
+
this.db.exec('ALTER TABLE devflow_retrieval_ledger ADD COLUMN source_version TEXT');
|
|
1049
|
+
}
|
|
1050
|
+
catch { }
|
|
1051
|
+
try {
|
|
1052
|
+
this.db.exec('ALTER TABLE devflow_retrieval_ledger ADD COLUMN source_content_hash TEXT');
|
|
1053
|
+
}
|
|
1054
|
+
catch { }
|
|
1055
|
+
try {
|
|
1056
|
+
this.db.exec("ALTER TABLE devflow_retrieval_ledger ADD COLUMN evidence_ids TEXT NOT NULL DEFAULT '[]'");
|
|
1057
|
+
}
|
|
1058
|
+
catch { }
|
|
1059
|
+
try {
|
|
1060
|
+
this.db.exec('ALTER TABLE devflow_retrieval_ledger ADD COLUMN reason_code TEXT');
|
|
1061
|
+
}
|
|
1062
|
+
catch { }
|
|
1063
|
+
try {
|
|
1064
|
+
this.db.exec('ALTER TABLE devflow_work_items ADD COLUMN source_hash TEXT');
|
|
1065
|
+
}
|
|
1066
|
+
catch { }
|
|
1067
|
+
try {
|
|
1068
|
+
this.db.exec('ALTER TABLE devflow_work_items ADD COLUMN task_spec_hash TEXT');
|
|
1069
|
+
}
|
|
1070
|
+
catch { }
|
|
1071
|
+
try {
|
|
1072
|
+
this.db.exec('ALTER TABLE devflow_work_items ADD COLUMN cancelled_at INTEGER');
|
|
1073
|
+
}
|
|
1074
|
+
catch { }
|
|
1075
|
+
try {
|
|
1076
|
+
this.db.exec('ALTER TABLE devflow_work_items ADD COLUMN cancellation_reason TEXT');
|
|
1077
|
+
}
|
|
1078
|
+
catch { }
|
|
1079
|
+
try {
|
|
1080
|
+
this.db.exec('ALTER TABLE devflow_work_items ADD COLUMN replay_of_id TEXT');
|
|
1081
|
+
}
|
|
1082
|
+
catch { }
|
|
1083
|
+
try {
|
|
1084
|
+
this.db.exec('ALTER TABLE devflow_work_items ADD COLUMN replay_count INTEGER NOT NULL DEFAULT 0');
|
|
1085
|
+
}
|
|
1086
|
+
catch { }
|
|
949
1087
|
this.db.exec('UPDATE devflow_rules SET updated_at = created_at WHERE updated_at IS NULL');
|
|
950
1088
|
this.db.exec('CREATE INDEX IF NOT EXISTS idx_devflow_rules_gate ON devflow_rules(gate, enabled)');
|
|
951
1089
|
// Migration: tool_metrics table for per-tool metrics collection
|
|
@@ -1294,14 +1432,20 @@ class DevFlowDatabase {
|
|
|
1294
1432
|
}
|
|
1295
1433
|
getToolCallEventByToolUseId(sessionId, toolUseId) {
|
|
1296
1434
|
const row = this.db.prepare(`
|
|
1297
|
-
SELECT event_id, timestamp, duration
|
|
1435
|
+
SELECT event_id, execution_id, timestamp, duration, input
|
|
1298
1436
|
FROM tool_call_events
|
|
1299
1437
|
WHERE session_id = ? AND tool_use_id = ?
|
|
1300
1438
|
ORDER BY timestamp DESC
|
|
1301
1439
|
LIMIT 1
|
|
1302
1440
|
`).get(sessionId, toolUseId);
|
|
1303
1441
|
return row
|
|
1304
|
-
? {
|
|
1442
|
+
? {
|
|
1443
|
+
eventId: row.event_id,
|
|
1444
|
+
executionId: row.execution_id,
|
|
1445
|
+
timestamp: row.timestamp,
|
|
1446
|
+
duration: row.duration ?? 0,
|
|
1447
|
+
input: parseJsonObject(row.input) ?? {},
|
|
1448
|
+
}
|
|
1305
1449
|
: null;
|
|
1306
1450
|
}
|
|
1307
1451
|
listToolCallEventsBySessions(sessionIds) {
|
|
@@ -2374,7 +2518,8 @@ class DevFlowDatabase {
|
|
|
2374
2518
|
throw new Error('Work idempotency key is required');
|
|
2375
2519
|
if (!input.projectRoot.trim())
|
|
2376
2520
|
throw new Error('Work project root is required');
|
|
2377
|
-
const maxAttempts = input.maxAttempts
|
|
2521
|
+
const maxAttempts = input.maxAttempts
|
|
2522
|
+
?? (input.kind === 'knowledge.ingest' || input.kind === 'knowledge.index_refresh' ? 3 : 5);
|
|
2378
2523
|
if (!Number.isSafeInteger(maxAttempts) || maxAttempts <= 0) {
|
|
2379
2524
|
throw new Error('Work maxAttempts must be a positive safe integer');
|
|
2380
2525
|
}
|
|
@@ -2385,10 +2530,10 @@ class DevFlowDatabase {
|
|
|
2385
2530
|
}
|
|
2386
2531
|
const row = this.db.prepare(`
|
|
2387
2532
|
INSERT INTO devflow_work_items (
|
|
2388
|
-
id, idempotency_key, kind, project_root, session_id, turn_id, payload,
|
|
2533
|
+
id, idempotency_key, kind, project_root, session_id, turn_id, source_hash, task_spec_hash, payload,
|
|
2389
2534
|
state, attempts, max_attempts, lease_owner, lease_expires_at,
|
|
2390
2535
|
next_attempt_at, error_category, error_message, created_at, updated_at, completed_at
|
|
2391
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, 'pending', 0, ?, NULL, NULL, ?, NULL, NULL, ?, ?, NULL)
|
|
2536
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', 0, ?, NULL, NULL, ?, NULL, NULL, ?, ?, NULL)
|
|
2392
2537
|
ON CONFLICT(idempotency_key) DO UPDATE SET
|
|
2393
2538
|
state = CASE
|
|
2394
2539
|
WHEN devflow_work_items.state = 'dead_letter'
|
|
@@ -2422,13 +2567,58 @@ class DevFlowDatabase {
|
|
|
2422
2567
|
ELSE devflow_work_items.updated_at
|
|
2423
2568
|
END
|
|
2424
2569
|
RETURNING *
|
|
2425
|
-
`).get((0, crypto_1.randomUUID)(), input.idempotencyKey, input.kind, input.projectRoot, input.sessionId ?? null, input.turnId ?? null, JSON.stringify(input.payload ?? null), maxAttempts, nextAttemptAt, createdAt, createdAt);
|
|
2570
|
+
`).get((0, crypto_1.randomUUID)(), input.idempotencyKey, input.kind, input.projectRoot, input.sessionId ?? null, input.turnId ?? null, input.sourceHash ?? null, input.taskSpecHash ?? null, JSON.stringify(input.payload ?? null), maxAttempts, nextAttemptAt, createdAt, createdAt);
|
|
2426
2571
|
return this.mapWorkItem(row);
|
|
2427
2572
|
}
|
|
2428
2573
|
getWorkByIdempotencyKey(idempotencyKey) {
|
|
2429
2574
|
const row = this.db.prepare('SELECT * FROM devflow_work_items WHERE idempotency_key = ?').get(idempotencyKey);
|
|
2430
2575
|
return row ? this.mapWorkItem(row) : null;
|
|
2431
2576
|
}
|
|
2577
|
+
getWorkById(id) {
|
|
2578
|
+
const row = this.db.prepare('SELECT * FROM devflow_work_items WHERE id = ?').get(id);
|
|
2579
|
+
return row ? this.mapWorkItem(row) : null;
|
|
2580
|
+
}
|
|
2581
|
+
acquireProjectMutationLease(input) {
|
|
2582
|
+
const now = input.now ?? Date.now();
|
|
2583
|
+
return this.db.prepare(`
|
|
2584
|
+
INSERT INTO devflow_project_mutation_leases
|
|
2585
|
+
(project_root, mutation_kind, owner, lease_expires_at, source_hash, updated_at)
|
|
2586
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
2587
|
+
ON CONFLICT(project_root, mutation_kind) DO UPDATE SET
|
|
2588
|
+
owner = excluded.owner, lease_expires_at = excluded.lease_expires_at,
|
|
2589
|
+
source_hash = excluded.source_hash, updated_at = excluded.updated_at
|
|
2590
|
+
WHERE devflow_project_mutation_leases.lease_expires_at <= ? OR devflow_project_mutation_leases.owner = excluded.owner
|
|
2591
|
+
`).run(input.projectRoot, input.mutationKind, input.owner, now + input.leaseMs, input.sourceHash ?? null, now, now).changes > 0;
|
|
2592
|
+
}
|
|
2593
|
+
releaseProjectMutationLease(projectRoot, mutationKind, owner) {
|
|
2594
|
+
return this.db.prepare('DELETE FROM devflow_project_mutation_leases WHERE project_root = ? AND mutation_kind = ? AND owner = ?')
|
|
2595
|
+
.run(projectRoot, mutationKind, owner).changes > 0;
|
|
2596
|
+
}
|
|
2597
|
+
cancelStaleWork(input) {
|
|
2598
|
+
const current = this.getWorkById(input.id);
|
|
2599
|
+
const stale = current && current.state === 'leased' && current.leaseOwner === input.owner
|
|
2600
|
+
&& ((input.sourceHash !== undefined && current.sourceHash !== input.sourceHash)
|
|
2601
|
+
|| (input.taskSpecHash !== undefined && current.taskSpecHash !== input.taskSpecHash));
|
|
2602
|
+
if (!stale)
|
|
2603
|
+
return false;
|
|
2604
|
+
const now = input.now ?? Date.now();
|
|
2605
|
+
try {
|
|
2606
|
+
return this.db.prepare(`UPDATE devflow_work_items SET state = 'cancelled', lease_owner = NULL, lease_expires_at = NULL, cancelled_at = ?, cancellation_reason = ?, updated_at = ? WHERE id = ? AND state = 'leased' AND lease_owner = ?`)
|
|
2607
|
+
.run(now, input.reason ?? 'stale_source_or_task_spec', now, input.id, input.owner).changes === 1;
|
|
2608
|
+
}
|
|
2609
|
+
catch {
|
|
2610
|
+
return this.db.prepare(`UPDATE devflow_work_items SET state = 'completed', lease_owner = NULL, lease_expires_at = NULL, cancelled_at = ?, cancellation_reason = ?, updated_at = ? WHERE id = ? AND state = 'leased' AND lease_owner = ?`)
|
|
2611
|
+
.run(now, input.reason ?? 'stale_source_or_task_spec', now, input.id, input.owner).changes === 1;
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2614
|
+
replayDeadLetterWork(id, input) {
|
|
2615
|
+
const original = this.getWorkById(id);
|
|
2616
|
+
if (!original || original.state !== 'dead_letter')
|
|
2617
|
+
return null;
|
|
2618
|
+
const replay = this.enqueueWork({ idempotencyKey: input.idempotencyKey, kind: original.kind, projectRoot: original.projectRoot, sessionId: original.sessionId, turnId: original.turnId, payload: original.payload, sourceHash: input.sourceHash ?? original.sourceHash, taskSpecHash: input.taskSpecHash ?? original.taskSpecHash, maxAttempts: original.maxAttempts, nextAttemptAt: input.now ?? Date.now() });
|
|
2619
|
+
this.db.prepare('UPDATE devflow_work_items SET replay_of_id = ?, replay_count = replay_count + 1 WHERE id = ?').run(original.id, replay.id);
|
|
2620
|
+
return this.getWorkById(replay.id);
|
|
2621
|
+
}
|
|
2432
2622
|
requestSessionClosure(input) {
|
|
2433
2623
|
if (!input.sessionId.trim())
|
|
2434
2624
|
throw new Error('Session closure requires a session ID');
|
|
@@ -2457,7 +2647,8 @@ class DevFlowDatabase {
|
|
|
2457
2647
|
SELECT COUNT(*) AS count
|
|
2458
2648
|
FROM devflow_work_items
|
|
2459
2649
|
WHERE project_root = ? AND session_id = ?
|
|
2460
|
-
AND state IN ('pending', 'leased', 'failed'
|
|
2650
|
+
AND state IN ('pending', 'leased', 'failed')
|
|
2651
|
+
AND attempts < max_attempts
|
|
2461
2652
|
`).get(input.projectRoot, input.sessionId);
|
|
2462
2653
|
const pendingWorkCount = Number(pending?.count ?? 0);
|
|
2463
2654
|
this.db.prepare(`
|
|
@@ -2518,6 +2709,8 @@ class DevFlowDatabase {
|
|
|
2518
2709
|
}
|
|
2519
2710
|
if (input.kinds?.length === 0)
|
|
2520
2711
|
return [];
|
|
2712
|
+
if (input.workItemIds?.length === 0)
|
|
2713
|
+
return [];
|
|
2521
2714
|
const now = input.now ?? Date.now();
|
|
2522
2715
|
const leaseExpiresAt = now + input.leaseMs;
|
|
2523
2716
|
if (!Number.isSafeInteger(now) || !Number.isSafeInteger(leaseExpiresAt)) {
|
|
@@ -2525,9 +2718,13 @@ class DevFlowDatabase {
|
|
|
2525
2718
|
}
|
|
2526
2719
|
const limit = Math.min(Math.floor(input.limit), 1000);
|
|
2527
2720
|
const kinds = input.kinds ? [...new Set(input.kinds)] : undefined;
|
|
2721
|
+
const workItemIds = input.workItemIds ? [...new Set(input.workItemIds)] : undefined;
|
|
2528
2722
|
const kindClause = kinds
|
|
2529
2723
|
? `AND kind IN (${kinds.map(() => '?').join(', ')})`
|
|
2530
2724
|
: '';
|
|
2725
|
+
const workItemClause = workItemIds
|
|
2726
|
+
? `AND id IN (${workItemIds.map(() => '?').join(', ')})`
|
|
2727
|
+
: '';
|
|
2531
2728
|
this.db.exec('BEGIN IMMEDIATE');
|
|
2532
2729
|
try {
|
|
2533
2730
|
const candidates = this.db.prepare(`
|
|
@@ -2540,9 +2737,10 @@ class DevFlowDatabase {
|
|
|
2540
2737
|
AND next_attempt_at <= ?
|
|
2541
2738
|
AND attempts < max_attempts
|
|
2542
2739
|
${kindClause}
|
|
2740
|
+
${workItemClause}
|
|
2543
2741
|
ORDER BY next_attempt_at ASC, created_at ASC, id ASC
|
|
2544
2742
|
LIMIT ?
|
|
2545
|
-
`).all(input.projectRoot, now, ...(kinds ?? []), limit);
|
|
2743
|
+
`).all(input.projectRoot, now, ...(kinds ?? []), ...(workItemIds ?? []), limit);
|
|
2546
2744
|
const leased = [];
|
|
2547
2745
|
for (const candidate of candidates) {
|
|
2548
2746
|
const result = this.db.prepare(`
|
|
@@ -2672,8 +2870,10 @@ class DevFlowDatabase {
|
|
|
2672
2870
|
projectRoot: row.project_root,
|
|
2673
2871
|
sessionId: row.session_id ?? undefined,
|
|
2674
2872
|
turnId: row.turn_id ?? undefined,
|
|
2873
|
+
sourceHash: row.source_hash ?? undefined,
|
|
2874
|
+
taskSpecHash: row.task_spec_hash ?? undefined,
|
|
2675
2875
|
payload: parseJson(row.payload),
|
|
2676
|
-
state: row.state,
|
|
2876
|
+
state: row.cancelled_at != null ? 'cancelled' : row.state,
|
|
2677
2877
|
attempts: Number(row.attempts),
|
|
2678
2878
|
maxAttempts: Number(row.max_attempts),
|
|
2679
2879
|
leaseOwner: row.lease_owner ?? undefined,
|
|
@@ -2684,6 +2884,10 @@ class DevFlowDatabase {
|
|
|
2684
2884
|
createdAt: Number(row.created_at),
|
|
2685
2885
|
updatedAt: Number(row.updated_at),
|
|
2686
2886
|
completedAt: row.completed_at == null ? undefined : Number(row.completed_at),
|
|
2887
|
+
cancelledAt: row.cancelled_at == null ? undefined : Number(row.cancelled_at),
|
|
2888
|
+
cancellationReason: row.cancellation_reason ?? undefined,
|
|
2889
|
+
replayOfId: row.replay_of_id ?? undefined,
|
|
2890
|
+
replayCount: Number(row.replay_count ?? 0),
|
|
2687
2891
|
};
|
|
2688
2892
|
}
|
|
2689
2893
|
mapSessionClosure(row) {
|
|
@@ -2714,28 +2918,11 @@ class DevFlowDatabase {
|
|
|
2714
2918
|
SELECT COUNT(*) AS count
|
|
2715
2919
|
FROM devflow_work_items
|
|
2716
2920
|
WHERE project_root = ? AND session_id = ?
|
|
2717
|
-
AND state IN ('pending', 'leased', 'failed'
|
|
2921
|
+
AND state IN ('pending', 'leased', 'failed')
|
|
2922
|
+
AND attempts < max_attempts
|
|
2718
2923
|
AND (? IS NULL OR id <> ?)
|
|
2719
2924
|
`).get(projectRoot, sessionId, excludingWorkItemId ?? null, excludingWorkItemId ?? null);
|
|
2720
|
-
|
|
2721
|
-
SELECT COUNT(*) AS count
|
|
2722
|
-
FROM devflow_session_obligations
|
|
2723
|
-
WHERE project_root = ? AND session_id = ?
|
|
2724
|
-
AND state IN ('open', 'degraded')
|
|
2725
|
-
`).get(projectRoot, sessionId);
|
|
2726
|
-
const legacyTurns = this.db.prepare(`
|
|
2727
|
-
SELECT COUNT(*) AS count
|
|
2728
|
-
FROM devflow_memory_turns t
|
|
2729
|
-
WHERE t.project_root = ? AND t.session_id = ? AND t.status = 'pending'
|
|
2730
|
-
AND NOT EXISTS (
|
|
2731
|
-
SELECT 1 FROM devflow_session_obligations o
|
|
2732
|
-
WHERE o.project_root = t.project_root AND o.session_id = t.session_id
|
|
2733
|
-
AND o.obligation_id = 'memory:' || t.turn_id
|
|
2734
|
-
)
|
|
2735
|
-
`).get(projectRoot, sessionId);
|
|
2736
|
-
return Number(work?.count ?? 0)
|
|
2737
|
-
+ Number(obligations?.count ?? 0)
|
|
2738
|
-
+ Number(legacyTurns?.count ?? 0);
|
|
2925
|
+
return Number(work?.count ?? 0);
|
|
2739
2926
|
}
|
|
2740
2927
|
// ---- Hook Lifecycle ----
|
|
2741
2928
|
getHookReceipt(projectRoot) {
|
|
@@ -3041,6 +3228,33 @@ class DevFlowDatabase {
|
|
|
3041
3228
|
requestId: row.request_id ?? undefined,
|
|
3042
3229
|
} : null;
|
|
3043
3230
|
}
|
|
3231
|
+
getContextReceiptForRequest(projectRoot, sessionId, requestId) {
|
|
3232
|
+
const row = this.db.prepare(`
|
|
3233
|
+
SELECT project_root, session_id, execution_id, context_hash, issued_at, expires_at,
|
|
3234
|
+
selected_files, memory_ids, canonical_next_action, canonical_action_json,
|
|
3235
|
+
action_attempts, action_satisfied_at, action_degradation_json, request_id
|
|
3236
|
+
FROM devflow_context_receipts
|
|
3237
|
+
WHERE project_root = ? AND session_id = ? AND request_id = ?
|
|
3238
|
+
ORDER BY issued_at DESC
|
|
3239
|
+
LIMIT 1
|
|
3240
|
+
`).get(projectRoot, sessionId, requestId);
|
|
3241
|
+
return row ? {
|
|
3242
|
+
projectRoot: row.project_root,
|
|
3243
|
+
sessionId: row.session_id,
|
|
3244
|
+
executionId: row.execution_id,
|
|
3245
|
+
contextHash: row.context_hash,
|
|
3246
|
+
issuedAt: row.issued_at,
|
|
3247
|
+
expiresAt: row.expires_at,
|
|
3248
|
+
selectedFiles: parseJsonStringArray(row.selected_files),
|
|
3249
|
+
memoryIds: parseJsonStringArray(row.memory_ids),
|
|
3250
|
+
canonicalNextAction: row.canonical_next_action ?? undefined,
|
|
3251
|
+
canonicalAction: parseJsonObject(row.canonical_action_json),
|
|
3252
|
+
actionAttempts: parseJsonArray(row.action_attempts),
|
|
3253
|
+
actionSatisfiedAt: row.action_satisfied_at ?? undefined,
|
|
3254
|
+
actionDegradation: parseJsonObject(row.action_degradation_json),
|
|
3255
|
+
requestId: row.request_id ?? undefined,
|
|
3256
|
+
} : null;
|
|
3257
|
+
}
|
|
3044
3258
|
recordContextSelectionEvent(event) {
|
|
3045
3259
|
return this.db.prepare(`
|
|
3046
3260
|
INSERT OR IGNORE INTO devflow_context_selection_events
|
|
@@ -3061,8 +3275,9 @@ class DevFlowDatabase {
|
|
|
3061
3275
|
id, project_root, session_id, execution_id, turn_id, request_id, context_receipt,
|
|
3062
3276
|
source_type, source_id, stage, rank, raw_score, normalized_score, final_score,
|
|
3063
3277
|
applicability, reason, tool_evidence, verification_receipt, payload, created_at
|
|
3064
|
-
|
|
3065
|
-
|
|
3278
|
+
, schema_version, task_spec_hash, actor, source_version, source_content_hash, evidence_ids, reason_code
|
|
3279
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3280
|
+
`).run(event.id, event.projectRoot, event.sessionId, event.executionId ?? null, event.turnId ?? null, event.requestId, event.contextReceipt, event.sourceType, event.sourceId, event.stage, event.rank ?? null, event.rawScore ?? null, event.normalizedScore ?? null, event.finalScore ?? null, JSON.stringify(event.applicability), event.reason ?? null, JSON.stringify(event.toolEvidence), event.verificationReceipt ?? null, JSON.stringify(event.payload), event.createdAt, event.schemaVersion ?? 'retrieval-ledger-event.v1', event.taskSpecHash ?? null, event.actor ?? null, event.sourceVersion ?? null, event.sourceContentHash ?? null, JSON.stringify(event.evidenceIds ?? []), event.reasonCode ?? null).changes > 0;
|
|
3066
3281
|
}
|
|
3067
3282
|
appendTaskIntentArtifact(record) {
|
|
3068
3283
|
(0, task_semantic_control_1.assertTaskIdentity)(record);
|
|
@@ -3252,6 +3467,98 @@ class DevFlowDatabase {
|
|
|
3252
3467
|
`).run(record.receiptId, record.projectRoot, record.projectId, record.hostId, record.sessionId, record.turnId, record.requestId, record.executionId ?? null, record.sequence, record.fromState ?? null, record.toState, record.sourceReceiptId ?? null, record.reason, (0, task_semantic_control_1.stableSemanticJson)(record.payload), record.createdAt);
|
|
3253
3468
|
return this.getTerminalTransition(record.receiptId);
|
|
3254
3469
|
}
|
|
3470
|
+
appendTaskRuntimeEvent(event) {
|
|
3471
|
+
const existing = this.getTaskRuntimeEvent(event.eventId);
|
|
3472
|
+
if (existing) {
|
|
3473
|
+
if ((0, task_runtime_1.stableTaskRuntimeJson)(existing) !== (0, task_runtime_1.stableTaskRuntimeJson)(event)) {
|
|
3474
|
+
throw new Error(`TASK_RUNTIME_EVENT_CONFLICT:${event.eventId}`);
|
|
3475
|
+
}
|
|
3476
|
+
return existing;
|
|
3477
|
+
}
|
|
3478
|
+
const atSequence = this.db.prepare(`
|
|
3479
|
+
SELECT event_id FROM devflow_task_runtime_events
|
|
3480
|
+
WHERE project_root = ? AND session_id = ? AND turn_id = ? AND sequence = ?
|
|
3481
|
+
`).get(event.identity.projectRoot, event.identity.sessionId, event.identity.turnId, event.sequence);
|
|
3482
|
+
if (atSequence)
|
|
3483
|
+
throw new Error(`TASK_RUNTIME_SEQUENCE_CONFLICT:${event.sequence}`);
|
|
3484
|
+
try {
|
|
3485
|
+
this.db.prepare(`
|
|
3486
|
+
INSERT INTO devflow_task_runtime_events (
|
|
3487
|
+
event_id, schema_version, producer, producer_version, project_root, project_id,
|
|
3488
|
+
host_id, session_id, turn_id, request_id, execution_id, task_spec_hash,
|
|
3489
|
+
sequence, kind, payload_json, created_at
|
|
3490
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3491
|
+
`).run(event.eventId, event.schemaVersion, event.producer, event.producerVersion, event.identity.projectRoot, event.identity.projectId, event.identity.hostId, event.identity.sessionId, event.identity.turnId, event.identity.requestId, event.identity.executionId ?? null, event.taskSpecHash, event.sequence, event.kind, (0, task_runtime_1.stableTaskRuntimeJson)(event.payload), event.createdAt);
|
|
3492
|
+
}
|
|
3493
|
+
catch (error) {
|
|
3494
|
+
const concurrentEvent = this.getTaskRuntimeEvent(event.eventId);
|
|
3495
|
+
if (concurrentEvent) {
|
|
3496
|
+
if ((0, task_runtime_1.stableTaskRuntimeJson)(concurrentEvent) === (0, task_runtime_1.stableTaskRuntimeJson)(event))
|
|
3497
|
+
return concurrentEvent;
|
|
3498
|
+
throw new Error(`TASK_RUNTIME_EVENT_CONFLICT:${event.eventId}`);
|
|
3499
|
+
}
|
|
3500
|
+
const concurrentSequence = this.db.prepare(`
|
|
3501
|
+
SELECT event_id FROM devflow_task_runtime_events
|
|
3502
|
+
WHERE project_root = ? AND session_id = ? AND turn_id = ? AND sequence = ?
|
|
3503
|
+
`).get(event.identity.projectRoot, event.identity.sessionId, event.identity.turnId, event.sequence);
|
|
3504
|
+
if (concurrentSequence)
|
|
3505
|
+
throw new Error(`TASK_RUNTIME_SEQUENCE_CONFLICT:${event.sequence}`);
|
|
3506
|
+
throw error;
|
|
3507
|
+
}
|
|
3508
|
+
return this.getTaskRuntimeEvent(event.eventId);
|
|
3509
|
+
}
|
|
3510
|
+
getTaskRuntimeEvent(eventId) {
|
|
3511
|
+
const row = this.db.prepare('SELECT * FROM devflow_task_runtime_events WHERE event_id = ?')
|
|
3512
|
+
.get(eventId);
|
|
3513
|
+
return row ? (0, task_runtime_1.mapTaskRuntimeEventRow)(row) : null;
|
|
3514
|
+
}
|
|
3515
|
+
listTaskRuntimeEvents(projectRoot, sessionId, turnId) {
|
|
3516
|
+
return this.db.prepare(`
|
|
3517
|
+
SELECT * FROM devflow_task_runtime_events
|
|
3518
|
+
WHERE project_root = ? AND session_id = ? AND turn_id = ?
|
|
3519
|
+
ORDER BY sequence ASC
|
|
3520
|
+
`).all(projectRoot, sessionId, turnId)
|
|
3521
|
+
.map(task_runtime_1.mapTaskRuntimeEventRow);
|
|
3522
|
+
}
|
|
3523
|
+
putTaskRuntimeSnapshot(snapshot) {
|
|
3524
|
+
const current = this.getTaskRuntimeSnapshot(snapshot.identity.projectRoot, snapshot.identity.sessionId, snapshot.identity.turnId);
|
|
3525
|
+
if (current && current.lastEventSequence > snapshot.lastEventSequence) {
|
|
3526
|
+
throw new Error('TASK_RUNTIME_SNAPSHOT_REGRESSION');
|
|
3527
|
+
}
|
|
3528
|
+
this.db.prepare(`
|
|
3529
|
+
INSERT INTO devflow_task_runtime_snapshots (
|
|
3530
|
+
project_root, session_id, turn_id, last_event_sequence, schema_version,
|
|
3531
|
+
snapshot_json, snapshot_hash, updated_at
|
|
3532
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
3533
|
+
ON CONFLICT(project_root, session_id, turn_id) DO UPDATE SET
|
|
3534
|
+
last_event_sequence = excluded.last_event_sequence,
|
|
3535
|
+
schema_version = excluded.schema_version,
|
|
3536
|
+
snapshot_json = excluded.snapshot_json,
|
|
3537
|
+
snapshot_hash = excluded.snapshot_hash,
|
|
3538
|
+
updated_at = excluded.updated_at
|
|
3539
|
+
WHERE excluded.last_event_sequence >= devflow_task_runtime_snapshots.last_event_sequence
|
|
3540
|
+
`).run(snapshot.identity.projectRoot, snapshot.identity.sessionId, snapshot.identity.turnId, snapshot.lastEventSequence, snapshot.schemaVersion, (0, task_runtime_1.stableTaskRuntimeJson)(snapshot), snapshot.snapshotHash, snapshot.updatedAt);
|
|
3541
|
+
return this.getTaskRuntimeSnapshot(snapshot.identity.projectRoot, snapshot.identity.sessionId, snapshot.identity.turnId);
|
|
3542
|
+
}
|
|
3543
|
+
appendTaskRuntimeEventAndSnapshot(event, snapshot) {
|
|
3544
|
+
return this.db.transaction(() => {
|
|
3545
|
+
this.appendTaskRuntimeEvent(event);
|
|
3546
|
+
return this.putTaskRuntimeSnapshot(snapshot);
|
|
3547
|
+
});
|
|
3548
|
+
}
|
|
3549
|
+
getTaskRuntimeSnapshot(projectRoot, sessionId, turnId) {
|
|
3550
|
+
const row = this.db.prepare(`
|
|
3551
|
+
SELECT * FROM devflow_task_runtime_snapshots
|
|
3552
|
+
WHERE project_root = ? AND session_id = ? AND turn_id = ?
|
|
3553
|
+
`).get(projectRoot, sessionId, turnId);
|
|
3554
|
+
return row ? (0, task_runtime_1.mapTaskRuntimeSnapshotRow)(row) : null;
|
|
3555
|
+
}
|
|
3556
|
+
deleteTaskRuntimeSnapshot(projectRoot, sessionId, turnId) {
|
|
3557
|
+
return this.db.prepare(`
|
|
3558
|
+
DELETE FROM devflow_task_runtime_snapshots
|
|
3559
|
+
WHERE project_root = ? AND session_id = ? AND turn_id = ?
|
|
3560
|
+
`).run(projectRoot, sessionId, turnId).changes > 0;
|
|
3561
|
+
}
|
|
3255
3562
|
getTerminalTransition(receiptId) {
|
|
3256
3563
|
const row = this.db.prepare(`
|
|
3257
3564
|
SELECT * FROM devflow_terminal_transitions WHERE receipt_id = ?
|
|
@@ -3307,7 +3614,11 @@ class DevFlowDatabase {
|
|
|
3307
3614
|
id: row.id, projectRoot: row.project_root, sessionId: row.session_id,
|
|
3308
3615
|
executionId: row.execution_id ?? undefined, turnId: row.turn_id ?? undefined,
|
|
3309
3616
|
requestId: row.request_id, contextReceipt: row.context_receipt,
|
|
3310
|
-
|
|
3617
|
+
schemaVersion: row.schema_version ?? 'retrieval-ledger-event.v1',
|
|
3618
|
+
sourceType: row.source_type, sourceId: row.source_id, stage: row.stage === 'adopted' ? 'consumed' : row.stage,
|
|
3619
|
+
taskSpecHash: row.task_spec_hash ?? undefined, actor: row.actor ?? undefined,
|
|
3620
|
+
sourceVersion: row.source_version ?? undefined, sourceContentHash: row.source_content_hash ?? undefined,
|
|
3621
|
+
evidenceIds: parseJsonStringArray(row.evidence_ids), reasonCode: row.reason_code ?? undefined,
|
|
3311
3622
|
rank: row.rank ?? undefined, rawScore: row.raw_score ?? undefined,
|
|
3312
3623
|
normalizedScore: row.normalized_score ?? undefined, finalScore: row.final_score ?? undefined,
|
|
3313
3624
|
applicability: parseJsonStringArray(row.applicability), reason: row.reason ?? undefined,
|
|
@@ -3459,6 +3770,15 @@ class DevFlowDatabase {
|
|
|
3459
3770
|
if (!existing)
|
|
3460
3771
|
throw new Error(`Session obligation ${input.obligationId} does not exist`);
|
|
3461
3772
|
if (existing.state !== 'open') {
|
|
3773
|
+
if (existing.state === 'degraded' && input.state === 'satisfied' && input.receiptId) {
|
|
3774
|
+
const resolvedAt = input.resolvedAt ?? Date.now();
|
|
3775
|
+
this.db.prepare(`
|
|
3776
|
+
UPDATE devflow_session_obligations
|
|
3777
|
+
SET state = 'satisfied', receipt_id = ?, reason = ?, resolved_at = ?, updated_at = ?
|
|
3778
|
+
WHERE project_root = ? AND session_id = ? AND obligation_id = ? AND state = 'degraded'
|
|
3779
|
+
`).run(input.receiptId, input.reason ?? 'superseded_by_corrected_evidence', resolvedAt, resolvedAt, input.projectRoot, input.sessionId, input.obligationId);
|
|
3780
|
+
return this.getSessionObligation(input.projectRoot, input.sessionId, input.obligationId);
|
|
3781
|
+
}
|
|
3462
3782
|
const sameResolution = existing.state === input.state
|
|
3463
3783
|
&& (input.receiptId === undefined || existing.receiptId === input.receiptId);
|
|
3464
3784
|
if (sameResolution)
|
|
@@ -3517,14 +3837,27 @@ class DevFlowDatabase {
|
|
|
3517
3837
|
});
|
|
3518
3838
|
return turn;
|
|
3519
3839
|
}
|
|
3840
|
+
updateCommittedMemoryTurnProjection(input) {
|
|
3841
|
+
const turnId = (0, obligation_ledger_1.normalizeTurnId)(input.turnId);
|
|
3842
|
+
this.db.prepare(`
|
|
3843
|
+
UPDATE devflow_memory_turns
|
|
3844
|
+
SET memory_ids = ?, reason = COALESCE(?, reason)
|
|
3845
|
+
WHERE turn_id = ? AND status = 'committed' AND source = 'explicit_intent'
|
|
3846
|
+
`).run(JSON.stringify([...new Set(input.memoryIds)]), input.reason ?? null, turnId);
|
|
3847
|
+
const turn = this.getMemoryTurn(turnId);
|
|
3848
|
+
if (!turn || turn.status !== 'committed') {
|
|
3849
|
+
throw new Error(`Committed explicit memory turn ${turnId} does not exist`);
|
|
3850
|
+
}
|
|
3851
|
+
return turn;
|
|
3852
|
+
}
|
|
3520
3853
|
skipMemoryTurn(input) {
|
|
3521
3854
|
const turnId = (0, obligation_ledger_1.normalizeTurnId)(input.turnId);
|
|
3522
3855
|
this.db.prepare(`
|
|
3523
3856
|
UPDATE devflow_memory_turns
|
|
3524
|
-
SET status = 'skipped', receipt_id = ?, memory_ids = '[]', source =
|
|
3857
|
+
SET status = 'skipped', receipt_id = ?, memory_ids = '[]', source = ?,
|
|
3525
3858
|
reason = ?, decided_at = ?
|
|
3526
3859
|
WHERE turn_id = ? AND status = 'pending'
|
|
3527
|
-
`).run(input.receiptId, input.reason, input.decidedAt ?? Date.now(), turnId);
|
|
3860
|
+
`).run(input.receiptId, input.source ?? 'host_skip', input.reason, input.decidedAt ?? Date.now(), turnId);
|
|
3528
3861
|
const turn = this.getMemoryTurn(turnId);
|
|
3529
3862
|
if (!turn || turn.status !== 'skipped') {
|
|
3530
3863
|
throw new Error(`Memory turn ${turnId} is not pending or does not exist`);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export { DevFlowDatabase, getGlobalDevFlowDbPath, openGlobalDevFlowDatabase, } from './database';
|
|
1
|
+
export { DevFlowDatabase, getGlobalDevFlowDbPath, openGlobalDevFlowDatabase, openGlobalDevFlowReadOnlyDatabase, } from './database';
|
|
2
2
|
export type { BenchmarkReportMetaRecord, BenchmarkReportRecord, FeedbackRecord, GovernanceAuditRecord, GovernanceRuleRecord, HookFallbackRecord, HookReceiptRecord, ContextReceiptRecord, ContextSelectionEventRecord, PolicyVerificationBaselineRecord, MemoryDistillCheckpointRecord, MemoryTurnRecord, MemoryTurnStatus, TelemetryFailureRecord, } from './database';
|
|
3
|
-
export type { EnqueueWorkInput, LeaseWorkInput, RequestSessionClosureInput, SessionClosureRecord, SessionClosureState, WorkError, WorkItemRecord, WorkKind, WorkQueueHealth, WorkState, } from './work-queue';
|
|
3
|
+
export type { EnqueueWorkInput, LeaseWorkInput, RequestSessionClosureInput, SessionClosureRecord, SessionClosureState, WorkError, WorkItemRecord, WorkKind, WorkQueueHealth, WorkState, ProjectMutationLeaseRecord, } from './work-queue';
|
|
4
4
|
export type { RetrievalLedgerEventRecord, RetrievalLedgerSourceType, RetrievalLedgerStage, } from './retrieval-ledger';
|
|
5
5
|
export { hostActionReportsEqual, isHostActionState, mapHostActionRow, serializeHostActionReport, } from './host-actions';
|
|
6
6
|
export { RETRIEVAL_MAX_CYCLES, isRetrievalSessionState } from './retrieval-sessions';
|
|
@@ -8,6 +8,8 @@ export type { AppendRetrievalCycleInput, CreateRetrievalSessionInput, RetrievalC
|
|
|
8
8
|
export type { FailHostActionInput, HostActionRecord, HostActionState, ReportHostActionInput, RequestHostActionInput, StartHostActionInput, VerifyHostActionInput, } from './host-actions';
|
|
9
9
|
export { mapSessionObligationRow, normalizeTurnId, } from './obligation-ledger';
|
|
10
10
|
export { assertTaskIdentity, mapChannelQueryPlanRow, mapTaskIntentArtifactRow, mapTerminalTransitionRow, mapToolNameResolutionRow, mapTranscriptCheckpointRow, stableSemanticJson, } from './task-semantic-control';
|
|
11
|
+
export { mapTaskRuntimeEventRow, mapTaskRuntimeSnapshotRow, stableTaskRuntimeHash, stableTaskRuntimeJson, } from './task-runtime';
|
|
12
|
+
export type { TaskRuntimeChannelRecord, TaskRuntimeChannelRequirement, TaskRuntimeChannelStatus, TaskRuntimeEventRecord, TaskRuntimeIdentityRecord, TaskRuntimeObligationRecord, TaskRuntimeObligationState, TaskRuntimeSnapshotRecord, } from './task-runtime';
|
|
11
13
|
export type { ChannelQueryPlanRecord, TaskIdentityRecord, TaskIntentArtifactRecord, TerminalTransitionRecord, ToolNameResolutionRecord, ToolNameResolutionStatus, TranscriptCheckpointRecord, } from './task-semantic-control';
|
|
12
14
|
export { LEGAL_WORKFLOW_WORKER_TRANSITIONS, TERMINAL_WORKFLOW_WORKER_STATES, mapWorkflowMergeRow, mapWorkflowWorkerEventRow, mapWorkflowWorkerRow, } from './workflow-workers';
|
|
13
15
|
export type { CreateWorkflowWorkerInput, EnqueueWorkflowMergeInput, TransitionWorkflowWorkerInput, WorkflowMergeRecord, WorkflowMergeState, WorkflowWorkerEventRecord, WorkflowWorkerRecord, WorkflowWorkerState, } from './workflow-workers';
|