@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/src/workflows/store.ts
CHANGED
|
@@ -7,8 +7,10 @@ import type {
|
|
|
7
7
|
WorkflowDefinition,
|
|
8
8
|
WorkflowDefinitionSnapshot,
|
|
9
9
|
WorkflowNodeDefinition,
|
|
10
|
+
WorkflowNodeResult,
|
|
10
11
|
WorkflowNodeSnapshot,
|
|
11
12
|
WorkflowRunState,
|
|
13
|
+
WorkflowStepRecord,
|
|
12
14
|
WorkflowSessionBinding,
|
|
13
15
|
WorkflowSessionCapture,
|
|
14
16
|
WorkflowSessionEntryRecord,
|
|
@@ -58,9 +60,57 @@ type RunContext = {
|
|
|
58
60
|
type RunRow = {
|
|
59
61
|
runId: string;
|
|
60
62
|
resourceId: string;
|
|
61
|
-
stateHash: Buffer;
|
|
62
63
|
definitionHash: Buffer;
|
|
64
|
+
definitionDigest: Buffer;
|
|
65
|
+
workflowRef: string;
|
|
66
|
+
sourceRef: string;
|
|
67
|
+
workflowSourceHash: Buffer;
|
|
68
|
+
workflowSourcesHash: Buffer | null;
|
|
69
|
+
parentRunId: string | null;
|
|
70
|
+
title: string | null;
|
|
63
71
|
status: string;
|
|
72
|
+
paused: number;
|
|
73
|
+
statusDetail: string | null;
|
|
74
|
+
inputHash: Buffer;
|
|
75
|
+
humanDecisionHash: Buffer | null;
|
|
76
|
+
finalOutputHash: Buffer | null;
|
|
77
|
+
errorHash: Buffer | null;
|
|
78
|
+
carriedStepCount: number;
|
|
79
|
+
currentNode: string | null;
|
|
80
|
+
currentAttemptId: string | null;
|
|
81
|
+
currentNodeStartedAt: number | null;
|
|
82
|
+
waitingOn: string | null;
|
|
83
|
+
createdAt: number;
|
|
84
|
+
updatedAt: number;
|
|
85
|
+
finishedAt: number | null;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
type StoredStepMetadata = Pick<WorkflowStepRecord, "action" | "assistantMessage" | "conversation">;
|
|
89
|
+
|
|
90
|
+
type StepRow = {
|
|
91
|
+
stepIndex: number;
|
|
92
|
+
attemptId: string;
|
|
93
|
+
nodeId: string;
|
|
94
|
+
nodeType: WorkflowStepRecord["nodeType"];
|
|
95
|
+
status: string;
|
|
96
|
+
promptHash: Buffer | null;
|
|
97
|
+
outputHash: Buffer | null;
|
|
98
|
+
outputOverrideHash: Buffer | null;
|
|
99
|
+
stepMetadataHash: Buffer | null;
|
|
100
|
+
errorHash: Buffer | null;
|
|
101
|
+
startedAt: number;
|
|
102
|
+
finishedAt: number;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
type UpdateRow = {
|
|
106
|
+
updateId: string;
|
|
107
|
+
runRevision: number;
|
|
108
|
+
nodeId: string;
|
|
109
|
+
attemptId: string;
|
|
110
|
+
updateType: string;
|
|
111
|
+
updateKey: string;
|
|
112
|
+
dataHash: Buffer;
|
|
113
|
+
recordedAt: number;
|
|
64
114
|
};
|
|
65
115
|
|
|
66
116
|
type EventRow = {
|
|
@@ -228,7 +278,14 @@ export class WorkflowRunStore {
|
|
|
228
278
|
const inputHash = this.state.putJson(state.input, now);
|
|
229
279
|
const workflowSourceHash = this.state.putJson(state.workflowSource ?? source, now);
|
|
230
280
|
const launchOptionsHash = this.state.putJson({}, now);
|
|
231
|
-
const
|
|
281
|
+
const workflowSourcesHash =
|
|
282
|
+
state.workflowSources === undefined
|
|
283
|
+
? null
|
|
284
|
+
: this.state.putJson(state.workflowSources, now);
|
|
285
|
+
const humanDecisionHash =
|
|
286
|
+
state.humanDecision === undefined ? null : this.state.putJson(state.humanDecision, now);
|
|
287
|
+
const finalOutputHash =
|
|
288
|
+
state.finalOutput === undefined ? null : this.state.putJson(state.finalOutput, now);
|
|
232
289
|
const errorHash = state.error === undefined ? null : this.state.putText(state.error, now);
|
|
233
290
|
this.state.connection
|
|
234
291
|
.prepare(
|
|
@@ -236,9 +293,11 @@ export class WorkflowRunStore {
|
|
|
236
293
|
run_id, resource_id, project_id, parent_run_id, definition_digest,
|
|
237
294
|
workflow_ref, workflow_source_hash, launch_options_hash,
|
|
238
295
|
source_type, source_ref, source_revision, title, status, paused,
|
|
239
|
-
status_detail, input_hash,
|
|
296
|
+
status_detail, input_hash, workflow_sources_hash, human_decision_hash,
|
|
297
|
+
final_output_hash, error_hash, carried_step_count, current_node,
|
|
298
|
+
current_attempt_id, current_node_started_at, waiting_on,
|
|
240
299
|
created_at, updated_at, finished_at
|
|
241
|
-
) VALUES (?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
300
|
+
) VALUES (?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
242
301
|
)
|
|
243
302
|
.run(
|
|
244
303
|
state.runId,
|
|
@@ -256,8 +315,17 @@ export class WorkflowRunStore {
|
|
|
256
315
|
state.paused === true ? 1 : 0,
|
|
257
316
|
state.statusDetail ?? null,
|
|
258
317
|
inputHash,
|
|
259
|
-
|
|
318
|
+
workflowSourcesHash,
|
|
319
|
+
humanDecisionHash,
|
|
320
|
+
finalOutputHash,
|
|
260
321
|
errorHash,
|
|
322
|
+
state.carriedStepCount ?? 0,
|
|
323
|
+
state.currentNode ?? null,
|
|
324
|
+
state.currentAttemptId ?? null,
|
|
325
|
+
state.currentNodeStartedAt === undefined
|
|
326
|
+
? null
|
|
327
|
+
: Date.parse(state.currentNodeStartedAt),
|
|
328
|
+
state.waitingOn ?? null,
|
|
261
329
|
Date.parse(state.startedAt),
|
|
262
330
|
now,
|
|
263
331
|
state.finishedAt === undefined ? null : Date.parse(state.finishedAt),
|
|
@@ -393,10 +461,20 @@ export class WorkflowRunStore {
|
|
|
393
461
|
this.state.connection
|
|
394
462
|
.prepare(
|
|
395
463
|
`INSERT INTO workflow_updates(
|
|
396
|
-
update_id, attempt_id, update_seq,
|
|
397
|
-
|
|
464
|
+
update_id, attempt_id, update_seq, run_revision,
|
|
465
|
+
update_type, update_key, data_hash, recorded_at
|
|
466
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
398
467
|
)
|
|
399
|
-
.run(
|
|
468
|
+
.run(
|
|
469
|
+
updateId,
|
|
470
|
+
attemptId,
|
|
471
|
+
nextUpdateSeq,
|
|
472
|
+
revision,
|
|
473
|
+
update.type,
|
|
474
|
+
update.key,
|
|
475
|
+
dataHash,
|
|
476
|
+
Date.now(),
|
|
477
|
+
);
|
|
400
478
|
insertRunEvent(this.state, run.resourceId, revision, at, event);
|
|
401
479
|
this.persistRunState(run, state, revision, Date.now());
|
|
402
480
|
context.revision = revision;
|
|
@@ -528,7 +606,7 @@ export class WorkflowRunStore {
|
|
|
528
606
|
...(attemptId !== undefined ? { captureAttemptId: attemptId } : {}),
|
|
529
607
|
},
|
|
530
608
|
});
|
|
531
|
-
const current = this.
|
|
609
|
+
const current = this.readRunState(run, this.readDefinition(run.definitionHash));
|
|
532
610
|
current.traceSeq = revision;
|
|
533
611
|
current.updatedAt = at;
|
|
534
612
|
this.persistRunState(run, current, revision, now);
|
|
@@ -616,24 +694,9 @@ export class WorkflowRunStore {
|
|
|
616
694
|
);
|
|
617
695
|
expected += 1;
|
|
618
696
|
}
|
|
619
|
-
const now = Date.now();
|
|
620
697
|
this.state.connection
|
|
621
698
|
.prepare("UPDATE session_segments SET event_count = ? WHERE segment_id = ?")
|
|
622
699
|
.run(expected - 1, current.segmentId);
|
|
623
|
-
const resourceId = segmentResourceId(current);
|
|
624
|
-
const revision = this.requireResourceRevision(resourceId);
|
|
625
|
-
this.bumpResource(resourceId, revision, now);
|
|
626
|
-
const payloadHash = this.state.putJson({ count: records.length, lastSeq: expected - 1 }, now);
|
|
627
|
-
insertGenericEvent(
|
|
628
|
-
this.state,
|
|
629
|
-
resourceId,
|
|
630
|
-
revision + 1,
|
|
631
|
-
"session.events_appended",
|
|
632
|
-
"session",
|
|
633
|
-
current.sessionId,
|
|
634
|
-
payloadHash,
|
|
635
|
-
now,
|
|
636
|
-
);
|
|
637
700
|
});
|
|
638
701
|
}
|
|
639
702
|
|
|
@@ -698,8 +761,8 @@ export class WorkflowRunStore {
|
|
|
698
761
|
readRun(runId: string, options: ReadWorkflowRunOptions = {}): LoadedWorkflowRun | null {
|
|
699
762
|
const row = this.readRunRow(runId);
|
|
700
763
|
if (row === undefined) return null;
|
|
701
|
-
const state = this.readState(row.stateHash);
|
|
702
764
|
const snapshot = this.readDefinition(row.definitionHash);
|
|
765
|
+
const state = this.readRunState(row, snapshot);
|
|
703
766
|
const segments = this.segmentRows(runId).map((segment) => this.loadSegment(segment, state));
|
|
704
767
|
const flat = segments.find((segment) => segment.attemptId === "") ?? emptySegment();
|
|
705
768
|
return {
|
|
@@ -831,13 +894,20 @@ export class WorkflowRunStore {
|
|
|
831
894
|
now: number,
|
|
832
895
|
): void {
|
|
833
896
|
this.bumpResource(run.resourceId, expectedRevision - 1, now);
|
|
834
|
-
const
|
|
897
|
+
const workflowSourcesHash =
|
|
898
|
+
state.workflowSources === undefined ? null : this.state.putJson(state.workflowSources, now);
|
|
899
|
+
const humanDecisionHash =
|
|
900
|
+
state.humanDecision === undefined ? null : this.state.putJson(state.humanDecision, now);
|
|
901
|
+
const finalOutputHash =
|
|
902
|
+
state.finalOutput === undefined ? null : this.state.putJson(state.finalOutput, now);
|
|
835
903
|
const errorHash = state.error === undefined ? null : this.state.putText(state.error, now);
|
|
836
904
|
const update = this.state.connection
|
|
837
905
|
.prepare(
|
|
838
906
|
`UPDATE runs
|
|
839
|
-
SET title = ?, status = ?, paused = ?, status_detail = ?,
|
|
840
|
-
|
|
907
|
+
SET title = ?, status = ?, paused = ?, status_detail = ?,
|
|
908
|
+
workflow_sources_hash = ?, human_decision_hash = ?, final_output_hash = ?,
|
|
909
|
+
error_hash = ?, carried_step_count = ?, current_node = ?, current_attempt_id = ?,
|
|
910
|
+
current_node_started_at = ?, waiting_on = ?, updated_at = ?, finished_at = ?
|
|
841
911
|
WHERE run_id = ?`,
|
|
842
912
|
)
|
|
843
913
|
.run(
|
|
@@ -845,8 +915,15 @@ export class WorkflowRunStore {
|
|
|
845
915
|
state.status,
|
|
846
916
|
state.paused === true ? 1 : 0,
|
|
847
917
|
state.statusDetail ?? null,
|
|
848
|
-
|
|
918
|
+
workflowSourcesHash,
|
|
919
|
+
humanDecisionHash,
|
|
920
|
+
finalOutputHash,
|
|
849
921
|
errorHash,
|
|
922
|
+
state.carriedStepCount ?? 0,
|
|
923
|
+
state.currentNode ?? null,
|
|
924
|
+
state.currentAttemptId ?? null,
|
|
925
|
+
state.currentNodeStartedAt === undefined ? null : Date.parse(state.currentNodeStartedAt),
|
|
926
|
+
state.waitingOn ?? null,
|
|
850
927
|
now,
|
|
851
928
|
state.finishedAt === undefined ? null : Date.parse(state.finishedAt),
|
|
852
929
|
state.runId,
|
|
@@ -855,7 +932,6 @@ export class WorkflowRunStore {
|
|
|
855
932
|
if (state.status !== "running") {
|
|
856
933
|
this.enqueueRunSettlementEffect(run.resourceId, expectedRevision, state, now);
|
|
857
934
|
}
|
|
858
|
-
run.stateHash = stateHash;
|
|
859
935
|
}
|
|
860
936
|
|
|
861
937
|
private enqueueRunSettlementEffect(
|
|
@@ -931,7 +1007,9 @@ export class WorkflowRunStore {
|
|
|
931
1007
|
now: number,
|
|
932
1008
|
): void {
|
|
933
1009
|
for (const step of state.steps.slice(state.carriedStepCount ?? 0)) {
|
|
934
|
-
const
|
|
1010
|
+
const metadata = stepMetadata(step);
|
|
1011
|
+
const stepMetadataHash =
|
|
1012
|
+
Object.keys(metadata).length === 0 ? null : this.state.putJson(metadata, now);
|
|
935
1013
|
const outputHash = step.output === undefined ? null : this.state.putJson(step.output, now);
|
|
936
1014
|
const errorHash = step.error === undefined ? null : this.state.putText(step.error, now);
|
|
937
1015
|
const promptHash = step.prompt === null ? null : this.state.putJson(step.prompt, now);
|
|
@@ -944,7 +1022,7 @@ export class WorkflowRunStore {
|
|
|
944
1022
|
.prepare(
|
|
945
1023
|
`INSERT INTO node_attempts(
|
|
946
1024
|
attempt_id, run_id, node_id, attempt_number, node_type, status,
|
|
947
|
-
|
|
1025
|
+
prompt_hash, output_hash, step_metadata_hash, error_hash,
|
|
948
1026
|
started_at, finished_at, created_at, updated_at
|
|
949
1027
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
950
1028
|
)
|
|
@@ -957,7 +1035,7 @@ export class WorkflowRunStore {
|
|
|
957
1035
|
outcomeStatus(step.outcome),
|
|
958
1036
|
promptHash,
|
|
959
1037
|
outputHash,
|
|
960
|
-
|
|
1038
|
+
stepMetadataHash,
|
|
961
1039
|
errorHash,
|
|
962
1040
|
Date.parse(step.startedAt),
|
|
963
1041
|
Date.parse(step.finishedAt),
|
|
@@ -968,7 +1046,7 @@ export class WorkflowRunStore {
|
|
|
968
1046
|
this.state.connection
|
|
969
1047
|
.prepare(
|
|
970
1048
|
`UPDATE node_attempts
|
|
971
|
-
SET status = ?,
|
|
1049
|
+
SET status = ?, prompt_hash = ?, output_hash = ?, step_metadata_hash = ?,
|
|
972
1050
|
error_hash = ?, finished_at = ?, updated_at = ?
|
|
973
1051
|
WHERE attempt_id = ?`,
|
|
974
1052
|
)
|
|
@@ -976,7 +1054,7 @@ export class WorkflowRunStore {
|
|
|
976
1054
|
outcomeStatus(step.outcome),
|
|
977
1055
|
promptHash,
|
|
978
1056
|
outputHash,
|
|
979
|
-
|
|
1057
|
+
stepMetadataHash,
|
|
980
1058
|
errorHash,
|
|
981
1059
|
Date.parse(step.finishedAt),
|
|
982
1060
|
now,
|
|
@@ -987,6 +1065,49 @@ export class WorkflowRunStore {
|
|
|
987
1065
|
if (state.currentAttemptId !== undefined && state.currentNode !== undefined) {
|
|
988
1066
|
this.ensureAttempt(state, state.currentNode, state.currentAttemptId, now, snapshot);
|
|
989
1067
|
}
|
|
1068
|
+
this.syncRunSteps(state, now);
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
private syncRunSteps(state: WorkflowRunState, now: number): void {
|
|
1072
|
+
const existingRows = this.state.connection
|
|
1073
|
+
.prepare(
|
|
1074
|
+
`SELECT step_index AS stepIndex, attempt_id AS attemptId
|
|
1075
|
+
FROM run_steps WHERE run_id = ? ORDER BY step_index`,
|
|
1076
|
+
)
|
|
1077
|
+
.all(state.runId)
|
|
1078
|
+
.filter(isRunStepIdentityRow);
|
|
1079
|
+
if (existingRows.length > state.steps.length) {
|
|
1080
|
+
throw new Error(`Workflow run steps cannot shrink: ${state.runId}`);
|
|
1081
|
+
}
|
|
1082
|
+
for (const existing of existingRows) {
|
|
1083
|
+
if (state.steps[existing.stepIndex]?.attemptId !== existing.attemptId) {
|
|
1084
|
+
throw new Error(`Workflow run step history changed at index ${existing.stepIndex}`);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
for (let stepIndex = existingRows.length; stepIndex < state.steps.length; stepIndex += 1) {
|
|
1088
|
+
const step = state.steps[stepIndex];
|
|
1089
|
+
/* istanbul ignore if -- array index follows a checked bound */
|
|
1090
|
+
if (step === undefined) throw new Error("Workflow run step became unavailable");
|
|
1091
|
+
const attempt = this.state.connection
|
|
1092
|
+
.prepare("SELECT output_hash AS outputHash FROM node_attempts WHERE attempt_id = ?")
|
|
1093
|
+
.get(step.attemptId);
|
|
1094
|
+
if (!isAttemptOutputRow(attempt)) {
|
|
1095
|
+
throw new Error(`Workflow node attempt is missing: ${step.attemptId}`);
|
|
1096
|
+
}
|
|
1097
|
+
const carried = stepIndex < (state.carriedStepCount ?? 0);
|
|
1098
|
+
const outputOverrideHash =
|
|
1099
|
+
carried &&
|
|
1100
|
+
(attempt.outputHash === null ||
|
|
1101
|
+
canonicalJson(this.state.readJson(attempt.outputHash)) !== canonicalJson(step.output))
|
|
1102
|
+
? this.state.putJson(step.output, now)
|
|
1103
|
+
: null;
|
|
1104
|
+
this.state.connection
|
|
1105
|
+
.prepare(
|
|
1106
|
+
`INSERT INTO run_steps(run_id, step_index, attempt_id, output_override_hash)
|
|
1107
|
+
VALUES (?, ?, ?, ?)`,
|
|
1108
|
+
)
|
|
1109
|
+
.run(state.runId, stepIndex, step.attemptId, outputOverrideHash);
|
|
1110
|
+
}
|
|
990
1111
|
}
|
|
991
1112
|
|
|
992
1113
|
private ensureAttempt(
|
|
@@ -1057,8 +1178,17 @@ export class WorkflowRunStore {
|
|
|
1057
1178
|
const row = this.state.connection
|
|
1058
1179
|
.prepare(
|
|
1059
1180
|
`SELECT r.run_id AS runId, r.resource_id AS resourceId,
|
|
1060
|
-
|
|
1061
|
-
r.
|
|
1181
|
+
d.definition_hash AS definitionHash, r.definition_digest AS definitionDigest,
|
|
1182
|
+
d.workflow_name AS workflowRef, r.source_ref AS sourceRef,
|
|
1183
|
+
r.workflow_source_hash AS workflowSourceHash,
|
|
1184
|
+
r.workflow_sources_hash AS workflowSourcesHash, r.parent_run_id AS parentRunId,
|
|
1185
|
+
r.title, r.status, r.paused, r.status_detail AS statusDetail,
|
|
1186
|
+
r.input_hash AS inputHash, r.human_decision_hash AS humanDecisionHash,
|
|
1187
|
+
r.final_output_hash AS finalOutputHash, r.error_hash AS errorHash,
|
|
1188
|
+
r.carried_step_count AS carriedStepCount, r.current_node AS currentNode,
|
|
1189
|
+
r.current_attempt_id AS currentAttemptId,
|
|
1190
|
+
r.current_node_started_at AS currentNodeStartedAt, r.waiting_on AS waitingOn,
|
|
1191
|
+
r.created_at AS createdAt, r.updated_at AS updatedAt, r.finished_at AS finishedAt
|
|
1062
1192
|
FROM runs r
|
|
1063
1193
|
JOIN workflow_definitions d ON d.definition_digest = r.definition_digest
|
|
1064
1194
|
WHERE r.run_id = ?`,
|
|
@@ -1073,12 +1203,151 @@ export class WorkflowRunStore {
|
|
|
1073
1203
|
return row;
|
|
1074
1204
|
}
|
|
1075
1205
|
|
|
1076
|
-
private
|
|
1077
|
-
const
|
|
1078
|
-
|
|
1079
|
-
|
|
1206
|
+
private readRunState(row: RunRow, snapshot: WorkflowDefinitionSnapshot): WorkflowRunState {
|
|
1207
|
+
const steps = this.readSteps(row.runId);
|
|
1208
|
+
const outputs: Record<string, unknown> = {};
|
|
1209
|
+
const results: Record<string, WorkflowNodeResult> = {};
|
|
1210
|
+
for (const step of steps) {
|
|
1211
|
+
const result = resultForStep(step);
|
|
1212
|
+
results[step.nodeId] = result;
|
|
1213
|
+
if (step.outcome === "ok") outputs[step.nodeId] = step.output;
|
|
1214
|
+
else delete outputs[step.nodeId];
|
|
1215
|
+
const node = snapshot.nodes[step.nodeId];
|
|
1216
|
+
const mountPath =
|
|
1217
|
+
node?.includeTransition === "exit" && node.mountPath !== undefined
|
|
1218
|
+
? node.mountPath.join("/")
|
|
1219
|
+
: undefined;
|
|
1220
|
+
if (mountPath !== undefined && step.outcome === "ok") {
|
|
1221
|
+
outputs[mountPath] = step.output;
|
|
1222
|
+
results[mountPath] = { ...result, nodeId: mountPath };
|
|
1223
|
+
}
|
|
1080
1224
|
}
|
|
1081
|
-
|
|
1225
|
+
const revision = this.requireResourceRevision(row.resourceId);
|
|
1226
|
+
return {
|
|
1227
|
+
schema: RUN_STATE_SCHEMA,
|
|
1228
|
+
traceSeq: revision,
|
|
1229
|
+
runId: row.runId,
|
|
1230
|
+
workflowName: row.workflowRef,
|
|
1231
|
+
...(row.parentRunId === null ? {} : { parentRunId: row.parentRunId }),
|
|
1232
|
+
...(row.carriedStepCount === 0 ? {} : { carriedStepCount: row.carriedStepCount }),
|
|
1233
|
+
...(row.title === null ? {} : { runTitle: row.title }),
|
|
1234
|
+
...(row.sourceRef.startsWith("inline:")
|
|
1235
|
+
? {}
|
|
1236
|
+
: { workflowSource: this.readJsonAs(row.workflowSourceHash) }),
|
|
1237
|
+
...(row.workflowSourcesHash === null
|
|
1238
|
+
? {}
|
|
1239
|
+
: { workflowSources: this.readJsonAs(row.workflowSourcesHash) }),
|
|
1240
|
+
...(row.workflowSourcesHash !== null || snapshot.composition?.mounts.length
|
|
1241
|
+
? { definitionDigest: `sha256:${row.definitionDigest.toString("hex")}` }
|
|
1242
|
+
: {}),
|
|
1243
|
+
startedAt: new Date(row.createdAt).toISOString(),
|
|
1244
|
+
...(row.finishedAt === null ? {} : { finishedAt: new Date(row.finishedAt).toISOString() }),
|
|
1245
|
+
updatedAt: new Date(row.updatedAt).toISOString(),
|
|
1246
|
+
status: row.status as WorkflowRunState["status"],
|
|
1247
|
+
input: this.state.readJson(row.inputHash),
|
|
1248
|
+
outputs,
|
|
1249
|
+
results,
|
|
1250
|
+
steps,
|
|
1251
|
+
...this.readUpdates(row.runId),
|
|
1252
|
+
...(row.currentNode === null ? {} : { currentNode: row.currentNode }),
|
|
1253
|
+
...(row.currentAttemptId === null ? {} : { currentAttemptId: row.currentAttemptId }),
|
|
1254
|
+
...(row.currentNodeStartedAt === null
|
|
1255
|
+
? {}
|
|
1256
|
+
: { currentNodeStartedAt: new Date(row.currentNodeStartedAt).toISOString() }),
|
|
1257
|
+
...(row.statusDetail === null ? {} : { statusDetail: row.statusDetail }),
|
|
1258
|
+
...(row.humanDecisionHash === null
|
|
1259
|
+
? {}
|
|
1260
|
+
: { humanDecision: this.readJsonAs(row.humanDecisionHash) }),
|
|
1261
|
+
...(row.paused === 0 ? {} : { paused: true }),
|
|
1262
|
+
...(row.waitingOn === null ? {} : { waitingOn: row.waitingOn }),
|
|
1263
|
+
...(row.finalOutputHash === null
|
|
1264
|
+
? {}
|
|
1265
|
+
: { finalOutput: this.state.readJson(row.finalOutputHash) }),
|
|
1266
|
+
...(row.errorHash === null ? {} : { error: this.readText(row.errorHash) }),
|
|
1267
|
+
};
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
private readSteps(runId: string): WorkflowStepRecord[] {
|
|
1271
|
+
const rows = this.state.connection
|
|
1272
|
+
.prepare(
|
|
1273
|
+
`SELECT s.step_index AS stepIndex, a.attempt_id AS attemptId, a.node_id AS nodeId,
|
|
1274
|
+
a.node_type AS nodeType, a.status, a.prompt_hash AS promptHash,
|
|
1275
|
+
a.output_hash AS outputHash, s.output_override_hash AS outputOverrideHash,
|
|
1276
|
+
a.step_metadata_hash AS stepMetadataHash, a.error_hash AS errorHash,
|
|
1277
|
+
a.started_at AS startedAt, a.finished_at AS finishedAt
|
|
1278
|
+
FROM run_steps s
|
|
1279
|
+
JOIN node_attempts a ON a.attempt_id = s.attempt_id
|
|
1280
|
+
WHERE s.run_id = ? ORDER BY s.step_index`,
|
|
1281
|
+
)
|
|
1282
|
+
.all(runId)
|
|
1283
|
+
.filter(isStepRow);
|
|
1284
|
+
return rows.map((row, index) => {
|
|
1285
|
+
if (row.stepIndex !== index)
|
|
1286
|
+
throw new Error(`Workflow run step sequence has a gap: ${runId}`);
|
|
1287
|
+
const metadata =
|
|
1288
|
+
row.stepMetadataHash === null
|
|
1289
|
+
? {}
|
|
1290
|
+
: this.readJsonAs<StoredStepMetadata>(row.stepMetadataHash);
|
|
1291
|
+
const prompt = row.promptHash === null ? null : this.readJsonAs<string>(row.promptHash);
|
|
1292
|
+
const outputHash = row.outputOverrideHash ?? row.outputHash;
|
|
1293
|
+
const output = outputHash === null ? null : this.state.readJson(outputHash);
|
|
1294
|
+
const error = row.errorHash === null ? undefined : this.readText(row.errorHash);
|
|
1295
|
+
return {
|
|
1296
|
+
attemptId: row.attemptId,
|
|
1297
|
+
nodeId: row.nodeId,
|
|
1298
|
+
nodeType: row.nodeType,
|
|
1299
|
+
outcome: outcomeForStatus(row.status),
|
|
1300
|
+
startedAt: new Date(row.startedAt).toISOString(),
|
|
1301
|
+
finishedAt: new Date(row.finishedAt).toISOString(),
|
|
1302
|
+
prompt,
|
|
1303
|
+
output,
|
|
1304
|
+
...(error === undefined ? {} : { error }),
|
|
1305
|
+
...metadata,
|
|
1306
|
+
};
|
|
1307
|
+
});
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
private readUpdates(runId: string): Pick<WorkflowRunState, "updates"> {
|
|
1311
|
+
const rows = this.state.connection
|
|
1312
|
+
.prepare(
|
|
1313
|
+
`SELECT u.update_id AS updateId, u.run_revision AS runRevision,
|
|
1314
|
+
a.node_id AS nodeId, u.attempt_id AS attemptId,
|
|
1315
|
+
u.update_type AS updateType, u.update_key AS updateKey,
|
|
1316
|
+
u.data_hash AS dataHash, u.recorded_at AS recordedAt
|
|
1317
|
+
FROM workflow_updates u
|
|
1318
|
+
JOIN node_attempts a ON a.attempt_id = u.attempt_id
|
|
1319
|
+
WHERE a.run_id = ? ORDER BY u.run_revision`,
|
|
1320
|
+
)
|
|
1321
|
+
.all(runId)
|
|
1322
|
+
.filter(isUpdateRow);
|
|
1323
|
+
if (rows.length === 0) return {};
|
|
1324
|
+
let updates: WorkflowUpdateRecord[] | undefined;
|
|
1325
|
+
for (const row of rows) {
|
|
1326
|
+
updates = updateProjection(updates, {
|
|
1327
|
+
updateId: row.updateId,
|
|
1328
|
+
seq: row.runRevision,
|
|
1329
|
+
at: new Date(row.recordedAt).toISOString(),
|
|
1330
|
+
runId,
|
|
1331
|
+
nodeId: row.nodeId,
|
|
1332
|
+
attemptId: row.attemptId,
|
|
1333
|
+
type: row.updateType,
|
|
1334
|
+
key: row.updateKey,
|
|
1335
|
+
data: this.readJsonAs<Record<string, unknown>>(row.dataHash),
|
|
1336
|
+
});
|
|
1337
|
+
}
|
|
1338
|
+
return updates === undefined ? {} : { updates };
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
private readJsonAs<T>(hash: Buffer): T {
|
|
1342
|
+
return this.state.readJson(hash) as T;
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
private readText(hash: Buffer): string {
|
|
1346
|
+
const blob = this.state.readBlob(hash);
|
|
1347
|
+
if (blob === undefined || blob.mediaType !== "text/plain") {
|
|
1348
|
+
throw new Error("Text blob is missing or has the wrong media type");
|
|
1349
|
+
}
|
|
1350
|
+
return blob.content.toString("utf8");
|
|
1082
1351
|
}
|
|
1083
1352
|
|
|
1084
1353
|
private readDefinition(hash: Buffer): WorkflowDefinitionSnapshot {
|
|
@@ -1318,12 +1587,13 @@ function insertRunEvent(
|
|
|
1318
1587
|
at: string,
|
|
1319
1588
|
event: WorkflowTraceEventDraft | WorkflowTraceEvent,
|
|
1320
1589
|
): void {
|
|
1590
|
+
const payload = compactTracePayload(event.type, event.payload);
|
|
1321
1591
|
const payloadHash = state.putJson(
|
|
1322
1592
|
{
|
|
1323
1593
|
scope: event.scope,
|
|
1324
1594
|
...(event.nodeId === undefined ? {} : { nodeId: event.nodeId }),
|
|
1325
1595
|
...(event.attemptId === undefined ? {} : { attemptId: event.attemptId }),
|
|
1326
|
-
payload
|
|
1596
|
+
payload,
|
|
1327
1597
|
},
|
|
1328
1598
|
Date.parse(at),
|
|
1329
1599
|
);
|
|
@@ -1339,6 +1609,28 @@ function insertRunEvent(
|
|
|
1339
1609
|
);
|
|
1340
1610
|
}
|
|
1341
1611
|
|
|
1612
|
+
function compactTracePayload(
|
|
1613
|
+
eventType: string,
|
|
1614
|
+
payload: Record<string, unknown>,
|
|
1615
|
+
): Record<string, unknown> {
|
|
1616
|
+
if (
|
|
1617
|
+
(eventType === "node_finished" || eventType === "include_exited") &&
|
|
1618
|
+
Object.hasOwn(payload, "output")
|
|
1619
|
+
) {
|
|
1620
|
+
const { output: _output, ...rest } = payload;
|
|
1621
|
+
return { ...rest, outputStored: true };
|
|
1622
|
+
}
|
|
1623
|
+
if (eventType === "run_started" && Object.hasOwn(payload, "input")) {
|
|
1624
|
+
const { input: _input, ...rest } = payload;
|
|
1625
|
+
return { ...rest, inputStored: true };
|
|
1626
|
+
}
|
|
1627
|
+
if (Object.hasOwn(payload, "finalOutput")) {
|
|
1628
|
+
const { finalOutput: _finalOutput, ...rest } = payload;
|
|
1629
|
+
return { ...rest, finalOutputStored: true };
|
|
1630
|
+
}
|
|
1631
|
+
return payload;
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1342
1634
|
function insertGenericEvent(
|
|
1343
1635
|
state: StateDatabase,
|
|
1344
1636
|
resourceId: string,
|
|
@@ -1407,6 +1699,43 @@ function outcomeStatus(outcome: string): string {
|
|
|
1407
1699
|
}
|
|
1408
1700
|
}
|
|
1409
1701
|
|
|
1702
|
+
function outcomeForStatus(status: string): WorkflowStepRecord["outcome"] {
|
|
1703
|
+
switch (status) {
|
|
1704
|
+
case "completed":
|
|
1705
|
+
return "ok";
|
|
1706
|
+
case "timed_out":
|
|
1707
|
+
return "timed_out";
|
|
1708
|
+
case "cancelled":
|
|
1709
|
+
return "cancelled";
|
|
1710
|
+
case "failed":
|
|
1711
|
+
return "failed";
|
|
1712
|
+
default:
|
|
1713
|
+
throw new Error(`Workflow step has nonterminal status: ${status}`);
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
function stepMetadata(step: WorkflowStepRecord): StoredStepMetadata {
|
|
1718
|
+
return {
|
|
1719
|
+
...(step.action === undefined ? {} : { action: step.action }),
|
|
1720
|
+
...(step.assistantMessage === undefined ? {} : { assistantMessage: step.assistantMessage }),
|
|
1721
|
+
...(step.conversation === undefined ? {} : { conversation: step.conversation }),
|
|
1722
|
+
};
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
function resultForStep(step: WorkflowStepRecord): WorkflowNodeResult {
|
|
1726
|
+
return {
|
|
1727
|
+
attemptId: step.attemptId,
|
|
1728
|
+
nodeId: step.nodeId,
|
|
1729
|
+
nodeType: step.nodeType,
|
|
1730
|
+
outcome: step.outcome,
|
|
1731
|
+
startedAt: step.startedAt,
|
|
1732
|
+
finishedAt: step.finishedAt,
|
|
1733
|
+
durationMs: Date.parse(step.finishedAt) - Date.parse(step.startedAt),
|
|
1734
|
+
...(step.outcome === "ok" ? { output: step.output } : {}),
|
|
1735
|
+
...(step.error === undefined ? {} : { error: step.error }),
|
|
1736
|
+
};
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1410
1739
|
function traceScope(value: unknown): WorkflowTraceEvent["scope"] {
|
|
1411
1740
|
return value === "node" || value === "agent" || value === "action" || value === "session"
|
|
1412
1741
|
? value
|
|
@@ -1438,6 +1767,12 @@ function validateSessionEventRecord(record: WorkflowSessionEventRecord): void {
|
|
|
1438
1767
|
) {
|
|
1439
1768
|
throw new Error("Session event is missing required envelope fields");
|
|
1440
1769
|
}
|
|
1770
|
+
if (
|
|
1771
|
+
record.type === "assistant_event" &&
|
|
1772
|
+
["text_delta", "thinking_delta", "toolcall_delta"].includes(String(record.payload.type))
|
|
1773
|
+
) {
|
|
1774
|
+
throw new Error("Incremental assistant events are not durable session facts");
|
|
1775
|
+
}
|
|
1441
1776
|
}
|
|
1442
1777
|
|
|
1443
1778
|
function validateSessionCapture(capture: WorkflowSessionCapture): void {
|
|
@@ -1480,6 +1815,25 @@ function isRunIdRow(value: unknown): value is { runId: string } {
|
|
|
1480
1815
|
return isRecord(value);
|
|
1481
1816
|
}
|
|
1482
1817
|
|
|
1818
|
+
function isRunStepIdentityRow(value: unknown): value is {
|
|
1819
|
+
stepIndex: number;
|
|
1820
|
+
attemptId: string;
|
|
1821
|
+
} {
|
|
1822
|
+
return isRecord(value);
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
function isAttemptOutputRow(value: unknown): value is { outputHash: Buffer | null } {
|
|
1826
|
+
return isRecord(value) && (value.outputHash === null || Buffer.isBuffer(value.outputHash));
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
function isStepRow(value: unknown): value is StepRow {
|
|
1830
|
+
return isRecord(value);
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
function isUpdateRow(value: unknown): value is UpdateRow {
|
|
1834
|
+
return isRecord(value);
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1483
1837
|
function isRevisionRow(value: unknown): value is { revision: number } {
|
|
1484
1838
|
return isRecord(value);
|
|
1485
1839
|
}
|
package/src/workflows/types.ts
CHANGED
|
@@ -760,7 +760,6 @@ export type WorkflowSessionEventType =
|
|
|
760
760
|
| "assistant_event"
|
|
761
761
|
| "message_finished"
|
|
762
762
|
| "tool_execution_started"
|
|
763
|
-
| "tool_execution_updated"
|
|
764
763
|
| "tool_execution_finished";
|
|
765
764
|
|
|
766
765
|
/** One normalized temporal Pi event in `session/events.ndjson`. */
|