ai-foreman 1.7.8 → 1.7.9
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/README.md +1 -11
- package/dist/activity.js +2 -2
- package/dist/adapters/claude.d.ts +13 -29
- package/dist/adapters/claude.js +76 -334
- package/dist/adapters/codex.d.ts +13 -18
- package/dist/adapters/codex.js +103 -195
- package/dist/adapters/recovering.d.ts +5 -6
- package/dist/adapters/recovering.js +5 -9
- package/dist/adapters/types.d.ts +24 -66
- package/dist/branch/currentGuard.d.ts +5 -5
- package/dist/branch/currentGuard.js +4 -6
- package/dist/branch/runner.d.ts +2 -0
- package/dist/branch/runner.js +2 -1
- package/dist/cli/start.js +90 -165
- package/dist/continuity.d.ts +6 -8
- package/dist/continuity.js +26 -72
- package/dist/foreman.d.ts +7 -1
- package/dist/foreman.js +25 -1
- package/dist/handoffs.js +3 -223
- package/dist/qaReview.d.ts +2 -0
- package/dist/qaReview.js +1 -4
- package/dist/sessionLifecycle.d.ts +19 -108
- package/dist/sessionLifecycle.js +310 -1024
- package/dist/statusReporter.d.ts +5 -27
- package/dist/statusReporter.js +20 -76
- package/dist/workflowDb.d.ts +10 -47
- package/dist/workflowDb.js +41 -146
- package/package.json +4 -4
package/dist/statusReporter.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ContextSample, SessionUsageSample } from "rafi-spec";
|
|
2
|
-
import type { BuilderAdapter, BuilderEvent, CompactResult, ContextUsage, ProviderSessionUsage, ProviderSettingSwitch, TurnResult } from "./adapters/types.js";
|
|
2
|
+
import type { BuilderAdapter, BuilderEvent, CompactResult, ContextUsage, NativeCompaction, ProviderSessionUsage, ProviderSettingSwitch, TurnResult } from "./adapters/types.js";
|
|
3
3
|
export interface AgentStatusSnapshot {
|
|
4
4
|
role: "builder" | "qa";
|
|
5
5
|
provider: "claude" | "codex";
|
|
@@ -21,13 +21,6 @@ export interface AgentStatusSnapshot {
|
|
|
21
21
|
contextSample: ContextSample;
|
|
22
22
|
sessionUsage?: SessionUsageSample;
|
|
23
23
|
sessionTransition: string;
|
|
24
|
-
lifecycleState?: string;
|
|
25
|
-
configuredCeilingPercent?: number;
|
|
26
|
-
installedNativeTokenLimit?: number;
|
|
27
|
-
installedNativePercent?: number;
|
|
28
|
-
compactionCount?: number;
|
|
29
|
-
compactMaximum?: number;
|
|
30
|
-
settingsRevision?: number;
|
|
31
24
|
at: string;
|
|
32
25
|
}
|
|
33
26
|
export interface StatusClock {
|
|
@@ -54,20 +47,6 @@ export interface AgentStatusReporterInput {
|
|
|
54
47
|
adapter: BuilderAdapter | (() => BuilderAdapter);
|
|
55
48
|
compactionCount?: number | (() => number);
|
|
56
49
|
handoffGeneration?: number | (() => number);
|
|
57
|
-
/** Shared enforcement snapshot. When supplied, status never polls occupancy independently. */
|
|
58
|
-
contextSnapshot?: () => {
|
|
59
|
-
role: "builder" | "qa";
|
|
60
|
-
provider: "claude" | "codex";
|
|
61
|
-
model: string;
|
|
62
|
-
lifecycleState: string;
|
|
63
|
-
contextSample: ContextSample;
|
|
64
|
-
configuredCeilingPercent: number;
|
|
65
|
-
installedNativeTokenLimit?: number;
|
|
66
|
-
installedNativePercent?: number;
|
|
67
|
-
compactionCount: number;
|
|
68
|
-
compactMaximum: number;
|
|
69
|
-
settingsRevision: number;
|
|
70
|
-
} | undefined;
|
|
71
50
|
}
|
|
72
51
|
/**
|
|
73
52
|
* A non-blocking ten-second reporter. It never invents occupancy or cost: an
|
|
@@ -80,7 +59,6 @@ export declare class AgentStatusReporter {
|
|
|
80
59
|
private readonly intervalMs;
|
|
81
60
|
private handle?;
|
|
82
61
|
private lastGoodContext?;
|
|
83
|
-
private readonly lastGoodByRole;
|
|
84
62
|
constructor(input: AgentStatusReporterInput, emit: (line: string, snapshot: AgentStatusSnapshot) => void, clock?: StatusClock, intervalMs?: number);
|
|
85
63
|
start(): void;
|
|
86
64
|
tick(): Promise<void>;
|
|
@@ -104,10 +82,10 @@ export declare class RoleStatusAdapter implements BuilderAdapter {
|
|
|
104
82
|
adoptSessionRef(ref: import("rafi-spec").ProviderSessionRefV1): void;
|
|
105
83
|
validateSession(): Promise<import("rafi-spec").SessionAvailabilityV1>;
|
|
106
84
|
compact(): Promise<CompactResult>;
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
85
|
+
prepareAutoCompaction(thresholdPercent?: number): Promise<void>;
|
|
86
|
+
drainNativeCompactions(): import("./adapters/types.js").NativeCompaction[];
|
|
87
|
+
restoreNativeCompactions(compactions: NativeCompaction[]): void;
|
|
88
|
+
contextUsageAfterNativeCompaction(compaction: NativeCompaction): Promise<ContextUsage | undefined>;
|
|
111
89
|
contextUsage(): Promise<ContextUsage | undefined>;
|
|
112
90
|
sessionUsage(): Promise<ProviderSessionUsage | undefined>;
|
|
113
91
|
switchSettings(settings: ProviderSettingSwitch): Promise<CompactResult>;
|
package/dist/statusReporter.js
CHANGED
|
@@ -19,7 +19,6 @@ export class AgentStatusReporter {
|
|
|
19
19
|
intervalMs;
|
|
20
20
|
handle;
|
|
21
21
|
lastGoodContext;
|
|
22
|
-
lastGoodByRole = new Map();
|
|
23
22
|
constructor(input, emit, clock = systemClock, intervalMs = 10_000) {
|
|
24
23
|
this.input = input;
|
|
25
24
|
this.emit = emit;
|
|
@@ -35,44 +34,23 @@ export class AgentStatusReporter {
|
|
|
35
34
|
async tick() {
|
|
36
35
|
const adapter = this.adapter();
|
|
37
36
|
const now = this.clock.now();
|
|
37
|
+
let observed;
|
|
38
|
+
try {
|
|
39
|
+
observed = await adapter.contextUsage?.();
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
observed = undefined;
|
|
43
|
+
}
|
|
38
44
|
let contextSample;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
contextSample = coordinated.contextSample;
|
|
43
|
-
this.input.provider = coordinated.provider;
|
|
44
|
-
this.input.model = coordinated.model;
|
|
45
|
-
this.input.settingsRevision = coordinated.settingsRevision;
|
|
46
|
-
this.input.compactionCount = coordinated.compactionCount;
|
|
47
|
-
if (contextSample.freshness === "fresh") {
|
|
48
|
-
this.lastGoodContext = contextSample;
|
|
49
|
-
this.lastGoodByRole.set(this.input.role, contextSample);
|
|
50
|
-
}
|
|
45
|
+
if (observed && Number.isFinite(observed.used)) {
|
|
46
|
+
contextSample = this.contextSample(observed, now);
|
|
47
|
+
this.lastGoodContext = contextSample;
|
|
51
48
|
}
|
|
52
|
-
else if (
|
|
53
|
-
|
|
54
|
-
try {
|
|
55
|
-
observed = await adapter.contextUsage?.();
|
|
56
|
-
}
|
|
57
|
-
catch {
|
|
58
|
-
observed = undefined;
|
|
59
|
-
}
|
|
60
|
-
if (observed && Number.isFinite(observed.used))
|
|
61
|
-
contextSample = this.contextSample(observed, now);
|
|
62
|
-
else if (this.lastGoodContext)
|
|
63
|
-
contextSample = { ...this.lastGoodContext, freshness: "stale" };
|
|
64
|
-
else
|
|
65
|
-
contextSample = { ...this.measuringSample(now), freshness: "unavailable" };
|
|
49
|
+
else if (this.lastGoodContext) {
|
|
50
|
+
contextSample = { ...this.lastGoodContext, freshness: "stale" };
|
|
66
51
|
}
|
|
67
52
|
else {
|
|
68
|
-
|
|
69
|
-
contextSample = prior
|
|
70
|
-
? { ...prior, freshness: "stale" }
|
|
71
|
-
: { ...this.measuringSample(now), freshness: "unavailable" };
|
|
72
|
-
}
|
|
73
|
-
if (contextSample.freshness === "fresh") {
|
|
74
|
-
this.lastGoodContext = contextSample;
|
|
75
|
-
this.lastGoodByRole.set(this.input.role, contextSample);
|
|
53
|
+
contextSample = { ...this.measuringSample(now), freshness: "unavailable" };
|
|
76
54
|
}
|
|
77
55
|
let sessionUsage;
|
|
78
56
|
if (this.input.displaySessionCost) {
|
|
@@ -112,19 +90,16 @@ export class AgentStatusReporter {
|
|
|
112
90
|
const priorRole = this.input.role;
|
|
113
91
|
const priorAdapter = this.adapter();
|
|
114
92
|
const priorSessionId = priorAdapter.sessionId();
|
|
115
|
-
if (this.lastGoodContext)
|
|
116
|
-
this.lastGoodByRole.set(priorRole, this.lastGoodContext);
|
|
117
93
|
Object.assign(this.input, patch);
|
|
118
94
|
if (priorRole !== this.input.role
|
|
119
95
|
|| priorAdapter !== this.adapter()
|
|
120
96
|
|| priorSessionId !== this.adapter().sessionId()
|
|
121
97
|
|| patch.sessionTransition !== undefined)
|
|
122
|
-
this.lastGoodContext =
|
|
98
|
+
this.lastGoodContext = undefined;
|
|
123
99
|
this.emitSnapshot(this.measuringSample(), undefined);
|
|
124
100
|
}
|
|
125
101
|
emitSnapshot(contextSample, sessionUsage, now = this.clock.now()) {
|
|
126
|
-
const
|
|
127
|
-
const { adapter: _adapter, displaySessionCost: _display, compactionCount: _compactions, handoffGeneration: _handoffs, contextSnapshot: _contextSnapshot, ...base } = this.input;
|
|
102
|
+
const { adapter: _adapter, displaySessionCost: _display, compactionCount: _compactions, handoffGeneration: _handoffs, ...base } = this.input;
|
|
128
103
|
const context = contextSample.used === undefined ? undefined : {
|
|
129
104
|
used: contextSample.used,
|
|
130
105
|
maximum: contextSample.maximum,
|
|
@@ -135,29 +110,13 @@ export class AgentStatusReporter {
|
|
|
135
110
|
...(context ? { context } : {}),
|
|
136
111
|
contextSample,
|
|
137
112
|
...(sessionUsage ? { sessionUsage } : {}),
|
|
138
|
-
...(coordinated && coordinated.role === this.input.role ? {
|
|
139
|
-
lifecycleState: coordinated.lifecycleState,
|
|
140
|
-
configuredCeilingPercent: coordinated.configuredCeilingPercent,
|
|
141
|
-
installedNativeTokenLimit: coordinated.installedNativeTokenLimit,
|
|
142
|
-
installedNativePercent: coordinated.installedNativePercent,
|
|
143
|
-
compactionCount: coordinated.compactionCount,
|
|
144
|
-
compactMaximum: coordinated.compactMaximum,
|
|
145
|
-
settingsRevision: coordinated.settingsRevision,
|
|
146
|
-
} : {
|
|
147
|
-
compactionCount: contextSample.compactionCount,
|
|
148
|
-
settingsRevision: contextSample.settingsRevision,
|
|
149
|
-
}),
|
|
150
113
|
at: now.toISOString(),
|
|
151
114
|
};
|
|
152
115
|
const line = [
|
|
153
116
|
`${snapshot.role} ${snapshot.provider}/${snapshot.model}`,
|
|
154
117
|
`activity=${snapshot.phase}`,
|
|
155
118
|
formatContext(contextSample),
|
|
156
|
-
|
|
157
|
-
...(snapshot.installedNativePercent === undefined ? [] : [`native=${snapshot.installedNativePercent.toFixed(1)}%${snapshot.installedNativeTokenLimit === undefined ? "" : `/${snapshot.installedNativeTokenLimit}`}`]),
|
|
158
|
-
...(snapshot.lifecycleState ? [`state=${formatLifecycle(snapshot.lifecycleState)}`] : []),
|
|
159
|
-
`compactions=${snapshot.compactionCount ?? contextSample.compactionCount}${snapshot.compactMaximum === undefined ? "" : `/${snapshot.compactMaximum}`}`,
|
|
160
|
-
`revision=${snapshot.settingsRevision ?? contextSample.settingsRevision}`,
|
|
119
|
+
`compactions=${contextSample.compactionCount}`,
|
|
161
120
|
`handoff=${contextSample.handoffGeneration}`,
|
|
162
121
|
`session=${snapshot.sessionTransition}`,
|
|
163
122
|
...(this.input.displaySessionCost ? [formatSessionUsage(sessionUsage)] : []),
|
|
@@ -218,12 +177,10 @@ export class RoleStatusAdapter {
|
|
|
218
177
|
adoptSessionRef(ref) { this.adapter.adoptSessionRef?.(ref); }
|
|
219
178
|
validateSession() { return this.adapter.validateSession?.() ?? Promise.resolve({ version: 1, status: "unknown", checkedAt: new Date().toISOString(), reason: "legacy-unscoped" }); }
|
|
220
179
|
compact() { this.onActive(this.adapter); return this.adapter.compact?.() ?? Promise.resolve({ ok: false, error: "native compaction unavailable" }); }
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
interruptTurnAtCompactionBoundary(providerEventId) { return this.adapter.interruptTurnAtCompactionBoundary?.(providerEventId) ?? Promise.resolve({ ok: false, error: "compaction-boundary interruption unavailable", providerEventId }); }
|
|
226
|
-
installManagedTurnDispatcher(dispatcher) { this.adapter.installManagedTurnDispatcher?.(dispatcher); }
|
|
180
|
+
prepareAutoCompaction(thresholdPercent) { return this.adapter.prepareAutoCompaction?.(thresholdPercent) ?? Promise.resolve(); }
|
|
181
|
+
drainNativeCompactions() { return this.adapter.drainNativeCompactions?.() ?? []; }
|
|
182
|
+
restoreNativeCompactions(compactions) { this.adapter.restoreNativeCompactions?.(compactions); }
|
|
183
|
+
contextUsageAfterNativeCompaction(compaction) { return this.adapter.contextUsageAfterNativeCompaction?.(compaction) ?? Promise.resolve(undefined); }
|
|
227
184
|
contextUsage() { return this.adapter.contextUsage?.() ?? Promise.resolve(undefined); }
|
|
228
185
|
sessionUsage() { return this.adapter.sessionUsage?.() ?? Promise.resolve(undefined); }
|
|
229
186
|
switchSettings(settings) { return this.adapter.switchSettings?.(settings) ?? Promise.resolve({ ok: false, error: "settings switch unavailable" }); }
|
|
@@ -262,19 +219,6 @@ function formatContext(sample) {
|
|
|
262
219
|
: `${sample.percentage.toFixed(1)}% (${sample.used ?? "unknown"}/${sample.maximum ?? "unknown"})`;
|
|
263
220
|
return `context ${occupancy}${sample.freshness === "stale" ? " stale" : ""}`;
|
|
264
221
|
}
|
|
265
|
-
function formatLifecycle(state) {
|
|
266
|
-
if (state === "initializing")
|
|
267
|
-
return "measuring";
|
|
268
|
-
if (state === "threshold_pending")
|
|
269
|
-
return "pending";
|
|
270
|
-
if (state === "native_compacting" || state === "host_compacting")
|
|
271
|
-
return "compacting";
|
|
272
|
-
if (state === "compacted_unverified")
|
|
273
|
-
return "verifying";
|
|
274
|
-
if (state === "handoff_required" || state === "resuming")
|
|
275
|
-
return "handing off";
|
|
276
|
-
return state;
|
|
277
|
-
}
|
|
278
222
|
function formatSessionUsage(sample) {
|
|
279
223
|
if (!sample || sample.source === "unavailable")
|
|
280
224
|
return "session usage unavailable";
|
package/dist/workflowDb.d.ts
CHANGED
|
@@ -55,45 +55,13 @@ export interface CompactionAttemptRecord {
|
|
|
55
55
|
sessionRef?: ProviderSessionRefV1;
|
|
56
56
|
sessionKey?: string;
|
|
57
57
|
crossingKey: string;
|
|
58
|
-
status: "started" | "succeeded" | "failed"
|
|
59
|
-
origin: "provider-auto" | "rafi-manual" | "recovery" | "boundary";
|
|
60
|
-
providerEventId?: string;
|
|
61
|
-
thresholdGenerationId?: string;
|
|
58
|
+
status: "started" | "succeeded" | "failed";
|
|
62
59
|
beforeSample?: ContextSample;
|
|
63
60
|
afterSample?: ContextSample;
|
|
64
61
|
error?: string;
|
|
65
62
|
createdAt: string;
|
|
66
63
|
updatedAt: string;
|
|
67
64
|
}
|
|
68
|
-
export type ThresholdLifecycleState = "initializing" | "armed" | "threshold_pending" | "native_compacting" | "host_compacting" | "compacted_unverified" | "retrying" | "handoff_required" | "resuming";
|
|
69
|
-
export interface ThresholdGenerationRecord {
|
|
70
|
-
generationId: string;
|
|
71
|
-
runId: string;
|
|
72
|
-
role: "builder" | "qa";
|
|
73
|
-
providerSessionId?: string;
|
|
74
|
-
sessionRef?: ProviderSessionRefV1;
|
|
75
|
-
sessionKey?: string;
|
|
76
|
-
generation: number;
|
|
77
|
-
state: ThresholdLifecycleState;
|
|
78
|
-
configuredCeilingPercent: number;
|
|
79
|
-
installedNativeTokenLimit?: number;
|
|
80
|
-
installedNativePercent?: number;
|
|
81
|
-
settingsRevision: number;
|
|
82
|
-
model: string;
|
|
83
|
-
latestSample?: ContextSample;
|
|
84
|
-
frozenActionDigest?: string;
|
|
85
|
-
providerEventId?: string;
|
|
86
|
-
createdAt: string;
|
|
87
|
-
updatedAt: string;
|
|
88
|
-
}
|
|
89
|
-
export interface ActiveRoleAdapterRecord {
|
|
90
|
-
runId: string;
|
|
91
|
-
role: "builder" | "qa";
|
|
92
|
-
providerSessionId?: string;
|
|
93
|
-
sessionKey?: string;
|
|
94
|
-
settingsRevision: number;
|
|
95
|
-
observedAt: string;
|
|
96
|
-
}
|
|
97
65
|
export interface RoleMutationLease {
|
|
98
66
|
runId: string;
|
|
99
67
|
role: "builder" | "qa";
|
|
@@ -140,8 +108,7 @@ export declare class WorkflowDb {
|
|
|
140
108
|
at: string;
|
|
141
109
|
}>;
|
|
142
110
|
recordSettings(runId: string, role: string, boundary: number, settings: ResolvedAgentSettings, now?: Date): void;
|
|
143
|
-
recordProjectSettingsRevision(revision: number, defaults: unknown,
|
|
144
|
-
projectSettingsRevisionTargets(revision: number): Record<string, string[]>;
|
|
111
|
+
recordProjectSettingsRevision(revision: number, defaults: unknown, now?: Date): void;
|
|
145
112
|
recordTelemetry(runId: string, snapshot: unknown, now?: Date): void;
|
|
146
113
|
recordContextSample(sample: ContextSample): void;
|
|
147
114
|
contextSamples(runId: string, role?: "builder" | "qa"): ContextSample[];
|
|
@@ -219,28 +186,24 @@ export declare class WorkflowDb {
|
|
|
219
186
|
sessionKey?: string;
|
|
220
187
|
crossingKey: string;
|
|
221
188
|
beforeSample?: ContextSample;
|
|
222
|
-
origin?: CompactionAttemptRecord["origin"];
|
|
223
|
-
providerEventId?: string;
|
|
224
|
-
thresholdGenerationId?: string;
|
|
225
189
|
}, now?: Date): CompactionAttemptRecord;
|
|
226
190
|
finishCompactionAttempt(idempotencyKey: string, input: {
|
|
227
191
|
ok: boolean;
|
|
228
192
|
afterSample?: ContextSample;
|
|
229
193
|
error?: string;
|
|
230
|
-
status?: "succeeded" | "failed" | "unverified";
|
|
231
194
|
}, now?: Date): CompactionAttemptRecord;
|
|
195
|
+
/** Attach an eventually available measurement without changing confirmed accounting. */
|
|
196
|
+
recordCompactionAfterSample(idempotencyKey: string, afterSample: ContextSample, now?: Date): CompactionAttemptRecord;
|
|
232
197
|
compactionAttempt(idempotencyKey: string): CompactionAttemptRecord | undefined;
|
|
233
|
-
linkCompactionProviderEvent(idempotencyKey: string, providerEventId: string): CompactionAttemptRecord;
|
|
234
|
-
compactionAttempts(runId: string, role: "builder" | "qa", providerSession?: string | ProviderSessionRefV1): CompactionAttemptRecord[];
|
|
235
198
|
successfulCompactionCount(runId: string, role: "builder" | "qa", providerSession?: string | ProviderSessionRefV1): number;
|
|
236
|
-
upsertThresholdGeneration(input: Omit<ThresholdGenerationRecord, "createdAt" | "updatedAt">, now?: Date): ThresholdGenerationRecord;
|
|
237
|
-
thresholdGeneration(generationId: string): ThresholdGenerationRecord | undefined;
|
|
238
|
-
thresholdGenerations(runId: string, role?: "builder" | "qa", sessionKey?: string): ThresholdGenerationRecord[];
|
|
239
|
-
markRoleAdapterActive(input: ActiveRoleAdapterRecord): void;
|
|
240
|
-
clearRoleAdapterActive(runId: string, role: "builder" | "qa", sessionKey?: string): void;
|
|
241
|
-
activeRoleAdapters(runId?: string, observedAfter?: string): ActiveRoleAdapterRecord[];
|
|
242
199
|
recordRecoveryDecision(receipt: BuildRecoveryDecisionReceipt): void;
|
|
243
200
|
recoveryDecisions(runId: string): BuildRecoveryDecisionReceipt[];
|
|
201
|
+
/**
|
|
202
|
+
* A process may die after dispatching a provider turn but before its durable
|
|
203
|
+
* continuity checkpoint. The provider may have compacted during that gap, so
|
|
204
|
+
* recovery must not treat that role session's compaction history as complete.
|
|
205
|
+
*/
|
|
206
|
+
hasUncheckpointedRoleTurn(runId: string, role: "builder" | "qa"): boolean;
|
|
244
207
|
beginPublication(runId: string, intent: unknown, previousDigests: unknown, now?: Date): PublicationTransaction;
|
|
245
208
|
updatePublication(transactionId: string, status: PublicationTransaction["status"], now?: Date): PublicationTransaction;
|
|
246
209
|
publication(transactionId: string): PublicationTransaction | undefined;
|
package/dist/workflowDb.js
CHANGED
|
@@ -109,24 +109,8 @@ export class WorkflowDb {
|
|
|
109
109
|
this.insertEvent(runId, "settings_boundary", `role:${role}:${boundary}`, { revision: settings.settings_revision }, now.toISOString());
|
|
110
110
|
})();
|
|
111
111
|
}
|
|
112
|
-
recordProjectSettingsRevision(revision, defaults,
|
|
113
|
-
|
|
114
|
-
const recordedAt = targetsOrNow instanceof Date ? targetsOrNow : now;
|
|
115
|
-
this.db.transaction(() => {
|
|
116
|
-
this.db.prepare(`INSERT INTO project_settings_revisions(revision,defaults_json,created_at) VALUES(?,?,?)
|
|
117
|
-
ON CONFLICT(revision) DO UPDATE SET defaults_json=excluded.defaults_json,created_at=excluded.created_at`).run(revision, json(defaults), recordedAt.toISOString());
|
|
118
|
-
const entries = Object.entries(targets);
|
|
119
|
-
if (entries.length) {
|
|
120
|
-
this.db.prepare("DELETE FROM project_settings_revision_targets WHERE revision=?").run(revision);
|
|
121
|
-
const insert = this.db.prepare("INSERT INTO project_settings_revision_targets(revision,role,fields_json) VALUES(?,?,?)");
|
|
122
|
-
for (const [role, fields] of entries)
|
|
123
|
-
insert.run(revision, role, json([...new Set(fields)].sort()));
|
|
124
|
-
}
|
|
125
|
-
})();
|
|
126
|
-
}
|
|
127
|
-
projectSettingsRevisionTargets(revision) {
|
|
128
|
-
const rows = this.db.prepare("SELECT role,fields_json FROM project_settings_revision_targets WHERE revision=? ORDER BY role").all(revision);
|
|
129
|
-
return Object.fromEntries(rows.map((row) => [row.role, parseJson(row.fields_json)]));
|
|
112
|
+
recordProjectSettingsRevision(revision, defaults, now = new Date()) {
|
|
113
|
+
this.db.prepare("INSERT OR REPLACE INTO project_settings_revisions(revision,defaults_json,created_at) VALUES(?,?,?)").run(revision, json(defaults), now.toISOString());
|
|
130
114
|
}
|
|
131
115
|
recordTelemetry(runId, snapshot, now = new Date()) {
|
|
132
116
|
this.ensureRun(runId, "build", now);
|
|
@@ -169,22 +153,12 @@ export class WorkflowDb {
|
|
|
169
153
|
this.db.prepare(`INSERT INTO live_settings_acknowledgments(run_id,role,provider_session_id,session_key,session_ref_json,revision,acknowledged_at)
|
|
170
154
|
VALUES(?,?,?,?,?,?,?) ON CONFLICT(run_id,role,revision) DO UPDATE SET provider_session_id=excluded.provider_session_id,session_key=excluded.session_key,session_ref_json=excluded.session_ref_json,acknowledged_at=excluded.acknowledged_at`)
|
|
171
155
|
.run(ack.runId, ack.role, session.id, session.key, session.refJson, ack.revision, ack.acknowledgedAt);
|
|
172
|
-
if (session.key) {
|
|
173
|
-
this.db.prepare(`INSERT INTO live_settings_session_acknowledgments(run_id,role,provider_session_id,session_key,session_ref_json,revision,acknowledged_at)
|
|
174
|
-
VALUES(?,?,?,?,?,?,?) ON CONFLICT(run_id,role,session_key,revision) DO UPDATE SET provider_session_id=excluded.provider_session_id,session_ref_json=excluded.session_ref_json,acknowledged_at=excluded.acknowledged_at`)
|
|
175
|
-
.run(ack.runId, ack.role, session.id, session.key, session.refJson, ack.revision, ack.acknowledgedAt);
|
|
176
|
-
}
|
|
177
156
|
this.insertEvent(ack.runId, "live_settings_acknowledged", `settings:${ack.revision}`, { role: ack.role, providerSessionId: session.id, sessionKey: session.key }, ack.acknowledgedAt);
|
|
178
157
|
})();
|
|
179
158
|
}
|
|
180
159
|
settingsAcknowledgments(revision) {
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
const legacy = this.db.prepare("SELECT * FROM live_settings_acknowledgments WHERE revision=? ORDER BY acknowledged_at").all(revision)
|
|
184
|
-
.filter((row) => !covered.has(`${row.run_id}:${row.role}`));
|
|
185
|
-
return [...scoped, ...legacy]
|
|
186
|
-
.sort((left, right) => left.acknowledged_at.localeCompare(right.acknowledged_at))
|
|
187
|
-
.map((row) => ({ runId: row.run_id, role: row.role, ...(row.provider_session_id ? { providerSessionId: row.provider_session_id } : {}), ...(row.session_key ? { sessionKey: row.session_key } : {}), ...(row.session_ref_json ? { sessionRef: parseJson(row.session_ref_json) } : {}), revision: row.revision, acknowledgedAt: row.acknowledged_at }));
|
|
160
|
+
const rows = this.db.prepare("SELECT * FROM live_settings_acknowledgments WHERE revision=? ORDER BY acknowledged_at").all(revision);
|
|
161
|
+
return rows.map((row) => ({ runId: row.run_id, role: row.role, ...(row.provider_session_id ? { providerSessionId: row.provider_session_id } : {}), ...(row.session_key ? { sessionKey: row.session_key } : {}), ...(row.session_ref_json ? { sessionRef: parseJson(row.session_ref_json) } : {}), revision: row.revision, acknowledgedAt: row.acknowledged_at }));
|
|
188
162
|
}
|
|
189
163
|
appendContinuityEvent(input, now = new Date()) {
|
|
190
164
|
this.ensureRun(input.runId, "build", now);
|
|
@@ -466,17 +440,9 @@ export class WorkflowDb {
|
|
|
466
440
|
this.ensureRun(input.runId);
|
|
467
441
|
const at = now.toISOString();
|
|
468
442
|
const session = sessionParts(input.sessionRef, input.providerSessionId, input.sessionKey);
|
|
469
|
-
this.db.prepare(`INSERT OR IGNORE INTO compaction_attempts(idempotency_key,run_id,role,provider_session_id,session_key,session_ref_json,crossing_key,status,before_sample_json,
|
|
470
|
-
VALUES(?,?,?,?,?,?,?,'started'
|
|
471
|
-
|
|
472
|
-
if (direct)
|
|
473
|
-
return direct;
|
|
474
|
-
if (session.key && input.providerEventId) {
|
|
475
|
-
const duplicate = this.db.prepare("SELECT * FROM compaction_attempts WHERE run_id=? AND role=? AND session_key=? AND provider_event_id=?").get(input.runId, input.role, session.key, input.providerEventId);
|
|
476
|
-
if (duplicate)
|
|
477
|
-
return compactionAttemptFromRow(duplicate);
|
|
478
|
-
}
|
|
479
|
-
throw new Error(`failed to persist compaction attempt ${input.idempotencyKey}`);
|
|
443
|
+
this.db.prepare(`INSERT OR IGNORE INTO compaction_attempts(idempotency_key,run_id,role,provider_session_id,session_key,session_ref_json,crossing_key,status,before_sample_json,created_at,updated_at)
|
|
444
|
+
VALUES(?,?,?,?,?,?,?,'started',?,?,?)`).run(input.idempotencyKey, input.runId, input.role, session.id, session.key, session.refJson, input.crossingKey, input.beforeSample ? json(input.beforeSample) : null, at, at);
|
|
445
|
+
return this.compactionAttempt(input.idempotencyKey);
|
|
480
446
|
}
|
|
481
447
|
finishCompactionAttempt(idempotencyKey, input, now = new Date()) {
|
|
482
448
|
const prior = this.compactionAttempt(idempotencyKey);
|
|
@@ -485,100 +451,34 @@ export class WorkflowDb {
|
|
|
485
451
|
if (prior.status !== "started")
|
|
486
452
|
return prior;
|
|
487
453
|
this.db.prepare("UPDATE compaction_attempts SET status=?,after_sample_json=?,error=?,updated_at=? WHERE idempotency_key=?")
|
|
488
|
-
.run(input.
|
|
489
|
-
|
|
490
|
-
this.insertEvent(prior.runId, `compaction_${status}`, `context:${prior.role}`, { idempotencyKey, providerSessionId: prior.providerSessionId, sessionKey: prior.sessionKey, crossingKey: prior.crossingKey, origin: prior.origin, providerEventId: prior.providerEventId }, now.toISOString());
|
|
454
|
+
.run(input.ok ? "succeeded" : "failed", input.afterSample ? json(input.afterSample) : null, input.error ? sanitizeText(input.error).slice(0, 2000) : null, now.toISOString(), idempotencyKey);
|
|
455
|
+
this.insertEvent(prior.runId, input.ok ? "compaction_succeeded" : "compaction_failed", `context:${prior.role}`, { idempotencyKey, providerSessionId: prior.providerSessionId, sessionKey: prior.sessionKey, crossingKey: prior.crossingKey }, now.toISOString());
|
|
491
456
|
return this.compactionAttempt(idempotencyKey);
|
|
492
457
|
}
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
return row ? compactionAttemptFromRow(row) : undefined;
|
|
496
|
-
}
|
|
497
|
-
linkCompactionProviderEvent(idempotencyKey, providerEventId) {
|
|
458
|
+
/** Attach an eventually available measurement without changing confirmed accounting. */
|
|
459
|
+
recordCompactionAfterSample(idempotencyKey, afterSample, now = new Date()) {
|
|
498
460
|
const prior = this.compactionAttempt(idempotencyKey);
|
|
499
461
|
if (!prior)
|
|
500
462
|
throw new Error(`compaction attempt not found: ${idempotencyKey}`);
|
|
501
|
-
if (prior.
|
|
463
|
+
if (prior.status !== "succeeded")
|
|
502
464
|
return prior;
|
|
503
|
-
this.db.prepare("UPDATE compaction_attempts SET
|
|
504
|
-
.run(
|
|
465
|
+
this.db.prepare("UPDATE compaction_attempts SET after_sample_json=?,updated_at=? WHERE idempotency_key=?")
|
|
466
|
+
.run(json(afterSample), now.toISOString(), idempotencyKey);
|
|
505
467
|
return this.compactionAttempt(idempotencyKey);
|
|
506
468
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
const session = sessionParts(typeof providerSession === "object" ? providerSession : undefined, typeof providerSession === "string" ? providerSession : undefined);
|
|
511
|
-
const rows = session.key
|
|
512
|
-
? this.db.prepare("SELECT * FROM compaction_attempts WHERE run_id=? AND role=? AND session_key=? ORDER BY created_at,idempotency_key").all(runId, role, session.key)
|
|
513
|
-
: this.db.prepare("SELECT * FROM compaction_attempts WHERE run_id=? AND role=? AND provider_session_id=? AND session_key IS NULL ORDER BY created_at,idempotency_key").all(runId, role, session.id);
|
|
514
|
-
return rows.map(compactionAttemptFromRow);
|
|
469
|
+
compactionAttempt(idempotencyKey) {
|
|
470
|
+
const row = this.db.prepare("SELECT * FROM compaction_attempts WHERE idempotency_key=?").get(idempotencyKey);
|
|
471
|
+
return row ? compactionAttemptFromRow(row) : undefined;
|
|
515
472
|
}
|
|
516
473
|
successfulCompactionCount(runId, role, providerSession) {
|
|
517
474
|
if (!providerSession)
|
|
518
475
|
return 0;
|
|
519
476
|
const session = sessionParts(typeof providerSession === "object" ? providerSession : undefined, typeof providerSession === "string" ? providerSession : undefined);
|
|
520
477
|
const row = session.key
|
|
521
|
-
? this.db.prepare(`SELECT COUNT(*) AS count FROM compaction_attempts WHERE run_id=? AND role=? AND session_key=? AND status
|
|
522
|
-
: this.db.prepare(`SELECT COUNT(*) AS count FROM compaction_attempts WHERE run_id=? AND role=? AND provider_session_id=? AND session_key IS NULL AND status
|
|
478
|
+
? this.db.prepare(`SELECT COUNT(*) AS count FROM compaction_attempts WHERE run_id=? AND role=? AND session_key=? AND status='succeeded'`).get(runId, role, session.key)
|
|
479
|
+
: this.db.prepare(`SELECT COUNT(*) AS count FROM compaction_attempts WHERE run_id=? AND role=? AND provider_session_id=? AND session_key IS NULL AND status='succeeded'`).get(runId, role, session.id);
|
|
523
480
|
return row.count;
|
|
524
481
|
}
|
|
525
|
-
upsertThresholdGeneration(input, now = new Date()) {
|
|
526
|
-
this.ensureRun(input.runId);
|
|
527
|
-
const at = now.toISOString();
|
|
528
|
-
const session = sessionParts(input.sessionRef, input.providerSessionId, input.sessionKey);
|
|
529
|
-
this.db.prepare(`INSERT INTO threshold_generations(
|
|
530
|
-
generation_id,run_id,role,provider_session_id,session_key,session_ref_json,generation,state,configured_ceiling_percent,
|
|
531
|
-
installed_native_token_limit,installed_native_percent,settings_revision,model,latest_sample_json,frozen_action_digest,provider_event_id,created_at,updated_at
|
|
532
|
-
) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
|
533
|
-
ON CONFLICT(generation_id) DO UPDATE SET
|
|
534
|
-
provider_session_id=excluded.provider_session_id,session_key=excluded.session_key,session_ref_json=excluded.session_ref_json,
|
|
535
|
-
state=excluded.state,configured_ceiling_percent=excluded.configured_ceiling_percent,
|
|
536
|
-
installed_native_token_limit=excluded.installed_native_token_limit,installed_native_percent=excluded.installed_native_percent,
|
|
537
|
-
settings_revision=excluded.settings_revision,model=excluded.model,latest_sample_json=excluded.latest_sample_json,
|
|
538
|
-
frozen_action_digest=COALESCE(excluded.frozen_action_digest,threshold_generations.frozen_action_digest),
|
|
539
|
-
provider_event_id=COALESCE(excluded.provider_event_id,threshold_generations.provider_event_id),updated_at=excluded.updated_at`).run(input.generationId, input.runId, input.role, session.id, session.key, session.refJson, input.generation, input.state, input.configuredCeilingPercent, input.installedNativeTokenLimit ?? null, input.installedNativePercent ?? null, input.settingsRevision, input.model, input.latestSample ? json(input.latestSample) : null, input.frozenActionDigest ?? null, input.providerEventId ?? null, at, at);
|
|
540
|
-
return this.thresholdGeneration(input.generationId);
|
|
541
|
-
}
|
|
542
|
-
thresholdGeneration(generationId) {
|
|
543
|
-
const row = this.db.prepare("SELECT * FROM threshold_generations WHERE generation_id=?").get(generationId);
|
|
544
|
-
return row ? thresholdGenerationFromRow(row) : undefined;
|
|
545
|
-
}
|
|
546
|
-
thresholdGenerations(runId, role, sessionKey) {
|
|
547
|
-
const rows = role && sessionKey
|
|
548
|
-
? this.db.prepare("SELECT * FROM threshold_generations WHERE run_id=? AND role=? AND session_key=? ORDER BY generation").all(runId, role, sessionKey)
|
|
549
|
-
: role
|
|
550
|
-
? this.db.prepare("SELECT * FROM threshold_generations WHERE run_id=? AND role=? ORDER BY created_at,generation").all(runId, role)
|
|
551
|
-
: this.db.prepare("SELECT * FROM threshold_generations WHERE run_id=? ORDER BY created_at,generation").all(runId);
|
|
552
|
-
return rows.map(thresholdGenerationFromRow);
|
|
553
|
-
}
|
|
554
|
-
markRoleAdapterActive(input) {
|
|
555
|
-
this.ensureRun(input.runId);
|
|
556
|
-
const sessionKey = input.sessionKey ?? `${input.role}:unscoped:${input.providerSessionId ?? "pending"}`;
|
|
557
|
-
this.db.prepare(`INSERT INTO active_role_adapter_sessions(run_id,role,provider_session_id,session_key,settings_revision,observed_at)
|
|
558
|
-
VALUES(?,?,?,?,?,?) ON CONFLICT(run_id,role,session_key) DO UPDATE SET provider_session_id=excluded.provider_session_id,settings_revision=excluded.settings_revision,observed_at=excluded.observed_at`)
|
|
559
|
-
.run(input.runId, input.role, input.providerSessionId ?? null, sessionKey, input.settingsRevision, input.observedAt);
|
|
560
|
-
}
|
|
561
|
-
clearRoleAdapterActive(runId, role, sessionKey) {
|
|
562
|
-
if (sessionKey)
|
|
563
|
-
this.db.prepare("DELETE FROM active_role_adapter_sessions WHERE run_id=? AND role=? AND session_key=?").run(runId, role, sessionKey);
|
|
564
|
-
else
|
|
565
|
-
this.db.prepare("DELETE FROM active_role_adapter_sessions WHERE run_id=? AND role=?").run(runId, role);
|
|
566
|
-
}
|
|
567
|
-
activeRoleAdapters(runId, observedAfter) {
|
|
568
|
-
const clauses = [];
|
|
569
|
-
const values = [];
|
|
570
|
-
if (runId) {
|
|
571
|
-
clauses.push("run_id=?");
|
|
572
|
-
values.push(runId);
|
|
573
|
-
}
|
|
574
|
-
if (observedAfter) {
|
|
575
|
-
clauses.push("observed_at>=?");
|
|
576
|
-
values.push(observedAfter);
|
|
577
|
-
}
|
|
578
|
-
const where = clauses.length ? ` WHERE ${clauses.join(" AND ")}` : "";
|
|
579
|
-
const rows = this.db.prepare(`SELECT * FROM active_role_adapter_sessions${where} ORDER BY run_id,role,session_key`).all(...values);
|
|
580
|
-
return rows.map((row) => ({ runId: row.run_id, role: row.role, ...(row.provider_session_id ? { providerSessionId: row.provider_session_id } : {}), ...(row.session_key ? { sessionKey: row.session_key } : {}), settingsRevision: row.settings_revision, observedAt: row.observed_at }));
|
|
581
|
-
}
|
|
582
482
|
recordRecoveryDecision(receipt) {
|
|
583
483
|
this.ensureRun(receipt.runId);
|
|
584
484
|
const digest = digestJson(receipt);
|
|
@@ -592,6 +492,25 @@ export class WorkflowDb {
|
|
|
592
492
|
recoveryDecisions(runId) {
|
|
593
493
|
return this.db.prepare("SELECT receipt_json FROM recovery_decisions WHERE run_id=? ORDER BY decision_id").all(runId).map((row) => parseJson(row.receipt_json));
|
|
594
494
|
}
|
|
495
|
+
/**
|
|
496
|
+
* A process may die after dispatching a provider turn but before its durable
|
|
497
|
+
* continuity checkpoint. The provider may have compacted during that gap, so
|
|
498
|
+
* recovery must not treat that role session's compaction history as complete.
|
|
499
|
+
*/
|
|
500
|
+
hasUncheckpointedRoleTurn(runId, role) {
|
|
501
|
+
const events = this.continuityEvents(runId);
|
|
502
|
+
let startSequence = 0;
|
|
503
|
+
for (const event of events) {
|
|
504
|
+
if (event.kind === "turn_started" && event.role === "host" && event.payload.role === role) {
|
|
505
|
+
startSequence = event.sequence;
|
|
506
|
+
}
|
|
507
|
+
else if (startSequence && event.sequence > startSequence
|
|
508
|
+
&& (event.kind === "turn_completed" || event.kind === "turn_completed_after_repair" || event.kind === "fresh_successor_accepted")) {
|
|
509
|
+
startSequence = 0;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
return startSequence > 0;
|
|
513
|
+
}
|
|
595
514
|
beginPublication(runId, intent, previousDigests, now = new Date()) {
|
|
596
515
|
const transactionId = randomUUID();
|
|
597
516
|
const at = now.toISOString();
|
|
@@ -706,7 +625,6 @@ export class WorkflowDb {
|
|
|
706
625
|
CREATE TABLE IF NOT EXISTS workflow_events(sequence INTEGER PRIMARY KEY AUTOINCREMENT,run_id TEXT NOT NULL REFERENCES workflow_runs(run_id),event_type TEXT NOT NULL,checkpoint TEXT NOT NULL,payload_json TEXT NOT NULL,created_at TEXT NOT NULL);
|
|
707
626
|
CREATE TABLE IF NOT EXISTS role_settings(run_id TEXT NOT NULL REFERENCES workflow_runs(run_id),role TEXT NOT NULL,boundary INTEGER NOT NULL,revision INTEGER NOT NULL,settings_json TEXT NOT NULL,created_at TEXT NOT NULL,PRIMARY KEY(run_id,role,boundary));
|
|
708
627
|
CREATE TABLE IF NOT EXISTS project_settings_revisions(revision INTEGER PRIMARY KEY,defaults_json TEXT NOT NULL,created_at TEXT NOT NULL);
|
|
709
|
-
CREATE TABLE IF NOT EXISTS project_settings_revision_targets(revision INTEGER NOT NULL REFERENCES project_settings_revisions(revision),role TEXT NOT NULL,fields_json TEXT NOT NULL,PRIMARY KEY(revision,role));
|
|
710
628
|
CREATE TABLE IF NOT EXISTS workflow_telemetry(id INTEGER PRIMARY KEY AUTOINCREMENT,run_id TEXT NOT NULL REFERENCES workflow_runs(run_id),snapshot_json TEXT NOT NULL,created_at TEXT NOT NULL);
|
|
711
629
|
CREATE TABLE IF NOT EXISTS provider_sessions(id INTEGER PRIMARY KEY AUTOINCREMENT,run_id TEXT NOT NULL REFERENCES workflow_runs(run_id),role TEXT NOT NULL,stream TEXT NOT NULL,provider TEXT NOT NULL,model TEXT NOT NULL,session_id TEXT,session_key TEXT,session_ref_json TEXT,transition TEXT NOT NULL,settings_revision INTEGER NOT NULL,created_at TEXT NOT NULL);
|
|
712
630
|
CREATE TABLE IF NOT EXISTS provider_session_bindings(session_key TEXT PRIMARY KEY,provider_session_id TEXT NOT NULL,role TEXT NOT NULL,session_ref_json TEXT NOT NULL,observed_at TEXT NOT NULL);
|
|
@@ -719,15 +637,12 @@ export class WorkflowDb {
|
|
|
719
637
|
CREATE TABLE IF NOT EXISTS context_samples(sample_id INTEGER PRIMARY KEY AUTOINCREMENT,run_id TEXT NOT NULL REFERENCES workflow_runs(run_id),role TEXT NOT NULL,provider_session_id TEXT,session_key TEXT,session_ref_json TEXT,sample_json TEXT NOT NULL,observed_at TEXT NOT NULL);
|
|
720
638
|
CREATE TABLE IF NOT EXISTS session_usage_samples(sample_id INTEGER PRIMARY KEY AUTOINCREMENT,run_id TEXT NOT NULL REFERENCES workflow_runs(run_id),role TEXT NOT NULL,provider_session_id TEXT,session_key TEXT,session_ref_json TEXT,sample_json TEXT NOT NULL,observed_at TEXT NOT NULL);
|
|
721
639
|
CREATE TABLE IF NOT EXISTS live_settings_acknowledgments(run_id TEXT NOT NULL REFERENCES workflow_runs(run_id),role TEXT NOT NULL,provider_session_id TEXT,session_key TEXT,session_ref_json TEXT,revision INTEGER NOT NULL,acknowledged_at TEXT NOT NULL,PRIMARY KEY(run_id,role,revision));
|
|
722
|
-
CREATE TABLE IF NOT EXISTS live_settings_session_acknowledgments(run_id TEXT NOT NULL REFERENCES workflow_runs(run_id),role TEXT NOT NULL,provider_session_id TEXT,session_key TEXT NOT NULL,session_ref_json TEXT,revision INTEGER NOT NULL,acknowledged_at TEXT NOT NULL,PRIMARY KEY(run_id,role,session_key,revision));
|
|
723
640
|
CREATE TABLE IF NOT EXISTS continuity_events(sequence INTEGER PRIMARY KEY AUTOINCREMENT,run_id TEXT NOT NULL REFERENCES workflow_runs(run_id),role TEXT NOT NULL,kind TEXT NOT NULL,payload_json TEXT NOT NULL,digest TEXT NOT NULL UNIQUE,authoritative_state_revision INTEGER NOT NULL,session_key TEXT,session_ref_json TEXT,created_at TEXT NOT NULL);
|
|
724
641
|
CREATE TABLE IF NOT EXISTS continuity_checkpoints(checkpoint_id INTEGER PRIMARY KEY AUTOINCREMENT,run_id TEXT NOT NULL REFERENCES workflow_runs(run_id),role TEXT NOT NULL,event_sequence INTEGER NOT NULL,state TEXT NOT NULL,delta_json TEXT NOT NULL,digest TEXT NOT NULL UNIQUE,predecessor_digest TEXT,authoritative_state_revision INTEGER NOT NULL,session_key TEXT,session_ref_json TEXT,created_at TEXT NOT NULL);
|
|
725
642
|
CREATE TABLE IF NOT EXISTS continuity_heads(run_id TEXT NOT NULL REFERENCES workflow_runs(run_id),role TEXT NOT NULL,state TEXT NOT NULL,event_sequence INTEGER NOT NULL,digest TEXT NOT NULL,authoritative_state_revision INTEGER NOT NULL,updated_at TEXT NOT NULL,PRIMARY KEY(run_id,role));
|
|
726
643
|
CREATE TABLE IF NOT EXISTS handoffs(run_id TEXT NOT NULL REFERENCES workflow_runs(run_id),generation INTEGER NOT NULL,role TEXT NOT NULL,manifest_digest TEXT NOT NULL,markdown_digest TEXT NOT NULL,predecessor_session_id TEXT,predecessor_session_key TEXT,predecessor_session_ref_json TEXT,successor_session_id TEXT,successor_session_key TEXT,successor_session_ref_json TEXT,state TEXT NOT NULL,failure TEXT,created_at TEXT NOT NULL,accepted_at TEXT,PRIMARY KEY(run_id,generation));
|
|
727
644
|
CREATE TABLE IF NOT EXISTS role_mutation_leases(run_id TEXT NOT NULL REFERENCES workflow_runs(run_id),role TEXT NOT NULL,generation INTEGER NOT NULL,provider_session_id TEXT NOT NULL,provider_session_key TEXT,provider_session_ref_json TEXT,moved_at TEXT NOT NULL,PRIMARY KEY(run_id,role));
|
|
728
|
-
CREATE TABLE IF NOT EXISTS compaction_attempts(idempotency_key TEXT PRIMARY KEY,run_id TEXT NOT NULL REFERENCES workflow_runs(run_id),role TEXT NOT NULL,provider_session_id TEXT,session_key TEXT,session_ref_json TEXT,crossing_key TEXT NOT NULL,status TEXT NOT NULL,before_sample_json TEXT,after_sample_json TEXT,error TEXT,
|
|
729
|
-
CREATE TABLE IF NOT EXISTS threshold_generations(generation_id TEXT PRIMARY KEY,run_id TEXT NOT NULL REFERENCES workflow_runs(run_id),role TEXT NOT NULL,provider_session_id TEXT,session_key TEXT,session_ref_json TEXT,generation INTEGER NOT NULL,state TEXT NOT NULL,configured_ceiling_percent REAL NOT NULL,installed_native_token_limit INTEGER,installed_native_percent REAL,settings_revision INTEGER NOT NULL,model TEXT NOT NULL,latest_sample_json TEXT,frozen_action_digest TEXT,provider_event_id TEXT,created_at TEXT NOT NULL,updated_at TEXT NOT NULL,UNIQUE(run_id,role,session_key,generation));
|
|
730
|
-
CREATE TABLE IF NOT EXISTS active_role_adapter_sessions(run_id TEXT NOT NULL REFERENCES workflow_runs(run_id),role TEXT NOT NULL,provider_session_id TEXT,session_key TEXT NOT NULL,settings_revision INTEGER NOT NULL,observed_at TEXT NOT NULL,PRIMARY KEY(run_id,role,session_key));
|
|
645
|
+
CREATE TABLE IF NOT EXISTS compaction_attempts(idempotency_key TEXT PRIMARY KEY,run_id TEXT NOT NULL REFERENCES workflow_runs(run_id),role TEXT NOT NULL,provider_session_id TEXT,session_key TEXT,session_ref_json TEXT,crossing_key TEXT NOT NULL,status TEXT NOT NULL,before_sample_json TEXT,after_sample_json TEXT,error TEXT,created_at TEXT NOT NULL,updated_at TEXT NOT NULL);
|
|
731
646
|
CREATE TABLE IF NOT EXISTS recovery_decisions(decision_id INTEGER PRIMARY KEY AUTOINCREMENT,run_id TEXT NOT NULL REFERENCES workflow_runs(run_id),decided_at TEXT NOT NULL,mode TEXT NOT NULL,digest TEXT NOT NULL UNIQUE,session_key TEXT,session_ref_json TEXT,receipt_json TEXT NOT NULL);
|
|
732
647
|
CREATE INDEX IF NOT EXISTS workflow_events_run ON workflow_events(run_id,sequence);
|
|
733
648
|
CREATE INDEX IF NOT EXISTS operations_run ON operation_journal(run_id,status);
|
|
@@ -745,7 +660,7 @@ export class WorkflowDb {
|
|
|
745
660
|
continuity_checkpoints: { session_key: "TEXT", session_ref_json: "TEXT" },
|
|
746
661
|
handoffs: { predecessor_session_key: "TEXT", predecessor_session_ref_json: "TEXT", successor_session_key: "TEXT", successor_session_ref_json: "TEXT" },
|
|
747
662
|
role_mutation_leases: { provider_session_key: "TEXT", provider_session_ref_json: "TEXT" },
|
|
748
|
-
compaction_attempts: { session_key: "TEXT", session_ref_json: "TEXT"
|
|
663
|
+
compaction_attempts: { session_key: "TEXT", session_ref_json: "TEXT" },
|
|
749
664
|
recovery_decisions: { session_key: "TEXT", session_ref_json: "TEXT" },
|
|
750
665
|
})) {
|
|
751
666
|
for (const [column, definition] of Object.entries(columns))
|
|
@@ -757,10 +672,6 @@ export class WorkflowDb {
|
|
|
757
672
|
CREATE INDEX IF NOT EXISTS context_samples_scoped ON context_samples(run_id,role,session_key,sample_id);
|
|
758
673
|
CREATE INDEX IF NOT EXISTS session_usage_scoped ON session_usage_samples(run_id,role,session_key,sample_id);
|
|
759
674
|
CREATE INDEX IF NOT EXISTS compaction_session_scoped ON compaction_attempts(run_id,role,session_key,status);
|
|
760
|
-
CREATE UNIQUE INDEX IF NOT EXISTS compaction_native_event_unique ON compaction_attempts(run_id,role,session_key,provider_event_id) WHERE provider_event_id IS NOT NULL;
|
|
761
|
-
CREATE INDEX IF NOT EXISTS threshold_generation_scoped ON threshold_generations(run_id,role,session_key,generation);
|
|
762
|
-
CREATE INDEX IF NOT EXISTS active_role_adapter_session_observed ON active_role_adapter_sessions(observed_at,run_id,role);
|
|
763
|
-
CREATE INDEX IF NOT EXISTS live_settings_session_revision ON live_settings_session_acknowledgments(revision,run_id,role,session_key);
|
|
764
675
|
`);
|
|
765
676
|
}
|
|
766
677
|
ensureColumn(table, column, definition) {
|
|
@@ -775,23 +686,7 @@ function continuityEventFromRow(row) { return { sequence: row.sequence, runId: r
|
|
|
775
686
|
function continuityCheckpointFromRow(row) { return { checkpointId: row.checkpoint_id, runId: row.run_id, role: row.role, sequence: row.event_sequence, state: row.state, delta: parseJson(row.delta_json), digest: row.digest, ...(row.predecessor_digest ? { predecessorDigest: row.predecessor_digest } : {}), authoritativeStateRevision: row.authoritative_state_revision, createdAt: row.created_at, ...(row.session_key ? { sessionKey: row.session_key } : {}), ...(row.session_ref_json ? { sessionRef: parseJson(row.session_ref_json) } : {}) }; }
|
|
776
687
|
function continuityHeadFromRow(row) { return { runId: row.run_id, role: row.role, state: row.state, sequence: row.event_sequence, digest: row.digest, authoritativeStateRevision: row.authoritative_state_revision, updatedAt: row.updated_at }; }
|
|
777
688
|
function handoffFromRow(row) { return { runId: row.run_id, generation: row.generation, manifestDigest: row.manifest_digest, markdownDigest: row.markdown_digest, ...(row.predecessor_session_id ? { predecessorSessionId: row.predecessor_session_id } : {}), ...(row.predecessor_session_ref_json ? { predecessorSessionRef: parseJson(row.predecessor_session_ref_json) } : {}), ...(row.successor_session_id ? { successorSessionId: row.successor_session_id } : {}), ...(row.successor_session_ref_json ? { successorSessionRef: parseJson(row.successor_session_ref_json) } : {}), state: row.state, createdAt: row.created_at, ...(row.accepted_at ? { acceptedAt: row.accepted_at } : {}) }; }
|
|
778
|
-
function compactionAttemptFromRow(row) { return { idempotencyKey: row.idempotency_key, runId: row.run_id, role: row.role, ...(row.provider_session_id ? { providerSessionId: row.provider_session_id } : {}), ...(row.session_key ? { sessionKey: row.session_key } : {}), ...(row.session_ref_json ? { sessionRef: parseJson(row.session_ref_json) } : {}), crossingKey: row.crossing_key, status: row.status,
|
|
779
|
-
function thresholdGenerationFromRow(row) {
|
|
780
|
-
return {
|
|
781
|
-
generationId: row.generation_id, runId: row.run_id, role: row.role,
|
|
782
|
-
...(row.provider_session_id ? { providerSessionId: row.provider_session_id } : {}),
|
|
783
|
-
...(row.session_key ? { sessionKey: row.session_key } : {}),
|
|
784
|
-
...(row.session_ref_json ? { sessionRef: parseJson(row.session_ref_json) } : {}),
|
|
785
|
-
generation: row.generation, state: row.state, configuredCeilingPercent: row.configured_ceiling_percent,
|
|
786
|
-
...(row.installed_native_token_limit === null ? {} : { installedNativeTokenLimit: row.installed_native_token_limit }),
|
|
787
|
-
...(row.installed_native_percent === null ? {} : { installedNativePercent: row.installed_native_percent }),
|
|
788
|
-
settingsRevision: row.settings_revision, model: row.model,
|
|
789
|
-
...(row.latest_sample_json ? { latestSample: parseJson(row.latest_sample_json) } : {}),
|
|
790
|
-
...(row.frozen_action_digest ? { frozenActionDigest: row.frozen_action_digest } : {}),
|
|
791
|
-
...(row.provider_event_id ? { providerEventId: row.provider_event_id } : {}),
|
|
792
|
-
createdAt: row.created_at, updatedAt: row.updated_at,
|
|
793
|
-
};
|
|
794
|
-
}
|
|
689
|
+
function compactionAttemptFromRow(row) { return { idempotencyKey: row.idempotency_key, runId: row.run_id, role: row.role, ...(row.provider_session_id ? { providerSessionId: row.provider_session_id } : {}), ...(row.session_key ? { sessionKey: row.session_key } : {}), ...(row.session_ref_json ? { sessionRef: parseJson(row.session_ref_json) } : {}), crossingKey: row.crossing_key, status: row.status, ...(row.before_sample_json ? { beforeSample: parseJson(row.before_sample_json) } : {}), ...(row.after_sample_json ? { afterSample: parseJson(row.after_sample_json) } : {}), ...(row.error ? { error: row.error } : {}), createdAt: row.created_at, updatedAt: row.updated_at }; }
|
|
795
690
|
function json(value) { return JSON.stringify(value ?? null); }
|
|
796
691
|
function parseJson(value) { return JSON.parse(value); }
|
|
797
692
|
function sessionParts(ref, rawId, suppliedKey) {
|