@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@auxiora/workflows",
3
- "version": "1.0.0",
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/logger": "1.0.0",
16
- "@auxiora/core": "1.0.0",
17
- "@auxiora/audit": "1.0.0"
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
- }