stagent 0.1.9 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +144 -62
- package/package.json +1 -2
- package/public/readme/cost-usage-list.png +0 -0
- package/public/readme/dashboard-bulk-select.png +0 -0
- package/public/readme/dashboard-card-edit.png +0 -0
- package/public/readme/dashboard-create-form-ai-applied.png +0 -0
- package/public/readme/dashboard-create-form-ai-assist.png +0 -0
- package/public/readme/dashboard-create-form-empty.png +0 -0
- package/public/readme/dashboard-create-form-filled.png +0 -0
- package/public/readme/dashboard-filtered.png +0 -0
- package/public/readme/dashboard-list.png +0 -0
- package/public/readme/dashboard-sorted.png +0 -0
- package/public/readme/dashboard-workflow-confirm.png +0 -0
- package/public/readme/documents-grid.png +0 -0
- package/public/readme/documents-list.png +0 -0
- package/public/readme/home-below-fold.png +0 -0
- package/public/readme/home-list.png +0 -0
- package/public/readme/inbox-list.png +0 -0
- package/public/readme/monitor-list.png +0 -0
- package/public/readme/profiles-list.png +0 -0
- package/public/readme/projects-detail.png +0 -0
- package/public/readme/projects-list.png +0 -0
- package/public/readme/schedules-list.png +0 -0
- package/public/readme/settings-list.png +0 -0
- package/public/readme/workflows-list.png +0 -0
- package/src/app/api/profiles/route.ts +0 -1
- package/src/app/api/workflows/from-assist/route.ts +143 -0
- package/src/app/dashboard/page.tsx +24 -2
- package/src/app/globals.css +0 -5
- package/src/app/tasks/page.tsx +5 -0
- package/src/app/workflows/from-assist/page.tsx +35 -0
- package/src/components/profiles/profile-detail-view.tsx +1 -16
- package/src/components/profiles/profile-form-view.tsx +0 -22
- package/src/components/projects/project-card.tsx +47 -35
- package/src/components/tasks/ai-assist-panel.tsx +31 -10
- package/src/components/tasks/task-card.tsx +16 -1
- package/src/components/tasks/task-create-panel.tsx +39 -0
- package/src/components/workflows/workflow-confirmation-view.tsx +447 -0
- package/src/lib/agents/__tests__/claude-agent.test.ts +7 -2
- package/src/lib/agents/__tests__/learned-context.test.ts +500 -0
- package/src/lib/agents/__tests__/pattern-extractor.test.ts +243 -0
- package/src/lib/agents/__tests__/sweep.test.ts +202 -0
- package/src/lib/agents/claude-agent.ts +104 -78
- package/src/lib/agents/learned-context.ts +5 -13
- package/src/lib/agents/pattern-extractor.ts +15 -64
- package/src/lib/agents/profiles/__tests__/suggest.test.ts +67 -0
- package/src/lib/agents/profiles/builtins/code-reviewer/profile.yaml +0 -1
- package/src/lib/agents/profiles/builtins/data-analyst/profile.yaml +0 -1
- package/src/lib/agents/profiles/builtins/devops-engineer/profile.yaml +0 -1
- package/src/lib/agents/profiles/builtins/document-writer/profile.yaml +0 -1
- package/src/lib/agents/profiles/builtins/general/profile.yaml +0 -1
- package/src/lib/agents/profiles/builtins/health-fitness-coach/profile.yaml +0 -1
- package/src/lib/agents/profiles/builtins/learning-coach/profile.yaml +0 -1
- package/src/lib/agents/profiles/builtins/project-manager/profile.yaml +0 -1
- package/src/lib/agents/profiles/builtins/researcher/profile.yaml +0 -1
- package/src/lib/agents/profiles/builtins/shopping-assistant/profile.yaml +0 -1
- package/src/lib/agents/profiles/builtins/sweep/profile.yaml +0 -1
- package/src/lib/agents/profiles/builtins/technical-writer/profile.yaml +0 -1
- package/src/lib/agents/profiles/builtins/travel-planner/profile.yaml +0 -1
- package/src/lib/agents/profiles/builtins/wealth-manager/profile.yaml +0 -1
- package/src/lib/agents/profiles/registry.ts +0 -1
- package/src/lib/agents/profiles/suggest.ts +36 -0
- package/src/lib/agents/profiles/types.ts +0 -1
- package/src/lib/agents/runtime/catalog.ts +1 -1
- package/src/lib/agents/runtime/claude.ts +102 -6
- package/src/lib/agents/runtime/task-assist-types.ts +12 -2
- package/src/lib/constants/task-status.ts +6 -0
- package/src/lib/data/__tests__/clear.test.ts +42 -0
- package/src/lib/data/clear.ts +3 -0
- package/src/lib/data/seed-data/profiles.ts +0 -3
- package/src/lib/notifications/permissions.ts +6 -2
- package/src/lib/usage/__tests__/ledger.test.ts +29 -5
- package/src/lib/usage/ledger.ts +3 -1
- package/src/lib/usage/pricing.ts +61 -7
- package/src/lib/validators/__tests__/profile.test.ts +0 -15
- package/src/lib/validators/profile.ts +0 -1
- package/src/lib/workflows/__tests__/assist-builder.test.ts +255 -0
- package/src/lib/workflows/__tests__/engine.test.ts +2 -0
- package/src/lib/workflows/assist-builder.ts +248 -0
- package/src/lib/workflows/assist-session.ts +78 -0
- package/src/lib/workflows/engine.ts +47 -1
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import type { WorkflowDefinition, WorkflowStep, WorkflowPattern } from "./types";
|
|
2
|
+
import type { TaskAssistResponse, TaskAssistBreakdownStep } from "@/lib/agents/runtime/task-assist-types";
|
|
3
|
+
import { validateWorkflowDefinition } from "./definition-validation";
|
|
4
|
+
|
|
5
|
+
interface AssistBuilderInput {
|
|
6
|
+
mainTask: {
|
|
7
|
+
title: string;
|
|
8
|
+
description: string;
|
|
9
|
+
agentProfile?: string;
|
|
10
|
+
};
|
|
11
|
+
assistResponse: TaskAssistResponse;
|
|
12
|
+
overrides?: {
|
|
13
|
+
pattern?: WorkflowPattern;
|
|
14
|
+
steps?: Partial<WorkflowStep>[];
|
|
15
|
+
loopConfig?: { maxIterations?: number; timeBudgetMs?: number };
|
|
16
|
+
swarmConfig?: { workerConcurrencyLimit?: number };
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function stepId(index: number): string {
|
|
21
|
+
return `step_${index + 1}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function buildStep(
|
|
25
|
+
index: number,
|
|
26
|
+
name: string,
|
|
27
|
+
prompt: string,
|
|
28
|
+
options?: {
|
|
29
|
+
agentProfile?: string;
|
|
30
|
+
requiresApproval?: boolean;
|
|
31
|
+
dependsOn?: string[];
|
|
32
|
+
}
|
|
33
|
+
): WorkflowStep {
|
|
34
|
+
return {
|
|
35
|
+
id: stepId(index),
|
|
36
|
+
name,
|
|
37
|
+
prompt,
|
|
38
|
+
agentProfile: options?.agentProfile,
|
|
39
|
+
requiresApproval: options?.requiresApproval,
|
|
40
|
+
dependsOn: options?.dependsOn,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function resolveProfile(
|
|
45
|
+
suggestedProfile: string | undefined,
|
|
46
|
+
fallbackProfile: string | undefined
|
|
47
|
+
): string | undefined {
|
|
48
|
+
const profile = suggestedProfile ?? fallbackProfile;
|
|
49
|
+
if (!profile || profile === "auto") return undefined;
|
|
50
|
+
return profile;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function breakdownToSteps(
|
|
54
|
+
mainTask: AssistBuilderInput["mainTask"],
|
|
55
|
+
breakdown: TaskAssistBreakdownStep[],
|
|
56
|
+
pattern: WorkflowPattern
|
|
57
|
+
): WorkflowStep[] {
|
|
58
|
+
const steps: WorkflowStep[] = [];
|
|
59
|
+
|
|
60
|
+
// Step 1 is always the main task
|
|
61
|
+
steps.push(
|
|
62
|
+
buildStep(0, mainTask.title, mainTask.description, {
|
|
63
|
+
agentProfile: resolveProfile(undefined, mainTask.agentProfile),
|
|
64
|
+
})
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
// Remaining steps from breakdown
|
|
68
|
+
for (let i = 0; i < breakdown.length; i++) {
|
|
69
|
+
const sub = breakdown[i];
|
|
70
|
+
const dependsOn = sub.dependsOn?.map((depIdx) => stepId(depIdx));
|
|
71
|
+
steps.push(
|
|
72
|
+
buildStep(i + 1, sub.title, sub.description, {
|
|
73
|
+
agentProfile: resolveProfile(sub.suggestedProfile, undefined),
|
|
74
|
+
requiresApproval: pattern === "checkpoint" ? sub.requiresApproval : undefined,
|
|
75
|
+
dependsOn,
|
|
76
|
+
})
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return steps;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function buildSequenceDefinition(
|
|
84
|
+
mainTask: AssistBuilderInput["mainTask"],
|
|
85
|
+
assist: TaskAssistResponse,
|
|
86
|
+
pattern: "sequence" | "planner-executor" | "checkpoint"
|
|
87
|
+
): WorkflowDefinition {
|
|
88
|
+
const steps = breakdownToSteps(mainTask, assist.breakdown, pattern);
|
|
89
|
+
return { pattern, steps };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function buildParallelDefinition(
|
|
93
|
+
mainTask: AssistBuilderInput["mainTask"],
|
|
94
|
+
assist: TaskAssistResponse
|
|
95
|
+
): WorkflowDefinition {
|
|
96
|
+
const breakdown = assist.breakdown;
|
|
97
|
+
|
|
98
|
+
// Steps with no dependsOn are branches; steps with dependsOn are synthesis
|
|
99
|
+
const hasSynthesis = breakdown.some((s) => s.dependsOn && s.dependsOn.length > 0);
|
|
100
|
+
|
|
101
|
+
const steps: WorkflowStep[] = [];
|
|
102
|
+
|
|
103
|
+
// Main task as first branch
|
|
104
|
+
steps.push(
|
|
105
|
+
buildStep(0, mainTask.title, mainTask.description, {
|
|
106
|
+
agentProfile: resolveProfile(undefined, mainTask.agentProfile),
|
|
107
|
+
})
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
// Add breakdown items as branches (no dependsOn) or synthesis (with dependsOn)
|
|
111
|
+
for (let i = 0; i < breakdown.length; i++) {
|
|
112
|
+
const sub = breakdown[i];
|
|
113
|
+
const dependsOn = sub.dependsOn?.map((depIdx) => stepId(depIdx));
|
|
114
|
+
steps.push(
|
|
115
|
+
buildStep(i + 1, sub.title, sub.description, {
|
|
116
|
+
agentProfile: resolveProfile(sub.suggestedProfile, undefined),
|
|
117
|
+
dependsOn,
|
|
118
|
+
})
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Auto-generate synthesis step if none exists
|
|
123
|
+
if (!hasSynthesis) {
|
|
124
|
+
const branchIds = steps.map((s) => s.id);
|
|
125
|
+
steps.push(
|
|
126
|
+
buildStep(steps.length, "Synthesize results", "Combine and synthesize the results from all parallel branches into a coherent summary.", {
|
|
127
|
+
dependsOn: branchIds,
|
|
128
|
+
})
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return { pattern: "parallel", steps };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function buildLoopDefinition(
|
|
136
|
+
mainTask: AssistBuilderInput["mainTask"],
|
|
137
|
+
assist: TaskAssistResponse,
|
|
138
|
+
overrides?: AssistBuilderInput["overrides"]
|
|
139
|
+
): WorkflowDefinition {
|
|
140
|
+
const loopConfig = {
|
|
141
|
+
maxIterations: overrides?.loopConfig?.maxIterations
|
|
142
|
+
?? assist.suggestedLoopConfig?.maxIterations
|
|
143
|
+
?? 5,
|
|
144
|
+
timeBudgetMs: overrides?.loopConfig?.timeBudgetMs
|
|
145
|
+
?? assist.suggestedLoopConfig?.timeBudgetMs,
|
|
146
|
+
agentProfile: resolveProfile(undefined, mainTask.agentProfile),
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const steps: WorkflowStep[] = [
|
|
150
|
+
buildStep(0, mainTask.title, mainTask.description, {
|
|
151
|
+
agentProfile: resolveProfile(undefined, mainTask.agentProfile),
|
|
152
|
+
}),
|
|
153
|
+
];
|
|
154
|
+
|
|
155
|
+
return { pattern: "loop", steps, loopConfig };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function buildSwarmDefinition(
|
|
159
|
+
mainTask: AssistBuilderInput["mainTask"],
|
|
160
|
+
assist: TaskAssistResponse,
|
|
161
|
+
overrides?: AssistBuilderInput["overrides"]
|
|
162
|
+
): WorkflowDefinition {
|
|
163
|
+
const breakdown = assist.breakdown;
|
|
164
|
+
const steps: WorkflowStep[] = [];
|
|
165
|
+
|
|
166
|
+
// Step 1 = mayor (main task)
|
|
167
|
+
steps.push(
|
|
168
|
+
buildStep(0, mainTask.title, mainTask.description, {
|
|
169
|
+
agentProfile: resolveProfile(undefined, mainTask.agentProfile),
|
|
170
|
+
})
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
// Steps 2..N-1 = workers (from breakdown)
|
|
174
|
+
for (let i = 0; i < breakdown.length; i++) {
|
|
175
|
+
const sub = breakdown[i];
|
|
176
|
+
steps.push(
|
|
177
|
+
buildStep(i + 1, sub.title, sub.description, {
|
|
178
|
+
agentProfile: resolveProfile(sub.suggestedProfile, undefined),
|
|
179
|
+
})
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Step N = refinery (auto-generated)
|
|
184
|
+
steps.push(
|
|
185
|
+
buildStep(steps.length, "Refine and merge results", "Review all worker outputs, resolve conflicts, and produce a unified final result.", {})
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
const swarmConfig = {
|
|
189
|
+
workerConcurrencyLimit: overrides?.swarmConfig?.workerConcurrencyLimit
|
|
190
|
+
?? assist.suggestedSwarmConfig?.workerConcurrencyLimit
|
|
191
|
+
?? 2,
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
return { pattern: "swarm", steps, swarmConfig };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Convert an AI Assist response into a validated WorkflowDefinition.
|
|
199
|
+
* Pure function — no side effects.
|
|
200
|
+
*/
|
|
201
|
+
export function buildWorkflowDefinitionFromAssist(
|
|
202
|
+
input: AssistBuilderInput
|
|
203
|
+
): WorkflowDefinition {
|
|
204
|
+
const pattern = input.overrides?.pattern ?? input.assistResponse.recommendedPattern as WorkflowPattern;
|
|
205
|
+
const { mainTask, assistResponse, overrides } = input;
|
|
206
|
+
|
|
207
|
+
let definition: WorkflowDefinition;
|
|
208
|
+
|
|
209
|
+
switch (pattern) {
|
|
210
|
+
case "sequence":
|
|
211
|
+
case "planner-executor":
|
|
212
|
+
case "checkpoint":
|
|
213
|
+
definition = buildSequenceDefinition(mainTask, assistResponse, pattern);
|
|
214
|
+
break;
|
|
215
|
+
case "parallel":
|
|
216
|
+
definition = buildParallelDefinition(mainTask, assistResponse);
|
|
217
|
+
break;
|
|
218
|
+
case "loop":
|
|
219
|
+
definition = buildLoopDefinition(mainTask, assistResponse, overrides);
|
|
220
|
+
break;
|
|
221
|
+
case "swarm":
|
|
222
|
+
definition = buildSwarmDefinition(mainTask, assistResponse, overrides);
|
|
223
|
+
break;
|
|
224
|
+
default:
|
|
225
|
+
// Fallback to sequence for unknown patterns
|
|
226
|
+
definition = buildSequenceDefinition(mainTask, assistResponse, "sequence");
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Apply step-level overrides
|
|
230
|
+
if (overrides?.steps) {
|
|
231
|
+
for (let i = 0; i < overrides.steps.length && i < definition.steps.length; i++) {
|
|
232
|
+
const stepOverride = overrides.steps[i];
|
|
233
|
+
if (stepOverride) {
|
|
234
|
+
Object.assign(definition.steps[i], stepOverride);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Validate
|
|
240
|
+
const validationError = validateWorkflowDefinition(definition);
|
|
241
|
+
if (validationError) {
|
|
242
|
+
throw new Error(`Invalid workflow definition: ${validationError}`);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return definition;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export type { AssistBuilderInput };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { TaskAssistResponse } from "@/lib/agents/runtime/task-assist-types";
|
|
2
|
+
|
|
3
|
+
const STORAGE_KEY = "stagent:workflow-from-assist";
|
|
4
|
+
|
|
5
|
+
export interface WorkflowAssistState {
|
|
6
|
+
assistResult: TaskAssistResponse;
|
|
7
|
+
formState: {
|
|
8
|
+
title: string;
|
|
9
|
+
description: string;
|
|
10
|
+
projectId: string;
|
|
11
|
+
priority: string;
|
|
12
|
+
agentProfile: string;
|
|
13
|
+
assignedAgent: string;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function saveAssistState(state: WorkflowAssistState): void {
|
|
18
|
+
try {
|
|
19
|
+
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(state));
|
|
20
|
+
} catch {
|
|
21
|
+
// sessionStorage may be unavailable (e.g. SSR)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function loadAssistState(): WorkflowAssistState | null {
|
|
26
|
+
try {
|
|
27
|
+
const raw = sessionStorage.getItem(STORAGE_KEY);
|
|
28
|
+
if (!raw) return null;
|
|
29
|
+
return JSON.parse(raw) as WorkflowAssistState;
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function clearAssistState(): void {
|
|
36
|
+
try {
|
|
37
|
+
sessionStorage.removeItem(STORAGE_KEY);
|
|
38
|
+
} catch {
|
|
39
|
+
// noop
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const FORM_RESTORE_KEY = "stagent:task-form-restore";
|
|
44
|
+
|
|
45
|
+
export interface TaskFormState {
|
|
46
|
+
title: string;
|
|
47
|
+
description: string;
|
|
48
|
+
projectId: string;
|
|
49
|
+
priority: string;
|
|
50
|
+
agentProfile: string;
|
|
51
|
+
assignedAgent: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function saveTaskFormState(state: TaskFormState): void {
|
|
55
|
+
try {
|
|
56
|
+
sessionStorage.setItem(FORM_RESTORE_KEY, JSON.stringify(state));
|
|
57
|
+
} catch {
|
|
58
|
+
// noop
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function loadTaskFormState(): TaskFormState | null {
|
|
63
|
+
try {
|
|
64
|
+
const raw = sessionStorage.getItem(FORM_RESTORE_KEY);
|
|
65
|
+
if (!raw) return null;
|
|
66
|
+
return JSON.parse(raw) as TaskFormState;
|
|
67
|
+
} catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function clearTaskFormState(): void {
|
|
73
|
+
try {
|
|
74
|
+
sessionStorage.removeItem(FORM_RESTORE_KEY);
|
|
75
|
+
} catch {
|
|
76
|
+
// noop
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -2,6 +2,7 @@ import { db } from "@/lib/db";
|
|
|
2
2
|
import { workflows, tasks, agentLogs, notifications } from "@/lib/db/schema";
|
|
3
3
|
import { eq } from "drizzle-orm";
|
|
4
4
|
import { executeTaskWithRuntime } from "@/lib/agents/runtime";
|
|
5
|
+
import { classifyTaskProfile } from "@/lib/agents/router";
|
|
5
6
|
import type { WorkflowDefinition, WorkflowState, StepState, LoopState } from "./types";
|
|
6
7
|
import { createInitialState } from "./types";
|
|
7
8
|
import { executeLoop } from "./loop-executor";
|
|
@@ -47,6 +48,8 @@ export async function executeWorkflow(workflowId: string): Promise<void> {
|
|
|
47
48
|
try {
|
|
48
49
|
await executeLoop(workflowId, definition);
|
|
49
50
|
|
|
51
|
+
await syncSourceTaskStatus(workflowId, "completed");
|
|
52
|
+
|
|
50
53
|
await db.insert(agentLogs).values({
|
|
51
54
|
id: crypto.randomUUID(),
|
|
52
55
|
taskId: null,
|
|
@@ -56,6 +59,8 @@ export async function executeWorkflow(workflowId: string): Promise<void> {
|
|
|
56
59
|
timestamp: new Date(),
|
|
57
60
|
});
|
|
58
61
|
} catch (error) {
|
|
62
|
+
await syncSourceTaskStatus(workflowId, "failed");
|
|
63
|
+
|
|
59
64
|
await db.insert(agentLogs).values({
|
|
60
65
|
id: crypto.randomUUID(),
|
|
61
66
|
taskId: null,
|
|
@@ -94,6 +99,9 @@ export async function executeWorkflow(workflowId: string): Promise<void> {
|
|
|
94
99
|
state.completedAt = new Date().toISOString();
|
|
95
100
|
await updateWorkflowState(workflowId, state, "completed");
|
|
96
101
|
|
|
102
|
+
// Sync parent task status
|
|
103
|
+
await syncSourceTaskStatus(workflowId, "completed");
|
|
104
|
+
|
|
97
105
|
await db.insert(agentLogs).values({
|
|
98
106
|
id: crypto.randomUUID(),
|
|
99
107
|
taskId: null,
|
|
@@ -106,6 +114,9 @@ export async function executeWorkflow(workflowId: string): Promise<void> {
|
|
|
106
114
|
state.status = "failed";
|
|
107
115
|
await updateWorkflowState(workflowId, state, "failed");
|
|
108
116
|
|
|
117
|
+
// Sync parent task status
|
|
118
|
+
await syncSourceTaskStatus(workflowId, "failed");
|
|
119
|
+
|
|
109
120
|
await db.insert(agentLogs).values({
|
|
110
121
|
id: crypto.randomUUID(),
|
|
111
122
|
taskId: null,
|
|
@@ -682,6 +693,12 @@ export async function executeChildTask(
|
|
|
682
693
|
.from(workflows)
|
|
683
694
|
.where(eq(workflows.id, workflowId));
|
|
684
695
|
|
|
696
|
+
// Resolve "auto" profile via multi-agent router
|
|
697
|
+
const resolvedProfile =
|
|
698
|
+
!agentProfile || agentProfile === "auto"
|
|
699
|
+
? classifyTaskProfile(name, prompt, assignedAgent)
|
|
700
|
+
: agentProfile;
|
|
701
|
+
|
|
685
702
|
const taskId = crypto.randomUUID();
|
|
686
703
|
await db.insert(tasks).values({
|
|
687
704
|
id: taskId,
|
|
@@ -693,7 +710,7 @@ export async function executeChildTask(
|
|
|
693
710
|
status: "queued",
|
|
694
711
|
priority: 1,
|
|
695
712
|
assignedAgent: assignedAgent ?? null,
|
|
696
|
-
agentProfile:
|
|
713
|
+
agentProfile: resolvedProfile ?? null,
|
|
697
714
|
createdAt: new Date(),
|
|
698
715
|
updatedAt: new Date(),
|
|
699
716
|
});
|
|
@@ -811,6 +828,35 @@ async function waitForApproval(
|
|
|
811
828
|
return false; // Timeout — treat as denied
|
|
812
829
|
}
|
|
813
830
|
|
|
831
|
+
/**
|
|
832
|
+
* Sync the parent (source) task's status with the workflow's final status.
|
|
833
|
+
* The parent task is linked via `sourceTaskId` in the workflow's definition JSON.
|
|
834
|
+
*/
|
|
835
|
+
async function syncSourceTaskStatus(
|
|
836
|
+
workflowId: string,
|
|
837
|
+
status: "completed" | "failed"
|
|
838
|
+
): Promise<void> {
|
|
839
|
+
try {
|
|
840
|
+
const result = await db
|
|
841
|
+
.select()
|
|
842
|
+
.from(workflows)
|
|
843
|
+
.where(eq(workflows.id, workflowId));
|
|
844
|
+
|
|
845
|
+
const workflow = Array.isArray(result) ? result[0] : undefined;
|
|
846
|
+
if (!workflow) return;
|
|
847
|
+
|
|
848
|
+
const def = JSON.parse(workflow.definition);
|
|
849
|
+
if (!def.sourceTaskId) return;
|
|
850
|
+
|
|
851
|
+
await db
|
|
852
|
+
.update(tasks)
|
|
853
|
+
.set({ status, updatedAt: new Date() })
|
|
854
|
+
.where(eq(tasks.id, def.sourceTaskId));
|
|
855
|
+
} catch (error) {
|
|
856
|
+
console.error(`[workflow-engine] Failed to sync source task status for workflow ${workflowId}:`, error);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
814
860
|
/**
|
|
815
861
|
* Update workflow state in the database.
|
|
816
862
|
*/
|