stagent 0.1.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/LICENSE +191 -0
- package/README.md +399 -0
- package/components.json +21 -0
- package/dist/cli.js +171 -0
- package/drizzle.config.ts +12 -0
- package/next.config.mjs +15 -0
- package/package.json +114 -0
- package/postcss.config.mjs +8 -0
- package/public/icon-512.png +0 -0
- package/public/icon.svg +13 -0
- package/public/readme/home-workspace.png +0 -0
- package/public/readme/inbox-approvals.png +0 -0
- package/public/readme/workflow-blueprints.png +0 -0
- package/public/stagent-s-128.png +0 -0
- package/public/stagent-s-64.png +0 -0
- package/src/app/api/blueprints/[id]/instantiate/route.ts +27 -0
- package/src/app/api/blueprints/[id]/route.ts +39 -0
- package/src/app/api/blueprints/import/route.ts +68 -0
- package/src/app/api/blueprints/route.ts +29 -0
- package/src/app/api/command-palette/recent/route.ts +31 -0
- package/src/app/api/data/clear/route.ts +22 -0
- package/src/app/api/data/seed/route.ts +22 -0
- package/src/app/api/documents/[id]/file/route.ts +44 -0
- package/src/app/api/documents/[id]/route.ts +123 -0
- package/src/app/api/documents/route.ts +59 -0
- package/src/app/api/logs/stream/route.ts +101 -0
- package/src/app/api/notifications/[id]/route.ts +36 -0
- package/src/app/api/notifications/mark-all-read/route.ts +13 -0
- package/src/app/api/notifications/pending-approvals/route.ts +10 -0
- package/src/app/api/notifications/pending-approvals/stream/route.ts +101 -0
- package/src/app/api/notifications/route.ts +34 -0
- package/src/app/api/permissions/route.ts +46 -0
- package/src/app/api/profiles/[id]/route.ts +79 -0
- package/src/app/api/profiles/[id]/test/route.ts +42 -0
- package/src/app/api/profiles/import/route.ts +108 -0
- package/src/app/api/profiles/route.ts +50 -0
- package/src/app/api/projects/[id]/route.ts +72 -0
- package/src/app/api/projects/route.ts +53 -0
- package/src/app/api/schedules/[id]/route.ts +185 -0
- package/src/app/api/schedules/route.ts +117 -0
- package/src/app/api/settings/budgets/route.ts +24 -0
- package/src/app/api/settings/openai/route.ts +24 -0
- package/src/app/api/settings/route.ts +21 -0
- package/src/app/api/settings/test/route.ts +26 -0
- package/src/app/api/tasks/[id]/cancel/route.ts +21 -0
- package/src/app/api/tasks/[id]/execute/route.ts +90 -0
- package/src/app/api/tasks/[id]/logs/route.ts +95 -0
- package/src/app/api/tasks/[id]/output/route.ts +47 -0
- package/src/app/api/tasks/[id]/respond/route.ts +64 -0
- package/src/app/api/tasks/[id]/resume/route.ts +76 -0
- package/src/app/api/tasks/[id]/route.ts +77 -0
- package/src/app/api/tasks/assist/route.ts +35 -0
- package/src/app/api/tasks/route.ts +82 -0
- package/src/app/api/uploads/[id]/route.ts +81 -0
- package/src/app/api/uploads/cleanup/route.ts +7 -0
- package/src/app/api/uploads/route.ts +66 -0
- package/src/app/api/workflows/[id]/execute/route.ts +82 -0
- package/src/app/api/workflows/[id]/route.ts +133 -0
- package/src/app/api/workflows/[id]/status/route.ts +54 -0
- package/src/app/api/workflows/[id]/steps/[stepId]/retry/route.ts +22 -0
- package/src/app/api/workflows/route.ts +61 -0
- package/src/app/apple-icon.tsx +31 -0
- package/src/app/costs/page.tsx +256 -0
- package/src/app/dashboard/page.tsx +44 -0
- package/src/app/documents/[id]/page.tsx +46 -0
- package/src/app/documents/page.tsx +45 -0
- package/src/app/error.tsx +26 -0
- package/src/app/global-error.tsx +23 -0
- package/src/app/globals.css +733 -0
- package/src/app/icon.tsx +30 -0
- package/src/app/inbox/loading.tsx +15 -0
- package/src/app/inbox/page.tsx +35 -0
- package/src/app/layout.tsx +78 -0
- package/src/app/manifest.ts +32 -0
- package/src/app/monitor/page.tsx +37 -0
- package/src/app/page.tsx +162 -0
- package/src/app/profiles/[id]/edit/page.tsx +39 -0
- package/src/app/profiles/[id]/page.tsx +33 -0
- package/src/app/profiles/new/page.tsx +22 -0
- package/src/app/profiles/page.tsx +19 -0
- package/src/app/projects/[id]/page.tsx +134 -0
- package/src/app/projects/loading.tsx +17 -0
- package/src/app/projects/page.tsx +32 -0
- package/src/app/schedules/[id]/page.tsx +47 -0
- package/src/app/schedules/page.tsx +18 -0
- package/src/app/settings/loading.tsx +24 -0
- package/src/app/settings/page.tsx +27 -0
- package/src/app/tasks/[id]/page.tsx +45 -0
- package/src/app/tasks/new/page.tsx +27 -0
- package/src/app/workflows/[id]/edit/page.tsx +66 -0
- package/src/app/workflows/[id]/page.tsx +37 -0
- package/src/app/workflows/blueprints/[id]/page.tsx +40 -0
- package/src/app/workflows/blueprints/new/page.tsx +20 -0
- package/src/app/workflows/blueprints/page.tsx +11 -0
- package/src/app/workflows/new/page.tsx +36 -0
- package/src/app/workflows/page.tsx +18 -0
- package/src/components/charts/donut-ring.tsx +64 -0
- package/src/components/charts/mini-bar.tsx +75 -0
- package/src/components/charts/sparkline.tsx +107 -0
- package/src/components/costs/cost-dashboard.tsx +877 -0
- package/src/components/costs/cost-filters.tsx +179 -0
- package/src/components/dashboard/activity-feed.tsx +95 -0
- package/src/components/dashboard/greeting.tsx +30 -0
- package/src/components/dashboard/priority-queue.tsx +79 -0
- package/src/components/dashboard/quick-actions.tsx +62 -0
- package/src/components/dashboard/recent-projects.tsx +79 -0
- package/src/components/dashboard/stats-cards.tsx +114 -0
- package/src/components/documents/document-browser.tsx +235 -0
- package/src/components/documents/document-detail-view.tsx +367 -0
- package/src/components/documents/document-grid.tsx +78 -0
- package/src/components/documents/document-preview.tsx +68 -0
- package/src/components/documents/document-table.tsx +119 -0
- package/src/components/documents/document-upload-dialog.tsx +153 -0
- package/src/components/documents/types.ts +6 -0
- package/src/components/documents/utils.ts +57 -0
- package/src/components/monitoring/connection-indicator.tsx +14 -0
- package/src/components/monitoring/log-entry.tsx +79 -0
- package/src/components/monitoring/log-filters.tsx +57 -0
- package/src/components/monitoring/log-stream.tsx +144 -0
- package/src/components/monitoring/monitor-overview-wrapper.tsx +64 -0
- package/src/components/monitoring/monitor-overview.tsx +119 -0
- package/src/components/notifications/failure-action.tsx +38 -0
- package/src/components/notifications/inbox-list.tsx +165 -0
- package/src/components/notifications/message-response.tsx +196 -0
- package/src/components/notifications/notification-item.tsx +250 -0
- package/src/components/notifications/pending-approval-host.tsx +478 -0
- package/src/components/notifications/permission-action.tsx +37 -0
- package/src/components/notifications/permission-response-actions.tsx +126 -0
- package/src/components/notifications/unread-badge.tsx +35 -0
- package/src/components/profiles/profile-browser.tsx +117 -0
- package/src/components/profiles/profile-card.tsx +78 -0
- package/src/components/profiles/profile-detail-view.tsx +564 -0
- package/src/components/profiles/profile-form-view.tsx +480 -0
- package/src/components/profiles/profile-import-dialog.tsx +113 -0
- package/src/components/projects/project-card.tsx +58 -0
- package/src/components/projects/project-create-dialog.tsx +140 -0
- package/src/components/projects/project-detail.tsx +68 -0
- package/src/components/projects/project-edit-dialog.tsx +219 -0
- package/src/components/projects/project-list.tsx +108 -0
- package/src/components/schedules/schedule-create-dialog.tsx +403 -0
- package/src/components/schedules/schedule-detail-view.tsx +274 -0
- package/src/components/schedules/schedule-list.tsx +242 -0
- package/src/components/schedules/schedule-status-badge.tsx +16 -0
- package/src/components/settings/api-key-form.tsx +141 -0
- package/src/components/settings/auth-config-section.tsx +141 -0
- package/src/components/settings/auth-method-selector.tsx +67 -0
- package/src/components/settings/auth-status-badge.tsx +40 -0
- package/src/components/settings/auth-status-dot.tsx +59 -0
- package/src/components/settings/budget-guardrails-section.tsx +842 -0
- package/src/components/settings/data-management-section.tsx +141 -0
- package/src/components/settings/openai-runtime-section.tsx +104 -0
- package/src/components/settings/permissions-section.tsx +91 -0
- package/src/components/shared/app-sidebar.tsx +123 -0
- package/src/components/shared/card-skeleton.tsx +42 -0
- package/src/components/shared/command-palette.tsx +250 -0
- package/src/components/shared/confirm-dialog.tsx +52 -0
- package/src/components/shared/empty-state.tsx +24 -0
- package/src/components/shared/error-state.tsx +32 -0
- package/src/components/shared/form-section-card.tsx +33 -0
- package/src/components/shared/section-heading.tsx +14 -0
- package/src/components/shared/stagent-logo.tsx +21 -0
- package/src/components/shared/theme-toggle.tsx +46 -0
- package/src/components/tasks/ai-assist-panel.tsx +210 -0
- package/src/components/tasks/content-preview.tsx +89 -0
- package/src/components/tasks/empty-board.tsx +12 -0
- package/src/components/tasks/file-upload.tsx +120 -0
- package/src/components/tasks/kanban-board.tsx +275 -0
- package/src/components/tasks/kanban-column.tsx +75 -0
- package/src/components/tasks/skeleton-board.tsx +21 -0
- package/src/components/tasks/task-attachments.tsx +114 -0
- package/src/components/tasks/task-card.tsx +101 -0
- package/src/components/tasks/task-create-panel.tsx +360 -0
- package/src/components/tasks/task-detail-view.tsx +356 -0
- package/src/components/ui/alert-dialog.tsx +196 -0
- package/src/components/ui/badge.tsx +50 -0
- package/src/components/ui/button.tsx +71 -0
- package/src/components/ui/card.tsx +92 -0
- package/src/components/ui/checkbox.tsx +32 -0
- package/src/components/ui/command.tsx +184 -0
- package/src/components/ui/dialog.tsx +158 -0
- package/src/components/ui/dropdown-menu.tsx +257 -0
- package/src/components/ui/form.tsx +167 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/label.tsx +24 -0
- package/src/components/ui/popover.tsx +89 -0
- package/src/components/ui/progress.tsx +31 -0
- package/src/components/ui/radio-group.tsx +45 -0
- package/src/components/ui/scroll-area.tsx +58 -0
- package/src/components/ui/select.tsx +190 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/sheet.tsx +143 -0
- package/src/components/ui/sidebar.tsx +726 -0
- package/src/components/ui/skeleton.tsx +13 -0
- package/src/components/ui/slider.tsx +63 -0
- package/src/components/ui/sonner.tsx +36 -0
- package/src/components/ui/switch.tsx +35 -0
- package/src/components/ui/table.tsx +116 -0
- package/src/components/ui/tabs.tsx +91 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/tooltip.tsx +57 -0
- package/src/components/workflows/blueprint-editor.tsx +109 -0
- package/src/components/workflows/blueprint-gallery.tsx +155 -0
- package/src/components/workflows/blueprint-preview.tsx +240 -0
- package/src/components/workflows/loop-status-view.tsx +272 -0
- package/src/components/workflows/swarm-dashboard.tsx +185 -0
- package/src/components/workflows/workflow-form-view.tsx +1376 -0
- package/src/components/workflows/workflow-list.tsx +230 -0
- package/src/components/workflows/workflow-status-view.tsx +477 -0
- package/src/hooks/use-mobile.ts +19 -0
- package/src/instrumentation.ts +7 -0
- package/src/lib/agents/claude-agent.ts +737 -0
- package/src/lib/agents/execution-manager.ts +27 -0
- package/src/lib/agents/profiles/assignment-validation.ts +75 -0
- package/src/lib/agents/profiles/builtins/code-reviewer/SKILL.md +21 -0
- package/src/lib/agents/profiles/builtins/code-reviewer/profile.yaml +28 -0
- package/src/lib/agents/profiles/builtins/data-analyst/SKILL.md +25 -0
- package/src/lib/agents/profiles/builtins/data-analyst/profile.yaml +27 -0
- package/src/lib/agents/profiles/builtins/devops-engineer/SKILL.md +34 -0
- package/src/lib/agents/profiles/builtins/devops-engineer/profile.yaml +27 -0
- package/src/lib/agents/profiles/builtins/document-writer/SKILL.md +16 -0
- package/src/lib/agents/profiles/builtins/document-writer/profile.yaml +27 -0
- package/src/lib/agents/profiles/builtins/general/SKILL.md +13 -0
- package/src/lib/agents/profiles/builtins/general/profile.yaml +18 -0
- package/src/lib/agents/profiles/builtins/health-fitness-coach/SKILL.md +34 -0
- package/src/lib/agents/profiles/builtins/health-fitness-coach/profile.yaml +26 -0
- package/src/lib/agents/profiles/builtins/learning-coach/SKILL.md +35 -0
- package/src/lib/agents/profiles/builtins/learning-coach/profile.yaml +26 -0
- package/src/lib/agents/profiles/builtins/project-manager/SKILL.md +26 -0
- package/src/lib/agents/profiles/builtins/project-manager/profile.yaml +26 -0
- package/src/lib/agents/profiles/builtins/researcher/SKILL.md +15 -0
- package/src/lib/agents/profiles/builtins/researcher/profile.yaml +27 -0
- package/src/lib/agents/profiles/builtins/shopping-assistant/SKILL.md +34 -0
- package/src/lib/agents/profiles/builtins/shopping-assistant/profile.yaml +26 -0
- package/src/lib/agents/profiles/builtins/technical-writer/SKILL.md +31 -0
- package/src/lib/agents/profiles/builtins/technical-writer/profile.yaml +29 -0
- package/src/lib/agents/profiles/builtins/travel-planner/SKILL.md +23 -0
- package/src/lib/agents/profiles/builtins/travel-planner/profile.yaml +26 -0
- package/src/lib/agents/profiles/builtins/wealth-manager/SKILL.md +24 -0
- package/src/lib/agents/profiles/builtins/wealth-manager/profile.yaml +26 -0
- package/src/lib/agents/profiles/compatibility.ts +109 -0
- package/src/lib/agents/profiles/registry.ts +293 -0
- package/src/lib/agents/profiles/test-runner.ts +18 -0
- package/src/lib/agents/profiles/test-types.ts +20 -0
- package/src/lib/agents/profiles/types.ts +43 -0
- package/src/lib/agents/router.ts +56 -0
- package/src/lib/agents/runtime/catalog.ts +85 -0
- package/src/lib/agents/runtime/claude-sdk.ts +12 -0
- package/src/lib/agents/runtime/claude.ts +370 -0
- package/src/lib/agents/runtime/codex-app-server-client.ts +289 -0
- package/src/lib/agents/runtime/index.ts +167 -0
- package/src/lib/agents/runtime/openai-codex.ts +1089 -0
- package/src/lib/agents/runtime/task-assist-types.ts +8 -0
- package/src/lib/agents/runtime/types.ts +30 -0
- package/src/lib/constants/settings.ts +13 -0
- package/src/lib/constants/status-colors.ts +44 -0
- package/src/lib/constants/task-status.ts +49 -0
- package/src/lib/data/clear.ts +63 -0
- package/src/lib/data/seed-data/documents.ts +715 -0
- package/src/lib/data/seed-data/logs.ts +195 -0
- package/src/lib/data/seed-data/notifications.ts +141 -0
- package/src/lib/data/seed-data/profiles.ts +175 -0
- package/src/lib/data/seed-data/projects.ts +61 -0
- package/src/lib/data/seed-data/schedules.ts +108 -0
- package/src/lib/data/seed-data/tasks.ts +341 -0
- package/src/lib/data/seed-data/usage-ledger.ts +130 -0
- package/src/lib/data/seed-data/workflows.ts +213 -0
- package/src/lib/data/seed.ts +129 -0
- package/src/lib/db/index.ts +221 -0
- package/src/lib/db/migrations/0000_aromatic_gargoyle.sql +59 -0
- package/src/lib/db/migrations/0001_first_iron_patriot.sql +6 -0
- package/src/lib/db/migrations/0002_add_resume_count.sql +1 -0
- package/src/lib/db/migrations/0003_add_settings.sql +5 -0
- package/src/lib/db/migrations/0004_add_documents.sql +20 -0
- package/src/lib/db/migrations/0005_add_document_preprocessing.sql +4 -0
- package/src/lib/db/migrations/0006_add_agent_profile.sql +2 -0
- package/src/lib/db/migrations/0007_add_usage_metering_ledger.sql +30 -0
- package/src/lib/db/migrations/0008_add_document_version.sql +1 -0
- package/src/lib/db/migrations/meta/0000_snapshot.json +416 -0
- package/src/lib/db/migrations/meta/0001_snapshot.json +461 -0
- package/src/lib/db/migrations/meta/0002_snapshot.json +469 -0
- package/src/lib/db/migrations/meta/_journal.json +27 -0
- package/src/lib/db/schema.ts +227 -0
- package/src/lib/documents/cleanup.ts +50 -0
- package/src/lib/documents/context-builder.ts +75 -0
- package/src/lib/documents/output-scanner.ts +166 -0
- package/src/lib/documents/processor.ts +120 -0
- package/src/lib/documents/processors/image.ts +21 -0
- package/src/lib/documents/processors/office.ts +36 -0
- package/src/lib/documents/processors/pdf.ts +12 -0
- package/src/lib/documents/processors/spreadsheet.ts +18 -0
- package/src/lib/documents/processors/text.ts +8 -0
- package/src/lib/documents/registry.ts +25 -0
- package/src/lib/notifications/actionable.ts +108 -0
- package/src/lib/notifications/permissions.ts +169 -0
- package/src/lib/queries/chart-data.ts +184 -0
- package/src/lib/schedules/interval-parser.ts +110 -0
- package/src/lib/schedules/scheduler.ts +220 -0
- package/src/lib/settings/auth.ts +98 -0
- package/src/lib/settings/budget-guardrails.ts +590 -0
- package/src/lib/settings/helpers.ts +23 -0
- package/src/lib/settings/openai-auth.ts +80 -0
- package/src/lib/settings/permissions.ts +102 -0
- package/src/lib/usage/ledger.ts +489 -0
- package/src/lib/usage/pricing.ts +68 -0
- package/src/lib/utils/crypto.ts +90 -0
- package/src/lib/utils/format-timestamp.ts +46 -0
- package/src/lib/utils/session-cleanup.ts +26 -0
- package/src/lib/utils/stagent-paths.ts +18 -0
- package/src/lib/utils.ts +6 -0
- package/src/lib/validators/blueprint.ts +43 -0
- package/src/lib/validators/profile.ts +64 -0
- package/src/lib/validators/project.ts +17 -0
- package/src/lib/validators/settings.ts +57 -0
- package/src/lib/validators/task.ts +30 -0
- package/src/lib/workflows/blueprints/builtins/code-review-pipeline.yaml +72 -0
- package/src/lib/workflows/blueprints/builtins/documentation-generation.yaml +62 -0
- package/src/lib/workflows/blueprints/builtins/investment-research.yaml +81 -0
- package/src/lib/workflows/blueprints/builtins/meal-planning.yaml +73 -0
- package/src/lib/workflows/blueprints/builtins/product-research.yaml +72 -0
- package/src/lib/workflows/blueprints/builtins/research-report.yaml +77 -0
- package/src/lib/workflows/blueprints/builtins/sprint-planning.yaml +77 -0
- package/src/lib/workflows/blueprints/builtins/travel-planning.yaml +80 -0
- package/src/lib/workflows/blueprints/instantiator.ts +131 -0
- package/src/lib/workflows/blueprints/registry.ts +128 -0
- package/src/lib/workflows/blueprints/template.ts +58 -0
- package/src/lib/workflows/blueprints/types.ts +38 -0
- package/src/lib/workflows/definition-validation.ts +121 -0
- package/src/lib/workflows/engine.ts +1113 -0
- package/src/lib/workflows/loop-executor.ts +270 -0
- package/src/lib/workflows/parallel.ts +55 -0
- package/src/lib/workflows/swarm.ts +97 -0
- package/src/lib/workflows/types.ts +112 -0
- package/tsconfig.json +41 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { db } from "@/lib/db";
|
|
2
|
+
import { workflows, agentLogs } from "@/lib/db/schema";
|
|
3
|
+
import { eq } from "drizzle-orm";
|
|
4
|
+
import { executeChildTask, updateWorkflowState } from "./engine";
|
|
5
|
+
import type { WorkflowDefinition, LoopState, IterationState, LoopStopReason } from "./types";
|
|
6
|
+
import { createInitialLoopState } from "./types";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Execute the loop pattern — autonomous iteration with stop conditions.
|
|
10
|
+
* Each iteration creates a child task, passes previous output as context,
|
|
11
|
+
* and checks for completion signals, time budgets, and pause requests.
|
|
12
|
+
*/
|
|
13
|
+
export async function executeLoop(
|
|
14
|
+
workflowId: string,
|
|
15
|
+
definition: WorkflowDefinition
|
|
16
|
+
): Promise<void> {
|
|
17
|
+
if (!definition.loopConfig) {
|
|
18
|
+
throw new Error("Loop pattern requires loopConfig");
|
|
19
|
+
}
|
|
20
|
+
if (!definition.steps.length) {
|
|
21
|
+
throw new Error("Loop pattern requires at least one step (the loop prompt)");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const {
|
|
25
|
+
maxIterations,
|
|
26
|
+
timeBudgetMs,
|
|
27
|
+
assignedAgent,
|
|
28
|
+
agentProfile,
|
|
29
|
+
completionSignals,
|
|
30
|
+
} = definition.loopConfig;
|
|
31
|
+
const loopPrompt = definition.steps[0].prompt;
|
|
32
|
+
|
|
33
|
+
// Restore existing state (resume) or create fresh
|
|
34
|
+
const loopState = await restoreOrCreateLoopState(workflowId);
|
|
35
|
+
loopState.status = "running";
|
|
36
|
+
await updateLoopState(workflowId, loopState, "active");
|
|
37
|
+
|
|
38
|
+
const startTime = new Date(loopState.startedAt).getTime();
|
|
39
|
+
let previousOutput = "";
|
|
40
|
+
|
|
41
|
+
// If resuming, grab the last completed iteration's result
|
|
42
|
+
if (loopState.iterations.length > 0) {
|
|
43
|
+
const lastCompleted = [...loopState.iterations]
|
|
44
|
+
.reverse()
|
|
45
|
+
.find((i) => i.status === "completed");
|
|
46
|
+
if (lastCompleted?.result) {
|
|
47
|
+
previousOutput = lastCompleted.result;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
while (loopState.currentIteration < maxIterations) {
|
|
53
|
+
// Check pause: re-fetch workflow status from DB
|
|
54
|
+
const [workflow] = await db
|
|
55
|
+
.select()
|
|
56
|
+
.from(workflows)
|
|
57
|
+
.where(eq(workflows.id, workflowId));
|
|
58
|
+
|
|
59
|
+
if (workflow?.status === "paused") {
|
|
60
|
+
loopState.status = "paused";
|
|
61
|
+
loopState.stopReason = "human_pause";
|
|
62
|
+
await updateLoopState(workflowId, loopState, "paused");
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (workflow?.status === "completed" || workflow?.status === "draft") {
|
|
67
|
+
loopState.status = "completed";
|
|
68
|
+
loopState.stopReason = "human_cancel";
|
|
69
|
+
await updateLoopState(workflowId, loopState, workflow.status as "completed" | "draft");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Check time budget
|
|
74
|
+
if (timeBudgetMs) {
|
|
75
|
+
const elapsed = Date.now() - startTime;
|
|
76
|
+
if (elapsed >= timeBudgetMs) {
|
|
77
|
+
await finalizeLoop(workflowId, loopState, "time_budget");
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const iterationNum = loopState.currentIteration + 1;
|
|
83
|
+
|
|
84
|
+
// Build iteration prompt
|
|
85
|
+
const prompt = buildIterationPrompt(
|
|
86
|
+
loopPrompt,
|
|
87
|
+
previousOutput,
|
|
88
|
+
iterationNum,
|
|
89
|
+
maxIterations
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
// Create iteration state
|
|
93
|
+
const iterationState: IterationState = {
|
|
94
|
+
iteration: iterationNum,
|
|
95
|
+
taskId: "",
|
|
96
|
+
status: "running",
|
|
97
|
+
startedAt: new Date().toISOString(),
|
|
98
|
+
};
|
|
99
|
+
loopState.iterations.push(iterationState);
|
|
100
|
+
await updateLoopState(workflowId, loopState, "active");
|
|
101
|
+
|
|
102
|
+
await db.insert(agentLogs).values({
|
|
103
|
+
id: crypto.randomUUID(),
|
|
104
|
+
taskId: null,
|
|
105
|
+
agentType: "loop-executor",
|
|
106
|
+
event: "loop_iteration_started",
|
|
107
|
+
payload: JSON.stringify({
|
|
108
|
+
workflowId,
|
|
109
|
+
iteration: iterationNum,
|
|
110
|
+
maxIterations,
|
|
111
|
+
}),
|
|
112
|
+
timestamp: new Date(),
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Execute child task
|
|
116
|
+
const result = await executeChildTask(
|
|
117
|
+
workflowId,
|
|
118
|
+
`Loop Iteration ${iterationNum}`,
|
|
119
|
+
prompt,
|
|
120
|
+
assignedAgent ?? definition.steps[0].assignedAgent,
|
|
121
|
+
agentProfile ?? definition.steps[0].agentProfile
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
// Update iteration state
|
|
125
|
+
const iterStartTime = new Date(iterationState.startedAt!).getTime();
|
|
126
|
+
iterationState.taskId = result.taskId;
|
|
127
|
+
iterationState.completedAt = new Date().toISOString();
|
|
128
|
+
iterationState.durationMs = Date.now() - iterStartTime;
|
|
129
|
+
|
|
130
|
+
if (result.status === "completed") {
|
|
131
|
+
iterationState.status = "completed";
|
|
132
|
+
iterationState.result = result.result;
|
|
133
|
+
previousOutput = result.result ?? "";
|
|
134
|
+
} else {
|
|
135
|
+
iterationState.status = "failed";
|
|
136
|
+
iterationState.error = result.error;
|
|
137
|
+
await finalizeLoop(workflowId, loopState, "error");
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
loopState.currentIteration = iterationNum;
|
|
142
|
+
await updateLoopState(workflowId, loopState, "active");
|
|
143
|
+
|
|
144
|
+
// Check completion signal
|
|
145
|
+
if (
|
|
146
|
+
result.result &&
|
|
147
|
+
detectCompletionSignal(result.result, completionSignals)
|
|
148
|
+
) {
|
|
149
|
+
await finalizeLoop(workflowId, loopState, "agent_signaled");
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Exhausted max iterations
|
|
155
|
+
await finalizeLoop(workflowId, loopState, "max_iterations");
|
|
156
|
+
} catch (error) {
|
|
157
|
+
loopState.status = "failed";
|
|
158
|
+
loopState.stopReason = "error";
|
|
159
|
+
loopState.completedAt = new Date().toISOString();
|
|
160
|
+
loopState.totalDurationMs =
|
|
161
|
+
Date.now() - new Date(loopState.startedAt).getTime();
|
|
162
|
+
await updateLoopState(workflowId, loopState, "active");
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Build the prompt for a single iteration, including previous output context.
|
|
169
|
+
*/
|
|
170
|
+
export function buildIterationPrompt(
|
|
171
|
+
template: string,
|
|
172
|
+
previousOutput: string,
|
|
173
|
+
iteration: number,
|
|
174
|
+
maxIterations: number
|
|
175
|
+
): string {
|
|
176
|
+
const parts: string[] = [];
|
|
177
|
+
|
|
178
|
+
parts.push(`Iteration ${iteration} of ${maxIterations}.`);
|
|
179
|
+
|
|
180
|
+
if (previousOutput) {
|
|
181
|
+
parts.push(`\nPrevious iteration output:\n${previousOutput}`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
parts.push(`\n---\n\n${template}`);
|
|
185
|
+
parts.push(
|
|
186
|
+
`\nWhen you are fully satisfied with the result, include "LOOP_COMPLETE" in your response.`
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
return parts.join("");
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Check if the output contains a completion signal.
|
|
194
|
+
* Case-insensitive substring match against the signal list.
|
|
195
|
+
*/
|
|
196
|
+
export function detectCompletionSignal(
|
|
197
|
+
output: string,
|
|
198
|
+
signals?: string[]
|
|
199
|
+
): boolean {
|
|
200
|
+
const effectiveSignals = signals?.length ? signals : ["LOOP_COMPLETE"];
|
|
201
|
+
const lowerOutput = output.toLowerCase();
|
|
202
|
+
return effectiveSignals.some((signal) =>
|
|
203
|
+
lowerOutput.includes(signal.toLowerCase())
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Store loop state in the workflow definition JSON alongside _state.
|
|
209
|
+
*/
|
|
210
|
+
export async function updateLoopState(
|
|
211
|
+
workflowId: string,
|
|
212
|
+
loopState: LoopState,
|
|
213
|
+
workflowStatus: "draft" | "active" | "paused" | "completed"
|
|
214
|
+
): Promise<void> {
|
|
215
|
+
const [workflow] = await db
|
|
216
|
+
.select()
|
|
217
|
+
.from(workflows)
|
|
218
|
+
.where(eq(workflows.id, workflowId));
|
|
219
|
+
|
|
220
|
+
if (!workflow) return;
|
|
221
|
+
|
|
222
|
+
const parsed = JSON.parse(workflow.definition);
|
|
223
|
+
const combined = { ...parsed, _loopState: loopState };
|
|
224
|
+
|
|
225
|
+
await db
|
|
226
|
+
.update(workflows)
|
|
227
|
+
.set({
|
|
228
|
+
definition: JSON.stringify(combined),
|
|
229
|
+
status: workflowStatus,
|
|
230
|
+
updatedAt: new Date(),
|
|
231
|
+
})
|
|
232
|
+
.where(eq(workflows.id, workflowId));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Restore existing loop state from DB or create a fresh one.
|
|
237
|
+
*/
|
|
238
|
+
async function restoreOrCreateLoopState(
|
|
239
|
+
workflowId: string
|
|
240
|
+
): Promise<LoopState> {
|
|
241
|
+
const [workflow] = await db
|
|
242
|
+
.select()
|
|
243
|
+
.from(workflows)
|
|
244
|
+
.where(eq(workflows.id, workflowId));
|
|
245
|
+
|
|
246
|
+
if (workflow) {
|
|
247
|
+
const parsed = JSON.parse(workflow.definition);
|
|
248
|
+
if (parsed._loopState) {
|
|
249
|
+
return parsed._loopState as LoopState;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return createInitialLoopState();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Finalize a loop with a stop reason and mark the workflow as completed.
|
|
258
|
+
*/
|
|
259
|
+
async function finalizeLoop(
|
|
260
|
+
workflowId: string,
|
|
261
|
+
loopState: LoopState,
|
|
262
|
+
stopReason: LoopStopReason
|
|
263
|
+
): Promise<void> {
|
|
264
|
+
loopState.status = "completed";
|
|
265
|
+
loopState.stopReason = stopReason;
|
|
266
|
+
loopState.completedAt = new Date().toISOString();
|
|
267
|
+
loopState.totalDurationMs =
|
|
268
|
+
Date.now() - new Date(loopState.startedAt).getTime();
|
|
269
|
+
await updateLoopState(workflowId, loopState, "completed");
|
|
270
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { WorkflowDefinition, WorkflowStep } from "./types";
|
|
2
|
+
|
|
3
|
+
export const MIN_PARALLEL_BRANCHES = 2;
|
|
4
|
+
export const MAX_PARALLEL_BRANCHES = 5;
|
|
5
|
+
export const PARALLEL_BRANCH_CONCURRENCY_LIMIT = 3;
|
|
6
|
+
|
|
7
|
+
export interface ParallelWorkflowStructure {
|
|
8
|
+
branchSteps: WorkflowStep[];
|
|
9
|
+
synthesisStep: WorkflowStep;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function getParallelWorkflowStructure(
|
|
13
|
+
definition: WorkflowDefinition
|
|
14
|
+
): ParallelWorkflowStructure | null {
|
|
15
|
+
if (definition.pattern !== "parallel") {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const branchSteps = definition.steps.filter(
|
|
20
|
+
(step) => !step.dependsOn || step.dependsOn.length === 0
|
|
21
|
+
);
|
|
22
|
+
const synthesisSteps = definition.steps.filter(
|
|
23
|
+
(step) => step.dependsOn && step.dependsOn.length > 0
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
if (synthesisSteps.length !== 1) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
branchSteps,
|
|
32
|
+
synthesisStep: synthesisSteps[0],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function buildParallelSynthesisPrompt(input: {
|
|
37
|
+
branchOutputs: Array<{ stepName: string; result: string }>;
|
|
38
|
+
synthesisPrompt: string;
|
|
39
|
+
}): string {
|
|
40
|
+
const sections = input.branchOutputs.map(
|
|
41
|
+
(branch, index) =>
|
|
42
|
+
`Branch ${index + 1} - ${branch.stepName}:\n${branch.result.trim()}`
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
return [
|
|
46
|
+
"You are synthesizing the completed results of parallel research branches.",
|
|
47
|
+
"",
|
|
48
|
+
"Parallel branch outputs:",
|
|
49
|
+
sections.join("\n\n---\n\n"),
|
|
50
|
+
"",
|
|
51
|
+
"---",
|
|
52
|
+
"",
|
|
53
|
+
input.synthesisPrompt,
|
|
54
|
+
].join("\n");
|
|
55
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { SwarmConfig, WorkflowDefinition, WorkflowStep } from "./types";
|
|
2
|
+
|
|
3
|
+
export const MIN_SWARM_WORKERS = 2;
|
|
4
|
+
export const MAX_SWARM_WORKERS = 5;
|
|
5
|
+
export const DEFAULT_SWARM_CONCURRENCY_LIMIT = 2;
|
|
6
|
+
|
|
7
|
+
export interface SwarmWorkflowStructure {
|
|
8
|
+
mayorStep: WorkflowStep;
|
|
9
|
+
workerSteps: WorkflowStep[];
|
|
10
|
+
refineryStep: WorkflowStep;
|
|
11
|
+
workerConcurrencyLimit: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function getSwarmWorkflowStructure(
|
|
15
|
+
definition: WorkflowDefinition
|
|
16
|
+
): SwarmWorkflowStructure | null {
|
|
17
|
+
if (definition.pattern !== "swarm" || definition.steps.length < 4) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const mayorStep = definition.steps[0];
|
|
22
|
+
const refineryStep = definition.steps.at(-1);
|
|
23
|
+
|
|
24
|
+
if (!mayorStep || !refineryStep) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const workerSteps = definition.steps.slice(1, -1);
|
|
29
|
+
if (workerSteps.length < MIN_SWARM_WORKERS) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
mayorStep,
|
|
35
|
+
workerSteps,
|
|
36
|
+
refineryStep,
|
|
37
|
+
workerConcurrencyLimit: normalizeSwarmConcurrencyLimit(
|
|
38
|
+
definition.swarmConfig,
|
|
39
|
+
workerSteps.length
|
|
40
|
+
),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function normalizeSwarmConcurrencyLimit(
|
|
45
|
+
swarmConfig: SwarmConfig | undefined,
|
|
46
|
+
workerCount: number
|
|
47
|
+
): number {
|
|
48
|
+
const rawLimit =
|
|
49
|
+
swarmConfig?.workerConcurrencyLimit ?? DEFAULT_SWARM_CONCURRENCY_LIMIT;
|
|
50
|
+
|
|
51
|
+
return Math.max(1, Math.min(rawLimit, Math.max(workerCount, 1)));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function buildSwarmWorkerPrompt(input: {
|
|
55
|
+
mayorName: string;
|
|
56
|
+
mayorResult: string;
|
|
57
|
+
workerName: string;
|
|
58
|
+
workerPrompt: string;
|
|
59
|
+
}): string {
|
|
60
|
+
return [
|
|
61
|
+
"You are one worker in a governed multi-agent swarm.",
|
|
62
|
+
"",
|
|
63
|
+
`${input.mayorName}:`,
|
|
64
|
+
input.mayorResult.trim(),
|
|
65
|
+
"",
|
|
66
|
+
`${input.workerName} assignment:`,
|
|
67
|
+
input.workerPrompt.trim(),
|
|
68
|
+
"",
|
|
69
|
+
"Complete only your assigned slice. Return concrete findings the refinery can merge.",
|
|
70
|
+
].join("\n");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function buildSwarmRefineryPrompt(input: {
|
|
74
|
+
mayorName: string;
|
|
75
|
+
mayorResult: string;
|
|
76
|
+
workerOutputs: Array<{ stepName: string; result: string }>;
|
|
77
|
+
refineryPrompt: string;
|
|
78
|
+
}): string {
|
|
79
|
+
const workerSections = input.workerOutputs.map(
|
|
80
|
+
(worker, index) =>
|
|
81
|
+
`Worker ${index + 1} - ${worker.stepName}:\n${worker.result.trim()}`
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
return [
|
|
85
|
+
"You are the refinery for a governed multi-agent swarm.",
|
|
86
|
+
"",
|
|
87
|
+
`${input.mayorName}:`,
|
|
88
|
+
input.mayorResult.trim(),
|
|
89
|
+
"",
|
|
90
|
+
"Worker outputs:",
|
|
91
|
+
workerSections.join("\n\n---\n\n"),
|
|
92
|
+
"",
|
|
93
|
+
"---",
|
|
94
|
+
"",
|
|
95
|
+
input.refineryPrompt.trim(),
|
|
96
|
+
].join("\n");
|
|
97
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
export type WorkflowPattern =
|
|
2
|
+
| "sequence"
|
|
3
|
+
| "planner-executor"
|
|
4
|
+
| "checkpoint"
|
|
5
|
+
| "loop"
|
|
6
|
+
| "parallel"
|
|
7
|
+
| "swarm";
|
|
8
|
+
|
|
9
|
+
export interface WorkflowStep {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
prompt: string;
|
|
13
|
+
requiresApproval?: boolean;
|
|
14
|
+
dependsOn?: string[];
|
|
15
|
+
assignedAgent?: string;
|
|
16
|
+
agentProfile?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface LoopConfig {
|
|
20
|
+
maxIterations: number;
|
|
21
|
+
timeBudgetMs?: number;
|
|
22
|
+
assignedAgent?: string;
|
|
23
|
+
agentProfile?: string;
|
|
24
|
+
completionSignals?: string[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface SwarmConfig {
|
|
28
|
+
workerConcurrencyLimit?: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface WorkflowDefinition {
|
|
32
|
+
pattern: WorkflowPattern;
|
|
33
|
+
steps: WorkflowStep[];
|
|
34
|
+
loopConfig?: LoopConfig;
|
|
35
|
+
swarmConfig?: SwarmConfig;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type LoopStopReason =
|
|
39
|
+
| "max_iterations"
|
|
40
|
+
| "time_budget"
|
|
41
|
+
| "agent_signaled"
|
|
42
|
+
| "human_cancel"
|
|
43
|
+
| "human_pause"
|
|
44
|
+
| "error";
|
|
45
|
+
|
|
46
|
+
export interface IterationState {
|
|
47
|
+
iteration: number;
|
|
48
|
+
taskId: string;
|
|
49
|
+
status: "pending" | "running" | "completed" | "failed";
|
|
50
|
+
result?: string;
|
|
51
|
+
error?: string;
|
|
52
|
+
startedAt?: string;
|
|
53
|
+
completedAt?: string;
|
|
54
|
+
durationMs?: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface LoopState {
|
|
58
|
+
currentIteration: number;
|
|
59
|
+
iterations: IterationState[];
|
|
60
|
+
status: "running" | "completed" | "paused" | "failed";
|
|
61
|
+
stopReason?: LoopStopReason;
|
|
62
|
+
startedAt: string;
|
|
63
|
+
completedAt?: string;
|
|
64
|
+
totalDurationMs?: number;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function createInitialLoopState(): LoopState {
|
|
68
|
+
return {
|
|
69
|
+
currentIteration: 0,
|
|
70
|
+
iterations: [],
|
|
71
|
+
status: "running",
|
|
72
|
+
startedAt: new Date().toISOString(),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export type WorkflowStepStatus =
|
|
77
|
+
| "pending"
|
|
78
|
+
| "running"
|
|
79
|
+
| "completed"
|
|
80
|
+
| "failed"
|
|
81
|
+
| "waiting_approval"
|
|
82
|
+
| "waiting_dependencies";
|
|
83
|
+
|
|
84
|
+
export interface StepState {
|
|
85
|
+
stepId: string;
|
|
86
|
+
status: WorkflowStepStatus;
|
|
87
|
+
taskId?: string;
|
|
88
|
+
result?: string;
|
|
89
|
+
error?: string;
|
|
90
|
+
startedAt?: string;
|
|
91
|
+
completedAt?: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface WorkflowState {
|
|
95
|
+
currentStepIndex: number;
|
|
96
|
+
stepStates: StepState[];
|
|
97
|
+
status: "running" | "completed" | "failed" | "paused";
|
|
98
|
+
startedAt: string;
|
|
99
|
+
completedAt?: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function createInitialState(definition: WorkflowDefinition): WorkflowState {
|
|
103
|
+
return {
|
|
104
|
+
currentStepIndex: 0,
|
|
105
|
+
stepStates: definition.steps.map((step) => ({
|
|
106
|
+
stepId: step.id,
|
|
107
|
+
status: "pending",
|
|
108
|
+
})),
|
|
109
|
+
status: "running",
|
|
110
|
+
startedAt: new Date().toISOString(),
|
|
111
|
+
};
|
|
112
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": [
|
|
5
|
+
"dom",
|
|
6
|
+
"dom.iterable",
|
|
7
|
+
"esnext"
|
|
8
|
+
],
|
|
9
|
+
"allowJs": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"strict": true,
|
|
12
|
+
"noEmit": true,
|
|
13
|
+
"esModuleInterop": true,
|
|
14
|
+
"module": "esnext",
|
|
15
|
+
"moduleResolution": "bundler",
|
|
16
|
+
"resolveJsonModule": true,
|
|
17
|
+
"isolatedModules": true,
|
|
18
|
+
"jsx": "react-jsx",
|
|
19
|
+
"incremental": true,
|
|
20
|
+
"paths": {
|
|
21
|
+
"@/*": [
|
|
22
|
+
"./src/*"
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
"plugins": [
|
|
26
|
+
{
|
|
27
|
+
"name": "next"
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
"include": [
|
|
32
|
+
"next-env.d.ts",
|
|
33
|
+
"**/*.ts",
|
|
34
|
+
"**/*.tsx",
|
|
35
|
+
".next/types/**/*.ts",
|
|
36
|
+
".next/dev/types/**/*.ts"
|
|
37
|
+
],
|
|
38
|
+
"exclude": [
|
|
39
|
+
"node_modules"
|
|
40
|
+
]
|
|
41
|
+
}
|