@osolmaz/pi-workflows 0.13.0 → 0.13.2
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 +11 -9
- package/dist/builtins/autodoc.workflow.d.ts +191 -4
- package/dist/builtins/autodoc.workflow.js +156 -30
- package/dist/builtins/autodoc.workflow.js.map +1 -1
- package/dist/builtins/autoimplement-command-batches.d.ts +1 -0
- package/dist/builtins/autoimplement-command-batches.js +29 -31
- package/dist/builtins/autoimplement-command-batches.js.map +1 -1
- package/dist/builtins/autoimplement.workflow.d.ts +1259 -29
- package/dist/builtins/autoimplement.workflow.js +416 -153
- package/dist/builtins/autoimplement.workflow.js.map +1 -1
- package/dist/builtins/autoplan.workflow.d.ts +6 -0
- package/dist/builtins/autoplan.workflow.js +68 -32
- package/dist/builtins/autoplan.workflow.js.map +1 -1
- package/dist/builtins/catalog.js +5 -5
- package/dist/builtins/catalog.js.map +1 -1
- package/dist/builtins/change-verification.workflow.d.ts +110 -0
- package/dist/builtins/change-verification.workflow.js +860 -0
- package/dist/builtins/change-verification.workflow.js.map +1 -0
- package/dist/builtins/monitor.workflow.js +4 -3
- package/dist/builtins/monitor.workflow.js.map +1 -1
- package/dist/builtins/plain-summary.workflow.js +35 -24
- package/dist/builtins/plain-summary.workflow.js.map +1 -1
- package/dist/builtins/plan-change.workflow.d.ts +361 -6
- package/dist/builtins/plan-change.workflow.js +26 -0
- package/dist/builtins/plan-change.workflow.js.map +1 -1
- package/dist/builtins/sanity-check.workflow.js +19 -22
- package/dist/builtins/sanity-check.workflow.js.map +1 -1
- package/dist/builtins/workspace-preparation.workflow.d.ts +75 -0
- package/dist/builtins/workspace-preparation.workflow.js +498 -0
- package/dist/builtins/workspace-preparation.workflow.js.map +1 -0
- package/dist/controllers/sqlite.js +16 -51
- package/dist/controllers/sqlite.js.map +1 -1
- package/dist/extension/decision-channels.js +45 -7
- package/dist/extension/decision-channels.js.map +1 -1
- package/dist/extension/executor.js +1 -4
- package/dist/extension/executor.js.map +1 -1
- package/dist/extension/index.js +2 -15
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/recorder.d.ts +1 -1
- package/dist/extension/recorder.js +8 -8
- package/dist/extension/recorder.js.map +1 -1
- package/dist/state/schema.js +22 -3
- package/dist/state/schema.js.map +1 -1
- package/dist/viewer/session-reducer.d.ts +0 -1
- package/dist/viewer/session-reducer.js +2 -24
- package/dist/viewer/session-reducer.js.map +1 -1
- package/dist/workflows/engine.js +4 -3
- package/dist/workflows/engine.js.map +1 -1
- package/dist/workflows/store.d.ts +6 -1
- package/dist/workflows/store.js +286 -33
- package/dist/workflows/store.js.map +1 -1
- package/dist/workflows/types.d.ts +1 -1
- package/docs/SQLITE_STATE.md +20 -14
- package/docs/WORKFLOW_COMPOSITION.md +8 -0
- package/docs/plans/2026-08-23-assistant-agent-completion-plan.md +1 -1
- package/docs/plans/2026-08-24-change-scoped-verification-plan.md +419 -0
- package/docs/plans/2026-08-25-autoplan-user-intent-capture-plan.md +107 -0
- package/docs/session-event-journal.md +2 -3
- package/docs/workflows.md +43 -21
- package/herdr-plugin.toml +1 -1
- package/package.json +1 -1
- package/skills/autodoc/SKILL.md +7 -0
- package/skills/autoimplement/SKILL.md +4 -0
- package/src/builtins/autodoc.workflow.ts +184 -33
- package/src/builtins/autoimplement-command-batches.ts +39 -33
- package/src/builtins/autoimplement.workflow.ts +483 -175
- package/src/builtins/autoplan.workflow.ts +80 -32
- package/src/builtins/catalog.ts +5 -5
- package/src/builtins/change-verification.workflow.ts +1143 -0
- package/src/builtins/monitor.workflow.ts +6 -3
- package/src/builtins/plain-summary.workflow.ts +38 -40
- package/src/builtins/plan-change.workflow.ts +35 -0
- package/src/builtins/sanity-check.workflow.ts +19 -22
- package/src/builtins/workspace-preparation.workflow.ts +668 -0
- package/src/controllers/sqlite.ts +19 -53
- package/src/extension/decision-channels.ts +47 -7
- package/src/extension/executor.ts +1 -4
- package/src/extension/index.ts +2 -15
- package/src/extension/recorder.ts +10 -8
- package/src/state/schema.ts +22 -3
- package/src/viewer/session-reducer.ts +2 -29
- package/src/workflows/engine.ts +4 -3
- package/src/workflows/store.ts +397 -43
- package/src/workflows/types.ts +0 -1
package/dist/workflows/store.js
CHANGED
|
@@ -103,17 +103,25 @@ export class WorkflowRunStore {
|
|
|
103
103
|
const inputHash = this.state.putJson(state.input, now);
|
|
104
104
|
const workflowSourceHash = this.state.putJson(state.workflowSource ?? source, now);
|
|
105
105
|
const launchOptionsHash = this.state.putJson({}, now);
|
|
106
|
-
const
|
|
106
|
+
const workflowSourcesHash = state.workflowSources === undefined
|
|
107
|
+
? null
|
|
108
|
+
: this.state.putJson(state.workflowSources, now);
|
|
109
|
+
const humanDecisionHash = state.humanDecision === undefined ? null : this.state.putJson(state.humanDecision, now);
|
|
110
|
+
const finalOutputHash = state.finalOutput === undefined ? null : this.state.putJson(state.finalOutput, now);
|
|
107
111
|
const errorHash = state.error === undefined ? null : this.state.putText(state.error, now);
|
|
108
112
|
this.state.connection
|
|
109
113
|
.prepare(`INSERT INTO runs(
|
|
110
114
|
run_id, resource_id, project_id, parent_run_id, definition_digest,
|
|
111
115
|
workflow_ref, workflow_source_hash, launch_options_hash,
|
|
112
116
|
source_type, source_ref, source_revision, title, status, paused,
|
|
113
|
-
status_detail, input_hash,
|
|
117
|
+
status_detail, input_hash, workflow_sources_hash, human_decision_hash,
|
|
118
|
+
final_output_hash, error_hash, carried_step_count, current_node,
|
|
119
|
+
current_attempt_id, current_node_started_at, waiting_on,
|
|
114
120
|
created_at, updated_at, finished_at
|
|
115
|
-
) VALUES (?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
116
|
-
.run(state.runId, resourceId, state.parentRunId ?? null, definitionDigest, state.workflowName, workflowSourceHash, launchOptionsHash, source.type, source.ref, source.revision, state.runTitle ?? null, state.status, state.paused === true ? 1 : 0, state.statusDetail ?? null, inputHash,
|
|
121
|
+
) VALUES (?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
122
|
+
.run(state.runId, resourceId, state.parentRunId ?? null, definitionDigest, state.workflowName, workflowSourceHash, launchOptionsHash, source.type, source.ref, source.revision, state.runTitle ?? null, state.status, state.paused === true ? 1 : 0, state.statusDetail ?? null, inputHash, workflowSourcesHash, humanDecisionHash, finalOutputHash, errorHash, state.carriedStepCount ?? 0, state.currentNode ?? null, state.currentAttemptId ?? null, state.currentNodeStartedAt === undefined
|
|
123
|
+
? null
|
|
124
|
+
: Date.parse(state.currentNodeStartedAt), state.waitingOn ?? null, Date.parse(state.startedAt), now, state.finishedAt === undefined ? null : Date.parse(state.finishedAt));
|
|
117
125
|
insertRunEvent(this.state, resourceId, 1, at, {
|
|
118
126
|
scope: "run",
|
|
119
127
|
type: "run_created",
|
|
@@ -230,9 +238,10 @@ export class WorkflowRunStore {
|
|
|
230
238
|
const nextUpdateSeq = this.nextUpdateSequence(attemptId);
|
|
231
239
|
this.state.connection
|
|
232
240
|
.prepare(`INSERT INTO workflow_updates(
|
|
233
|
-
update_id, attempt_id, update_seq,
|
|
234
|
-
|
|
235
|
-
|
|
241
|
+
update_id, attempt_id, update_seq, run_revision,
|
|
242
|
+
update_type, update_key, data_hash, recorded_at
|
|
243
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
244
|
+
.run(updateId, attemptId, nextUpdateSeq, revision, update.type, update.key, dataHash, Date.now());
|
|
236
245
|
insertRunEvent(this.state, run.resourceId, revision, at, event);
|
|
237
246
|
this.persistRunState(run, state, revision, Date.now());
|
|
238
247
|
context.revision = revision;
|
|
@@ -329,7 +338,7 @@ export class WorkflowRunStore {
|
|
|
329
338
|
...(attemptId !== undefined ? { captureAttemptId: attemptId } : {}),
|
|
330
339
|
},
|
|
331
340
|
});
|
|
332
|
-
const current = this.
|
|
341
|
+
const current = this.readRunState(run, this.readDefinition(run.definitionHash));
|
|
333
342
|
current.traceSeq = revision;
|
|
334
343
|
current.updatedAt = at;
|
|
335
344
|
this.persistRunState(run, current, revision, now);
|
|
@@ -385,15 +394,9 @@ export class WorkflowRunStore {
|
|
|
385
394
|
.run(current.segmentId, record.seq, record.type, record.nodeId, record.attemptId, record.turnId ?? null, record.messageId ?? null, record.toolCallId ?? null, payloadHash, Date.parse(record.at));
|
|
386
395
|
expected += 1;
|
|
387
396
|
}
|
|
388
|
-
const now = Date.now();
|
|
389
397
|
this.state.connection
|
|
390
398
|
.prepare("UPDATE session_segments SET event_count = ? WHERE segment_id = ?")
|
|
391
399
|
.run(expected - 1, current.segmentId);
|
|
392
|
-
const resourceId = segmentResourceId(current);
|
|
393
|
-
const revision = this.requireResourceRevision(resourceId);
|
|
394
|
-
this.bumpResource(resourceId, revision, now);
|
|
395
|
-
const payloadHash = this.state.putJson({ count: records.length, lastSeq: expected - 1 }, now);
|
|
396
|
-
insertGenericEvent(this.state, resourceId, revision + 1, "session.events_appended", "session", current.sessionId, payloadHash, now);
|
|
397
400
|
});
|
|
398
401
|
}
|
|
399
402
|
async writeSessionCapture(runId, capture, attemptId) {
|
|
@@ -432,8 +435,8 @@ export class WorkflowRunStore {
|
|
|
432
435
|
const row = this.readRunRow(runId);
|
|
433
436
|
if (row === undefined)
|
|
434
437
|
return null;
|
|
435
|
-
const state = this.readState(row.stateHash);
|
|
436
438
|
const snapshot = this.readDefinition(row.definitionHash);
|
|
439
|
+
const state = this.readRunState(row, snapshot);
|
|
437
440
|
const segments = this.segmentRows(runId).map((segment) => this.loadSegment(segment, state));
|
|
438
441
|
const flat = segments.find((segment) => segment.attemptId === "") ?? emptySegment();
|
|
439
442
|
return {
|
|
@@ -551,20 +554,23 @@ export class WorkflowRunStore {
|
|
|
551
554
|
}
|
|
552
555
|
persistRunState(run, state, expectedRevision, now) {
|
|
553
556
|
this.bumpResource(run.resourceId, expectedRevision - 1, now);
|
|
554
|
-
const
|
|
557
|
+
const workflowSourcesHash = state.workflowSources === undefined ? null : this.state.putJson(state.workflowSources, now);
|
|
558
|
+
const humanDecisionHash = state.humanDecision === undefined ? null : this.state.putJson(state.humanDecision, now);
|
|
559
|
+
const finalOutputHash = state.finalOutput === undefined ? null : this.state.putJson(state.finalOutput, now);
|
|
555
560
|
const errorHash = state.error === undefined ? null : this.state.putText(state.error, now);
|
|
556
561
|
const update = this.state.connection
|
|
557
562
|
.prepare(`UPDATE runs
|
|
558
|
-
SET title = ?, status = ?, paused = ?, status_detail = ?,
|
|
559
|
-
|
|
563
|
+
SET title = ?, status = ?, paused = ?, status_detail = ?,
|
|
564
|
+
workflow_sources_hash = ?, human_decision_hash = ?, final_output_hash = ?,
|
|
565
|
+
error_hash = ?, carried_step_count = ?, current_node = ?, current_attempt_id = ?,
|
|
566
|
+
current_node_started_at = ?, waiting_on = ?, updated_at = ?, finished_at = ?
|
|
560
567
|
WHERE run_id = ?`)
|
|
561
|
-
.run(state.runTitle ?? null, state.status, state.paused === true ? 1 : 0, state.statusDetail ?? null,
|
|
568
|
+
.run(state.runTitle ?? null, state.status, state.paused === true ? 1 : 0, state.statusDetail ?? null, workflowSourcesHash, humanDecisionHash, finalOutputHash, errorHash, state.carriedStepCount ?? 0, state.currentNode ?? null, state.currentAttemptId ?? null, state.currentNodeStartedAt === undefined ? null : Date.parse(state.currentNodeStartedAt), state.waitingOn ?? null, now, state.finishedAt === undefined ? null : Date.parse(state.finishedAt), state.runId);
|
|
562
569
|
if (update.changes !== 1)
|
|
563
570
|
throw new Error(`Workflow run is missing: ${state.runId}`);
|
|
564
571
|
if (state.status !== "running") {
|
|
565
572
|
this.enqueueRunSettlementEffect(run.resourceId, expectedRevision, state, now);
|
|
566
573
|
}
|
|
567
|
-
run.stateHash = stateHash;
|
|
568
574
|
}
|
|
569
575
|
enqueueRunSettlementEffect(runResourceId, sourceRevision, state, now) {
|
|
570
576
|
if (this.state.connection.prepare("SELECT 1 FROM run_queue WHERE run_id = ?").get(state.runId) ===
|
|
@@ -602,7 +608,8 @@ export class WorkflowRunStore {
|
|
|
602
608
|
}
|
|
603
609
|
syncNodeAttempts(state, snapshot, now) {
|
|
604
610
|
for (const step of state.steps.slice(state.carriedStepCount ?? 0)) {
|
|
605
|
-
const
|
|
611
|
+
const metadata = stepMetadata(step);
|
|
612
|
+
const stepMetadataHash = Object.keys(metadata).length === 0 ? null : this.state.putJson(metadata, now);
|
|
606
613
|
const outputHash = step.output === undefined ? null : this.state.putJson(step.output, now);
|
|
607
614
|
const errorHash = step.error === undefined ? null : this.state.putText(step.error, now);
|
|
608
615
|
const promptHash = step.prompt === null ? null : this.state.putJson(step.prompt, now);
|
|
@@ -614,23 +621,61 @@ export class WorkflowRunStore {
|
|
|
614
621
|
this.state.connection
|
|
615
622
|
.prepare(`INSERT INTO node_attempts(
|
|
616
623
|
attempt_id, run_id, node_id, attempt_number, node_type, status,
|
|
617
|
-
|
|
624
|
+
prompt_hash, output_hash, step_metadata_hash, error_hash,
|
|
618
625
|
started_at, finished_at, created_at, updated_at
|
|
619
626
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
620
|
-
.run(step.attemptId, state.runId, step.nodeId, attemptNumber, step.nodeType, outcomeStatus(step.outcome), promptHash, outputHash,
|
|
627
|
+
.run(step.attemptId, state.runId, step.nodeId, attemptNumber, step.nodeType, outcomeStatus(step.outcome), promptHash, outputHash, stepMetadataHash, errorHash, Date.parse(step.startedAt), Date.parse(step.finishedAt), Date.parse(step.startedAt), now);
|
|
621
628
|
}
|
|
622
629
|
else {
|
|
623
630
|
this.state.connection
|
|
624
631
|
.prepare(`UPDATE node_attempts
|
|
625
|
-
SET status = ?,
|
|
632
|
+
SET status = ?, prompt_hash = ?, output_hash = ?, step_metadata_hash = ?,
|
|
626
633
|
error_hash = ?, finished_at = ?, updated_at = ?
|
|
627
634
|
WHERE attempt_id = ?`)
|
|
628
|
-
.run(outcomeStatus(step.outcome), promptHash, outputHash,
|
|
635
|
+
.run(outcomeStatus(step.outcome), promptHash, outputHash, stepMetadataHash, errorHash, Date.parse(step.finishedAt), now, step.attemptId);
|
|
629
636
|
}
|
|
630
637
|
}
|
|
631
638
|
if (state.currentAttemptId !== undefined && state.currentNode !== undefined) {
|
|
632
639
|
this.ensureAttempt(state, state.currentNode, state.currentAttemptId, now, snapshot);
|
|
633
640
|
}
|
|
641
|
+
this.syncRunSteps(state, now);
|
|
642
|
+
}
|
|
643
|
+
syncRunSteps(state, now) {
|
|
644
|
+
const existingRows = this.state.connection
|
|
645
|
+
.prepare(`SELECT step_index AS stepIndex, attempt_id AS attemptId
|
|
646
|
+
FROM run_steps WHERE run_id = ? ORDER BY step_index`)
|
|
647
|
+
.all(state.runId)
|
|
648
|
+
.filter(isRunStepIdentityRow);
|
|
649
|
+
if (existingRows.length > state.steps.length) {
|
|
650
|
+
throw new Error(`Workflow run steps cannot shrink: ${state.runId}`);
|
|
651
|
+
}
|
|
652
|
+
for (const existing of existingRows) {
|
|
653
|
+
if (state.steps[existing.stepIndex]?.attemptId !== existing.attemptId) {
|
|
654
|
+
throw new Error(`Workflow run step history changed at index ${existing.stepIndex}`);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
for (let stepIndex = existingRows.length; stepIndex < state.steps.length; stepIndex += 1) {
|
|
658
|
+
const step = state.steps[stepIndex];
|
|
659
|
+
/* istanbul ignore if -- array index follows a checked bound */
|
|
660
|
+
if (step === undefined)
|
|
661
|
+
throw new Error("Workflow run step became unavailable");
|
|
662
|
+
const attempt = this.state.connection
|
|
663
|
+
.prepare("SELECT output_hash AS outputHash FROM node_attempts WHERE attempt_id = ?")
|
|
664
|
+
.get(step.attemptId);
|
|
665
|
+
if (!isAttemptOutputRow(attempt)) {
|
|
666
|
+
throw new Error(`Workflow node attempt is missing: ${step.attemptId}`);
|
|
667
|
+
}
|
|
668
|
+
const carried = stepIndex < (state.carriedStepCount ?? 0);
|
|
669
|
+
const outputOverrideHash = carried &&
|
|
670
|
+
(attempt.outputHash === null ||
|
|
671
|
+
canonicalJson(this.state.readJson(attempt.outputHash)) !== canonicalJson(step.output))
|
|
672
|
+
? this.state.putJson(step.output, now)
|
|
673
|
+
: null;
|
|
674
|
+
this.state.connection
|
|
675
|
+
.prepare(`INSERT INTO run_steps(run_id, step_index, attempt_id, output_override_hash)
|
|
676
|
+
VALUES (?, ?, ?, ?)`)
|
|
677
|
+
.run(state.runId, stepIndex, step.attemptId, outputOverrideHash);
|
|
678
|
+
}
|
|
634
679
|
}
|
|
635
680
|
ensureAttempt(state, nodeId, attemptId, now, snapshot) {
|
|
636
681
|
const existing = this.state.connection
|
|
@@ -675,8 +720,17 @@ export class WorkflowRunStore {
|
|
|
675
720
|
readRunRow(runId) {
|
|
676
721
|
const row = this.state.connection
|
|
677
722
|
.prepare(`SELECT r.run_id AS runId, r.resource_id AS resourceId,
|
|
678
|
-
|
|
679
|
-
r.
|
|
723
|
+
d.definition_hash AS definitionHash, r.definition_digest AS definitionDigest,
|
|
724
|
+
d.workflow_name AS workflowRef, r.source_ref AS sourceRef,
|
|
725
|
+
r.workflow_source_hash AS workflowSourceHash,
|
|
726
|
+
r.workflow_sources_hash AS workflowSourcesHash, r.parent_run_id AS parentRunId,
|
|
727
|
+
r.title, r.status, r.paused, r.status_detail AS statusDetail,
|
|
728
|
+
r.input_hash AS inputHash, r.human_decision_hash AS humanDecisionHash,
|
|
729
|
+
r.final_output_hash AS finalOutputHash, r.error_hash AS errorHash,
|
|
730
|
+
r.carried_step_count AS carriedStepCount, r.current_node AS currentNode,
|
|
731
|
+
r.current_attempt_id AS currentAttemptId,
|
|
732
|
+
r.current_node_started_at AS currentNodeStartedAt, r.waiting_on AS waitingOn,
|
|
733
|
+
r.created_at AS createdAt, r.updated_at AS updatedAt, r.finished_at AS finishedAt
|
|
680
734
|
FROM runs r
|
|
681
735
|
JOIN workflow_definitions d ON d.definition_digest = r.definition_digest
|
|
682
736
|
WHERE r.run_id = ?`)
|
|
@@ -689,12 +743,144 @@ export class WorkflowRunStore {
|
|
|
689
743
|
throw new Error(`Workflow run is missing: ${runId}`);
|
|
690
744
|
return row;
|
|
691
745
|
}
|
|
692
|
-
|
|
693
|
-
const
|
|
694
|
-
|
|
695
|
-
|
|
746
|
+
readRunState(row, snapshot) {
|
|
747
|
+
const steps = this.readSteps(row.runId);
|
|
748
|
+
const outputs = {};
|
|
749
|
+
const results = {};
|
|
750
|
+
for (const step of steps) {
|
|
751
|
+
const result = resultForStep(step);
|
|
752
|
+
results[step.nodeId] = result;
|
|
753
|
+
if (step.outcome === "ok")
|
|
754
|
+
outputs[step.nodeId] = step.output;
|
|
755
|
+
else
|
|
756
|
+
delete outputs[step.nodeId];
|
|
757
|
+
const node = snapshot.nodes[step.nodeId];
|
|
758
|
+
const mountPath = node?.includeTransition === "exit" && node.mountPath !== undefined
|
|
759
|
+
? node.mountPath.join("/")
|
|
760
|
+
: undefined;
|
|
761
|
+
if (mountPath !== undefined && step.outcome === "ok") {
|
|
762
|
+
outputs[mountPath] = step.output;
|
|
763
|
+
results[mountPath] = { ...result, nodeId: mountPath };
|
|
764
|
+
}
|
|
696
765
|
}
|
|
697
|
-
|
|
766
|
+
const revision = this.requireResourceRevision(row.resourceId);
|
|
767
|
+
return {
|
|
768
|
+
schema: RUN_STATE_SCHEMA,
|
|
769
|
+
traceSeq: revision,
|
|
770
|
+
runId: row.runId,
|
|
771
|
+
workflowName: row.workflowRef,
|
|
772
|
+
...(row.parentRunId === null ? {} : { parentRunId: row.parentRunId }),
|
|
773
|
+
...(row.carriedStepCount === 0 ? {} : { carriedStepCount: row.carriedStepCount }),
|
|
774
|
+
...(row.title === null ? {} : { runTitle: row.title }),
|
|
775
|
+
...(row.sourceRef.startsWith("inline:")
|
|
776
|
+
? {}
|
|
777
|
+
: { workflowSource: this.readJsonAs(row.workflowSourceHash) }),
|
|
778
|
+
...(row.workflowSourcesHash === null
|
|
779
|
+
? {}
|
|
780
|
+
: { workflowSources: this.readJsonAs(row.workflowSourcesHash) }),
|
|
781
|
+
...(row.workflowSourcesHash !== null || snapshot.composition?.mounts.length
|
|
782
|
+
? { definitionDigest: `sha256:${row.definitionDigest.toString("hex")}` }
|
|
783
|
+
: {}),
|
|
784
|
+
startedAt: new Date(row.createdAt).toISOString(),
|
|
785
|
+
...(row.finishedAt === null ? {} : { finishedAt: new Date(row.finishedAt).toISOString() }),
|
|
786
|
+
updatedAt: new Date(row.updatedAt).toISOString(),
|
|
787
|
+
status: row.status,
|
|
788
|
+
input: this.state.readJson(row.inputHash),
|
|
789
|
+
outputs,
|
|
790
|
+
results,
|
|
791
|
+
steps,
|
|
792
|
+
...this.readUpdates(row.runId),
|
|
793
|
+
...(row.currentNode === null ? {} : { currentNode: row.currentNode }),
|
|
794
|
+
...(row.currentAttemptId === null ? {} : { currentAttemptId: row.currentAttemptId }),
|
|
795
|
+
...(row.currentNodeStartedAt === null
|
|
796
|
+
? {}
|
|
797
|
+
: { currentNodeStartedAt: new Date(row.currentNodeStartedAt).toISOString() }),
|
|
798
|
+
...(row.statusDetail === null ? {} : { statusDetail: row.statusDetail }),
|
|
799
|
+
...(row.humanDecisionHash === null
|
|
800
|
+
? {}
|
|
801
|
+
: { humanDecision: this.readJsonAs(row.humanDecisionHash) }),
|
|
802
|
+
...(row.paused === 0 ? {} : { paused: true }),
|
|
803
|
+
...(row.waitingOn === null ? {} : { waitingOn: row.waitingOn }),
|
|
804
|
+
...(row.finalOutputHash === null
|
|
805
|
+
? {}
|
|
806
|
+
: { finalOutput: this.state.readJson(row.finalOutputHash) }),
|
|
807
|
+
...(row.errorHash === null ? {} : { error: this.readText(row.errorHash) }),
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
readSteps(runId) {
|
|
811
|
+
const rows = this.state.connection
|
|
812
|
+
.prepare(`SELECT s.step_index AS stepIndex, a.attempt_id AS attemptId, a.node_id AS nodeId,
|
|
813
|
+
a.node_type AS nodeType, a.status, a.prompt_hash AS promptHash,
|
|
814
|
+
a.output_hash AS outputHash, s.output_override_hash AS outputOverrideHash,
|
|
815
|
+
a.step_metadata_hash AS stepMetadataHash, a.error_hash AS errorHash,
|
|
816
|
+
a.started_at AS startedAt, a.finished_at AS finishedAt
|
|
817
|
+
FROM run_steps s
|
|
818
|
+
JOIN node_attempts a ON a.attempt_id = s.attempt_id
|
|
819
|
+
WHERE s.run_id = ? ORDER BY s.step_index`)
|
|
820
|
+
.all(runId)
|
|
821
|
+
.filter(isStepRow);
|
|
822
|
+
return rows.map((row, index) => {
|
|
823
|
+
if (row.stepIndex !== index)
|
|
824
|
+
throw new Error(`Workflow run step sequence has a gap: ${runId}`);
|
|
825
|
+
const metadata = row.stepMetadataHash === null
|
|
826
|
+
? {}
|
|
827
|
+
: this.readJsonAs(row.stepMetadataHash);
|
|
828
|
+
const prompt = row.promptHash === null ? null : this.readJsonAs(row.promptHash);
|
|
829
|
+
const outputHash = row.outputOverrideHash ?? row.outputHash;
|
|
830
|
+
const output = outputHash === null ? null : this.state.readJson(outputHash);
|
|
831
|
+
const error = row.errorHash === null ? undefined : this.readText(row.errorHash);
|
|
832
|
+
return {
|
|
833
|
+
attemptId: row.attemptId,
|
|
834
|
+
nodeId: row.nodeId,
|
|
835
|
+
nodeType: row.nodeType,
|
|
836
|
+
outcome: outcomeForStatus(row.status),
|
|
837
|
+
startedAt: new Date(row.startedAt).toISOString(),
|
|
838
|
+
finishedAt: new Date(row.finishedAt).toISOString(),
|
|
839
|
+
prompt,
|
|
840
|
+
output,
|
|
841
|
+
...(error === undefined ? {} : { error }),
|
|
842
|
+
...metadata,
|
|
843
|
+
};
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
readUpdates(runId) {
|
|
847
|
+
const rows = this.state.connection
|
|
848
|
+
.prepare(`SELECT u.update_id AS updateId, u.run_revision AS runRevision,
|
|
849
|
+
a.node_id AS nodeId, u.attempt_id AS attemptId,
|
|
850
|
+
u.update_type AS updateType, u.update_key AS updateKey,
|
|
851
|
+
u.data_hash AS dataHash, u.recorded_at AS recordedAt
|
|
852
|
+
FROM workflow_updates u
|
|
853
|
+
JOIN node_attempts a ON a.attempt_id = u.attempt_id
|
|
854
|
+
WHERE a.run_id = ? ORDER BY u.run_revision`)
|
|
855
|
+
.all(runId)
|
|
856
|
+
.filter(isUpdateRow);
|
|
857
|
+
if (rows.length === 0)
|
|
858
|
+
return {};
|
|
859
|
+
let updates;
|
|
860
|
+
for (const row of rows) {
|
|
861
|
+
updates = updateProjection(updates, {
|
|
862
|
+
updateId: row.updateId,
|
|
863
|
+
seq: row.runRevision,
|
|
864
|
+
at: new Date(row.recordedAt).toISOString(),
|
|
865
|
+
runId,
|
|
866
|
+
nodeId: row.nodeId,
|
|
867
|
+
attemptId: row.attemptId,
|
|
868
|
+
type: row.updateType,
|
|
869
|
+
key: row.updateKey,
|
|
870
|
+
data: this.readJsonAs(row.dataHash),
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
return updates === undefined ? {} : { updates };
|
|
874
|
+
}
|
|
875
|
+
readJsonAs(hash) {
|
|
876
|
+
return this.state.readJson(hash);
|
|
877
|
+
}
|
|
878
|
+
readText(hash) {
|
|
879
|
+
const blob = this.state.readBlob(hash);
|
|
880
|
+
if (blob === undefined || blob.mediaType !== "text/plain") {
|
|
881
|
+
throw new Error("Text blob is missing or has the wrong media type");
|
|
882
|
+
}
|
|
883
|
+
return blob.content.toString("utf8");
|
|
698
884
|
}
|
|
699
885
|
readDefinition(hash) {
|
|
700
886
|
const value = this.state.readJson(hash);
|
|
@@ -892,14 +1078,31 @@ export function readLastTraceEvent(runId, options = {}) {
|
|
|
892
1078
|
}
|
|
893
1079
|
}
|
|
894
1080
|
function insertRunEvent(state, resourceId, revision, at, event) {
|
|
1081
|
+
const payload = compactTracePayload(event.type, event.payload);
|
|
895
1082
|
const payloadHash = state.putJson({
|
|
896
1083
|
scope: event.scope,
|
|
897
1084
|
...(event.nodeId === undefined ? {} : { nodeId: event.nodeId }),
|
|
898
1085
|
...(event.attemptId === undefined ? {} : { attemptId: event.attemptId }),
|
|
899
|
-
payload
|
|
1086
|
+
payload,
|
|
900
1087
|
}, Date.parse(at));
|
|
901
1088
|
insertGenericEvent(state, resourceId, revision, event.type, "system", null, payloadHash, Date.parse(at));
|
|
902
1089
|
}
|
|
1090
|
+
function compactTracePayload(eventType, payload) {
|
|
1091
|
+
if ((eventType === "node_finished" || eventType === "include_exited") &&
|
|
1092
|
+
Object.hasOwn(payload, "output")) {
|
|
1093
|
+
const { output: _output, ...rest } = payload;
|
|
1094
|
+
return { ...rest, outputStored: true };
|
|
1095
|
+
}
|
|
1096
|
+
if (eventType === "run_started" && Object.hasOwn(payload, "input")) {
|
|
1097
|
+
const { input: _input, ...rest } = payload;
|
|
1098
|
+
return { ...rest, inputStored: true };
|
|
1099
|
+
}
|
|
1100
|
+
if (Object.hasOwn(payload, "finalOutput")) {
|
|
1101
|
+
const { finalOutput: _finalOutput, ...rest } = payload;
|
|
1102
|
+
return { ...rest, finalOutputStored: true };
|
|
1103
|
+
}
|
|
1104
|
+
return payload;
|
|
1105
|
+
}
|
|
903
1106
|
function insertGenericEvent(state, resourceId, revision, eventType, actorType, actorId, payloadHash, now) {
|
|
904
1107
|
state.connection
|
|
905
1108
|
.prepare(`INSERT INTO events(
|
|
@@ -941,6 +1144,40 @@ function outcomeStatus(outcome) {
|
|
|
941
1144
|
return "failed";
|
|
942
1145
|
}
|
|
943
1146
|
}
|
|
1147
|
+
function outcomeForStatus(status) {
|
|
1148
|
+
switch (status) {
|
|
1149
|
+
case "completed":
|
|
1150
|
+
return "ok";
|
|
1151
|
+
case "timed_out":
|
|
1152
|
+
return "timed_out";
|
|
1153
|
+
case "cancelled":
|
|
1154
|
+
return "cancelled";
|
|
1155
|
+
case "failed":
|
|
1156
|
+
return "failed";
|
|
1157
|
+
default:
|
|
1158
|
+
throw new Error(`Workflow step has nonterminal status: ${status}`);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
function stepMetadata(step) {
|
|
1162
|
+
return {
|
|
1163
|
+
...(step.action === undefined ? {} : { action: step.action }),
|
|
1164
|
+
...(step.assistantMessage === undefined ? {} : { assistantMessage: step.assistantMessage }),
|
|
1165
|
+
...(step.conversation === undefined ? {} : { conversation: step.conversation }),
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
function resultForStep(step) {
|
|
1169
|
+
return {
|
|
1170
|
+
attemptId: step.attemptId,
|
|
1171
|
+
nodeId: step.nodeId,
|
|
1172
|
+
nodeType: step.nodeType,
|
|
1173
|
+
outcome: step.outcome,
|
|
1174
|
+
startedAt: step.startedAt,
|
|
1175
|
+
finishedAt: step.finishedAt,
|
|
1176
|
+
durationMs: Date.parse(step.finishedAt) - Date.parse(step.startedAt),
|
|
1177
|
+
...(step.outcome === "ok" ? { output: step.output } : {}),
|
|
1178
|
+
...(step.error === undefined ? {} : { error: step.error }),
|
|
1179
|
+
};
|
|
1180
|
+
}
|
|
944
1181
|
function traceScope(value) {
|
|
945
1182
|
return value === "node" || value === "agent" || value === "action" || value === "session"
|
|
946
1183
|
? value
|
|
@@ -968,6 +1205,10 @@ function validateSessionEventRecord(record) {
|
|
|
968
1205
|
Array.isArray(record.payload)) {
|
|
969
1206
|
throw new Error("Session event is missing required envelope fields");
|
|
970
1207
|
}
|
|
1208
|
+
if (record.type === "assistant_event" &&
|
|
1209
|
+
["text_delta", "thinking_delta", "toolcall_delta"].includes(String(record.payload.type))) {
|
|
1210
|
+
throw new Error("Incremental assistant events are not durable session facts");
|
|
1211
|
+
}
|
|
971
1212
|
}
|
|
972
1213
|
function validateSessionCapture(capture) {
|
|
973
1214
|
if (capture.schema !== SESSION_CAPTURE_SCHEMA ||
|
|
@@ -1002,6 +1243,18 @@ function isRunRow(value) {
|
|
|
1002
1243
|
function isRunIdRow(value) {
|
|
1003
1244
|
return isRecord(value);
|
|
1004
1245
|
}
|
|
1246
|
+
function isRunStepIdentityRow(value) {
|
|
1247
|
+
return isRecord(value);
|
|
1248
|
+
}
|
|
1249
|
+
function isAttemptOutputRow(value) {
|
|
1250
|
+
return isRecord(value) && (value.outputHash === null || Buffer.isBuffer(value.outputHash));
|
|
1251
|
+
}
|
|
1252
|
+
function isStepRow(value) {
|
|
1253
|
+
return isRecord(value);
|
|
1254
|
+
}
|
|
1255
|
+
function isUpdateRow(value) {
|
|
1256
|
+
return isRecord(value);
|
|
1257
|
+
}
|
|
1005
1258
|
function isRevisionRow(value) {
|
|
1006
1259
|
return isRecord(value);
|
|
1007
1260
|
}
|