@devflow-tools/database 0.16.10 → 0.16.12
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 +16 -0
- package/__tests__/database.memory-turn-receipts.test.ts +27 -0
- package/__tests__/database.skill-executions.test.ts +94 -1
- package/__tests__/database.work-queue.test.ts +240 -0
- package/dist/database.d.ts +22 -0
- package/dist/database.js +514 -16
- package/dist/index.d.ts +1 -0
- package/dist/node-sqlite.js +1 -6
- package/dist/work-queue.d.ts +74 -0
- package/dist/work-queue.js +2 -0
- package/package.json +3 -3
- package/src/database.ts +605 -15
- package/src/index.ts +12 -0
- package/src/node-sqlite.ts +1 -6
- package/src/work-queue.ts +94 -0
- package/tsconfig.tsbuildinfo +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,22 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [0.16.12](https://github.com/shilongfeicool/dev-flow/compare/v0.16.11...v0.16.12) (2026-07-26)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* **database:** support Node 24 sqlite result types ([40efed3](https://github.com/shilongfeicool/dev-flow/commit/40efed3d8c629990f5e29324b3aba673c16a091b))
|
|
12
|
+
* **memory:** make embedding model resilient ([e24fd08](https://github.com/shilongfeicool/dev-flow/commit/e24fd086b09d0327e5a69c118f02501caffcf280))
|
|
13
|
+
* **runtime:** close final reliability gaps ([86edd2b](https://github.com/shilongfeicool/dev-flow/commit/86edd2b49acf3559c9e66643ca913fc3a9728731))
|
|
14
|
+
* **runtime:** harden session reconciliation retries ([fc38e56](https://github.com/shilongfeicool/dev-flow/commit/fc38e561450540adecf7883666453f22fa4a9271))
|
|
15
|
+
* **runtime:** preserve reconciliation retry state ([2cec7fe](https://github.com/shilongfeicool/dev-flow/commit/2cec7fe5156a7ab48fbf19c7a427cbca71e7de09))
|
|
16
|
+
* **runtime:** reconcile session lifecycle telemetry ([87b2f7c](https://github.com/shilongfeicool/dev-flow/commit/87b2f7c0c04a572ab396d0da1cc13eada7779691))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
6
22
|
## [0.16.10](https://github.com/shilongfeicool/dev-flow/compare/v0.16.9...v0.16.10) (2026-07-24)
|
|
7
23
|
|
|
8
24
|
|
|
@@ -68,4 +68,31 @@ describe('DevFlowDatabase memory turn receipts', () => {
|
|
|
68
68
|
stopPromptedAt: 350,
|
|
69
69
|
});
|
|
70
70
|
});
|
|
71
|
+
|
|
72
|
+
it('lists every session turn in stable ascending order for reconciliation', () => {
|
|
73
|
+
for (let index = 0; index < 125; index += 1) {
|
|
74
|
+
database.beginMemoryTurn({
|
|
75
|
+
turnId: `turn:${String(index).padStart(3, '0')}`,
|
|
76
|
+
projectRoot: '/project',
|
|
77
|
+
sessionId: 'long-session',
|
|
78
|
+
promptHash: `hash-${index}`,
|
|
79
|
+
eventId: `event-${index}`,
|
|
80
|
+
createdAt: 1_000 + index,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
database.beginMemoryTurn({
|
|
84
|
+
turnId: 'turn:other',
|
|
85
|
+
projectRoot: '/project',
|
|
86
|
+
sessionId: 'other-session',
|
|
87
|
+
promptHash: 'other',
|
|
88
|
+
eventId: 'event-other',
|
|
89
|
+
createdAt: 1,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const turns = database.listSessionMemoryTurnsForReconciliation('/project', 'long-session');
|
|
93
|
+
expect(turns).toHaveLength(125);
|
|
94
|
+
expect(turns[0]?.turnId).toBe('turn:000');
|
|
95
|
+
expect(turns.at(-1)?.turnId).toBe('turn:124');
|
|
96
|
+
expect(turns.every((turn, index) => turn.createdAt === 1_000 + index)).toBe(true);
|
|
97
|
+
});
|
|
71
98
|
});
|
|
@@ -242,7 +242,14 @@ describe('DevFlowDatabase - Skill Executions', () => {
|
|
|
242
242
|
attempts: 1, createdAt: 170,
|
|
243
243
|
});
|
|
244
244
|
|
|
245
|
-
db.reconcileSkillExecution('exec-facts', 'completed', 200
|
|
245
|
+
db.reconcileSkillExecution('exec-facts', 'completed', 200, {
|
|
246
|
+
missedTools: [],
|
|
247
|
+
actualFailureCount: 0,
|
|
248
|
+
blockedCount: 99,
|
|
249
|
+
fallbackCount: 0,
|
|
250
|
+
fallbackReasons: ['stale_supplied_reason'],
|
|
251
|
+
evidenceContractOutcomes: [{ status: 'resolved' }],
|
|
252
|
+
});
|
|
246
253
|
|
|
247
254
|
expect(db.getSkillExecution('exec-facts')).toMatchObject({
|
|
248
255
|
missedMcpTools: ['react:context', 'react:domain'],
|
|
@@ -251,14 +258,68 @@ describe('DevFlowDatabase - Skill Executions', () => {
|
|
|
251
258
|
fallbackCount: 2,
|
|
252
259
|
fallbackReasons: ['direct_tool_during_context', 'daemon_timeout'],
|
|
253
260
|
metadata: expect.objectContaining({
|
|
261
|
+
missedTools: ['react:context', 'react:domain'],
|
|
254
262
|
actualFailureCount: 1,
|
|
263
|
+
blockedCount: 0,
|
|
255
264
|
fallbackCount: 2,
|
|
265
|
+
fallbackReasons: ['direct_tool_during_context', 'daemon_timeout'],
|
|
256
266
|
memoryReceiptIds: ['memory-receipt:host-commit'],
|
|
257
267
|
distillReceiptIds: ['distill-receipt:batch-1'],
|
|
268
|
+
evidenceContractOutcomes: [{ status: 'resolved' }],
|
|
258
269
|
}),
|
|
259
270
|
});
|
|
260
271
|
});
|
|
261
272
|
|
|
273
|
+
it('unions evidence metadata across retries without allowing stale counters', () => {
|
|
274
|
+
db.ensureSession({ id: 'session-evidence-retry', projectRoot: '/project', startedAt: 100 });
|
|
275
|
+
db.insertSkillExecution({
|
|
276
|
+
executionId: 'exec-evidence-retry',
|
|
277
|
+
sessionId: 'session-evidence-retry',
|
|
278
|
+
skillName: 'devflow:context',
|
|
279
|
+
startedAt: 100,
|
|
280
|
+
status: 'running',
|
|
281
|
+
});
|
|
282
|
+
db.insertToolCallEvent({
|
|
283
|
+
eventId: 'event-context', executionId: 'exec-evidence-retry',
|
|
284
|
+
sessionId: 'session-evidence-retry', timestamp: 120,
|
|
285
|
+
toolName: 'get_project_context', toolType: 'mcp', isMcpTool: true,
|
|
286
|
+
mcpToolName: 'get_project_context', mcpEnforced: true, mcpFallback: false,
|
|
287
|
+
input: {}, output: { files: ['src/App.tsx'] }, duration: 10, blocked: false,
|
|
288
|
+
});
|
|
289
|
+
const degradation = {
|
|
290
|
+
obligationId: 'obligation-1', contractId: 'contract-1', reason: 'bounded_fail_open',
|
|
291
|
+
degradedAt: 150, attempt: 2,
|
|
292
|
+
};
|
|
293
|
+
const outcome = {
|
|
294
|
+
obligationId: 'obligation-1', contractId: 'contract-1', status: 'degraded',
|
|
295
|
+
completedAt: 150, attempt: 2, reason: 'bounded_fail_open',
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
db.reconcileSkillExecution('exec-evidence-retry', 'completed', 200, {
|
|
299
|
+
evidenceDegradations: [degradation],
|
|
300
|
+
evidenceContractOutcomes: [outcome],
|
|
301
|
+
pendingEvidenceContractIds: ['contract-pending'],
|
|
302
|
+
});
|
|
303
|
+
db.reconcileSkillExecution('exec-evidence-retry', 'completed', 220, {
|
|
304
|
+
evidenceDegradations: [],
|
|
305
|
+
evidenceContractOutcomes: [
|
|
306
|
+
{ attempt: 2, completedAt: 150, reason: 'bounded_fail_open', status: 'degraded',
|
|
307
|
+
contractId: 'contract-1', obligationId: 'obligation-1' },
|
|
308
|
+
],
|
|
309
|
+
pendingEvidenceContractIds: [],
|
|
310
|
+
missedTools: ['stale'],
|
|
311
|
+
actualFailureCount: 99,
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
expect(db.getSkillExecution('exec-evidence-retry')?.metadata).toMatchObject({
|
|
315
|
+
evidenceDegradations: [degradation],
|
|
316
|
+
evidenceContractOutcomes: [outcome],
|
|
317
|
+
pendingEvidenceContractIds: ['contract-pending'],
|
|
318
|
+
missedTools: [],
|
|
319
|
+
actualFailureCount: 0,
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
|
|
262
323
|
it('aggregates an empty findings collection as an empty result', () => {
|
|
263
324
|
db.insertSkillExecution({
|
|
264
325
|
executionId: 'exec_empty_findings',
|
|
@@ -360,4 +421,36 @@ describe('DevFlowDatabase - Skill Executions', () => {
|
|
|
360
421
|
}),
|
|
361
422
|
);
|
|
362
423
|
});
|
|
424
|
+
|
|
425
|
+
it('reconciles every running execution owned by a closing session', () => {
|
|
426
|
+
for (const executionId of ['exec_implicit_a', 'exec_implicit_b']) {
|
|
427
|
+
db.insertSkillExecution({
|
|
428
|
+
executionId,
|
|
429
|
+
sessionId: 'session-closing',
|
|
430
|
+
skillName: 'devflow:implicit',
|
|
431
|
+
startedAt: 1_000,
|
|
432
|
+
status: 'running',
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
db.insertSkillExecution({
|
|
436
|
+
executionId: 'exec_other_session',
|
|
437
|
+
sessionId: 'session-other',
|
|
438
|
+
skillName: 'devflow:implicit',
|
|
439
|
+
startedAt: 1_000,
|
|
440
|
+
status: 'running',
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
const reconciled = db.reconcileRunningSkillExecutionsForSession(
|
|
444
|
+
'session-closing',
|
|
445
|
+
5_000,
|
|
446
|
+
{ closureReason: 'session_finalize' },
|
|
447
|
+
);
|
|
448
|
+
|
|
449
|
+
expect(reconciled.map(execution => execution.executionId)).toEqual([
|
|
450
|
+
'exec_implicit_a',
|
|
451
|
+
'exec_implicit_b',
|
|
452
|
+
]);
|
|
453
|
+
expect(reconciled.every(execution => execution.status === 'completed')).toBe(true);
|
|
454
|
+
expect(db.getSkillExecution('exec_other_session')).toMatchObject({ status: 'running' });
|
|
455
|
+
});
|
|
363
456
|
});
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
5
|
+
import { DevFlowDatabase } from '../src/database';
|
|
6
|
+
|
|
7
|
+
describe('DevFlowDatabase durable work queue', () => {
|
|
8
|
+
let directory: string;
|
|
9
|
+
let database: DevFlowDatabase;
|
|
10
|
+
const projectRoot = '/project';
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
directory = mkdtempSync(join(tmpdir(), 'devflow-work-queue-'));
|
|
14
|
+
database = new DevFlowDatabase(directory);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
database.close();
|
|
19
|
+
rmSync(directory, { recursive: true, force: true });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('returns the existing item for an idempotency conflict without resetting completed work', () => {
|
|
23
|
+
const first = database.enqueueWork({
|
|
24
|
+
idempotencyKey: 'memory:session-1:turn-1',
|
|
25
|
+
kind: 'memory.explicit_commit',
|
|
26
|
+
projectRoot,
|
|
27
|
+
sessionId: 'session-1',
|
|
28
|
+
turnId: 'turn-1',
|
|
29
|
+
payload: { text: 'remember this' },
|
|
30
|
+
nextAttemptAt: 10,
|
|
31
|
+
});
|
|
32
|
+
const [leased] = database.leaseWork({
|
|
33
|
+
projectRoot,
|
|
34
|
+
owner: 'worker-a',
|
|
35
|
+
limit: 1,
|
|
36
|
+
leaseMs: 100,
|
|
37
|
+
now: 10,
|
|
38
|
+
});
|
|
39
|
+
expect(leased?.id).toBe(first.id);
|
|
40
|
+
expect(database.completeWork(first.id, 'worker-a', 20)).toBe(true);
|
|
41
|
+
|
|
42
|
+
const duplicate = database.enqueueWork({
|
|
43
|
+
idempotencyKey: 'memory:session-1:turn-1',
|
|
44
|
+
kind: 'telemetry.reconcile',
|
|
45
|
+
projectRoot: '/different-project',
|
|
46
|
+
payload: { replacement: true },
|
|
47
|
+
maxAttempts: 1,
|
|
48
|
+
nextAttemptAt: 999,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
expect(duplicate).toMatchObject({
|
|
52
|
+
id: first.id,
|
|
53
|
+
kind: 'memory.explicit_commit',
|
|
54
|
+
projectRoot,
|
|
55
|
+
payload: { text: 'remember this' },
|
|
56
|
+
state: 'completed',
|
|
57
|
+
attempts: 1,
|
|
58
|
+
maxAttempts: 5,
|
|
59
|
+
completedAt: 20,
|
|
60
|
+
});
|
|
61
|
+
expect(database.all('SELECT id FROM devflow_work_items')).toHaveLength(1);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('leases atomically across database connections and requires the lease owner to complete', () => {
|
|
65
|
+
database.enqueueWork({
|
|
66
|
+
idempotencyKey: 'session:finalize:1',
|
|
67
|
+
kind: 'session.finalize',
|
|
68
|
+
projectRoot,
|
|
69
|
+
payload: { sessionId: 'session-1' },
|
|
70
|
+
nextAttemptAt: 100,
|
|
71
|
+
});
|
|
72
|
+
const secondConnection = new DevFlowDatabase(directory);
|
|
73
|
+
try {
|
|
74
|
+
const firstLease = database.leaseWork({
|
|
75
|
+
projectRoot,
|
|
76
|
+
owner: 'worker-a',
|
|
77
|
+
kinds: ['session.finalize'],
|
|
78
|
+
limit: 1,
|
|
79
|
+
leaseMs: 1_000,
|
|
80
|
+
now: 100,
|
|
81
|
+
});
|
|
82
|
+
const competingLease = secondConnection.leaseWork({
|
|
83
|
+
projectRoot,
|
|
84
|
+
owner: 'worker-b',
|
|
85
|
+
limit: 1,
|
|
86
|
+
leaseMs: 1_000,
|
|
87
|
+
now: 100,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
expect(firstLease).toHaveLength(1);
|
|
91
|
+
expect(firstLease[0]).toMatchObject({
|
|
92
|
+
state: 'leased',
|
|
93
|
+
attempts: 1,
|
|
94
|
+
leaseOwner: 'worker-a',
|
|
95
|
+
leaseExpiresAt: 1_100,
|
|
96
|
+
});
|
|
97
|
+
expect(competingLease).toEqual([]);
|
|
98
|
+
expect(secondConnection.completeWork(firstLease[0]!.id, 'worker-b', 200)).toBe(false);
|
|
99
|
+
expect(database.completeWork(firstLease[0]!.id, 'worker-a', 200)).toBe(true);
|
|
100
|
+
expect(database.completeWork(firstLease[0]!.id, 'worker-a', 201)).toBe(false);
|
|
101
|
+
} finally {
|
|
102
|
+
secondConnection.close();
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('applies owner and state predicates to retries and dead letters', () => {
|
|
107
|
+
const item = database.enqueueWork({
|
|
108
|
+
idempotencyKey: 'telemetry:reconcile:1',
|
|
109
|
+
kind: 'telemetry.reconcile',
|
|
110
|
+
projectRoot,
|
|
111
|
+
payload: {},
|
|
112
|
+
nextAttemptAt: 50,
|
|
113
|
+
});
|
|
114
|
+
database.leaseWork({
|
|
115
|
+
projectRoot,
|
|
116
|
+
owner: 'worker-a',
|
|
117
|
+
limit: 1,
|
|
118
|
+
leaseMs: 100,
|
|
119
|
+
now: 50,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
expect(database.retryWork(
|
|
123
|
+
item.id,
|
|
124
|
+
'worker-b',
|
|
125
|
+
{ category: 'temporary', message: 'try again' },
|
|
126
|
+
75,
|
|
127
|
+
)).toBe(false);
|
|
128
|
+
expect(database.retryWork(
|
|
129
|
+
item.id,
|
|
130
|
+
'worker-a',
|
|
131
|
+
{ category: 'temporary', message: 'try again' },
|
|
132
|
+
75,
|
|
133
|
+
)).toBe(true);
|
|
134
|
+
expect(database.getWorkQueueHealth(projectRoot, 75)).toMatchObject({
|
|
135
|
+
queueDepth: 1,
|
|
136
|
+
failed: 1,
|
|
137
|
+
deadLetters: 0,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const [retried] = database.leaseWork({
|
|
141
|
+
projectRoot,
|
|
142
|
+
owner: 'worker-b',
|
|
143
|
+
limit: 1,
|
|
144
|
+
leaseMs: 100,
|
|
145
|
+
now: 75,
|
|
146
|
+
});
|
|
147
|
+
expect(retried).toMatchObject({ attempts: 2, errorCategory: 'temporary' });
|
|
148
|
+
expect(database.deadLetterWork(
|
|
149
|
+
item.id,
|
|
150
|
+
'worker-a',
|
|
151
|
+
{ category: 'invalid_schema', message: 'payload is invalid' },
|
|
152
|
+
)).toBe(false);
|
|
153
|
+
expect(database.deadLetterWork(
|
|
154
|
+
item.id,
|
|
155
|
+
'worker-b',
|
|
156
|
+
{ category: 'invalid_schema', message: 'payload is invalid' },
|
|
157
|
+
)).toBe(true);
|
|
158
|
+
expect(database.deadLetterWork(
|
|
159
|
+
item.id,
|
|
160
|
+
'worker-b',
|
|
161
|
+
{ category: 'invalid_schema', message: 'payload is invalid' },
|
|
162
|
+
)).toBe(false);
|
|
163
|
+
expect(database.getWorkQueueHealth(projectRoot, 80)).toMatchObject({
|
|
164
|
+
queueDepth: 0,
|
|
165
|
+
deadLetters: 1,
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('recovers expired leases and dead-letters exhausted work', () => {
|
|
170
|
+
database.enqueueWork({
|
|
171
|
+
idempotencyKey: 'memory:distill:retryable',
|
|
172
|
+
kind: 'memory.turn_distill',
|
|
173
|
+
projectRoot,
|
|
174
|
+
payload: {},
|
|
175
|
+
maxAttempts: 2,
|
|
176
|
+
nextAttemptAt: 100,
|
|
177
|
+
});
|
|
178
|
+
database.enqueueWork({
|
|
179
|
+
idempotencyKey: 'memory:vector:exhausted',
|
|
180
|
+
kind: 'memory.vector_backfill',
|
|
181
|
+
projectRoot,
|
|
182
|
+
payload: {},
|
|
183
|
+
maxAttempts: 1,
|
|
184
|
+
nextAttemptAt: 100,
|
|
185
|
+
});
|
|
186
|
+
database.leaseWork({
|
|
187
|
+
projectRoot,
|
|
188
|
+
owner: 'worker-a',
|
|
189
|
+
limit: 2,
|
|
190
|
+
leaseMs: 10,
|
|
191
|
+
now: 100,
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
expect(database.getWorkQueueHealth(projectRoot, 110)).toMatchObject({
|
|
195
|
+
queueDepth: 2,
|
|
196
|
+
leased: 2,
|
|
197
|
+
expiredLeases: 2,
|
|
198
|
+
});
|
|
199
|
+
expect(database.recoverExpiredWork(projectRoot, 110)).toBe(2);
|
|
200
|
+
expect(database.getWorkQueueHealth(projectRoot, 110)).toMatchObject({
|
|
201
|
+
queueDepth: 1,
|
|
202
|
+
leased: 0,
|
|
203
|
+
failed: 1,
|
|
204
|
+
expiredLeases: 0,
|
|
205
|
+
deadLetters: 1,
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
const recovered = database.leaseWork({
|
|
209
|
+
projectRoot,
|
|
210
|
+
owner: 'worker-b',
|
|
211
|
+
limit: 2,
|
|
212
|
+
leaseMs: 20,
|
|
213
|
+
now: 110,
|
|
214
|
+
});
|
|
215
|
+
expect(recovered).toHaveLength(1);
|
|
216
|
+
expect(recovered[0]).toMatchObject({
|
|
217
|
+
idempotencyKey: 'memory:distill:retryable',
|
|
218
|
+
state: 'leased',
|
|
219
|
+
attempts: 2,
|
|
220
|
+
leaseOwner: 'worker-b',
|
|
221
|
+
errorCategory: 'lease_expired',
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const revived = database.enqueueWork({
|
|
225
|
+
idempotencyKey: 'memory:vector:exhausted',
|
|
226
|
+
kind: 'memory.vector_backfill',
|
|
227
|
+
projectRoot,
|
|
228
|
+
payload: {},
|
|
229
|
+
maxAttempts: 20,
|
|
230
|
+
nextAttemptAt: 120,
|
|
231
|
+
});
|
|
232
|
+
expect(revived).toMatchObject({
|
|
233
|
+
state: 'failed',
|
|
234
|
+
attempts: 1,
|
|
235
|
+
maxAttempts: 20,
|
|
236
|
+
nextAttemptAt: 120,
|
|
237
|
+
errorCategory: undefined,
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
});
|
package/dist/database.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { EnqueueWorkInput, LeaseWorkInput, RequestSessionClosureInput, SessionClosureRecord, WorkError, WorkItemRecord, WorkQueueHealth } from './work-queue';
|
|
1
2
|
export interface BenchmarkReportRecord {
|
|
2
3
|
runId: string;
|
|
3
4
|
suiteId: string;
|
|
@@ -81,6 +82,7 @@ export interface MemoryDistillCheckpointRecord {
|
|
|
81
82
|
trigger: 'pre_compact' | 'session_end';
|
|
82
83
|
pendingEvents: number;
|
|
83
84
|
releasedLeases: number;
|
|
85
|
+
details?: Record<string, unknown>;
|
|
84
86
|
createdAt: number;
|
|
85
87
|
}
|
|
86
88
|
export type MemoryTurnStatus = 'pending' | 'committed' | 'skipped';
|
|
@@ -167,6 +169,7 @@ export declare class DevFlowDatabase {
|
|
|
167
169
|
availableMcpTools?: string[];
|
|
168
170
|
}): void;
|
|
169
171
|
getSkillExecution(executionId: string): any | null;
|
|
172
|
+
getLatestSkillExecutionForSession(sessionId: string): any | null;
|
|
170
173
|
updateSkillExecution(executionId: string, updates: {
|
|
171
174
|
status?: string;
|
|
172
175
|
finishedAt?: number;
|
|
@@ -183,6 +186,7 @@ export declare class DevFlowDatabase {
|
|
|
183
186
|
subagentCount?: number;
|
|
184
187
|
metadata?: Record<string, unknown>;
|
|
185
188
|
}): void;
|
|
189
|
+
mergeSkillExecutionMetadata(executionId: string, incoming: Record<string, unknown>): Record<string, unknown> | null;
|
|
186
190
|
listSkillExecutions(limit: number, skillName?: string): any[];
|
|
187
191
|
insertToolCallEvent(params: {
|
|
188
192
|
eventId: string;
|
|
@@ -217,6 +221,7 @@ export declare class DevFlowDatabase {
|
|
|
217
221
|
}): boolean;
|
|
218
222
|
markToolCallBlocked(eventId: string, reason: string): boolean;
|
|
219
223
|
reconcileSkillExecution(executionId: string, status: 'completed' | 'failed', finishedAt?: number, metadata?: Record<string, unknown>): void;
|
|
224
|
+
reconcileRunningSkillExecutionsForSession(sessionId: string, finishedAt?: number, metadata?: Record<string, unknown>): any[];
|
|
220
225
|
getExecutionObligationCompliance(executionId: string): {
|
|
221
226
|
rate: number;
|
|
222
227
|
applicable: number;
|
|
@@ -300,6 +305,22 @@ export declare class DevFlowDatabase {
|
|
|
300
305
|
items: GovernanceAuditRecord[];
|
|
301
306
|
total: number;
|
|
302
307
|
};
|
|
308
|
+
enqueueWork(input: EnqueueWorkInput): WorkItemRecord;
|
|
309
|
+
getWorkByIdempotencyKey(idempotencyKey: string): WorkItemRecord | null;
|
|
310
|
+
requestSessionClosure(input: RequestSessionClosureInput): SessionClosureRecord;
|
|
311
|
+
getSessionClosure(projectRoot: string, sessionId: string): SessionClosureRecord | null;
|
|
312
|
+
completeSessionClosure(projectRoot: string, sessionId: string, excludingWorkItemId?: string, closedAt?: number): SessionClosureRecord;
|
|
313
|
+
listSessionClosures(projectRoot: string, limit?: number): SessionClosureRecord[];
|
|
314
|
+
leaseWork(input: LeaseWorkInput): WorkItemRecord[];
|
|
315
|
+
completeWork(id: string, owner: string, now?: number): boolean;
|
|
316
|
+
retryWork(id: string, owner: string, error: WorkError, nextAttemptAt: number): boolean;
|
|
317
|
+
deferWork(id: string, owner: string, error: WorkError, nextAttemptAt: number): boolean;
|
|
318
|
+
deadLetterWork(id: string, owner: string, error: WorkError): boolean;
|
|
319
|
+
recoverExpiredWork(projectRoot: string, now?: number): number;
|
|
320
|
+
getWorkQueueHealth(projectRoot: string, now?: number): WorkQueueHealth;
|
|
321
|
+
private mapWorkItem;
|
|
322
|
+
private mapSessionClosure;
|
|
323
|
+
private refreshClosedSessionClosure;
|
|
303
324
|
getHookReceipt(projectRoot: string): HookReceiptRecord | null;
|
|
304
325
|
updateHookReceipt(projectRoot: string, updater: (current: HookReceiptRecord | null) => Omit<HookReceiptRecord, 'projectRoot' | 'updatedAt'>): HookReceiptRecord;
|
|
305
326
|
deleteHookReceipt(projectRoot: string): boolean;
|
|
@@ -326,6 +347,7 @@ export declare class DevFlowDatabase {
|
|
|
326
347
|
}): MemoryTurnRecord;
|
|
327
348
|
markMemoryTurnStopPrompted(turnId: string, promptedAt?: number): boolean;
|
|
328
349
|
listMemoryTurns(projectRoot: string, sessionId?: string, limit?: number): MemoryTurnRecord[];
|
|
350
|
+
listSessionMemoryTurnsForReconciliation(projectRoot: string, sessionId: string): MemoryTurnRecord[];
|
|
329
351
|
insertHookFallback(record: HookFallbackRecord): boolean;
|
|
330
352
|
listHookFallbacks(filter?: {
|
|
331
353
|
projectRoot?: string;
|