@crewx/workflow 0.3.22-rc.21 → 0.3.22-rc.210

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 (48) hide show
  1. package/SKILL.md +4 -4
  2. package/dist/cli.d.ts +1 -1
  3. package/dist/cli.js +21 -1012
  4. package/dist/src/card-state.d.ts +20 -0
  5. package/dist/src/card-state.js +1 -0
  6. package/dist/src/engine.d.ts +10 -3
  7. package/dist/src/engine.js +1 -348
  8. package/dist/src/failure.d.ts +41 -0
  9. package/dist/src/failure.js +1 -0
  10. package/dist/src/file-lock.d.ts +1 -1
  11. package/dist/src/file-lock.js +1 -97
  12. package/dist/src/list-view.d.ts +22 -0
  13. package/dist/src/list-view.js +3 -0
  14. package/dist/src/mermaid.d.ts +1 -1
  15. package/dist/src/mermaid.js +2 -133
  16. package/dist/src/parent-task-sync.d.ts +139 -0
  17. package/dist/src/parent-task-sync.js +1 -0
  18. package/dist/src/retry-policy.d.ts +38 -0
  19. package/dist/src/retry-policy.js +1 -0
  20. package/dist/src/run-manager.d.ts +37 -3
  21. package/dist/src/run-manager.js +4 -1228
  22. package/dist/src/types.d.ts +37 -4
  23. package/dist/src/types.js +1 -3
  24. package/dist/src/utils/output-format.d.ts +1 -1
  25. package/dist/src/utils/output-format.js +12 -157
  26. package/dist/src/utils/shell-security.d.ts +1 -1
  27. package/dist/src/utils/shell-security.js +1 -138
  28. package/dist/src/workflow-persistence.d.ts +48 -0
  29. package/dist/src/workflow-persistence.js +1 -0
  30. package/dist/workflow-schema.json +25 -4
  31. package/package.json +23 -4
  32. package/workflow-schema.json +25 -4
  33. package/dist/cli.d.ts.map +0 -1
  34. package/dist/cli.js.map +0 -1
  35. package/dist/src/engine.d.ts.map +0 -1
  36. package/dist/src/engine.js.map +0 -1
  37. package/dist/src/file-lock.d.ts.map +0 -1
  38. package/dist/src/file-lock.js.map +0 -1
  39. package/dist/src/mermaid.d.ts.map +0 -1
  40. package/dist/src/mermaid.js.map +0 -1
  41. package/dist/src/run-manager.d.ts.map +0 -1
  42. package/dist/src/run-manager.js.map +0 -1
  43. package/dist/src/types.d.ts.map +0 -1
  44. package/dist/src/types.js.map +0 -1
  45. package/dist/src/utils/output-format.d.ts.map +0 -1
  46. package/dist/src/utils/output-format.js.map +0 -1
  47. package/dist/src/utils/shell-security.d.ts.map +0 -1
  48. package/dist/src/utils/shell-security.js.map +0 -1
@@ -1,133 +1,2 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.safeNodeId = safeNodeId;
4
- exports.escapeMermaidLabel = escapeMermaidLabel;
5
- exports.toMermaid = toMermaid;
6
- const MERMAID_RESERVED = new Set([
7
- 'end', 'subgraph', 'direction', 'click', 'style', 'classDef', 'class', 'linkStyle',
8
- ]);
9
- function safeNodeId(nodeId) {
10
- return MERMAID_RESERVED.has(nodeId) ? `node_${nodeId}` : nodeId;
11
- }
12
- function escapeMermaidLabel(s) {
13
- return s
14
- .replace(/"/g, '#quot;')
15
- .replace(/</g, '#lt;')
16
- .replace(/>/g, '#gt;')
17
- .replace(/\{/g, '#123;')
18
- .replace(/\}/g, '#125;')
19
- .replace(/`/g, '#96;')
20
- .slice(0, 50);
21
- }
22
- function buildLabel(nodeId, node) {
23
- if (node.type === 'end')
24
- return 'END';
25
- if (node.type === 'error')
26
- return 'ERROR';
27
- let subtitle = '';
28
- if (node.agent) {
29
- subtitle = `@${escapeMermaidLabel(node.agent)}`;
30
- }
31
- else if (node.type === 'skill_task' && node.skill) {
32
- subtitle = `skill:${escapeMermaidLabel(node.skill)}`;
33
- }
34
- else if (node.type === 'shell_task' && node.command && node.command[0]) {
35
- subtitle = `$ ${escapeMermaidLabel(node.command[0])}`;
36
- }
37
- const desc = node.description ?? node.input?.split('\n')[0]?.trim() ?? '';
38
- const escapedDesc = escapeMermaidLabel(desc);
39
- const idPart = subtitle
40
- ? `${escapeMermaidLabel(nodeId)}(${subtitle})`
41
- : escapeMermaidLabel(nodeId);
42
- if (!escapedDesc || escapedDesc === escapeMermaidLabel(nodeId)) {
43
- return `*${idPart}*`;
44
- }
45
- return `*${idPart}*\n${escapedDesc}`;
46
- }
47
- function wrapShape(nodeId, label, type) {
48
- const safe = safeNodeId(nodeId);
49
- if (type === 'end' || type === 'error') {
50
- return `${safe}(("${label}"))`;
51
- }
52
- const q = `"\`${label}\`"`;
53
- switch (type) {
54
- case 'branch':
55
- return `${safe}{${q}}`;
56
- case 'expression':
57
- return `${safe}{{${q}}}`;
58
- case 'parallel':
59
- return `${safe}[/${q}/]`;
60
- case 'join':
61
- return `${safe}[\\${q}\\]`;
62
- case 'skill_task':
63
- return `${safe}[[${q}]]`;
64
- case 'shell_task':
65
- return `${safe}[(${q})]`;
66
- default:
67
- return `${safe}[${q}]`;
68
- }
69
- }
70
- const NODE_STYLES = {
71
- approval: 'fill:#e0f2fe,stroke:#93c5fd,color:#1e3a5f',
72
- expression: 'fill:#fef3c7,stroke:#f59e0b,color:#78350f',
73
- error: 'fill:#fee2e2,stroke:#f87171,color:#7f1d1d',
74
- };
75
- function toMermaid(spec) {
76
- const lines = ['flowchart TD'];
77
- const nodes = spec.nodes;
78
- for (const [nodeId, node] of Object.entries(nodes)) {
79
- const label = buildLabel(nodeId, node);
80
- lines.push(` ${wrapShape(nodeId, label, node.type)}`);
81
- }
82
- lines.push('');
83
- for (const [nodeId, node] of Object.entries(nodes)) {
84
- const src = safeNodeId(nodeId);
85
- if (node.next) {
86
- lines.push(` ${src} --> ${safeNodeId(node.next)}`);
87
- }
88
- if (node.type === 'branch' && node.branches && !Array.isArray(node.branches)) {
89
- for (const [condLabel, target] of Object.entries(node.branches)) {
90
- const edgeLabel = escapeMermaidLabel(condLabel === 'true' ? '통과' : condLabel === 'false' ? '반려' : condLabel);
91
- lines.push(` ${src} -->|${edgeLabel}| ${safeNodeId(target)}`);
92
- }
93
- }
94
- if (node.type === 'parallel' && Array.isArray(node.branches)) {
95
- for (const branchId of node.branches) {
96
- lines.push(` ${src} --> ${safeNodeId(branchId)}`);
97
- }
98
- if (node.join) {
99
- for (const branchId of node.branches) {
100
- const branchNode = nodes[branchId];
101
- if (!branchNode?.next) {
102
- lines.push(` ${safeNodeId(branchId)} --> ${safeNodeId(node.join)}`);
103
- }
104
- }
105
- }
106
- }
107
- if (node.type === 'approval') {
108
- if (node.on_approve) {
109
- lines.push(` ${src} -->|${escapeMermaidLabel('승인')}| ${safeNodeId(node.on_approve)}`);
110
- }
111
- if (node.on_reject) {
112
- lines.push(` ${src} -->|${escapeMermaidLabel('반려')}| ${safeNodeId(node.on_reject)}`);
113
- }
114
- }
115
- }
116
- const nodesByType = new Map();
117
- for (const [nodeId, node] of Object.entries(nodes)) {
118
- const list = nodesByType.get(node.type) ?? [];
119
- list.push(safeNodeId(nodeId));
120
- nodesByType.set(node.type, list);
121
- }
122
- lines.push('');
123
- for (const [type, style] of Object.entries(NODE_STYLES)) {
124
- lines.push(` classDef ${type} ${style}`);
125
- }
126
- for (const [type, ids] of nodesByType) {
127
- if (NODE_STYLES[type]) {
128
- lines.push(` class ${ids.join(',')} ${type}`);
129
- }
130
- }
131
- return lines.join('\n');
132
- }
133
- //# sourceMappingURL=mermaid.js.map
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.safeNodeId=o;exports.escapeMermaidLabel=a;exports.toMermaid=d;const u=new Set(["end","subgraph","direction","click","style","classDef","class","linkStyle"]);function o(c){return u.has(c)?`node_${c}`:c}function a(c){return c.replace(/"/g,"#quot;").replace(/</g,"#lt;").replace(/>/g,"#gt;").replace(/\{/g,"#123;").replace(/\}/g,"#125;").replace(/`/g,"#96;").slice(0,50)}function h(c,r){if(r.type==="end")return"END";if(r.type==="error")return"ERROR";let t="";if(r.agent){t=`@${a(r.agent)}`}else if(r.type==="skill_task"&&r.skill){t=`skill:${a(r.skill)}`}else if(r.type==="shell_task"&&r.command&&r.command[0]){t=`$ ${a(r.command[0])}`}const n=r.description??r.input?.split("\n")[0]?.trim()??"";const s=a(n);const e=t?`${a(c)}(${t})`:a(c);if(!s||s===a(c)){return`*${e}*`}return`*${e}*
2
+ ${s}`}function b(c,r,t){const n=o(c);if(t==="end"||t==="error"){return`${n}(("${r}"))`}const s=`"\`${r}\`"`;switch(t){case"branch":return`${n}{${s}}`;case"expression":return`${n}{{${s}}}`;case"parallel":return`${n}[/${s}/]`;case"join":return`${n}[\\${s}\\]`;case"skill_task":return`${n}[[${s}]]`;case"shell_task":return`${n}[(${s})]`;default:return`${n}[${s}]`}}const p={approval:"fill:#e0f2fe,stroke:#93c5fd,color:#1e3a5f",expression:"fill:#fef3c7,stroke:#f59e0b,color:#78350f",error:"fill:#fee2e2,stroke:#f87171,color:#7f1d1d"};function d(c){const r=["flowchart TD"];const t=c.nodes;for(const[s,e]of Object.entries(t)){const i=h(s,e);r.push(` ${b(s,i,e.type)}`)}r.push("");for(const[s,e]of Object.entries(t)){const i=o(s);if(e.next){r.push(` ${i} --> ${o(e.next)}`)}if(e.type==="branch"&&e.branches&&!Array.isArray(e.branches)){for(const[l,f]of Object.entries(e.branches)){const $=a(l==="true"?"Approve":l==="false"?"Reject":l);r.push(` ${i} -->|${$}| ${o(f)}`)}}if(e.type==="parallel"&&Array.isArray(e.branches)){for(const l of e.branches){r.push(` ${i} --> ${o(l)}`)}if(e.join){for(const l of e.branches){const f=t[l];if(!f?.next){r.push(` ${o(l)} --> ${o(e.join)}`)}}}}if(e.type==="approval"){if(e.on_approve){r.push(` ${i} -->|${a("Approve")}| ${o(e.on_approve)}`)}if(e.on_reject){r.push(` ${i} -->|${a("Reject")}| ${o(e.on_reject)}`)}}}const n=new Map;for(const[s,e]of Object.entries(t)){const i=n.get(e.type)??[];i.push(o(s));n.set(e.type,i)}r.push("");for(const[s,e]of Object.entries(p)){r.push(` classDef ${s} ${e}`)}for(const[s,e]of n){if(p[s]){r.push(` class ${e.join(",")} ${s}`)}}return r.join("\n")}
@@ -0,0 +1,139 @@
1
+ import type { RunExecution } from './types.js';
2
+ export type WorkflowParentTaskStatus = 'pending' | 'running' | 'success' | 'failed';
3
+ export type WorkflowParentTaskKind = 'workflow_card' | 'workflow_ui_instance';
4
+ export interface WorkflowParentTaskLookup {
5
+ workspaceId: string;
6
+ workflowRunId: string;
7
+ kind: WorkflowParentTaskKind;
8
+ }
9
+ export interface WorkflowParentLifecycle {
10
+ status: WorkflowParentTaskStatus;
11
+ error: string | null;
12
+ completedAt: string | null;
13
+ durationMs: number | null;
14
+ }
15
+ export interface WorkflowParentLifecycleSnapshot {
16
+ status: string;
17
+ error: string | null;
18
+ completedAt: string | null;
19
+ durationMs: number | null;
20
+ }
21
+ export interface WorkflowParentLifecycleChange {
22
+ id: string;
23
+ kind: WorkflowParentTaskKind;
24
+ before: WorkflowParentLifecycleSnapshot;
25
+ after: WorkflowParentLifecycle;
26
+ reason: string;
27
+ }
28
+ export interface WorkflowParentTaskRow {
29
+ id: string;
30
+ agent_id: string;
31
+ thread_id: string | null;
32
+ workspace_id: string | null;
33
+ platform: string | null;
34
+ status: string;
35
+ error: string | null;
36
+ completed_at: string | null;
37
+ duration_ms: number | null;
38
+ started_at: string;
39
+ metadata: string | null;
40
+ }
41
+ export interface WorkflowParentTaskRepository {
42
+ findRunningWorkflowParentTasks(): WorkflowParentTaskRow[];
43
+ findWorkflowParentTask(opts: WorkflowParentTaskLookup): WorkflowParentTaskRow | undefined;
44
+ updateTaskLifecycle(opts: {
45
+ id: string;
46
+ status: WorkflowParentTaskStatus;
47
+ error: string | null;
48
+ completedAt: string | null;
49
+ durationMs: number | null;
50
+ workspaceId: string;
51
+ }): void;
52
+ }
53
+ export interface WorkflowParentPersistTaskRepository {
54
+ findWorkflowParentTask(opts: WorkflowParentTaskLookup): WorkflowParentTaskRow | undefined;
55
+ updateTaskLifecycle: WorkflowParentTaskRepository['updateTaskLifecycle'];
56
+ }
57
+ export interface SyncWorkflowParentTaskLifecycleOptions {
58
+ taskRepo: Pick<WorkflowParentPersistTaskRepository, 'updateTaskLifecycle'>;
59
+ row: WorkflowParentTaskRow;
60
+ run: RunExecution;
61
+ workspaceId: string;
62
+ kind: WorkflowParentTaskKind;
63
+ snapshotUpdatedAt?: string | null;
64
+ }
65
+ export interface WorkflowParentPersistSinkOptions {
66
+ taskRepo: WorkflowParentPersistTaskRepository;
67
+ workspaceId: string;
68
+ parentKinds?: readonly WorkflowParentTaskKind[];
69
+ }
70
+ export interface WorkflowParentPersistResult {
71
+ changedParentTaskIds: string[];
72
+ linkedAuditInstanceIds: string[];
73
+ }
74
+ export interface DeriveWorkflowParentLifecycleChangeOptions {
75
+ row: WorkflowParentTaskRow;
76
+ run: RunExecution;
77
+ workspaceId: string;
78
+ kind: WorkflowParentTaskKind;
79
+ snapshotUpdatedAt?: string | null;
80
+ }
81
+ export type WorkflowParentLifecycleDecisionStatus = 'ineligible' | 'unchanged' | 'stale-suppressed' | 'changed';
82
+ export interface WorkflowParentLifecycleDecision {
83
+ status: WorkflowParentLifecycleDecisionStatus;
84
+ change: WorkflowParentLifecycleChange | null;
85
+ linkedAuditInstanceId: string | null;
86
+ }
87
+ export interface PreviewWorkflowParentRepairOptions {
88
+ taskRepo: Pick<WorkflowParentPersistTaskRepository, 'findWorkflowParentTask'>;
89
+ run: RunExecution;
90
+ workspaceId: string;
91
+ parentKinds?: readonly WorkflowParentTaskKind[];
92
+ }
93
+ export interface WorkflowParentRepairPreview {
94
+ runId: string;
95
+ runStatus: NonNullable<RunExecution['status']>;
96
+ changes: WorkflowParentLifecycleChange[];
97
+ linkedAuditInstanceIds: string[];
98
+ }
99
+ export interface ReconcileWorkflowParentTasksOptions {
100
+ taskRepo: WorkflowParentTaskRepository;
101
+ resolveWorkspacePath: (workspaceId: string) => string | undefined;
102
+ loadRun: (workspacePath: string, workflowRunId: string) => RunExecution | null;
103
+ orphanGraceMs?: number;
104
+ nowMs?: number;
105
+ }
106
+ export interface WorkflowParentWorkspace {
107
+ workspaceId: string;
108
+ workspacePath: string;
109
+ }
110
+ export type WorkflowParentReconciliationSkipReason = 'no-marker' | 'no-workspace-id' | 'no-workspace-path' | 'run-missing' | 'not-stale' | 'ineligible' | 'suppressed';
111
+ export type WorkflowParentReconciliationSkipCounts = Partial<Record<WorkflowParentReconciliationSkipReason, number>>;
112
+ export interface ReconcileWorkflowParentRunFilesOptions {
113
+ taskRepo: WorkflowParentPersistTaskRepository;
114
+ workspaces: readonly WorkflowParentWorkspace[];
115
+ listRunIds: (workspacePath: string) => readonly string[];
116
+ readRun: (workspacePath: string, workflowRunId: string) => RunExecution | null;
117
+ listWorkflowParentTasks?: (workspaceId: string) => readonly WorkflowParentTaskRow[];
118
+ parentKinds?: readonly WorkflowParentTaskKind[];
119
+ }
120
+ export interface WorkflowParentReconciliationResult {
121
+ aligned: number;
122
+ orphaned: number;
123
+ skipped: number;
124
+ scanned?: number;
125
+ skippedBy?: WorkflowParentReconciliationSkipCounts;
126
+ runFilesScanned?: number;
127
+ }
128
+ export declare const WORKFLOW_PARENT_ORPHAN_GRACE_MS: number;
129
+ export declare const WORKFLOW_PARENT_ORPHAN_ERROR = "Workflow run file missing after grace period (orphaned workflow task)";
130
+ export declare const WORKFLOW_PARENT_TASK_KINDS: readonly WorkflowParentTaskKind[];
131
+ export declare function deriveWorkflowParentLifecycle(run: RunExecution): WorkflowParentLifecycle | null;
132
+ export declare function deriveWorkflowParentLifecycleDecision(options: DeriveWorkflowParentLifecycleChangeOptions): WorkflowParentLifecycleDecision;
133
+ export declare function deriveWorkflowParentLifecycleChange(options: DeriveWorkflowParentLifecycleChangeOptions): WorkflowParentLifecycleChange | null;
134
+ export declare function syncWorkflowParentTaskLifecycle(options: SyncWorkflowParentTaskLifecycleOptions): boolean;
135
+ export declare function previewWorkflowParentRepair(options: PreviewWorkflowParentRepairOptions): WorkflowParentRepairPreview;
136
+ export declare function createWorkflowParentPersistSink(options: WorkflowParentPersistSinkOptions): (run: RunExecution) => WorkflowParentPersistResult;
137
+ export declare function reconcileWorkflowParentTasks(options: ReconcileWorkflowParentTasksOptions): WorkflowParentReconciliationResult;
138
+ export declare function reconcileWorkflowParentRunFiles(options: ReconcileWorkflowParentRunFilesOptions): WorkflowParentReconciliationResult;
139
+
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.WORKFLOW_PARENT_TASK_KINDS=exports.WORKFLOW_PARENT_ORPHAN_ERROR=exports.WORKFLOW_PARENT_ORPHAN_GRACE_MS=void 0;exports.deriveWorkflowParentLifecycle=p;exports.deriveWorkflowParentLifecycleDecision=k;exports.deriveWorkflowParentLifecycleChange=T;exports.syncWorkflowParentTaskLifecycle=K;exports.previewWorkflowParentRepair=M;exports.createWorkflowParentPersistSink=L;exports.reconcileWorkflowParentTasks=E;exports.reconcileWorkflowParentRunFiles=v;exports.WORKFLOW_PARENT_ORPHAN_GRACE_MS=10*60*1e3;exports.WORKFLOW_PARENT_ORPHAN_ERROR="Workflow run file missing after grace period (orphaned workflow task)";exports.WORKFLOW_PARENT_TASK_KINDS=["workflow_card","workflow_ui_instance"];function w(e){let n;for(const t of e.audit??[]){const r=Date.parse(t.ended_at);if(!Number.isFinite(r))continue;if(!n||r>=n.timestamp){n={value:t.ended_at,timestamp:r}}}return n?.value??null}function p(e){if(e.status==="completed"||e.status==="failed"||e.status==="cancelled"){const n=w(e)??e.updated_at;const t=Date.parse(e.created_at);const r=Date.parse(n);const a=Number.isFinite(t)&&Number.isFinite(r)?Math.max(0,r-t):null;if(e.status==="completed"){return{status:"success",error:null,completedAt:n,durationMs:a}}return{status:"failed",error:e.error??(e.status==="cancelled"?"Workflow run cancelled":"Workflow run failed"),completedAt:n,durationMs:a}}if(e.status==="running"||e.status==="retrying"){return{status:"running",error:null,completedAt:null,durationMs:null}}if(e.status==="pending"||e.status===void 0){return{status:"pending",error:null,completedAt:null,durationMs:null}}return null}function _(e){if(!e)return null;try{const n=JSON.parse(e);if(!n||typeof n!=="object"||Array.isArray(n))return null;const t=n;return(t.kind==="workflow_card"||t.kind==="workflow_ui_instance")&&typeof t.workflowRunId==="string"?{kind:t.kind,workflowRunId:t.workflowRunId,...typeof t.uiInstanceId==="string"&&t.uiInstanceId.trim()?{uiInstanceId:t.uiInstanceId.trim()}:{}}:null}catch{return null}}function h(e,n){return e.status===n.status&&e.error===n.error&&e.completed_at===n.completedAt&&e.duration_ms===n.durationMs}function O(e){return e==="success"||e==="completed"||e==="failed"||e==="cancelled"||e==="error"}function N(e,n,t){if(!O(e.status))return false;if(!e.completed_at)return false;const r=Date.parse(e.completed_at);const a=Date.parse(t??n.updated_at);if(!Number.isFinite(r)||!Number.isFinite(a))return true;return r>=a}function k(e){const n=()=>({status:"ineligible",change:null,linkedAuditInstanceId:null});if(e.row.platform!=="workflow"||e.row.workspace_id!==e.workspaceId){return n()}const t=_(e.row.metadata);if(!t||t.kind!==e.kind||t.workflowRunId!==e.run.id){return n()}const r=p(e.run);if(!r)return n();const a=t.kind==="workflow_ui_instance"?t.uiInstanceId??null:null;if(h(e.row,r)){return{status:"unchanged",change:null,linkedAuditInstanceId:a}}if((r.status==="running"||r.status==="pending")&&N(e.row,e.run,e.snapshotUpdatedAt)){return{status:"stale-suppressed",change:null,linkedAuditInstanceId:null}}const i=e.run.status??"pending";const o=i==="pending"?"Persisted run is pending (or legacy statusless); no execution outcome is inferred.":`Persisted run status "${i}" is projected to the parent lifecycle.`;return{status:"changed",linkedAuditInstanceId:a,change:{id:e.row.id,kind:e.kind,before:{status:e.row.status,error:e.row.error,completedAt:e.row.completed_at,durationMs:e.row.duration_ms},after:r,reason:o}}}function T(e){return k(e).change}function R(e,n){e.taskRepo.updateTaskLifecycle({id:e.row.id,status:n.after.status,error:n.after.error,completedAt:n.after.completedAt,durationMs:n.after.durationMs,workspaceId:e.workspaceId})}function K(e){const n=k(e).change;if(!n)return false;R(e,n);return true}function A(e){const n=[];for(const t of e.parentKinds){const r=e.taskRepo.findWorkflowParentTask({workspaceId:e.workspaceId,workflowRunId:e.run.id,kind:t});if(!r)continue;n.push({kind:t,row:r,decision:k({row:r,run:e.run,workspaceId:e.workspaceId,kind:t,snapshotUpdatedAt:w(e.run)})})}return n}function S(e,n){const t=new Map;for(const r of n){if(r.platform!=="workflow"||r.workspace_id!==e)continue;const a=_(r.metadata);if(!a)continue;const i=t.get(a.kind)??new Map;if(!i.has(a.workflowRunId)){i.set(a.workflowRunId,r)}t.set(a.kind,i)}return{findWorkflowParentTask:r=>{if(r.workspaceId!==e)return void 0;return t.get(r.kind)?.get(r.workflowRunId)}}}function M(e){const n=e.parentKinds??exports.WORKFLOW_PARENT_TASK_KINDS;const t=[];const r=new Set;for(const{decision:a}of A({taskRepo:e.taskRepo,run:e.run,workspaceId:e.workspaceId,parentKinds:n})){if(a.linkedAuditInstanceId){r.add(a.linkedAuditInstanceId)}if(a.change)t.push(a.change)}return{runId:e.run.id,runStatus:e.run.status??"pending",changes:t,linkedAuditInstanceIds:[...r]}}function L(e){const n=e.parentKinds??exports.WORKFLOW_PARENT_TASK_KINDS;return t=>{const r=[];const a=new Set;if(!p(t)){return{changedParentTaskIds:r,linkedAuditInstanceIds:[]}}for(const{kind:i,row:o,decision:s}of A({taskRepo:e.taskRepo,run:t,workspaceId:e.workspaceId,parentKinds:n})){if(s.linkedAuditInstanceId){a.add(s.linkedAuditInstanceId)}if(s.change){R({taskRepo:e.taskRepo,row:o,run:t,workspaceId:e.workspaceId,kind:i,snapshotUpdatedAt:w(t)},s.change);r.push(o.id)}}return{changedParentTaskIds:r,linkedAuditInstanceIds:[...a]}}}function F(e,n,t){const r=Date.parse(e);return Number.isFinite(r)&&n-r>=t}function m(){return{}}function u(e,n){e.skipped+=1;const t=e.skippedBy??(e.skippedBy={});t[n]=(t[n]??0)+1}function E(e){const n=e.orphanGraceMs??exports.WORKFLOW_PARENT_ORPHAN_GRACE_MS;const t=e.nowMs??Date.now();const r={aligned:0,orphaned:0,scanned:0,skipped:0,skippedBy:m()};const a=s=>{u(r,s)};const i=new Map;const o=s=>{if(!i.has(s)){i.set(s,e.resolveWorkspacePath(s))}return i.get(s)};for(const s of e.taskRepo.findRunningWorkflowParentTasks()){r.scanned=(r.scanned??0)+1;const l=_(s.metadata);if(s.platform!=="workflow"||!l){a("no-marker");continue}const c=s.workspace_id;if(!c){a("no-workspace-id");continue}const d=o(c);if(!d){a("no-workspace-path");continue}const W=l.workflowRunId;let f=null;try{f=e.loadRun(d,W)}catch{}if(!f){if(s.status!=="running"||!F(s.started_at,t,n)){a(s.status==="running"?"not-stale":"run-missing");continue}const y=s.completed_at??new Date(t).toISOString();const g=s.duration_ms??Math.max(0,t-Date.parse(s.started_at));e.taskRepo.updateTaskLifecycle({id:s.id,status:"failed",error:exports.WORKFLOW_PARENT_ORPHAN_ERROR,completedAt:y,durationMs:Number.isFinite(g)?g:null,workspaceId:c});r.orphaned+=1;continue}const P=p(f);if(!P){a("not-stale");continue}if(h(s,P))continue;const I=k({row:s,run:f,workspaceId:c,kind:l.kind});if(I.status==="stale-suppressed"){a("not-stale");continue}if(I.change){R({taskRepo:e.taskRepo,row:s,run:f,workspaceId:c,kind:l.kind},I.change);r.aligned+=1}}return r}function v(e){const n={aligned:0,orphaned:0,skipped:0,skippedBy:m(),runFilesScanned:0};for(const t of e.workspaces){if(!t.workspaceId){u(n,"no-workspace-id");continue}if(!t.workspacePath){u(n,"no-workspace-path");continue}const r=e.listWorkflowParentTasks?S(t.workspaceId,e.listWorkflowParentTasks(t.workspaceId)):e.taskRepo;let a=[];try{a=e.listRunIds(t.workspacePath)}catch{u(n,"no-workspace-path");continue}for(const i of a){n.runFilesScanned=(n.runFilesScanned??0)+1;let o=null;try{o=e.readRun(t.workspacePath,i)}catch{}if(!o||typeof o.id!=="string"){u(n,"run-missing");continue}const s=e.parentKinds??exports.WORKFLOW_PARENT_TASK_KINDS;for(const{kind:l,row:c,decision:d}of A({taskRepo:r,run:o,workspaceId:t.workspaceId,parentKinds:s})){if(d.status==="stale-suppressed"){u(n,"suppressed");continue}if(d.status==="ineligible"){u(n,"ineligible");continue}if(!d.change)continue;R({taskRepo:e.taskRepo,row:c,run:o,workspaceId:t.workspaceId,kind:l,snapshotUpdatedAt:w(o)},d.change);n.aligned+=1}}}return n}
@@ -0,0 +1,38 @@
1
+ import type { RetryPolicy } from './types';
2
+ export declare const RETRY_BASE_DELAY_MS = 1000;
3
+ export declare const RETRY_DEFAULT_MAX_DELAY_MS = 30000;
4
+ export declare const RETRY_HARD_MAX_DELAY_MS = 60000;
5
+ export declare const RETRY_MAX_ATTEMPTS_CEILING = 10;
6
+ export declare const DEFAULT_RETRYABLE_CODES: readonly ["NODE_EXIT_NONZERO", "NODE_TIMEOUT", "SPAWN_FAILED"];
7
+ export declare const RETRYABLE_NODE_TYPES: readonly ["agent_task", "skill_task", "shell_task"];
8
+ export type RetryPolicyParseResult = {
9
+ ok: true;
10
+ policy: RetryPolicy | undefined;
11
+ } | {
12
+ ok: false;
13
+ path: string;
14
+ reason: string;
15
+ };
16
+ export declare function parseRetryPolicy(node: unknown): RetryPolicyParseResult;
17
+ export interface RetryDecisionRun {
18
+ status?: 'pending' | 'running' | 'retrying' | 'completed' | 'failed' | 'cancelled';
19
+ last_failure?: {
20
+ failedNode?: unknown;
21
+ attempt?: unknown;
22
+ causeCode?: unknown;
23
+ };
24
+ node_attempts?: Record<string, unknown>;
25
+ }
26
+ export interface RetryDecisionInput {
27
+ policy?: RetryPolicy;
28
+ run: RetryDecisionRun;
29
+ nodeId: string;
30
+ }
31
+ export interface RetryDecision {
32
+ retry: boolean;
33
+ reason: string;
34
+ attempt?: number;
35
+ }
36
+ export declare function decideRetry(input: RetryDecisionInput): RetryDecision;
37
+ export declare function computeRetryDelayMs(policy: RetryPolicy, attempt: number): number;
38
+
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.RETRYABLE_NODE_TYPES=exports.DEFAULT_RETRYABLE_CODES=exports.RETRY_MAX_ATTEMPTS_CEILING=exports.RETRY_HARD_MAX_DELAY_MS=exports.RETRY_DEFAULT_MAX_DELAY_MS=exports.RETRY_BASE_DELAY_MS=void 0;exports.parseRetryPolicy=f;exports.decideRetry=A;exports.computeRetryDelayMs=T;const d=require("./types");exports.RETRY_BASE_DELAY_MS=1e3;exports.RETRY_DEFAULT_MAX_DELAY_MS=3e4;exports.RETRY_HARD_MAX_DELAY_MS=6e4;exports.RETRY_MAX_ATTEMPTS_CEILING=10;exports.DEFAULT_RETRYABLE_CODES=["NODE_EXIT_NONZERO","NODE_TIMEOUT","SPAWN_FAILED"];exports.RETRYABLE_NODE_TYPES=["agent_task","skill_task","shell_task"];const R=new Set(["CLI_UNRESOLVABLE","SPAWN_FAILED","NODE_TIMEOUT","NODE_EXIT_NONZERO","OUTPUT_INVALID","RATE_LIMITED","CONFIG_INVALID","INTERRUPTED","CANCELLED","UNKNOWN"]);function l(t){return typeof t==="object"&&t!==null&&!Array.isArray(t)}function r(t,e){return{ok:false,path:t,reason:e}}function f(t){if(!l(t))return r("retry","node must be an object");const e=t.retry;if(e===void 0)return{ok:true,policy:void 0};if(!l(e))return r("retry","retry must be an object");const _=t.type;if(typeof _!=="string"||!exports.RETRYABLE_NODE_TYPES.includes(_)){return r("retry","retry is only supported on agent_task, skill_task, and shell_task nodes")}const n=e.max;if(typeof n!=="number"||!Number.isFinite(n)){return r("retry.max","retry.max must be a finite number")}if(!Number.isInteger(n))return r("retry.max","retry.max must be an integer");if(n<0||n>exports.RETRY_MAX_ATTEMPTS_CEILING){return r("retry.max",`retry.max must be between 0 and ${exports.RETRY_MAX_ATTEMPTS_CEILING}`)}const a=e.delay;if(a!=="fixed"&&a!=="exponential"){return r("retry.delay",'retry.delay must be "fixed" or "exponential"')}let i=exports.RETRY_DEFAULT_MAX_DELAY_MS;if(e.max_delay!==void 0){if(typeof e.max_delay!=="number"||!Number.isFinite(e.max_delay)){return r("retry.max_delay","retry.max_delay must be a finite number")}if(!Number.isInteger(e.max_delay)||e.max_delay<0){return r("retry.max_delay","retry.max_delay must be a non-negative integer")}i=e.max_delay}let u=[...exports.DEFAULT_RETRYABLE_CODES];if(e.on!==void 0){if(!Array.isArray(e.on))return r("retry.on","retry.on must be an array");u=[];for(const[E,o]of e.on.entries()){if(typeof o!=="string"||!R.has(o)){return r(`retry.on[${E}]`,`retry.on[${E}] is not a supported workflow failure code`)}const y=o;if(d.NEVER_RETRYABLE.includes(y)){return r(`retry.on[${E}]`,`${y} cannot be retried`)}u.push(y)}}return{ok:true,policy:{max:n,delay:a,max_delay:i,on:u}}}function s(t){return{retry:false,reason:t}}function A(t){const{policy:e,run:_,nodeId:n}=t;if(!e)return s("retry policy is not configured");if(_.status!=="failed")return s("run is not failed");const a=_.last_failure;if(!a||a.failedNode!==n)return s("latest failure is not for this node");const i=a.attempt;const u=_.node_attempts?.[n];if(typeof i!=="number"||!Number.isInteger(i)||i<=0||u!==i){return s("latest attempt is stale")}const E=a.causeCode;if(typeof E!=="string")return s("latest failure has no supported cause");if(d.NEVER_RETRYABLE.includes(E)){return s("latest failure is never retryable")}if(!e.on.includes(E)){return s("latest failure is outside the retry policy")}if(i>=e.max+1)return s("retry budget is exhausted");return{retry:true,reason:"retry policy matched",attempt:i}}function T(t,e){const _=Number.isInteger(e)&&e>0?e:1;const n=t.delay==="exponential"?exports.RETRY_BASE_DELAY_MS*2**(_-1):exports.RETRY_BASE_DELAY_MS;const a=typeof t.max_delay==="number"&&Number.isFinite(t.max_delay)?Math.max(0,t.max_delay):exports.RETRY_DEFAULT_MAX_DELAY_MS;return Math.min(n,a,exports.RETRY_HARD_MAX_DELAY_MS)}
@@ -1,23 +1,47 @@
1
- import type { RunExecution, AutoRunResult } from './types';
1
+ import type { RunExecution, WorkflowSpec, AutoRunResult } from './types';
2
+ export { FileLock } from './file-lock';
2
3
  export declare const SKILL_TASK_DEFAULT_TIMEOUT = 600000;
4
+ export declare const WORKFLOW_CANCEL_GRACE_MS = 2000;
5
+ export declare const WORKFLOW_RUN_INTERRUPTED_ERROR = "Workflow run interrupted: runner process exited before completion";
6
+ export declare const WORKFLOW_RUN_LEGACY_RECOVERY_GRACE_MS: number;
7
+ export type RunPersistedSink = (run: RunExecution) => void;
8
+ export interface RunManagerDependencies {
9
+ sleep?: (ms: number) => Promise<void>;
10
+ onRunPersisted?: RunPersistedSink;
11
+ }
3
12
  export declare function parseSetArgs(args: string[]): Record<string, unknown>;
4
13
  export declare function readStdin(): Promise<string>;
5
14
  export declare function interpolateTemplate(template: string, state: Record<string, unknown>): string;
6
15
  export declare function safeEvaluate(expr: string, state: Record<string, unknown>): unknown;
7
16
  export declare function safeEvaluateBranchCondition(expr: string, state: Record<string, unknown>): unknown;
8
17
  export declare function validateSafeBranchCondition(expr: string): void;
18
+ export declare function normalizeRunStatus(run: RunExecution, nowMs?: number): RunExecution;
19
+ export declare function isRetryableFailure(run: RunExecution, spec: WorkflowSpec): boolean;
9
20
  export declare class RunManager {
10
21
  private runsDir;
11
22
  private readonly resolvedDir;
12
23
  private readonly projectRoot;
13
- constructor(runsDir?: string, projectRoot?: string);
24
+ private readonly sleep;
25
+ private readonly onRunPersisted?;
26
+ constructor(runsDir?: string, projectRoot?: string, deps?: RunManagerDependencies);
14
27
  ensureRunsDir(): void;
15
28
  private validateExecId;
29
+ private isRunCancelled;
30
+ private processCancellation;
31
+ private readRunRaw;
32
+ readRun(execId: string): RunExecution | null;
33
+ private isLegacyStatuslessStale;
34
+ withRunLock<T>(execId: string, callback: (run: RunExecution) => T): T;
35
+ private hasStaleRunner;
36
+ private recoverStaleRunner;
16
37
  loadRun(execId: string): RunExecution | null;
17
38
  saveRun(run: RunExecution): void;
18
39
  atomicUpdate(execId: string, updater: (run: RunExecution) => void): RunExecution;
19
40
  atomicUpdateAsync(execId: string, updater: (run: RunExecution) => void): Promise<RunExecution>;
41
+ private reserveNodeAttempt;
42
+ private clearNodeAttempt;
20
43
  private loadWorkflowYaml;
44
+ getNodeType(workflowFile: string, workflowId: string, nodeId: string): string | null;
21
45
  start(yamlPath: string, workflowId?: string, overrides?: Record<string, unknown>): RunExecution;
22
46
  getState(execId: string): Record<string, unknown>;
23
47
  setState(execId: string, updates: Record<string, unknown>): void;
@@ -26,11 +50,21 @@ export declare class RunManager {
26
50
  dryRun?: boolean;
27
51
  trigger?: 'manual' | 'auto';
28
52
  }): Promise<RunExecution>;
53
+ private claimAutoRunLease;
54
+ private releaseAutoRunLease;
55
+ private failAutoConfiguration;
56
+ private reserveAutoRetry;
57
+ private waitForRetry;
58
+ private runNodeWithRetry;
29
59
  runAuto(execId: string): Promise<AutoRunResult>;
30
60
  cancelRun(execId: string, reason?: string): RunExecution;
31
61
  reset(execId: string): RunExecution;
62
+ retry(execId: string, options?: {
63
+ autoRun?: boolean;
64
+ }): RunExecution;
65
+ listRunIds(): string[];
32
66
  listRuns(opts?: {
33
67
  workflowId?: string;
34
68
  }): RunExecution[];
35
69
  }
36
- //# sourceMappingURL=run-manager.d.ts.map
70
+