@auxiora/workflows 1.0.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +10 -4
- package/src/approval.ts +0 -135
- package/src/autonomous-executor.ts +0 -312
- package/src/engine.ts +0 -296
- package/src/index.ts +0 -23
- package/src/reminder.ts +0 -129
- package/src/types.ts +0 -73
- package/tests/autonomous-executor.test.ts +0 -369
- package/tests/workflows.test.ts +0 -290
- package/tsconfig.json +0 -13
- package/tsconfig.tsbuildinfo +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@auxiora/workflows",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Cross-person workflow orchestration with approvals, reminders, and escalation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -12,13 +12,19 @@
|
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@auxiora/
|
|
16
|
-
"@auxiora/
|
|
17
|
-
"@auxiora/audit": "1.
|
|
15
|
+
"@auxiora/core": "1.3.0",
|
|
16
|
+
"@auxiora/logger": "1.3.0",
|
|
17
|
+
"@auxiora/audit": "1.3.0"
|
|
18
18
|
},
|
|
19
19
|
"engines": {
|
|
20
20
|
"node": ">=22.0.0"
|
|
21
21
|
},
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist/"
|
|
27
|
+
],
|
|
22
28
|
"scripts": {
|
|
23
29
|
"build": "tsc",
|
|
24
30
|
"clean": "rm -rf dist",
|
package/src/approval.ts
DELETED
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import * as fs from 'node:fs/promises';
|
|
2
|
-
import * as path from 'node:path';
|
|
3
|
-
import * as crypto from 'node:crypto';
|
|
4
|
-
import { getLogger } from '@auxiora/logger';
|
|
5
|
-
import { audit } from '@auxiora/audit';
|
|
6
|
-
import { getAuxioraDir } from '@auxiora/core';
|
|
7
|
-
|
|
8
|
-
const logger = getLogger('workflows:approval');
|
|
9
|
-
|
|
10
|
-
export type ApprovalStatus = 'pending' | 'approved' | 'rejected' | 'expired';
|
|
11
|
-
|
|
12
|
-
export interface ApprovalRequest {
|
|
13
|
-
id: string;
|
|
14
|
-
workflowId: string;
|
|
15
|
-
stepId: string;
|
|
16
|
-
requestedBy: string;
|
|
17
|
-
approverIds: string[];
|
|
18
|
-
description: string;
|
|
19
|
-
status: ApprovalStatus;
|
|
20
|
-
decidedBy?: string;
|
|
21
|
-
decisionReason?: string;
|
|
22
|
-
createdAt: number;
|
|
23
|
-
decidedAt?: number;
|
|
24
|
-
expiresAt?: number;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export class ApprovalManager {
|
|
28
|
-
private filePath: string;
|
|
29
|
-
|
|
30
|
-
constructor(options?: { dir?: string }) {
|
|
31
|
-
const dir = options?.dir ?? path.join(getAuxioraDir(), 'workflows');
|
|
32
|
-
this.filePath = path.join(dir, 'approvals.json');
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async requestApproval(
|
|
36
|
-
workflowId: string,
|
|
37
|
-
stepId: string,
|
|
38
|
-
requestedBy: string,
|
|
39
|
-
approverIds: string[],
|
|
40
|
-
description: string,
|
|
41
|
-
expiresInMs?: number,
|
|
42
|
-
): Promise<ApprovalRequest> {
|
|
43
|
-
const approvals = await this.readFile();
|
|
44
|
-
const now = Date.now();
|
|
45
|
-
|
|
46
|
-
const request: ApprovalRequest = {
|
|
47
|
-
id: `appr-${crypto.randomUUID().slice(0, 8)}`,
|
|
48
|
-
workflowId,
|
|
49
|
-
stepId,
|
|
50
|
-
requestedBy,
|
|
51
|
-
approverIds,
|
|
52
|
-
description,
|
|
53
|
-
status: 'pending',
|
|
54
|
-
createdAt: now,
|
|
55
|
-
...(expiresInMs ? { expiresAt: now + expiresInMs } : {}),
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
approvals.push(request);
|
|
59
|
-
await this.writeFile(approvals);
|
|
60
|
-
void audit('workflow.approval_requested', { id: request.id, workflowId, stepId });
|
|
61
|
-
logger.debug('Approval requested', { id: request.id });
|
|
62
|
-
return request;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
async approve(approvalId: string, decidedBy: string, reason?: string): Promise<ApprovalRequest | undefined> {
|
|
66
|
-
return this.decide(approvalId, 'approved', decidedBy, reason);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
async reject(approvalId: string, decidedBy: string, reason?: string): Promise<ApprovalRequest | undefined> {
|
|
70
|
-
return this.decide(approvalId, 'rejected', decidedBy, reason);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
async getPending(userId?: string): Promise<ApprovalRequest[]> {
|
|
74
|
-
const approvals = await this.readFile();
|
|
75
|
-
const now = Date.now();
|
|
76
|
-
|
|
77
|
-
return approvals.filter(a => {
|
|
78
|
-
if (a.status !== 'pending') return false;
|
|
79
|
-
if (a.expiresAt && a.expiresAt <= now) return false;
|
|
80
|
-
if (userId && !a.approverIds.includes(userId)) return false;
|
|
81
|
-
return true;
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
async getByWorkflow(workflowId: string): Promise<ApprovalRequest[]> {
|
|
86
|
-
const approvals = await this.readFile();
|
|
87
|
-
return approvals.filter(a => a.workflowId === workflowId);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async get(approvalId: string): Promise<ApprovalRequest | undefined> {
|
|
91
|
-
const approvals = await this.readFile();
|
|
92
|
-
return approvals.find(a => a.id === approvalId);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
private async decide(
|
|
96
|
-
approvalId: string,
|
|
97
|
-
status: 'approved' | 'rejected',
|
|
98
|
-
decidedBy: string,
|
|
99
|
-
reason?: string,
|
|
100
|
-
): Promise<ApprovalRequest | undefined> {
|
|
101
|
-
const approvals = await this.readFile();
|
|
102
|
-
const approval = approvals.find(a => a.id === approvalId);
|
|
103
|
-
if (!approval || approval.status !== 'pending') return undefined;
|
|
104
|
-
|
|
105
|
-
if (!approval.approverIds.includes(decidedBy)) return undefined;
|
|
106
|
-
|
|
107
|
-
approval.status = status;
|
|
108
|
-
approval.decidedBy = decidedBy;
|
|
109
|
-
approval.decisionReason = reason;
|
|
110
|
-
approval.decidedAt = Date.now();
|
|
111
|
-
|
|
112
|
-
await this.writeFile(approvals);
|
|
113
|
-
void audit(`workflow.${status}`, { id: approvalId, decidedBy });
|
|
114
|
-
logger.debug(`Approval ${status}`, { id: approvalId, decidedBy });
|
|
115
|
-
return approval;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
private async readFile(): Promise<ApprovalRequest[]> {
|
|
119
|
-
try {
|
|
120
|
-
const content = await fs.readFile(this.filePath, 'utf-8');
|
|
121
|
-
return JSON.parse(content) as ApprovalRequest[];
|
|
122
|
-
} catch (error) {
|
|
123
|
-
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
124
|
-
return [];
|
|
125
|
-
}
|
|
126
|
-
throw error;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
private async writeFile(approvals: ApprovalRequest[]): Promise<void> {
|
|
131
|
-
const dir = path.dirname(this.filePath);
|
|
132
|
-
await fs.mkdir(dir, { recursive: true });
|
|
133
|
-
await fs.writeFile(this.filePath, JSON.stringify(approvals, null, 2), 'utf-8');
|
|
134
|
-
}
|
|
135
|
-
}
|
|
@@ -1,312 +0,0 @@
|
|
|
1
|
-
import { getLogger } from '@auxiora/logger';
|
|
2
|
-
import { audit } from '@auxiora/audit';
|
|
3
|
-
import type { WorkflowEngine } from './engine.js';
|
|
4
|
-
import type { HumanWorkflow, WorkflowStep } from './types.js';
|
|
5
|
-
|
|
6
|
-
const logger = getLogger('workflows:autonomous');
|
|
7
|
-
|
|
8
|
-
/** Result of executing a tool. */
|
|
9
|
-
export interface ToolResult {
|
|
10
|
-
success: boolean;
|
|
11
|
-
output?: string;
|
|
12
|
-
error?: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** Trust gate check result. */
|
|
16
|
-
export interface GateCheckResult {
|
|
17
|
-
allowed: boolean;
|
|
18
|
-
message: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/** Audit entry for recording autonomous actions. */
|
|
22
|
-
export interface AuditEntry {
|
|
23
|
-
trustLevel: number;
|
|
24
|
-
domain: string;
|
|
25
|
-
intent: string;
|
|
26
|
-
plan: string;
|
|
27
|
-
executed: boolean;
|
|
28
|
-
outcome: 'success' | 'failure' | 'pending' | 'rolled_back';
|
|
29
|
-
reasoning: string;
|
|
30
|
-
rollbackAvailable: boolean;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** Result of a single tick. */
|
|
34
|
-
export interface TickResult {
|
|
35
|
-
workflowsProcessed: number;
|
|
36
|
-
stepsExecuted: number;
|
|
37
|
-
stepsSkipped: number;
|
|
38
|
-
stepsFailed: number;
|
|
39
|
-
workflowsCompleted: number;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** Dependencies for the AutonomousExecutor (injected). */
|
|
43
|
-
export interface AutonomousExecutorDeps {
|
|
44
|
-
workflowEngine: WorkflowEngine;
|
|
45
|
-
trustGate: {
|
|
46
|
-
gate(domain: string, action: string, requiredLevel: number): GateCheckResult;
|
|
47
|
-
};
|
|
48
|
-
trustEngine: {
|
|
49
|
-
recordOutcome(domain: string, success: boolean): void;
|
|
50
|
-
};
|
|
51
|
-
auditTrail: {
|
|
52
|
-
record(entry: AuditEntry): Promise<{ id: string }>;
|
|
53
|
-
markRolledBack(id: string): Promise<boolean>;
|
|
54
|
-
};
|
|
55
|
-
executeTool: (name: string, params: Record<string, unknown>) => Promise<ToolResult>;
|
|
56
|
-
onStepCompleted?: (workflowId: string, stepId: string, result: string) => void;
|
|
57
|
-
onStepFailed?: (workflowId: string, stepId: string, error: string) => void;
|
|
58
|
-
onWorkflowCompleted?: (workflowId: string) => void;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Executes autonomous workflow steps in the background.
|
|
63
|
-
*
|
|
64
|
-
* Runs on a timer, checking active workflows for steps that have an
|
|
65
|
-
* `action` field and auto-executing them through the trust-gated
|
|
66
|
-
* tool pipeline with full audit trail recording.
|
|
67
|
-
*/
|
|
68
|
-
export class AutonomousExecutor {
|
|
69
|
-
private readonly deps: AutonomousExecutorDeps;
|
|
70
|
-
private timer: ReturnType<typeof setInterval> | undefined;
|
|
71
|
-
private running = false;
|
|
72
|
-
private ticking = false;
|
|
73
|
-
|
|
74
|
-
constructor(deps: AutonomousExecutorDeps) {
|
|
75
|
-
this.deps = deps;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/** Start the background execution loop. */
|
|
79
|
-
start(intervalMs = 30_000): void {
|
|
80
|
-
if (this.running) return;
|
|
81
|
-
this.running = true;
|
|
82
|
-
this.timer = setInterval(() => {
|
|
83
|
-
void this.tick();
|
|
84
|
-
}, intervalMs);
|
|
85
|
-
logger.debug('Autonomous executor started', { intervalMs });
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/** Stop the background execution loop. */
|
|
89
|
-
stop(): void {
|
|
90
|
-
if (this.timer) {
|
|
91
|
-
clearInterval(this.timer);
|
|
92
|
-
this.timer = undefined;
|
|
93
|
-
}
|
|
94
|
-
this.running = false;
|
|
95
|
-
logger.debug('Autonomous executor stopped');
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/** Whether the executor is running. */
|
|
99
|
-
isRunning(): boolean {
|
|
100
|
-
return this.running;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Advance all active autonomous workflows one tick.
|
|
105
|
-
* Processes each active step with an action through the trust-gated pipeline.
|
|
106
|
-
*/
|
|
107
|
-
async tick(): Promise<TickResult> {
|
|
108
|
-
// Prevent concurrent ticks
|
|
109
|
-
if (this.ticking) {
|
|
110
|
-
return { workflowsProcessed: 0, stepsExecuted: 0, stepsSkipped: 0, stepsFailed: 0, workflowsCompleted: 0 };
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
this.ticking = true;
|
|
114
|
-
const result: TickResult = {
|
|
115
|
-
workflowsProcessed: 0,
|
|
116
|
-
stepsExecuted: 0,
|
|
117
|
-
stepsSkipped: 0,
|
|
118
|
-
stepsFailed: 0,
|
|
119
|
-
workflowsCompleted: 0,
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
try {
|
|
123
|
-
const activeWorkflows = await this.deps.workflowEngine.listActive();
|
|
124
|
-
const autonomousWorkflows = activeWorkflows.filter(
|
|
125
|
-
(w) => w.autonomous && w.status === 'active',
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
for (const workflow of autonomousWorkflows) {
|
|
129
|
-
result.workflowsProcessed++;
|
|
130
|
-
await this.processWorkflow(workflow, result);
|
|
131
|
-
}
|
|
132
|
-
} catch (error) {
|
|
133
|
-
logger.error('Tick failed', error instanceof Error ? error : new Error(String(error)));
|
|
134
|
-
} finally {
|
|
135
|
-
this.ticking = false;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return result;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
private async processWorkflow(workflow: HumanWorkflow, result: TickResult): Promise<void> {
|
|
142
|
-
const readySteps = workflow.steps.filter(
|
|
143
|
-
(s) => s.status === 'active' && s.action,
|
|
144
|
-
);
|
|
145
|
-
|
|
146
|
-
for (const step of readySteps) {
|
|
147
|
-
await this.executeStep(workflow, step, result);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Check if workflow completed after executing steps
|
|
151
|
-
const updated = await this.deps.workflowEngine.getWorkflow(workflow.id);
|
|
152
|
-
if (updated?.status === 'completed') {
|
|
153
|
-
result.workflowsCompleted++;
|
|
154
|
-
this.deps.onWorkflowCompleted?.(workflow.id);
|
|
155
|
-
void audit('workflow.autonomous_completed', { id: workflow.id, name: workflow.name });
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
private async executeStep(
|
|
160
|
-
workflow: HumanWorkflow,
|
|
161
|
-
step: WorkflowStep,
|
|
162
|
-
result: TickResult,
|
|
163
|
-
): Promise<void> {
|
|
164
|
-
const action = step.action!;
|
|
165
|
-
|
|
166
|
-
// 1. Trust gate check
|
|
167
|
-
const gateResult = this.deps.trustGate.gate(
|
|
168
|
-
action.trustDomain,
|
|
169
|
-
action.tool,
|
|
170
|
-
action.trustRequired,
|
|
171
|
-
);
|
|
172
|
-
|
|
173
|
-
if (!gateResult.allowed) {
|
|
174
|
-
result.stepsSkipped++;
|
|
175
|
-
logger.debug('Step trust-denied', {
|
|
176
|
-
workflowId: workflow.id,
|
|
177
|
-
stepId: step.id,
|
|
178
|
-
tool: action.tool,
|
|
179
|
-
reason: gateResult.message,
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
// Record as event but don't fail — trust level may increase later
|
|
183
|
-
await this.deps.workflowEngine.addEvent(workflow.id, 'step_trust_denied', {
|
|
184
|
-
stepId: step.id,
|
|
185
|
-
details: gateResult.message,
|
|
186
|
-
});
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// 2. Record audit entry (pending)
|
|
191
|
-
const auditEntry = await this.deps.auditTrail.record({
|
|
192
|
-
trustLevel: action.trustRequired,
|
|
193
|
-
domain: action.trustDomain,
|
|
194
|
-
intent: `Execute ${action.tool} for workflow step "${step.name}"`,
|
|
195
|
-
plan: `tool=${action.tool} params=${JSON.stringify(action.params)}`,
|
|
196
|
-
executed: true,
|
|
197
|
-
outcome: 'pending',
|
|
198
|
-
reasoning: `Autonomous workflow "${workflow.name}" step "${step.name}"`,
|
|
199
|
-
rollbackAvailable: !!action.rollbackTool,
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
// 3. Execute tool
|
|
203
|
-
try {
|
|
204
|
-
const toolResult = await this.deps.executeTool(action.tool, action.params);
|
|
205
|
-
|
|
206
|
-
if (toolResult.success) {
|
|
207
|
-
// 4a. Success — record outcome and mark complete
|
|
208
|
-
this.deps.trustEngine.recordOutcome(action.trustDomain, true);
|
|
209
|
-
await this.deps.auditTrail.record({
|
|
210
|
-
...this.auditBase(action, workflow, step),
|
|
211
|
-
outcome: 'success',
|
|
212
|
-
executed: true,
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
const resultText = toolResult.output ?? 'Success';
|
|
216
|
-
await this.deps.workflowEngine.completeStep(
|
|
217
|
-
workflow.id,
|
|
218
|
-
step.id,
|
|
219
|
-
'autonomous-executor',
|
|
220
|
-
resultText,
|
|
221
|
-
);
|
|
222
|
-
result.stepsExecuted++;
|
|
223
|
-
this.deps.onStepCompleted?.(workflow.id, step.id, resultText);
|
|
224
|
-
|
|
225
|
-
void audit('workflow.step_auto_executed', {
|
|
226
|
-
workflowId: workflow.id,
|
|
227
|
-
stepId: step.id,
|
|
228
|
-
tool: action.tool,
|
|
229
|
-
success: true,
|
|
230
|
-
});
|
|
231
|
-
} else {
|
|
232
|
-
// 4b. Tool returned failure
|
|
233
|
-
await this.handleStepFailure(
|
|
234
|
-
workflow,
|
|
235
|
-
step,
|
|
236
|
-
action,
|
|
237
|
-
auditEntry.id,
|
|
238
|
-
toolResult.error ?? 'Tool execution failed',
|
|
239
|
-
result,
|
|
240
|
-
);
|
|
241
|
-
}
|
|
242
|
-
} catch (error) {
|
|
243
|
-
// 4c. Tool threw an exception
|
|
244
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
245
|
-
await this.handleStepFailure(
|
|
246
|
-
workflow,
|
|
247
|
-
step,
|
|
248
|
-
action,
|
|
249
|
-
auditEntry.id,
|
|
250
|
-
errorMessage,
|
|
251
|
-
result,
|
|
252
|
-
);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
private async handleStepFailure(
|
|
257
|
-
workflow: HumanWorkflow,
|
|
258
|
-
step: WorkflowStep,
|
|
259
|
-
action: NonNullable<WorkflowStep['action']>,
|
|
260
|
-
auditEntryId: string,
|
|
261
|
-
errorMessage: string,
|
|
262
|
-
result: TickResult,
|
|
263
|
-
): Promise<void> {
|
|
264
|
-
// Record trust failure
|
|
265
|
-
this.deps.trustEngine.recordOutcome(action.trustDomain, false);
|
|
266
|
-
|
|
267
|
-
// Attempt rollback if available
|
|
268
|
-
if (action.rollbackTool) {
|
|
269
|
-
try {
|
|
270
|
-
await this.deps.executeTool(action.rollbackTool, action.rollbackParams ?? {});
|
|
271
|
-
await this.deps.auditTrail.markRolledBack(auditEntryId);
|
|
272
|
-
logger.debug('Rollback succeeded', {
|
|
273
|
-
workflowId: workflow.id,
|
|
274
|
-
stepId: step.id,
|
|
275
|
-
rollbackTool: action.rollbackTool,
|
|
276
|
-
});
|
|
277
|
-
} catch (rollbackError) {
|
|
278
|
-
logger.error('Rollback failed', rollbackError instanceof Error ? rollbackError : new Error(String(rollbackError)));
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// Mark step as failed
|
|
283
|
-
await this.deps.workflowEngine.failStep(workflow.id, step.id, errorMessage);
|
|
284
|
-
result.stepsFailed++;
|
|
285
|
-
this.deps.onStepFailed?.(workflow.id, step.id, errorMessage);
|
|
286
|
-
|
|
287
|
-
void audit('workflow.step_auto_executed', {
|
|
288
|
-
workflowId: workflow.id,
|
|
289
|
-
stepId: step.id,
|
|
290
|
-
tool: action.tool,
|
|
291
|
-
success: false,
|
|
292
|
-
error: errorMessage,
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
private auditBase(
|
|
297
|
-
action: NonNullable<WorkflowStep['action']>,
|
|
298
|
-
workflow: HumanWorkflow,
|
|
299
|
-
step: WorkflowStep,
|
|
300
|
-
): AuditEntry {
|
|
301
|
-
return {
|
|
302
|
-
trustLevel: action.trustRequired,
|
|
303
|
-
domain: action.trustDomain,
|
|
304
|
-
intent: `Execute ${action.tool} for workflow step "${step.name}"`,
|
|
305
|
-
plan: `tool=${action.tool} params=${JSON.stringify(action.params)}`,
|
|
306
|
-
executed: true,
|
|
307
|
-
outcome: 'pending',
|
|
308
|
-
reasoning: `Autonomous workflow "${workflow.name}" step "${step.name}"`,
|
|
309
|
-
rollbackAvailable: !!action.rollbackTool,
|
|
310
|
-
};
|
|
311
|
-
}
|
|
312
|
-
}
|