stageflow 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/activity.d.ts +3 -0
- package/dist/agent/claudeAdapter.js +84 -7
- package/dist/agent/claudeSession.d.ts +3 -0
- package/dist/agent/piAdapter.d.ts +2 -0
- package/dist/agent/piAdapter.js +29 -5
- package/dist/agent/port.d.ts +3 -0
- package/dist/cli/runOutput.js +32 -8
- package/dist/package-meta.d.ts +1 -1
- package/dist/package-meta.js +1 -1
- package/dist/runstore/port.d.ts +10 -0
- package/dist/runstore/runProjection.js +9 -0
- package/dist/runstore/sqlite/SqliteRunStore.js +27 -4
- package/dist/runstore/sqlite/schema.d.ts +1 -1
- package/dist/runstore/sqlite/schema.js +2 -0
- package/dist/runstore/stageExecution.d.ts +3 -0
- package/dist/runstore/stageExecution.js +6 -2
- package/dist/runstore/stageSnapshot.js +11 -0
- package/dist/runtime/stageRunner.js +2 -2
- package/dist/types/usage.d.ts +16 -0
- package/dist/types/usage.js +24 -0
- package/dist/ui/assets/{index-uVHiae8s.css → index-D2Wt8hwH.css} +1 -1
- package/dist/ui/assets/{index-GtyiWiZg.js → index-XWJovnp7.js} +26 -26
- package/dist/ui/index.html +2 -2
- package/package.json +1 -1
package/dist/agent/activity.d.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* those Q&A bodies.
|
|
12
12
|
*/
|
|
13
13
|
import type { AskOperatorAnswer, AskOperatorPrompt } from "../tools/askOperator.js";
|
|
14
|
+
import type { StageUsage } from "../types/usage.js";
|
|
14
15
|
export declare const ACTIVITY_TEXT_LIMIT = 2000;
|
|
15
16
|
export declare const ACTIVITY_TEXT_LIMIT_ENV = "STAGEFLOW_ACTIVITY_TEXT_LIMIT";
|
|
16
17
|
export declare const ACTIVITY_VERBOSE_ENV = "STAGEFLOW_ACTIVITY_VERBOSE";
|
|
@@ -65,9 +66,11 @@ export type StageLifecycleEvent = {
|
|
|
65
66
|
event: "resumed";
|
|
66
67
|
} | {
|
|
67
68
|
event: "succeeded";
|
|
69
|
+
usage?: StageUsage;
|
|
68
70
|
} | {
|
|
69
71
|
event: "failed";
|
|
70
72
|
reason: string;
|
|
73
|
+
usage?: StageUsage;
|
|
71
74
|
} | {
|
|
72
75
|
event: "skipped";
|
|
73
76
|
}
|
|
@@ -43,10 +43,12 @@ import { isAdvancingEnvelope } from "../envelope/check.js";
|
|
|
43
43
|
import { assertAnswerMatchesPrompt, parseAskOperatorAnswer, summarizeAnswer, } from "../tools/askOperator.js";
|
|
44
44
|
import { buildStageflowMcpServer, EMIT_STAGE_ENVELOPE_TOOL_NAME, STAGEFLOW_MCP_SERVER_NAME, WRITE_STAGE_ARTIFACT_TOOL_NAME, ASK_OPERATOR_TOOL_NAME, } from "./claudeTools.js";
|
|
45
45
|
import { createClaudeActivityMapper } from "./claudeActivity.js";
|
|
46
|
+
import { readActivityVerbose } from "./activity.js";
|
|
46
47
|
import { composeStageUserPrompt } from "./piAdapter.js";
|
|
47
48
|
import { claudeSessionMarkerPath, clearClaudeSessionMarker, readClaudeSessionMarker, writeClaudeSessionMarker, } from "./claudeSession.js";
|
|
48
49
|
import { DEFAULT_STAGE_TIMEOUT_MS, runStageViaOpen, runtimeStageId, } from "./port.js";
|
|
49
50
|
import { StageMcpError } from "../config/resolveStageMcpServers.js";
|
|
51
|
+
import { addModelUsage, emptyStageUsage } from "../types/usage.js";
|
|
50
52
|
/** Built-in tool allowlist, mirroring Pi's sealed-session base set (read/bash/write/edit). */
|
|
51
53
|
const CLAUDE_BUILTIN_TOOLS = ["Read", "Write", "Edit", "Bash"];
|
|
52
54
|
function resolveClaudeModel(stageModel) {
|
|
@@ -102,6 +104,65 @@ function failedPassedMcpConnect(resolved, listed) {
|
|
|
102
104
|
}
|
|
103
105
|
return undefined;
|
|
104
106
|
}
|
|
107
|
+
/** Merge one query() call's per-model totals (this call's own turns only) into the stage-level accumulator. */
|
|
108
|
+
function mergeClaudeModelUsage(usage, modelUsage) {
|
|
109
|
+
if (!modelUsage)
|
|
110
|
+
return;
|
|
111
|
+
for (const [key, entry] of Object.entries(modelUsage)) {
|
|
112
|
+
addModelUsage(usage, entry.canonicalModel ?? key, {
|
|
113
|
+
inputTokens: entry.inputTokens,
|
|
114
|
+
outputTokens: entry.outputTokens,
|
|
115
|
+
cacheReadInputTokens: entry.cacheReadInputTokens,
|
|
116
|
+
cacheCreationInputTokens: entry.cacheCreationInputTokens,
|
|
117
|
+
costUsd: entry.costUSD,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const TRAILING_RESULT_WAIT_MS = 2000;
|
|
122
|
+
/**
|
|
123
|
+
* emit_stage_envelope's tool_result lands on a "user" message, which the
|
|
124
|
+
* caller interrupts and breaks on immediately (never-let-it-go-dangling) —
|
|
125
|
+
* well before the SDK's own "result" message (the one carrying cost/token
|
|
126
|
+
* totals) would naturally arrive. There can be more than one trailing frame
|
|
127
|
+
* after the interrupted tool-result (observed: another "user" frame before
|
|
128
|
+
* "result"), so this drains messages in a loop, bounded by one overall time
|
|
129
|
+
* budget rather than a single peek. By the docstring above, `interrupt()`
|
|
130
|
+
* resolving means the CLI subprocess has already exited, so its trailing
|
|
131
|
+
* frames are normally already queued or arrive within milliseconds — this
|
|
132
|
+
* claims them without risking a real hang.
|
|
133
|
+
*/
|
|
134
|
+
async function drainTrailingResultUsage(stream, usage) {
|
|
135
|
+
const debug = readActivityVerbose();
|
|
136
|
+
const deadline = Date.now() + TRAILING_RESULT_WAIT_MS;
|
|
137
|
+
try {
|
|
138
|
+
while (Date.now() < deadline) {
|
|
139
|
+
const next = await Promise.race([
|
|
140
|
+
stream.next(),
|
|
141
|
+
new Promise((resolve) => setTimeout(() => resolve({ done: true, value: undefined }), deadline - Date.now())),
|
|
142
|
+
]);
|
|
143
|
+
if (debug) {
|
|
144
|
+
console.error(`[claudeAdapter] drainTrailingResultUsage: done=${next.done} type=${!next.done ? next.value?.type : "n/a"}`);
|
|
145
|
+
}
|
|
146
|
+
if (next.done)
|
|
147
|
+
return;
|
|
148
|
+
const message = next.value;
|
|
149
|
+
if (message.type === "result") {
|
|
150
|
+
if (debug) {
|
|
151
|
+
console.error(`[claudeAdapter] drainTrailingResultUsage: modelUsage=${JSON.stringify(message.modelUsage)}`);
|
|
152
|
+
}
|
|
153
|
+
mergeClaudeModelUsage(usage, message.modelUsage);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
// not "result" yet (e.g. another "user"/"system" frame) — keep draining within the budget
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
if (debug) {
|
|
161
|
+
console.error(`[claudeAdapter] drainTrailingResultUsage: threw ${err instanceof Error ? err.message : String(err)}`);
|
|
162
|
+
}
|
|
163
|
+
// best-effort; a natural stop may already have ended the turn
|
|
164
|
+
}
|
|
165
|
+
}
|
|
105
166
|
async function* singleUserMessage(text) {
|
|
106
167
|
yield {
|
|
107
168
|
type: "user",
|
|
@@ -110,7 +171,7 @@ async function* singleUserMessage(text) {
|
|
|
110
171
|
};
|
|
111
172
|
}
|
|
112
173
|
/** One `query()` call — either the stage's initial turn or a resume turn. */
|
|
113
|
-
async function runTurn(input, model, markerPath, promptText, resumeSessionId) {
|
|
174
|
+
async function runTurn(input, model, markerPath, promptText, resumeSessionId, usage) {
|
|
114
175
|
const timeoutMs = input.timeoutMs ?? DEFAULT_STAGE_TIMEOUT_MS;
|
|
115
176
|
const controller = new AbortController();
|
|
116
177
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
@@ -187,15 +248,23 @@ async function runTurn(input, model, markerPath, promptText, resumeSessionId) {
|
|
|
187
248
|
}
|
|
188
249
|
if (message.type === "user" &&
|
|
189
250
|
(emitCapture.envelope !== undefined || askCapture.prompt !== undefined)) {
|
|
251
|
+
if (readActivityVerbose()) {
|
|
252
|
+
console.error(`[claudeAdapter] interrupting on user tool-result frame`);
|
|
253
|
+
}
|
|
190
254
|
try {
|
|
191
255
|
await stream.interrupt();
|
|
192
256
|
}
|
|
193
257
|
catch {
|
|
194
258
|
// best-effort backstop; a natural stop may have already ended the turn
|
|
195
259
|
}
|
|
260
|
+
await drainTrailingResultUsage(stream, usage);
|
|
196
261
|
break;
|
|
197
262
|
}
|
|
198
263
|
if (message.type === "result") {
|
|
264
|
+
if (readActivityVerbose()) {
|
|
265
|
+
console.error(`[claudeAdapter] main loop saw result: modelUsage=${JSON.stringify(message.modelUsage)}`);
|
|
266
|
+
}
|
|
267
|
+
mergeClaudeModelUsage(usage, message.modelUsage);
|
|
199
268
|
break;
|
|
200
269
|
}
|
|
201
270
|
}
|
|
@@ -204,26 +273,27 @@ async function runTurn(input, model, markerPath, promptText, resumeSessionId) {
|
|
|
204
273
|
if (resolvedSessionId === undefined) {
|
|
205
274
|
return {
|
|
206
275
|
kind: "completed",
|
|
207
|
-
result: { ok: false, reason: "ask_operator called but no session_id was observed" },
|
|
276
|
+
result: { ok: false, reason: "ask_operator called but no session_id was observed", usage },
|
|
208
277
|
};
|
|
209
278
|
}
|
|
210
279
|
await writeClaudeSessionMarker(markerPath, {
|
|
211
280
|
sessionId: resolvedSessionId,
|
|
212
281
|
prompt: askCapture.prompt,
|
|
282
|
+
usage,
|
|
213
283
|
});
|
|
214
284
|
return { kind: "waiting", sessionId: resolvedSessionId, request: askCapture.prompt };
|
|
215
285
|
}
|
|
216
286
|
await clearClaudeSessionMarker(markerPath);
|
|
217
|
-
return { kind: "completed", result: resultFromCapture(emitCapture) };
|
|
287
|
+
return { kind: "completed", result: { ...resultFromCapture(emitCapture), usage } };
|
|
218
288
|
}
|
|
219
289
|
catch (err) {
|
|
220
290
|
if (askCapture.prompt !== undefined && sessionId !== undefined) {
|
|
221
|
-
await writeClaudeSessionMarker(markerPath, { sessionId, prompt: askCapture.prompt });
|
|
291
|
+
await writeClaudeSessionMarker(markerPath, { sessionId, prompt: askCapture.prompt, usage });
|
|
222
292
|
return { kind: "waiting", sessionId, request: askCapture.prompt };
|
|
223
293
|
}
|
|
224
294
|
if (emitCapture.envelope && isAdvancingEnvelope(emitCapture.envelope)) {
|
|
225
295
|
await clearClaudeSessionMarker(markerPath);
|
|
226
|
-
return { kind: "completed", result: { ok: true, envelope: emitCapture.envelope } };
|
|
296
|
+
return { kind: "completed", result: { ok: true, envelope: emitCapture.envelope, usage } };
|
|
227
297
|
}
|
|
228
298
|
return {
|
|
229
299
|
kind: "completed",
|
|
@@ -231,6 +301,7 @@ async function runTurn(input, model, markerPath, promptText, resumeSessionId) {
|
|
|
231
301
|
ok: false,
|
|
232
302
|
reason: err instanceof Error ? err.message : String(err),
|
|
233
303
|
envelope: emitCapture.envelope,
|
|
304
|
+
usage,
|
|
234
305
|
},
|
|
235
306
|
};
|
|
236
307
|
}
|
|
@@ -254,6 +325,7 @@ export class ClaudeAgentAdapter {
|
|
|
254
325
|
let bootstrapped = false;
|
|
255
326
|
let waiting;
|
|
256
327
|
let pendingAnswer;
|
|
328
|
+
const usage = emptyStageUsage();
|
|
257
329
|
const applyOutcome = (outcome) => {
|
|
258
330
|
if (outcome.kind === "waiting") {
|
|
259
331
|
waiting = { sessionId: outcome.sessionId, prompt: outcome.request };
|
|
@@ -275,6 +347,11 @@ export class ClaudeAgentAdapter {
|
|
|
275
347
|
if (marker?.prompt !== undefined) {
|
|
276
348
|
waiting = { sessionId: marker.sessionId, prompt: marker.prompt };
|
|
277
349
|
}
|
|
350
|
+
if (marker?.usage) {
|
|
351
|
+
for (const [model, breakdown] of Object.entries(marker.usage.models)) {
|
|
352
|
+
addModelUsage(usage, model, breakdown);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
278
355
|
}
|
|
279
356
|
if (waiting) {
|
|
280
357
|
if (pendingAnswer === undefined) {
|
|
@@ -301,14 +378,14 @@ export class ClaudeAgentAdapter {
|
|
|
301
378
|
await clearClaudeSessionMarker(markerPath);
|
|
302
379
|
return { status: "completed", result: { ok: false, reason: pre.reason } };
|
|
303
380
|
}
|
|
304
|
-
const outcome = await runTurn(input, pre.model, markerPath, resumePromptText(parsedAnswer), sessionId);
|
|
381
|
+
const outcome = await runTurn(input, pre.model, markerPath, resumePromptText(parsedAnswer), sessionId, usage);
|
|
305
382
|
return applyOutcome(outcome);
|
|
306
383
|
}
|
|
307
384
|
const pre = preflight(input);
|
|
308
385
|
if (!pre.ok) {
|
|
309
386
|
return { status: "completed", result: { ok: false, reason: pre.reason } };
|
|
310
387
|
}
|
|
311
|
-
const outcome = await runTurn(input, pre.model, markerPath, composeStageUserPrompt(input, EMIT_STAGE_ENVELOPE_TOOL_NAME, undefined, WRITE_STAGE_ARTIFACT_TOOL_NAME), undefined);
|
|
388
|
+
const outcome = await runTurn(input, pre.model, markerPath, composeStageUserPrompt(input, EMIT_STAGE_ENVELOPE_TOOL_NAME, undefined, WRITE_STAGE_ARTIFACT_TOOL_NAME), undefined, usage);
|
|
312
389
|
return applyOutcome(outcome);
|
|
313
390
|
},
|
|
314
391
|
deliverAnswer(answer) {
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import type { AskOperatorPrompt } from "../tools/askOperator.js";
|
|
2
2
|
import type { StageRunInput } from "./port.js";
|
|
3
|
+
import type { StageUsage } from "../types/usage.js";
|
|
3
4
|
export type ClaudeSessionMarker = {
|
|
4
5
|
sessionId: string;
|
|
5
6
|
prompt?: AskOperatorPrompt;
|
|
7
|
+
/** Cost/tokens accumulated by turns run so far, so a restart resuming this stage doesn't lose prior spend. */
|
|
8
|
+
usage?: StageUsage;
|
|
6
9
|
};
|
|
7
10
|
/**
|
|
8
11
|
* Derives a stable, per-attempt path from `input.resumeToken` — the same
|
|
@@ -5,6 +5,7 @@ import "./cursorProvider.js";
|
|
|
5
5
|
import { type StageActivityObserver } from "./activityObserver.js";
|
|
6
6
|
import type { AgentPort, OpaqueAnswer, StageHandle, StageHandleCloseOptions, StageRunInput, StageRunResult } from "./port.js";
|
|
7
7
|
import type { StageGateKind } from "../types/stage.js";
|
|
8
|
+
import { type StageUsage } from "../types/usage.js";
|
|
8
9
|
/**
|
|
9
10
|
* Stage tool allowlist for sealed Pi sessions.
|
|
10
11
|
* Includes `ask_operator` unless `gateKinds` is an empty list (R6).
|
|
@@ -56,6 +57,7 @@ export declare function extractPartialResultText(partialResult: unknown): string
|
|
|
56
57
|
export type RouteSessionEventToProgressOptions = {
|
|
57
58
|
observer: StageActivityObserver;
|
|
58
59
|
verbose: boolean;
|
|
60
|
+
usage?: StageUsage;
|
|
59
61
|
};
|
|
60
62
|
/**
|
|
61
63
|
* Adapter-edge routing: stream deltas to the observer, map milestones to
|
package/dist/agent/piAdapter.js
CHANGED
|
@@ -47,6 +47,7 @@ import { findProviderSupport } from "./providerSupport.js";
|
|
|
47
47
|
import { mapSessionEventToActivity, readActivityVerbose } from "./activity.js";
|
|
48
48
|
import { createStageActivityObserver, } from "./activityObserver.js";
|
|
49
49
|
import { DEFAULT_STAGE_TIMEOUT_MS, runtimeStageId, runStageViaOpen } from "./port.js";
|
|
50
|
+
import { addModelUsage, emptyStageUsage } from "../types/usage.js";
|
|
50
51
|
/**
|
|
51
52
|
* Stage tool allowlist for sealed Pi sessions.
|
|
52
53
|
* Includes `ask_operator` unless `gateKinds` is an empty list (R6).
|
|
@@ -302,13 +303,29 @@ export function extractPartialResultText(partialResult) {
|
|
|
302
303
|
}
|
|
303
304
|
return String(partialResult);
|
|
304
305
|
}
|
|
306
|
+
/** Merge one assistant message's usage/cost (as reported by pi-ai) into the stage-level accumulator. */
|
|
307
|
+
function mergePiAssistantUsage(usage, message) {
|
|
308
|
+
if (!message || typeof message !== "object")
|
|
309
|
+
return;
|
|
310
|
+
const msg = message;
|
|
311
|
+
if (msg.role !== "assistant" || typeof msg.model !== "string" || !msg.usage)
|
|
312
|
+
return;
|
|
313
|
+
const u = msg.usage;
|
|
314
|
+
addModelUsage(usage, msg.model, {
|
|
315
|
+
inputTokens: u.input,
|
|
316
|
+
outputTokens: u.output,
|
|
317
|
+
cacheReadInputTokens: u.cacheRead,
|
|
318
|
+
cacheCreationInputTokens: u.cacheWrite,
|
|
319
|
+
costUsd: u.cost?.total ?? 0,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
305
322
|
/**
|
|
306
323
|
* Adapter-edge routing: stream deltas to the observer, map milestones to
|
|
307
324
|
* StageActivityEvent. Verbose thinking / live tool partials go to observer
|
|
308
325
|
* hooks (stderr streams + coalesced/throttled persist via onActivity).
|
|
309
326
|
*/
|
|
310
327
|
export function routeSessionEventToProgress(event, options) {
|
|
311
|
-
const { observer, verbose } = options;
|
|
328
|
+
const { observer, verbose, usage } = options;
|
|
312
329
|
if (event.type === "message_update") {
|
|
313
330
|
const ame = event.assistantMessageEvent;
|
|
314
331
|
if (ame && typeof ame === "object") {
|
|
@@ -334,6 +351,9 @@ export function routeSessionEventToProgress(event, options) {
|
|
|
334
351
|
}
|
|
335
352
|
if (event.type === "message_end" || event.type === "agent_end") {
|
|
336
353
|
observer.onStreamBoundary();
|
|
354
|
+
if (usage && event.type === "message_end") {
|
|
355
|
+
mergePiAssistantUsage(usage, event.message);
|
|
356
|
+
}
|
|
337
357
|
}
|
|
338
358
|
if (event.type === "agent_start" ||
|
|
339
359
|
event.type === "turn_start" ||
|
|
@@ -350,13 +370,14 @@ export function routeSessionEventToProgress(event, options) {
|
|
|
350
370
|
* Map Pi session events to StageActivityEvent at the adapter edge, then
|
|
351
371
|
* forward to the activity observer (stderr + onActivity).
|
|
352
372
|
*/
|
|
353
|
-
function attachStageProgress(session, onActivity) {
|
|
373
|
+
function attachStageProgress(session, onActivity, usage) {
|
|
354
374
|
const observer = createStageActivityObserver({ onActivity, writeStderr: true });
|
|
355
375
|
const verbose = readActivityVerbose();
|
|
356
376
|
const unsubscribe = session.subscribe((event) => {
|
|
357
377
|
routeSessionEventToProgress(event, {
|
|
358
378
|
observer,
|
|
359
379
|
verbose,
|
|
380
|
+
usage,
|
|
360
381
|
});
|
|
361
382
|
});
|
|
362
383
|
return () => {
|
|
@@ -752,6 +773,7 @@ async function prepareStageSessionWiring(input, sessionManager, existingAskWaitC
|
|
|
752
773
|
const { roots } = input;
|
|
753
774
|
const provider = findProviderSupport(input.stage.model);
|
|
754
775
|
const capture = {};
|
|
776
|
+
const usage = emptyStageUsage();
|
|
755
777
|
const askWaitChannel = existingAskWaitChannel ?? new AskOperatorWaitChannel();
|
|
756
778
|
const emitDef = createEmitStageEnvelopeTool(capture, input.stage.payload_schema, input.forkEmitContext, input.cloneEmitContext, {
|
|
757
779
|
checks: input.stage.pre_emit_checks,
|
|
@@ -864,6 +886,7 @@ async function prepareStageSessionWiring(input, sessionManager, existingAskWaitC
|
|
|
864
886
|
providerEmitHint: provider?.emitToolHint?.(emitDef.name),
|
|
865
887
|
restoreProvider,
|
|
866
888
|
capture,
|
|
889
|
+
usage,
|
|
867
890
|
askWaitChannel,
|
|
868
891
|
connecting: attached.connecting,
|
|
869
892
|
cancelConnecting: attached.cancel,
|
|
@@ -1090,7 +1113,7 @@ export class PiAgentAdapter {
|
|
|
1090
1113
|
return;
|
|
1091
1114
|
}
|
|
1092
1115
|
session = bound;
|
|
1093
|
-
unsubscribeProgress = attachStageProgress(session, input.onActivity);
|
|
1116
|
+
unsubscribeProgress = attachStageProgress(session, input.onActivity, wiring.usage);
|
|
1094
1117
|
})();
|
|
1095
1118
|
const runWithTimeout = async (work) => {
|
|
1096
1119
|
await preparePromise;
|
|
@@ -1109,16 +1132,17 @@ export class PiAgentAdapter {
|
|
|
1109
1132
|
});
|
|
1110
1133
|
});
|
|
1111
1134
|
await Promise.race([workPromise, abortPromise]);
|
|
1112
|
-
return resultFromCapture(wiring.capture);
|
|
1135
|
+
return { ...resultFromCapture(wiring.capture), usage: wiring.usage };
|
|
1113
1136
|
}
|
|
1114
1137
|
catch (err) {
|
|
1115
1138
|
if (wiring.capture.envelope && isAdvancingEnvelope(wiring.capture.envelope)) {
|
|
1116
|
-
return { ok: true, envelope: wiring.capture.envelope };
|
|
1139
|
+
return { ok: true, envelope: wiring.capture.envelope, usage: wiring.usage };
|
|
1117
1140
|
}
|
|
1118
1141
|
return {
|
|
1119
1142
|
ok: false,
|
|
1120
1143
|
reason: err instanceof Error ? err.message : String(err),
|
|
1121
1144
|
envelope: wiring.capture.envelope,
|
|
1145
|
+
usage: wiring.usage,
|
|
1122
1146
|
};
|
|
1123
1147
|
}
|
|
1124
1148
|
finally {
|
package/dist/agent/port.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type { StageActivityEvent } from "./activity.js";
|
|
|
8
8
|
import type { ResolvedMcpServers } from "../config/resolveStageMcpServers.js";
|
|
9
9
|
import type { StageRoots } from "../runtime/stageRoots.js";
|
|
10
10
|
import type { QaExchange } from "../hitl/qaTrail.js";
|
|
11
|
+
import type { StageUsage } from "../types/usage.js";
|
|
11
12
|
/** Opaque to runtime; adapters interpret. */
|
|
12
13
|
export type StageResumeToken = string;
|
|
13
14
|
export type StageSessionMode = "fresh" | "waiting_resume" | "feedback_resume" | "new_session";
|
|
@@ -77,10 +78,12 @@ export declare function runtimeStageId(input: Pick<StageRunInput, "stage" | "sta
|
|
|
77
78
|
export type StageRunResult = {
|
|
78
79
|
ok: true;
|
|
79
80
|
envelope: StageEnvelope;
|
|
81
|
+
usage?: StageUsage;
|
|
80
82
|
} | {
|
|
81
83
|
ok: false;
|
|
82
84
|
reason: string;
|
|
83
85
|
envelope?: StageEnvelope;
|
|
86
|
+
usage?: StageUsage;
|
|
84
87
|
};
|
|
85
88
|
/** Opaque wait-request blob; T2 owns concrete prompt shapes later. */
|
|
86
89
|
export type OpaqueWaitRequest = unknown;
|
package/dist/cli/runOutput.js
CHANGED
|
@@ -2,6 +2,21 @@ import { projectRun } from "../projection/projectRun.js";
|
|
|
2
2
|
function stringify(payload) {
|
|
3
3
|
return JSON.stringify(payload, null, 2);
|
|
4
4
|
}
|
|
5
|
+
function formatUsd(amount) {
|
|
6
|
+
return `$${amount.toFixed(4)}`;
|
|
7
|
+
}
|
|
8
|
+
/** Best-effort total cost for a run; undefined (not thrown) on any read failure or when no stage reported usage. */
|
|
9
|
+
async function tryReadTotalCostUsd(store, runId) {
|
|
10
|
+
if (!store)
|
|
11
|
+
return undefined;
|
|
12
|
+
try {
|
|
13
|
+
const detail = await store.readRun(runId);
|
|
14
|
+
return detail.total_cost_usd;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
5
20
|
function isBusyCode(code) {
|
|
6
21
|
return code === "busy_capacity" || code === "busy_checkout";
|
|
7
22
|
}
|
|
@@ -80,14 +95,19 @@ function baseRunCompletionPayload(result) {
|
|
|
80
95
|
}
|
|
81
96
|
return payload;
|
|
82
97
|
}
|
|
83
|
-
function
|
|
84
|
-
|
|
98
|
+
async function formatRunCompletionJsonWithCost(result, store) {
|
|
99
|
+
const totalCostUsd = await tryReadTotalCostUsd(store, result.runId);
|
|
100
|
+
return stringify({
|
|
101
|
+
...baseRunCompletionPayload(result),
|
|
102
|
+
...(totalCostUsd !== undefined ? { total_cost_usd: totalCostUsd } : {}),
|
|
103
|
+
});
|
|
85
104
|
}
|
|
86
105
|
async function formatRunCompletionJsonWithStages(result, store) {
|
|
87
106
|
const detail = await store.readRun(result.runId);
|
|
88
107
|
const projection = projectRun(detail);
|
|
89
108
|
return stringify({
|
|
90
109
|
...baseRunCompletionPayload(result),
|
|
110
|
+
...(detail.total_cost_usd !== undefined ? { total_cost_usd: detail.total_cost_usd } : {}),
|
|
91
111
|
stages: projection.stages,
|
|
92
112
|
});
|
|
93
113
|
}
|
|
@@ -101,18 +121,21 @@ function writeStartFailureHuman(started, io) {
|
|
|
101
121
|
io.error(`conflictingCheckout: ${started.conflictingCheckout}`);
|
|
102
122
|
}
|
|
103
123
|
}
|
|
104
|
-
function writeCompletionHuman(result, io) {
|
|
124
|
+
function writeCompletionHuman(result, io, totalCostUsd) {
|
|
105
125
|
switch (result.outcome) {
|
|
106
126
|
case "waiting":
|
|
107
127
|
io.error(`Pipeline waiting. Run folder: ${result.runDir} (${result.runId})`);
|
|
108
|
-
|
|
128
|
+
break;
|
|
109
129
|
case "failed":
|
|
110
130
|
io.error(`Pipeline failed: ${result.reason}`);
|
|
111
131
|
io.error(`Run folder: ${result.runDir}`);
|
|
112
|
-
|
|
132
|
+
break;
|
|
113
133
|
case "succeeded":
|
|
114
134
|
io.log(`Pipeline succeeded. Run folder: ${result.runDir}`);
|
|
115
|
-
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
if (totalCostUsd !== undefined) {
|
|
138
|
+
io.error(`Cost: ${formatUsd(totalCostUsd)}`);
|
|
116
139
|
}
|
|
117
140
|
}
|
|
118
141
|
function exitCodeForRunOutcome(outcome) {
|
|
@@ -151,11 +174,12 @@ export async function reportCliRun(event, options) {
|
|
|
151
174
|
}
|
|
152
175
|
}
|
|
153
176
|
else {
|
|
154
|
-
options.io.log(
|
|
177
|
+
options.io.log(await formatRunCompletionJsonWithCost(event.result, options.store));
|
|
155
178
|
}
|
|
156
179
|
}
|
|
157
180
|
else {
|
|
158
|
-
|
|
181
|
+
const totalCostUsd = await tryReadTotalCostUsd(options.store, event.result.runId);
|
|
182
|
+
writeCompletionHuman(event.result, options.io, totalCostUsd);
|
|
159
183
|
}
|
|
160
184
|
return exitCodeForRunOutcome(event.result.outcome);
|
|
161
185
|
}
|
package/dist/package-meta.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export declare const PACKAGE_NAME = "stageflow";
|
|
2
|
-
export declare const PACKAGE_VERSION = "0.
|
|
2
|
+
export declare const PACKAGE_VERSION = "0.16.0";
|
package/dist/package-meta.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export const PACKAGE_NAME = "stageflow";
|
|
2
|
-
export const PACKAGE_VERSION = "0.
|
|
2
|
+
export const PACKAGE_VERSION = "0.16.0";
|
package/dist/runstore/port.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { StageEnvelope } from "../types/envelope.js";
|
|
|
4
4
|
import type { FeedbackLoopConfig, ResolvedPipelineDag } from "../types/pipeline.js";
|
|
5
5
|
import type { CompletionCheck } from "../types/completion.js";
|
|
6
6
|
import type { StageGateKind } from "../types/stage.js";
|
|
7
|
+
import type { StageUsage } from "../types/usage.js";
|
|
7
8
|
export type RunStatus = "created" | "running" | "succeeded" | "failed";
|
|
8
9
|
export type RunPipelineDagSnapshot = ResolvedPipelineDag & {
|
|
9
10
|
stage_ids: string[];
|
|
@@ -70,6 +71,8 @@ export type StageSnapshot = {
|
|
|
70
71
|
last_at?: string;
|
|
71
72
|
pending_prompt?: AskOperatorPrompt;
|
|
72
73
|
attempt_count: number;
|
|
74
|
+
/** Total $ spent on this stage across every attempt; omitted when no attempt reported usage. */
|
|
75
|
+
cost_usd?: number;
|
|
73
76
|
};
|
|
74
77
|
export type StageExecution = {
|
|
75
78
|
run_id: string;
|
|
@@ -80,6 +83,8 @@ export type StageExecution = {
|
|
|
80
83
|
started_at?: string;
|
|
81
84
|
finished_at?: string;
|
|
82
85
|
envelope: StageEnvelope | null;
|
|
86
|
+
cost_usd?: number;
|
|
87
|
+
usage?: StageUsage;
|
|
83
88
|
};
|
|
84
89
|
export type StageExecutionPatch = {
|
|
85
90
|
status?: StageSnapshot["status"];
|
|
@@ -87,6 +92,8 @@ export type StageExecutionPatch = {
|
|
|
87
92
|
started_at?: string;
|
|
88
93
|
finished_at?: string;
|
|
89
94
|
envelope?: StageEnvelope | null;
|
|
95
|
+
cost_usd?: number;
|
|
96
|
+
usage?: StageUsage;
|
|
90
97
|
};
|
|
91
98
|
/** The durable disposition of completion verification for one stage attempt. */
|
|
92
99
|
export type VerificationOutcome = "not_run" | "passed" | "failed" | "error";
|
|
@@ -220,6 +227,7 @@ export type CompactStage = {
|
|
|
220
227
|
status: StageSnapshot["status"];
|
|
221
228
|
attempt_count: number;
|
|
222
229
|
definition_id?: string;
|
|
230
|
+
cost_usd?: number;
|
|
223
231
|
};
|
|
224
232
|
export type RunSummary = {
|
|
225
233
|
run_id: string;
|
|
@@ -246,6 +254,8 @@ export type RunSummary = {
|
|
|
246
254
|
failed_reason?: string;
|
|
247
255
|
/** The active loop, when a run is currently replaying or awaiting a decision. */
|
|
248
256
|
active_feedback_loop?: FeedbackLoopRecord;
|
|
257
|
+
/** Sum of every stage's cost_usd; omitted when no stage reported usage. */
|
|
258
|
+
total_cost_usd?: number;
|
|
249
259
|
};
|
|
250
260
|
export type RunDetail = Omit<RunSummary, "stages"> & {
|
|
251
261
|
task_yaml: string;
|
|
@@ -26,8 +26,15 @@ function compactStages(stages) {
|
|
|
26
26
|
status: s.status,
|
|
27
27
|
attempt_count: s.attempt_count,
|
|
28
28
|
...(s.definition_id !== undefined ? { definition_id: s.definition_id } : {}),
|
|
29
|
+
...(s.cost_usd !== undefined ? { cost_usd: s.cost_usd } : {}),
|
|
29
30
|
}));
|
|
30
31
|
}
|
|
32
|
+
function totalCostUsd(stages) {
|
|
33
|
+
const costs = stages.map((s) => s.cost_usd).filter((c) => c !== undefined);
|
|
34
|
+
if (costs.length === 0)
|
|
35
|
+
return undefined;
|
|
36
|
+
return costs.reduce((sum, c) => sum + c, 0);
|
|
37
|
+
}
|
|
31
38
|
function waitingSummaryFromPrompt(prompt) {
|
|
32
39
|
if (!prompt)
|
|
33
40
|
return "Waiting for input";
|
|
@@ -89,6 +96,7 @@ function resolveListedStatus(stages, meta, dag) {
|
|
|
89
96
|
export function projectRunSummary(meta, stages, feedback) {
|
|
90
97
|
const dag = meta.pipeline_dag;
|
|
91
98
|
const waiting = waitingFieldsFromStages(stages);
|
|
99
|
+
const cost = totalCostUsd(stages);
|
|
92
100
|
if (feedback?.active_feedback_loop?.state === "waiting_for_human" &&
|
|
93
101
|
waiting.waiting_stage_id !== undefined &&
|
|
94
102
|
waiting.waiting_kind === undefined) {
|
|
@@ -114,6 +122,7 @@ export function projectRunSummary(meta, stages, feedback) {
|
|
|
114
122
|
...(feedback?.active_feedback_loop !== undefined
|
|
115
123
|
? { active_feedback_loop: feedback.active_feedback_loop }
|
|
116
124
|
: {}),
|
|
125
|
+
...(cost !== undefined ? { total_cost_usd: cost } : {}),
|
|
117
126
|
};
|
|
118
127
|
}
|
|
119
128
|
export function projectRunDetail(meta, stages, task_yaml, dagSnapshot, feedback) {
|
|
@@ -78,6 +78,18 @@ function ensureStageExecutionVerificationOutcomeColumn(db) {
|
|
|
78
78
|
db.exec(`ALTER TABLE stage_executions ADD COLUMN verification_outcome TEXT NOT NULL DEFAULT 'not_run'`);
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
|
+
function ensureStageExecutionCostColumns(db) {
|
|
82
|
+
const cols = db
|
|
83
|
+
.prepare(`PRAGMA table_info(stage_executions)`)
|
|
84
|
+
.all();
|
|
85
|
+
const names = new Set(cols.map((c) => c.name));
|
|
86
|
+
if (!names.has("cost_usd")) {
|
|
87
|
+
db.exec(`ALTER TABLE stage_executions ADD COLUMN cost_usd REAL`);
|
|
88
|
+
}
|
|
89
|
+
if (!names.has("usage_json")) {
|
|
90
|
+
db.exec(`ALTER TABLE stage_executions ADD COLUMN usage_json TEXT`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
81
93
|
function backfillVerificationOutcomes(db) {
|
|
82
94
|
db.exec(`
|
|
83
95
|
UPDATE stage_executions
|
|
@@ -184,6 +196,8 @@ function executionFromRow(row) {
|
|
|
184
196
|
envelope: row.envelope_json
|
|
185
197
|
? JSON.parse(row.envelope_json)
|
|
186
198
|
: null,
|
|
199
|
+
...(row.cost_usd != null ? { cost_usd: row.cost_usd } : {}),
|
|
200
|
+
...(row.usage_json != null ? { usage: JSON.parse(row.usage_json) } : {}),
|
|
187
201
|
};
|
|
188
202
|
}
|
|
189
203
|
function verificationCheckResultFromRow(row) {
|
|
@@ -292,6 +306,7 @@ export class SqliteRunStore {
|
|
|
292
306
|
ensureRunLocatorColumns(this.db);
|
|
293
307
|
ensureStageExecutionsTable(this.db);
|
|
294
308
|
ensureStageExecutionVerificationOutcomeColumn(this.db);
|
|
309
|
+
ensureStageExecutionCostColumns(this.db);
|
|
295
310
|
ensureStageEventsAttemptColumn(this.db);
|
|
296
311
|
ensureVerificationCheckResultsTable(this.db);
|
|
297
312
|
ensureFeedbackReplayStagePassEnvelopeColumn(this.db);
|
|
@@ -432,7 +447,7 @@ export class SqliteRunStore {
|
|
|
432
447
|
verification_outcome: "not_run",
|
|
433
448
|
});
|
|
434
449
|
const row = this.db
|
|
435
|
-
.prepare(`SELECT run_id, stage_id, attempt, status, verification_outcome, started_at, finished_at, envelope_json
|
|
450
|
+
.prepare(`SELECT run_id, stage_id, attempt, status, verification_outcome, started_at, finished_at, envelope_json, cost_usd, usage_json
|
|
436
451
|
FROM stage_executions
|
|
437
452
|
WHERE run_id = ? AND stage_id = ? AND attempt = ?`)
|
|
438
453
|
.get(runId, stageId, attempt);
|
|
@@ -442,7 +457,7 @@ export class SqliteRunStore {
|
|
|
442
457
|
async listStageExecutions(runId, stageId) {
|
|
443
458
|
await this.ready();
|
|
444
459
|
const rows = this.db
|
|
445
|
-
.prepare(`SELECT run_id, stage_id, attempt, status, verification_outcome, started_at, finished_at, envelope_json
|
|
460
|
+
.prepare(`SELECT run_id, stage_id, attempt, status, verification_outcome, started_at, finished_at, envelope_json, cost_usd, usage_json
|
|
446
461
|
FROM stage_executions
|
|
447
462
|
WHERE run_id = ? AND stage_id = ?
|
|
448
463
|
ORDER BY attempt ASC`)
|
|
@@ -452,7 +467,7 @@ export class SqliteRunStore {
|
|
|
452
467
|
async getLatestStageExecution(runId, stageId) {
|
|
453
468
|
await this.ready();
|
|
454
469
|
const row = this.db
|
|
455
|
-
.prepare(`SELECT run_id, stage_id, attempt, status, verification_outcome, started_at, finished_at, envelope_json
|
|
470
|
+
.prepare(`SELECT run_id, stage_id, attempt, status, verification_outcome, started_at, finished_at, envelope_json, cost_usd, usage_json
|
|
456
471
|
FROM stage_executions
|
|
457
472
|
WHERE run_id = ? AND stage_id = ?
|
|
458
473
|
ORDER BY attempt DESC
|
|
@@ -471,7 +486,7 @@ export class SqliteRunStore {
|
|
|
471
486
|
async getStageExecution(runId, stageId, attempt) {
|
|
472
487
|
await this.ready();
|
|
473
488
|
const row = this.db
|
|
474
|
-
.prepare(`SELECT run_id, stage_id, attempt, status, verification_outcome, started_at, finished_at, envelope_json
|
|
489
|
+
.prepare(`SELECT run_id, stage_id, attempt, status, verification_outcome, started_at, finished_at, envelope_json, cost_usd, usage_json
|
|
475
490
|
FROM stage_executions
|
|
476
491
|
WHERE run_id = ? AND stage_id = ? AND attempt = ?`)
|
|
477
492
|
.get(runId, stageId, attempt);
|
|
@@ -509,6 +524,14 @@ export class SqliteRunStore {
|
|
|
509
524
|
params.envelope_json =
|
|
510
525
|
patch.envelope != null ? JSON.stringify(patch.envelope) : null;
|
|
511
526
|
}
|
|
527
|
+
if (patch.cost_usd !== undefined) {
|
|
528
|
+
sets.push("cost_usd = @cost_usd");
|
|
529
|
+
params.cost_usd = patch.cost_usd;
|
|
530
|
+
}
|
|
531
|
+
if (patch.usage !== undefined) {
|
|
532
|
+
sets.push("usage_json = @usage_json");
|
|
533
|
+
params.usage_json = JSON.stringify(patch.usage);
|
|
534
|
+
}
|
|
512
535
|
if (sets.length === 0)
|
|
513
536
|
return;
|
|
514
537
|
const result = this.db
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const SCHEMA_SQL = "\nCREATE TABLE IF NOT EXISTS runs (\n run_id TEXT PRIMARY KEY,\n pipeline_id TEXT NOT NULL,\n task_id TEXT,\n task_yaml TEXT NOT NULL,\n status TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n checkout_root TEXT,\n pipeline_dag_json TEXT,\n git_sha TEXT,\n ci_pr_url TEXT,\n ci_job_url TEXT,\n pipeline_path TEXT,\n task_path TEXT,\n project_root TEXT\n);\n\nCREATE TABLE IF NOT EXISTS stages (\n run_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n status TEXT,\n summary TEXT,\n envelope_json TEXT,\n started_at TEXT,\n finished_at TEXT,\n PRIMARY KEY (run_id, stage_id),\n FOREIGN KEY (run_id) REFERENCES runs(run_id)\n);\n\nCREATE TABLE IF NOT EXISTS stage_events (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n run_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n at TEXT NOT NULL,\n event TEXT NOT NULL,\n payload_json TEXT,\n FOREIGN KEY (run_id) REFERENCES runs(run_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_runs_status_created\n ON runs (status, created_at DESC);\n\nCREATE INDEX IF NOT EXISTS idx_runs_pipeline_created\n ON runs (pipeline_id, created_at DESC);\n\nCREATE INDEX IF NOT EXISTS idx_stage_events_run_stage_at\n ON stage_events (run_id, stage_id, at);\n\nCREATE TABLE IF NOT EXISTS stage_executions (\n run_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n attempt INTEGER NOT NULL,\n status TEXT NOT NULL,\n verification_outcome TEXT NOT NULL DEFAULT 'not_run',\n started_at TEXT,\n finished_at TEXT,\n envelope_json TEXT,\n PRIMARY KEY (run_id, stage_id, attempt),\n FOREIGN KEY (run_id) REFERENCES runs(run_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_stage_executions_run_stage\n ON stage_executions (run_id, stage_id, attempt);\n\nCREATE TABLE IF NOT EXISTS verification_check_results (\n run_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n attempt INTEGER NOT NULL,\n check_id TEXT NOT NULL,\n check_type TEXT NOT NULL,\n status TEXT NOT NULL,\n started_at TEXT,\n finished_at TEXT,\n evidence_json TEXT,\n PRIMARY KEY (run_id, stage_id, attempt, check_id),\n FOREIGN KEY (run_id, stage_id, attempt)\n REFERENCES stage_executions(run_id, stage_id, attempt)\n);\n\nCREATE INDEX IF NOT EXISTS idx_verification_check_results_execution\n ON verification_check_results (run_id, stage_id, attempt, check_id);\n\nCREATE TABLE IF NOT EXISTS feedback_loops (\n run_id TEXT NOT NULL,\n loop_id TEXT NOT NULL,\n source_stage_id TEXT NOT NULL,\n source_attempt INTEGER NOT NULL,\n policy_json TEXT NOT NULL,\n state TEXT NOT NULL,\n current_replay_id TEXT,\n current_replay_number INTEGER,\n deferred_send_back_json TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n PRIMARY KEY (run_id, loop_id),\n FOREIGN KEY (run_id) REFERENCES runs(run_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_feedback_loops_run_state\n ON feedback_loops (run_id, state, created_at);\n\nCREATE TABLE IF NOT EXISTS feedback_replays (\n run_id TEXT NOT NULL,\n replay_id TEXT NOT NULL,\n loop_id TEXT NOT NULL,\n source_stage_id TEXT NOT NULL,\n source_attempt INTEGER NOT NULL,\n target_stage_id TEXT NOT NULL,\n replay_number INTEGER NOT NULL,\n max_replays INTEGER NOT NULL,\n replay_session TEXT NOT NULL,\n route_stage_ids_json TEXT NOT NULL,\n feedback_envelope_json TEXT NOT NULL,\n status TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n PRIMARY KEY (run_id, replay_id),\n FOREIGN KEY (run_id, loop_id) REFERENCES feedback_loops(run_id, loop_id)\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS idx_feedback_replays_loop_number\n ON feedback_replays (run_id, loop_id, replay_number);\nCREATE INDEX IF NOT EXISTS idx_feedback_replays_run_loop\n ON feedback_replays (run_id, loop_id, created_at);\n\nCREATE TABLE IF NOT EXISTS feedback_replay_stage_passes (\n run_id TEXT NOT NULL,\n replay_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n stage_attempt INTEGER NOT NULL,\n session_origin_attempt INTEGER,\n session_mode TEXT NOT NULL,\n status TEXT NOT NULL,\n started_at TEXT,\n finished_at TEXT,\n emitted_envelope_json TEXT,\n PRIMARY KEY (run_id, replay_id, stage_id),\n FOREIGN KEY (run_id, replay_id) REFERENCES feedback_replays(run_id, replay_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_feedback_replay_stage_passes_replay\n ON feedback_replay_stage_passes (run_id, replay_id, stage_id);\n\nCREATE TABLE IF NOT EXISTS fork_generations (\n run_id TEXT NOT NULL,\n generation_id TEXT NOT NULL,\n replay_id TEXT,\n fork_parent_stage_id TEXT NOT NULL,\n generation_number INTEGER NOT NULL,\n clone_stage_ids_json TEXT NOT NULL,\n status TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n PRIMARY KEY (run_id, generation_id),\n FOREIGN KEY (run_id, replay_id) REFERENCES feedback_replays(run_id, replay_id)\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS idx_fork_generations_parent_number\n ON fork_generations (run_id, fork_parent_stage_id, generation_number);\nCREATE INDEX IF NOT EXISTS idx_fork_generations_run_replay\n ON fork_generations (run_id, replay_id, created_at);\n";
|
|
1
|
+
export declare const SCHEMA_SQL = "\nCREATE TABLE IF NOT EXISTS runs (\n run_id TEXT PRIMARY KEY,\n pipeline_id TEXT NOT NULL,\n task_id TEXT,\n task_yaml TEXT NOT NULL,\n status TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n checkout_root TEXT,\n pipeline_dag_json TEXT,\n git_sha TEXT,\n ci_pr_url TEXT,\n ci_job_url TEXT,\n pipeline_path TEXT,\n task_path TEXT,\n project_root TEXT\n);\n\nCREATE TABLE IF NOT EXISTS stages (\n run_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n status TEXT,\n summary TEXT,\n envelope_json TEXT,\n started_at TEXT,\n finished_at TEXT,\n PRIMARY KEY (run_id, stage_id),\n FOREIGN KEY (run_id) REFERENCES runs(run_id)\n);\n\nCREATE TABLE IF NOT EXISTS stage_events (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n run_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n at TEXT NOT NULL,\n event TEXT NOT NULL,\n payload_json TEXT,\n FOREIGN KEY (run_id) REFERENCES runs(run_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_runs_status_created\n ON runs (status, created_at DESC);\n\nCREATE INDEX IF NOT EXISTS idx_runs_pipeline_created\n ON runs (pipeline_id, created_at DESC);\n\nCREATE INDEX IF NOT EXISTS idx_stage_events_run_stage_at\n ON stage_events (run_id, stage_id, at);\n\nCREATE TABLE IF NOT EXISTS stage_executions (\n run_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n attempt INTEGER NOT NULL,\n status TEXT NOT NULL,\n verification_outcome TEXT NOT NULL DEFAULT 'not_run',\n started_at TEXT,\n finished_at TEXT,\n envelope_json TEXT,\n cost_usd REAL,\n usage_json TEXT,\n PRIMARY KEY (run_id, stage_id, attempt),\n FOREIGN KEY (run_id) REFERENCES runs(run_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_stage_executions_run_stage\n ON stage_executions (run_id, stage_id, attempt);\n\nCREATE TABLE IF NOT EXISTS verification_check_results (\n run_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n attempt INTEGER NOT NULL,\n check_id TEXT NOT NULL,\n check_type TEXT NOT NULL,\n status TEXT NOT NULL,\n started_at TEXT,\n finished_at TEXT,\n evidence_json TEXT,\n PRIMARY KEY (run_id, stage_id, attempt, check_id),\n FOREIGN KEY (run_id, stage_id, attempt)\n REFERENCES stage_executions(run_id, stage_id, attempt)\n);\n\nCREATE INDEX IF NOT EXISTS idx_verification_check_results_execution\n ON verification_check_results (run_id, stage_id, attempt, check_id);\n\nCREATE TABLE IF NOT EXISTS feedback_loops (\n run_id TEXT NOT NULL,\n loop_id TEXT NOT NULL,\n source_stage_id TEXT NOT NULL,\n source_attempt INTEGER NOT NULL,\n policy_json TEXT NOT NULL,\n state TEXT NOT NULL,\n current_replay_id TEXT,\n current_replay_number INTEGER,\n deferred_send_back_json TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n PRIMARY KEY (run_id, loop_id),\n FOREIGN KEY (run_id) REFERENCES runs(run_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_feedback_loops_run_state\n ON feedback_loops (run_id, state, created_at);\n\nCREATE TABLE IF NOT EXISTS feedback_replays (\n run_id TEXT NOT NULL,\n replay_id TEXT NOT NULL,\n loop_id TEXT NOT NULL,\n source_stage_id TEXT NOT NULL,\n source_attempt INTEGER NOT NULL,\n target_stage_id TEXT NOT NULL,\n replay_number INTEGER NOT NULL,\n max_replays INTEGER NOT NULL,\n replay_session TEXT NOT NULL,\n route_stage_ids_json TEXT NOT NULL,\n feedback_envelope_json TEXT NOT NULL,\n status TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n PRIMARY KEY (run_id, replay_id),\n FOREIGN KEY (run_id, loop_id) REFERENCES feedback_loops(run_id, loop_id)\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS idx_feedback_replays_loop_number\n ON feedback_replays (run_id, loop_id, replay_number);\nCREATE INDEX IF NOT EXISTS idx_feedback_replays_run_loop\n ON feedback_replays (run_id, loop_id, created_at);\n\nCREATE TABLE IF NOT EXISTS feedback_replay_stage_passes (\n run_id TEXT NOT NULL,\n replay_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n stage_attempt INTEGER NOT NULL,\n session_origin_attempt INTEGER,\n session_mode TEXT NOT NULL,\n status TEXT NOT NULL,\n started_at TEXT,\n finished_at TEXT,\n emitted_envelope_json TEXT,\n PRIMARY KEY (run_id, replay_id, stage_id),\n FOREIGN KEY (run_id, replay_id) REFERENCES feedback_replays(run_id, replay_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_feedback_replay_stage_passes_replay\n ON feedback_replay_stage_passes (run_id, replay_id, stage_id);\n\nCREATE TABLE IF NOT EXISTS fork_generations (\n run_id TEXT NOT NULL,\n generation_id TEXT NOT NULL,\n replay_id TEXT,\n fork_parent_stage_id TEXT NOT NULL,\n generation_number INTEGER NOT NULL,\n clone_stage_ids_json TEXT NOT NULL,\n status TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n PRIMARY KEY (run_id, generation_id),\n FOREIGN KEY (run_id, replay_id) REFERENCES feedback_replays(run_id, replay_id)\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS idx_fork_generations_parent_number\n ON fork_generations (run_id, fork_parent_stage_id, generation_number);\nCREATE INDEX IF NOT EXISTS idx_fork_generations_run_replay\n ON fork_generations (run_id, replay_id, created_at);\n";
|