@wichayutdew/pi-workflows 0.3.0 → 1.0.1

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.
Files changed (144) hide show
  1. package/README.md +55 -39
  2. package/dist/index.js +5771 -4794
  3. package/examples/mr-comments.workflow.yaml +2 -0
  4. package/package.json +1 -1
  5. package/schemas/workflow.schema.json +10 -1
  6. package/src/command-names.ts +7 -1
  7. package/src/commands.ts +126 -52
  8. package/src/config/catalog.ts +162 -0
  9. package/src/config/ceiling.ts +120 -116
  10. package/src/config/command-conflicts.ts +4 -4
  11. package/src/config/diagnostics.ts +28 -0
  12. package/src/config/load-settings.ts +37 -0
  13. package/src/config/load-types.ts +53 -0
  14. package/src/config/load-workflows.ts +135 -0
  15. package/src/config/load.ts +43 -311
  16. package/src/config/types.ts +150 -137
  17. package/src/config/validate.ts +12 -1261
  18. package/src/config/validation/permissions.ts +312 -0
  19. package/src/config/validation/prompt.ts +21 -0
  20. package/src/config/validation/settings.ts +122 -0
  21. package/src/config/validation/shared.ts +129 -0
  22. package/src/config/validation/shortcut.ts +118 -0
  23. package/src/config/validation/step.ts +228 -0
  24. package/src/config/validation/subagent.ts +288 -0
  25. package/src/config/validation/workflow.ts +156 -0
  26. package/src/config/yaml.ts +29 -0
  27. package/src/digest.ts +38 -6
  28. package/src/engine/checkpoint.ts +13 -9
  29. package/src/engine/create-run.ts +43 -0
  30. package/src/engine/gate-transitions.ts +187 -0
  31. package/src/engine/reconciliation-history.ts +88 -0
  32. package/src/engine/resume.ts +19 -7
  33. package/src/engine/run-advance.ts +122 -0
  34. package/src/engine/run-lifecycle.ts +124 -0
  35. package/src/engine/run-reconciliation.ts +116 -0
  36. package/src/engine/run-validation.ts +129 -0
  37. package/src/engine/state-types.ts +66 -0
  38. package/src/engine/state.ts +10 -193
  39. package/src/engine/transition-helpers.ts +28 -0
  40. package/src/engine/transition-types.ts +8 -0
  41. package/src/engine/transitions.ts +17 -471
  42. package/src/harness/action-context.ts +181 -0
  43. package/src/harness/catalog.ts +45 -0
  44. package/src/harness/context-idle.ts +21 -0
  45. package/src/harness/core-actions.ts +299 -0
  46. package/src/harness/delegation-control-actions.ts +250 -0
  47. package/src/harness/delegation-failure.ts +215 -0
  48. package/src/harness/delegation-plan.ts +219 -0
  49. package/src/harness/delegation-recovery-validation.ts +159 -0
  50. package/src/harness/delegation-response-actions.ts +295 -0
  51. package/src/harness/delegation-retry-policy.ts +118 -0
  52. package/src/harness/dependencies.ts +165 -0
  53. package/src/harness/gate-submission-action.ts +154 -0
  54. package/src/harness/lifecycle-actions.ts +154 -0
  55. package/src/harness/pause-actions.ts +120 -0
  56. package/src/harness/plannotator-result-actions.ts +123 -0
  57. package/src/harness/prompt-gate-actions.ts +270 -0
  58. package/src/harness/resume-action.ts +261 -0
  59. package/src/harness/start-actions.ts +183 -0
  60. package/src/harness/status-actions.ts +191 -0
  61. package/src/harness/step-execution-actions.ts +283 -0
  62. package/src/harness/types.ts +76 -0
  63. package/src/harness.ts +236 -2303
  64. package/src/index.ts +73 -11
  65. package/src/integrations/plannotator-requests.ts +127 -0
  66. package/src/integrations/plannotator-responses.ts +141 -0
  67. package/src/integrations/plannotator-types.ts +44 -0
  68. package/src/integrations/plannotator.ts +16 -235
  69. package/src/integrations/prompt-gate.ts +17 -10
  70. package/src/integrations/subagents/child-policy-envelope.ts +119 -0
  71. package/src/integrations/subagents/child-policy-paths.ts +66 -0
  72. package/src/integrations/subagents/child-policy-sections.ts +186 -0
  73. package/src/integrations/subagents/child-policy-types.ts +32 -0
  74. package/src/integrations/subagents/child-policy-validation.ts +163 -0
  75. package/src/integrations/subagents/child-runtime-completion.ts +56 -0
  76. package/src/integrations/subagents/child-runtime-dependencies.ts +47 -0
  77. package/src/integrations/subagents/child-runtime-files.ts +156 -0
  78. package/src/integrations/subagents/child-runtime-policy.ts +83 -0
  79. package/src/integrations/subagents/child-runtime-types.ts +30 -0
  80. package/src/integrations/subagents/child-runtime.ts +140 -301
  81. package/src/integrations/subagents/client-delegation.ts +181 -0
  82. package/src/integrations/subagents/client-messages.ts +66 -0
  83. package/src/integrations/subagents/client-types.ts +36 -0
  84. package/src/integrations/subagents/client.ts +127 -219
  85. package/src/integrations/subagents/delegated-result.ts +31 -0
  86. package/src/integrations/subagents/diagnostic-format.ts +40 -0
  87. package/src/integrations/subagents/diagnostic-text.ts +114 -0
  88. package/src/integrations/subagents/diagnostic-types.ts +77 -0
  89. package/src/integrations/subagents/diagnostics.ts +24 -977
  90. package/src/integrations/subagents/failure-correlation.ts +235 -0
  91. package/src/integrations/subagents/failure-transcript.ts +192 -0
  92. package/src/integrations/subagents/hidden-bash-failure.ts +98 -0
  93. package/src/integrations/subagents/protocol-events.ts +27 -0
  94. package/src/integrations/subagents/protocol.ts +30 -375
  95. package/src/integrations/subagents/replay-audit.ts +147 -0
  96. package/src/integrations/subagents/replay-safety.ts +82 -0
  97. package/src/integrations/subagents/session-diagnostics.ts +258 -0
  98. package/src/policy/approved-command-extraction.ts +71 -0
  99. package/src/policy/approved-commands.ts +15 -432
  100. package/src/policy/bash-authorization.ts +86 -0
  101. package/src/policy/bash-read-only.ts +161 -0
  102. package/src/policy/bash-types.ts +20 -0
  103. package/src/policy/bash.ts +4 -346
  104. package/src/policy/completion-batch.ts +21 -16
  105. package/src/policy/immutable-input.ts +5 -2
  106. package/src/policy/mcp-authorization.ts +71 -0
  107. package/src/policy/restricted-command.ts +141 -0
  108. package/src/policy/restricted-git.ts +30 -0
  109. package/src/policy/reviewed-artifact.ts +97 -0
  110. package/src/policy/reviewed-command-safety.ts +150 -0
  111. package/src/policy/reviewed-command-shape.ts +49 -0
  112. package/src/policy/reviewed-repository-contract.ts +138 -0
  113. package/src/policy/reviewed-repository-cwd.ts +102 -0
  114. package/src/policy/tool-call-authorization.ts +58 -0
  115. package/src/policy/tool-selection.ts +72 -0
  116. package/src/policy/tool-types.ts +14 -0
  117. package/src/policy/tools.ts +11 -150
  118. package/src/preflight.ts +96 -61
  119. package/src/prompt/main-workflow-notice.ts +41 -0
  120. package/src/prompt/retry-task.ts +83 -0
  121. package/src/prompt/step-contract.ts +101 -0
  122. package/src/prompt/step-sections.ts +75 -0
  123. package/src/prompt/step-task.ts +156 -0
  124. package/src/prompt/template.ts +83 -0
  125. package/src/prompt.ts +10 -254
  126. package/src/runtime/completion-tool.ts +3 -0
  127. package/src/runtime/main-step-completion.ts +78 -0
  128. package/src/runtime/main-step-lifecycle.ts +37 -0
  129. package/src/runtime/main-step-policy.ts +84 -0
  130. package/src/runtime/main-step-runtime-types.ts +64 -0
  131. package/src/runtime/main-step-runtime.ts +129 -200
  132. package/src/runtime/main-step-state.ts +85 -0
  133. package/src/runtime/serial-task-queue.ts +48 -13
  134. package/src/runtime/step-result.ts +48 -27
  135. package/src/workflow-list.ts +15 -6
  136. package/src/workflow-status/format-status.ts +52 -0
  137. package/src/workflow-status/formatting.ts +178 -0
  138. package/src/workflow-status/layout.ts +109 -0
  139. package/src/workflow-status/render-board.ts +101 -0
  140. package/src/workflow-status/render-path.ts +123 -0
  141. package/src/workflow-status/render-summary.ts +144 -0
  142. package/src/workflow-status/types.ts +40 -0
  143. package/src/workflow-status/view.ts +273 -0
  144. package/src/workflow-status.ts +20 -820
@@ -0,0 +1,116 @@
1
+ import type { LoadedWorkflow } from '../config/types.ts';
2
+ import {
3
+ rebuildVisits,
4
+ refreshApprovedGateHistory,
5
+ retainedReviewedArtifact,
6
+ } from './reconciliation-history.ts';
7
+ import type { WorkflowRun } from './state-types.ts';
8
+ import { withRunUpdate } from './transition-helpers.ts';
9
+ import type { ReconcileResult } from './transition-types.ts';
10
+
11
+ /**
12
+ * Reconciles persisted run state with a reloaded workflow configuration.
13
+ *
14
+ * The earliest changed completed step is restarted. Changes limited to the
15
+ * current step restart that step, while incompatible removals return an error.
16
+ *
17
+ * @param run - Persisted workflow state.
18
+ * @param workflow - Reloaded workflow.
19
+ * @param now - Reconciliation timestamp.
20
+ * @returns Reconciled state and restart metadata.
21
+ */
22
+ export const reconcileRun = (
23
+ run: WorkflowRun,
24
+ workflow: LoadedWorkflow,
25
+ now: number,
26
+ ): ReconcileResult => {
27
+ if (run.workflowId !== workflow.definition.id) {
28
+ return {
29
+ changed: false,
30
+ error: `run belongs to "${run.workflowId}", not "${workflow.definition.id}"`,
31
+ };
32
+ }
33
+ if (run.workflowDigest === workflow.digest) {
34
+ return { run, changed: false };
35
+ }
36
+ if (!workflow.definition.steps[run.currentStepId]) {
37
+ return {
38
+ changed: true,
39
+ error: `current step "${run.currentStepId}" was removed; abort or restore the configuration`,
40
+ };
41
+ }
42
+
43
+ const reconciledRun = refreshApprovedGateHistory(run, workflow);
44
+ const changedHistoryIndex = reconciledRun.history.findIndex(
45
+ (entry) => workflow.stepDigests[entry.stepId] !== entry.stepDigest,
46
+ );
47
+ if (changedHistoryIndex >= 0) {
48
+ const changedEntry = reconciledRun.history[changedHistoryIndex];
49
+ if (!changedEntry || !workflow.definition.steps[changedEntry.stepId]) {
50
+ return {
51
+ changed: true,
52
+ error:
53
+ 'a completed step was removed; abort or restore the configuration',
54
+ };
55
+ }
56
+
57
+ const retainedHistory = reconciledRun.history.slice(0, changedHistoryIndex);
58
+ const restartedStep = changedEntry.stepId;
59
+ const stepHandoff = retainedHistory.at(-1)?.summary ?? '';
60
+ return {
61
+ changed: true,
62
+ restartedStep,
63
+ run: withRunUpdate(
64
+ reconciledRun,
65
+ {
66
+ workflowDigest: workflow.digest,
67
+ status: 'paused',
68
+ currentStepId: restartedStep,
69
+ currentStepDigest: workflow.stepDigests[restartedStep] ?? '',
70
+ history: retainedHistory,
71
+ visits: rebuildVisits(retainedHistory, restartedStep),
72
+ reviewedArtifact: retainedReviewedArtifact(
73
+ workflow,
74
+ reconciledRun,
75
+ retainedHistory,
76
+ ),
77
+ stepHandoff,
78
+ lastSummary: stepHandoff,
79
+ pendingGate: undefined,
80
+ pausedFrom: 'running',
81
+ failedStepId: undefined,
82
+ pauseReason: `Configuration changed; restarted step "${restartedStep}"`,
83
+ gateFeedback: '',
84
+ },
85
+ now,
86
+ ),
87
+ };
88
+ }
89
+
90
+ const currentDigest = workflow.stepDigests[run.currentStepId] ?? '';
91
+ const hasCurrentStepChanged =
92
+ currentDigest !== reconciledRun.currentStepDigest;
93
+ const restartChanges: Partial<WorkflowRun> = hasCurrentStepChanged
94
+ ? {
95
+ status: 'paused',
96
+ pendingGate: undefined,
97
+ pausedFrom: 'running',
98
+ failedStepId: undefined,
99
+ pauseReason: `Configuration changed; restarted step "${run.currentStepId}"`,
100
+ gateFeedback: '',
101
+ }
102
+ : {};
103
+ return {
104
+ changed: true,
105
+ ...(hasCurrentStepChanged ? { restartedStep: run.currentStepId } : {}),
106
+ run: withRunUpdate(
107
+ reconciledRun,
108
+ {
109
+ ...restartChanges,
110
+ workflowDigest: workflow.digest,
111
+ currentStepDigest: currentDigest,
112
+ },
113
+ now,
114
+ ),
115
+ };
116
+ };
@@ -0,0 +1,129 @@
1
+ import {
2
+ RUN_STATE_VERSION,
3
+ type GateResolution,
4
+ type PendingGate,
5
+ type StepHistoryEntry,
6
+ type WorkflowRun,
7
+ type WorkflowRunStatus,
8
+ } from './state-types.ts';
9
+
10
+ type UnknownRecord = Readonly<Record<string, unknown>>;
11
+
12
+ const isRecord = (value: unknown): value is UnknownRecord =>
13
+ value !== null && typeof value === 'object' && !Array.isArray(value);
14
+
15
+ const isStepHistoryEntry = (value: unknown): value is StepHistoryEntry =>
16
+ isRecord(value) &&
17
+ typeof value.stepId === 'string' &&
18
+ typeof value.stepDigest === 'string' &&
19
+ typeof value.outcome === 'string' &&
20
+ typeof value.summary === 'string' &&
21
+ typeof value.completedAt === 'number';
22
+
23
+ const isGateResolution = (value: unknown): value is GateResolution =>
24
+ isRecord(value) &&
25
+ typeof value.approved === 'boolean' &&
26
+ typeof value.feedback === 'string' &&
27
+ typeof value.resolvedAt === 'number';
28
+
29
+ const isPendingGate = (value: unknown): value is PendingGate =>
30
+ isRecord(value) &&
31
+ (value.provider === 'prompt' || value.provider === 'plannotator') &&
32
+ typeof value.requestId === 'string' &&
33
+ value.requestId.length > 0 &&
34
+ typeof value.stepId === 'string' &&
35
+ typeof value.artifact === 'string' &&
36
+ (value.summary === undefined || typeof value.summary === 'string') &&
37
+ typeof value.submittedOutcome === 'string' &&
38
+ typeof value.requestedAt === 'number' &&
39
+ (value.reviewId === undefined || typeof value.reviewId === 'string') &&
40
+ (value.resolution === undefined || isGateResolution(value.resolution));
41
+
42
+ const isVisitCounts = (value: unknown): value is Record<string, number> =>
43
+ isRecord(value) &&
44
+ Object.values(value).every(
45
+ (count) =>
46
+ typeof count === 'number' && Number.isInteger(count) && count >= 0,
47
+ );
48
+
49
+ const isOptionalString = (value: unknown): value is string | undefined =>
50
+ value === undefined || typeof value === 'string';
51
+
52
+ const isWorkflowRunStatus = (value: unknown): value is WorkflowRunStatus =>
53
+ value === 'running' ||
54
+ value === 'paused' ||
55
+ value === 'awaiting-gate' ||
56
+ value === 'completed' ||
57
+ value === 'aborted';
58
+
59
+ const hasValidPauseState = (run: UnknownRecord): boolean =>
60
+ run.status === 'paused'
61
+ ? run.pausedFrom === 'running' || run.pausedFrom === 'awaiting-gate'
62
+ : run.pausedFrom === undefined;
63
+
64
+ const hasValidFailureState = (run: UnknownRecord): boolean =>
65
+ run.failedStepId === undefined ||
66
+ (run.status === 'paused' && run.failedStepId === run.currentStepId);
67
+
68
+ const hasValidGateState = (
69
+ run: UnknownRecord,
70
+ pendingGate: PendingGate | undefined,
71
+ ): boolean =>
72
+ pendingGate === undefined
73
+ ? run.status !== 'awaiting-gate' && run.pausedFrom !== 'awaiting-gate'
74
+ : pendingGate.stepId === run.currentStepId &&
75
+ (run.status === 'awaiting-gate' ||
76
+ (run.status === 'paused' && run.pausedFrom === 'awaiting-gate'));
77
+
78
+ /**
79
+ * Validates an unknown value as a persisted workflow run.
80
+ *
81
+ * State invariants are checked in addition to field types so corrupt
82
+ * checkpoints cannot resume into an impossible gate, pause, or failure state.
83
+ *
84
+ * @param value - Persisted value to validate.
85
+ * @returns `true` when the value is a valid workflow run.
86
+ */
87
+ export const isWorkflowRun = (value: unknown): value is WorkflowRun => {
88
+ if (!isRecord(value)) return false;
89
+
90
+ const hasValidRequiredFields =
91
+ value.stateVersion === RUN_STATE_VERSION &&
92
+ typeof value.runId === 'string' &&
93
+ typeof value.workflowId === 'string' &&
94
+ typeof value.workflowDigest === 'string' &&
95
+ typeof value.input === 'string' &&
96
+ isWorkflowRunStatus(value.status) &&
97
+ typeof value.currentStepId === 'string' &&
98
+ typeof value.currentStepDigest === 'string' &&
99
+ Array.isArray(value.baselineTools) &&
100
+ value.baselineTools.every((tool) => typeof tool === 'string') &&
101
+ Array.isArray(value.history) &&
102
+ value.history.every(isStepHistoryEntry) &&
103
+ isVisitCounts(value.visits) &&
104
+ typeof value.startedAt === 'number' &&
105
+ typeof value.updatedAt === 'number' &&
106
+ typeof value.lastSummary === 'string' &&
107
+ typeof value.gateFeedback === 'string';
108
+ if (!hasValidRequiredFields) return false;
109
+
110
+ const hasValidOptionalFields =
111
+ isOptionalString(value.reviewedArtifact) &&
112
+ isOptionalString(value.reviewedFeedback) &&
113
+ isOptionalString(value.stepHandoff) &&
114
+ isOptionalString(value.pauseReason) &&
115
+ isOptionalString(value.failedStepId) &&
116
+ (value.pausedFrom === undefined ||
117
+ value.pausedFrom === 'running' ||
118
+ value.pausedFrom === 'awaiting-gate');
119
+ if (!hasValidOptionalFields) return false;
120
+
121
+ const pendingGate = value.pendingGate;
122
+ if (pendingGate !== undefined && !isPendingGate(pendingGate)) return false;
123
+
124
+ return (
125
+ hasValidPauseState(value) &&
126
+ hasValidFailureState(value) &&
127
+ hasValidGateState(value, pendingGate)
128
+ );
129
+ };
@@ -0,0 +1,66 @@
1
+ export const RUN_STATE_VERSION = 1 as const;
2
+
3
+ export type WorkflowRunStatus =
4
+ 'running' | 'paused' | 'awaiting-gate' | 'completed' | 'aborted';
5
+
6
+ export type StepHistoryEntry = {
7
+ readonly stepId: string;
8
+ readonly stepDigest: string;
9
+ readonly outcome: string;
10
+ readonly summary: string;
11
+ readonly completedAt: number;
12
+ };
13
+
14
+ export type GateResolution = {
15
+ readonly approved: boolean;
16
+ readonly feedback: string;
17
+ readonly resolvedAt: number;
18
+ };
19
+
20
+ export type PendingGate = {
21
+ readonly provider: 'prompt' | 'plannotator';
22
+ readonly requestId: string;
23
+ readonly stepId: string;
24
+ readonly artifact: string;
25
+ /** Legacy v1 field. New runs use the reviewed artifact as the handoff. */
26
+ summary?: string | undefined;
27
+ readonly submittedOutcome: string;
28
+ readonly requestedAt: number;
29
+ readonly reviewId?: string | undefined;
30
+ readonly resolution?: GateResolution | undefined;
31
+ };
32
+
33
+ export type WorkflowRun = {
34
+ readonly stateVersion: typeof RUN_STATE_VERSION;
35
+ readonly runId: string;
36
+ readonly workflowId: string;
37
+ readonly workflowDigest: string;
38
+ readonly input: string;
39
+ readonly status: WorkflowRunStatus;
40
+ readonly currentStepId: string;
41
+ readonly currentStepDigest: string;
42
+ readonly baselineTools: ReadonlyArray<string>;
43
+ readonly visits: Readonly<Record<string, number>>;
44
+ readonly history: ReadonlyArray<StepHistoryEntry>;
45
+ readonly startedAt: number;
46
+ readonly updatedAt: number;
47
+ /**
48
+ * Most recent human-approved gate artifact. This is the only provenance
49
+ * source from which reviewed Bash capabilities may be derived.
50
+ */
51
+ readonly reviewedArtifact?: string | undefined;
52
+ /** Final feedback paired with the immutable approved artifact. */
53
+ readonly reviewedFeedback?: string | undefined;
54
+ /**
55
+ * Input inherited from the previous completed step. Unlike `lastSummary`,
56
+ * this survives a paused attempt of the current step.
57
+ */
58
+ readonly stepHandoff?: string | undefined;
59
+ readonly lastSummary: string;
60
+ readonly gateFeedback: string;
61
+ readonly pauseReason?: string | undefined;
62
+ readonly pausedFrom?: 'running' | 'awaiting-gate' | undefined;
63
+ /** Current step when execution failed and was paused for a resumable retry. */
64
+ readonly failedStepId?: string | undefined;
65
+ readonly pendingGate?: PendingGate | undefined;
66
+ };
@@ -1,193 +1,10 @@
1
- import type { LoadedWorkflow } from '../config/types.ts';
2
-
3
- export const RUN_STATE_VERSION = 1 as const;
4
-
5
- export type WorkflowRunStatus =
6
- 'running' | 'paused' | 'awaiting-gate' | 'completed' | 'aborted';
7
-
8
- export interface StepHistoryEntry {
9
- stepId: string;
10
- stepDigest: string;
11
- outcome: string;
12
- summary: string;
13
- completedAt: number;
14
- }
15
-
16
- export interface GateResolution {
17
- approved: boolean;
18
- feedback: string;
19
- resolvedAt: number;
20
- }
21
-
22
- export interface PendingGate {
23
- provider: 'prompt' | 'plannotator';
24
- requestId: string;
25
- stepId: string;
26
- artifact: string;
27
- /** Legacy v1 field. New runs use the reviewed artifact as the handoff. */
28
- summary?: string | undefined;
29
- submittedOutcome: string;
30
- requestedAt: number;
31
- reviewId?: string | undefined;
32
- resolution?: GateResolution | undefined;
33
- }
34
-
35
- export interface WorkflowRun {
36
- stateVersion: typeof RUN_STATE_VERSION;
37
- runId: string;
38
- workflowId: string;
39
- workflowDigest: string;
40
- input: string;
41
- status: WorkflowRunStatus;
42
- currentStepId: string;
43
- currentStepDigest: string;
44
- baselineTools: string[];
45
- visits: Record<string, number>;
46
- history: StepHistoryEntry[];
47
- startedAt: number;
48
- updatedAt: number;
49
- /**
50
- * Most recent human-approved gate artifact. This is the only provenance
51
- * source from which reviewed Bash capabilities may be derived.
52
- */
53
- reviewedArtifact?: string | undefined;
54
- /**
55
- * Input inherited from the previous completed step. Unlike `lastSummary`,
56
- * this survives a paused attempt of the current step.
57
- */
58
- stepHandoff?: string | undefined;
59
- lastSummary: string;
60
- gateFeedback: string;
61
- pauseReason?: string | undefined;
62
- pausedFrom?: 'running' | 'awaiting-gate' | undefined;
63
- /** Current step when execution failed and was paused for a resumable retry. */
64
- failedStepId?: string | undefined;
65
- pendingGate?: PendingGate | undefined;
66
- }
67
-
68
- export function createRun(
69
- workflow: LoadedWorkflow,
70
- input: string,
71
- baselineTools: string[],
72
- runId: string,
73
- now: number,
74
- ): WorkflowRun {
75
- const start = workflow.definition.start;
76
- return {
77
- stateVersion: RUN_STATE_VERSION,
78
- runId,
79
- workflowId: workflow.definition.id,
80
- workflowDigest: workflow.digest,
81
- input,
82
- status: 'running',
83
- currentStepId: start,
84
- currentStepDigest: workflow.stepDigests[start] ?? '',
85
- baselineTools: [...new Set(baselineTools)],
86
- visits: { [start]: 1 },
87
- history: [],
88
- startedAt: now,
89
- updatedAt: now,
90
- reviewedArtifact: '',
91
- stepHandoff: '',
92
- lastSummary: '',
93
- gateFeedback: '',
94
- };
95
- }
96
-
97
- export function isWorkflowRun(value: unknown): value is WorkflowRun {
98
- if (value === null || typeof value !== 'object') return false;
99
- const run = value as Partial<WorkflowRun>;
100
- const historyIsValid =
101
- Array.isArray(run.history) &&
102
- run.history.every(
103
- (entry) =>
104
- entry !== null &&
105
- typeof entry === 'object' &&
106
- typeof entry.stepId === 'string' &&
107
- typeof entry.stepDigest === 'string' &&
108
- typeof entry.outcome === 'string' &&
109
- typeof entry.summary === 'string' &&
110
- typeof entry.completedAt === 'number',
111
- );
112
- const visitsAreValid =
113
- run.visits !== null &&
114
- typeof run.visits === 'object' &&
115
- !Array.isArray(run.visits) &&
116
- Object.values(run.visits).every(
117
- (count) => Number.isInteger(count) && count >= 0,
118
- );
119
- const gateIsValid =
120
- run.pendingGate === undefined ||
121
- (run.pendingGate !== null &&
122
- typeof run.pendingGate === 'object' &&
123
- (run.pendingGate.provider === 'prompt' ||
124
- run.pendingGate.provider === 'plannotator') &&
125
- typeof run.pendingGate.requestId === 'string' &&
126
- run.pendingGate.requestId.length > 0 &&
127
- typeof run.pendingGate.stepId === 'string' &&
128
- typeof run.pendingGate.artifact === 'string' &&
129
- (run.pendingGate.summary === undefined ||
130
- typeof run.pendingGate.summary === 'string') &&
131
- typeof run.pendingGate.submittedOutcome === 'string' &&
132
- typeof run.pendingGate.requestedAt === 'number' &&
133
- (run.pendingGate.reviewId === undefined ||
134
- typeof run.pendingGate.reviewId === 'string') &&
135
- (run.pendingGate.resolution === undefined ||
136
- (run.pendingGate.resolution !== null &&
137
- typeof run.pendingGate.resolution === 'object' &&
138
- typeof run.pendingGate.resolution.approved === 'boolean' &&
139
- typeof run.pendingGate.resolution.feedback === 'string' &&
140
- typeof run.pendingGate.resolution.resolvedAt === 'number')));
141
- const optionalsAreValid =
142
- (run.reviewedArtifact === undefined ||
143
- typeof run.reviewedArtifact === 'string') &&
144
- (run.stepHandoff === undefined || typeof run.stepHandoff === 'string') &&
145
- (run.pauseReason === undefined || typeof run.pauseReason === 'string') &&
146
- (run.failedStepId === undefined || typeof run.failedStepId === 'string') &&
147
- (run.pausedFrom === undefined ||
148
- run.pausedFrom === 'running' ||
149
- run.pausedFrom === 'awaiting-gate');
150
- const statusIsValid =
151
- run.status === 'running' ||
152
- run.status === 'paused' ||
153
- run.status === 'awaiting-gate' ||
154
- run.status === 'completed' ||
155
- run.status === 'aborted';
156
- const pauseStateIsValid =
157
- run.status === 'paused'
158
- ? run.pausedFrom === 'running' || run.pausedFrom === 'awaiting-gate'
159
- : run.pausedFrom === undefined;
160
- const failureStateIsValid =
161
- run.failedStepId === undefined ||
162
- (run.status === 'paused' && run.failedStepId === run.currentStepId);
163
- const gateStateIsValid = !gateIsValid
164
- ? false
165
- : run.pendingGate === undefined
166
- ? run.status !== 'awaiting-gate' && run.pausedFrom !== 'awaiting-gate'
167
- : run.pendingGate.stepId === run.currentStepId &&
168
- (run.status === 'awaiting-gate' ||
169
- (run.status === 'paused' && run.pausedFrom === 'awaiting-gate'));
170
- return (
171
- run.stateVersion === RUN_STATE_VERSION &&
172
- typeof run.runId === 'string' &&
173
- typeof run.workflowId === 'string' &&
174
- typeof run.workflowDigest === 'string' &&
175
- typeof run.input === 'string' &&
176
- typeof run.currentStepId === 'string' &&
177
- typeof run.currentStepDigest === 'string' &&
178
- Array.isArray(run.baselineTools) &&
179
- run.baselineTools.every((tool) => typeof tool === 'string') &&
180
- historyIsValid &&
181
- visitsAreValid &&
182
- gateIsValid &&
183
- optionalsAreValid &&
184
- statusIsValid &&
185
- pauseStateIsValid &&
186
- failureStateIsValid &&
187
- gateStateIsValid &&
188
- typeof run.startedAt === 'number' &&
189
- typeof run.updatedAt === 'number' &&
190
- typeof run.lastSummary === 'string' &&
191
- typeof run.gateFeedback === 'string'
192
- );
193
- }
1
+ export { createRun } from './create-run.ts';
2
+ export { isWorkflowRun } from './run-validation.ts';
3
+ export { RUN_STATE_VERSION } from './state-types.ts';
4
+ export type {
5
+ GateResolution,
6
+ PendingGate,
7
+ StepHistoryEntry,
8
+ WorkflowRun,
9
+ WorkflowRunStatus,
10
+ } from './state-types.ts';
@@ -0,0 +1,28 @@
1
+ import type { LoadedWorkflow, WorkflowStep } from '../config/types.ts';
2
+ import type { WorkflowRun } from './state-types.ts';
3
+
4
+ /**
5
+ * Finds the workflow definition for a run's current step.
6
+ *
7
+ * @param workflow - Loaded workflow.
8
+ * @param run - Current workflow state.
9
+ * @returns The current step, or `undefined` if configuration drift removed it.
10
+ */
11
+ export const currentStep = (
12
+ workflow: LoadedWorkflow,
13
+ run: WorkflowRun,
14
+ ): WorkflowStep | undefined => workflow.definition.steps[run.currentStepId];
15
+
16
+ /**
17
+ * Produces a workflow state with changes and a refreshed update timestamp.
18
+ *
19
+ * @param run - Existing workflow state.
20
+ * @param changes - Fields to replace.
21
+ * @param now - Update timestamp.
22
+ * @returns A new workflow state.
23
+ */
24
+ export const withRunUpdate = (
25
+ run: WorkflowRun,
26
+ changes: Partial<WorkflowRun>,
27
+ now: number,
28
+ ): WorkflowRun => ({ ...run, ...changes, updatedAt: now });
@@ -0,0 +1,8 @@
1
+ import type { WorkflowRun } from './state-types.ts';
2
+
3
+ export type ReconcileResult = {
4
+ readonly run?: WorkflowRun;
5
+ readonly changed: boolean;
6
+ readonly restartedStep?: string;
7
+ readonly error?: string;
8
+ };