stagent 0.9.2 → 0.9.5
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/dist/cli.js +36 -1
- package/docs/superpowers/specs/2026-04-06-workflow-intelligence-stack-design.md +388 -0
- package/package.json +1 -1
- package/src/app/api/license/route.ts +3 -2
- package/src/app/api/workflows/[id]/debug/route.ts +18 -0
- package/src/app/api/workflows/[id]/execute/route.ts +39 -8
- package/src/app/api/workflows/optimize/route.ts +30 -0
- package/src/app/layout.tsx +4 -2
- package/src/components/chat/chat-message-markdown.tsx +78 -3
- package/src/components/chat/chat-message.tsx +12 -4
- package/src/components/settings/cloud-account-section.tsx +14 -12
- package/src/components/workflows/error-timeline.tsx +83 -0
- package/src/components/workflows/step-live-metrics.tsx +182 -0
- package/src/components/workflows/step-progress-bar.tsx +77 -0
- package/src/components/workflows/workflow-debug-panel.tsx +192 -0
- package/src/components/workflows/workflow-optimizer-panel.tsx +227 -0
- package/src/lib/agents/claude-agent.ts +4 -4
- package/src/lib/agents/runtime/anthropic-direct.ts +3 -3
- package/src/lib/agents/runtime/catalog.ts +30 -1
- package/src/lib/agents/runtime/openai-direct.ts +3 -3
- package/src/lib/billing/products.ts +6 -6
- package/src/lib/book/chapter-mapping.ts +6 -0
- package/src/lib/book/content.ts +10 -0
- package/src/lib/book/reading-paths.ts +1 -1
- package/src/lib/chat/__tests__/engine-stream-helpers.test.ts +57 -0
- package/src/lib/chat/engine.ts +68 -7
- package/src/lib/chat/stagent-tools.ts +2 -0
- package/src/lib/chat/tools/runtime-tools.ts +28 -0
- package/src/lib/chat/tools/schedule-tools.ts +44 -1
- package/src/lib/chat/tools/settings-tools.ts +40 -10
- package/src/lib/chat/tools/workflow-tools.ts +93 -4
- package/src/lib/chat/types.ts +21 -0
- package/src/lib/data/clear.ts +3 -0
- package/src/lib/db/bootstrap.ts +38 -0
- package/src/lib/db/migrations/0022_workflow_intelligence_phase1.sql +5 -0
- package/src/lib/db/migrations/0023_add_execution_stats.sql +15 -0
- package/src/lib/db/schema.ts +41 -1
- package/src/lib/license/__tests__/manager.test.ts +64 -0
- package/src/lib/license/manager.ts +80 -25
- package/src/lib/schedules/__tests__/interval-parser.test.ts +87 -0
- package/src/lib/schedules/__tests__/prompt-analyzer.test.ts +51 -0
- package/src/lib/schedules/interval-parser.ts +187 -0
- package/src/lib/schedules/prompt-analyzer.ts +87 -0
- package/src/lib/schedules/scheduler.ts +179 -9
- package/src/lib/workflows/cost-estimator.ts +141 -0
- package/src/lib/workflows/engine.ts +245 -45
- package/src/lib/workflows/error-analysis.ts +249 -0
- package/src/lib/workflows/execution-stats.ts +252 -0
- package/src/lib/workflows/optimizer.ts +193 -0
- package/src/lib/workflows/types.ts +6 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from "react";
|
|
4
|
+
import { ChevronDown, ChevronUp, AlertTriangle, RefreshCw, FileText } from "lucide-react";
|
|
5
|
+
import { Button } from "@/components/ui/button";
|
|
6
|
+
import { Badge } from "@/components/ui/badge";
|
|
7
|
+
import { ErrorTimeline } from "./error-timeline";
|
|
8
|
+
|
|
9
|
+
interface DebugAnalysis {
|
|
10
|
+
rootCause: {
|
|
11
|
+
type: "budget_exceeded" | "timeout" | "transient" | "unknown";
|
|
12
|
+
summary: string;
|
|
13
|
+
};
|
|
14
|
+
timeline: Array<{
|
|
15
|
+
timestamp: string;
|
|
16
|
+
event: string;
|
|
17
|
+
severity: "success" | "warning" | "error";
|
|
18
|
+
details: string;
|
|
19
|
+
}>;
|
|
20
|
+
suggestions: Array<{
|
|
21
|
+
tier: "quick" | "better" | "best";
|
|
22
|
+
title: string;
|
|
23
|
+
description: string;
|
|
24
|
+
}>;
|
|
25
|
+
stepErrors: Array<{
|
|
26
|
+
stepId: string;
|
|
27
|
+
stepName: string;
|
|
28
|
+
error: string;
|
|
29
|
+
}>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const TIER_STYLES = {
|
|
33
|
+
quick: "bg-emerald-50 text-emerald-700 border-emerald-200",
|
|
34
|
+
better: "bg-amber-50 text-amber-700 border-amber-200",
|
|
35
|
+
best: "bg-blue-50 text-blue-700 border-blue-200",
|
|
36
|
+
} as const;
|
|
37
|
+
|
|
38
|
+
const CAUSE_LABELS: Record<string, string> = {
|
|
39
|
+
budget_exceeded: "Budget Exceeded",
|
|
40
|
+
timeout: "Timeout / Turn Limit",
|
|
41
|
+
transient: "Transient Error",
|
|
42
|
+
unknown: "Unknown",
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
interface WorkflowDebugPanelProps {
|
|
46
|
+
workflowId: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function WorkflowDebugPanel({ workflowId }: WorkflowDebugPanelProps) {
|
|
50
|
+
const [open, setOpen] = useState(false);
|
|
51
|
+
const [analysis, setAnalysis] = useState<DebugAnalysis | null>(null);
|
|
52
|
+
const [loading, setLoading] = useState(false);
|
|
53
|
+
const [error, setError] = useState<string | null>(null);
|
|
54
|
+
const [retrying, setRetrying] = useState(false);
|
|
55
|
+
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (!open || analysis) return;
|
|
58
|
+
|
|
59
|
+
setLoading(true);
|
|
60
|
+
fetch(`/api/workflows/${workflowId}/debug`)
|
|
61
|
+
.then((res) => res.json())
|
|
62
|
+
.then((data) => {
|
|
63
|
+
if (data.error) {
|
|
64
|
+
setError(data.error);
|
|
65
|
+
} else {
|
|
66
|
+
setAnalysis(data);
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
.catch((err) => setError(err.message))
|
|
70
|
+
.finally(() => setLoading(false));
|
|
71
|
+
}, [open, workflowId, analysis]);
|
|
72
|
+
|
|
73
|
+
const handleRerun = async () => {
|
|
74
|
+
setRetrying(true);
|
|
75
|
+
try {
|
|
76
|
+
await fetch(`/api/workflows/${workflowId}/execute`, { method: "POST" });
|
|
77
|
+
} finally {
|
|
78
|
+
setRetrying(false);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<div className="rounded-lg border border-red-200 bg-white overflow-hidden">
|
|
84
|
+
<button
|
|
85
|
+
onClick={() => setOpen(!open)}
|
|
86
|
+
className="flex w-full items-center justify-between px-4 py-3 text-left hover:bg-red-50/50 transition-colors"
|
|
87
|
+
>
|
|
88
|
+
<div className="flex items-center gap-2">
|
|
89
|
+
<AlertTriangle className="h-4 w-4 text-red-500" />
|
|
90
|
+
<span className="text-sm font-medium">Debug Analysis</span>
|
|
91
|
+
</div>
|
|
92
|
+
{open ? (
|
|
93
|
+
<ChevronUp className="h-4 w-4 text-muted-foreground" />
|
|
94
|
+
) : (
|
|
95
|
+
<ChevronDown className="h-4 w-4 text-muted-foreground" />
|
|
96
|
+
)}
|
|
97
|
+
</button>
|
|
98
|
+
|
|
99
|
+
{open && (
|
|
100
|
+
<div className="border-t px-4 py-4 space-y-4">
|
|
101
|
+
{loading && (
|
|
102
|
+
<div className="space-y-3">
|
|
103
|
+
<div className="h-16 rounded-lg bg-muted animate-pulse" />
|
|
104
|
+
<div className="h-32 rounded-lg bg-muted animate-pulse" />
|
|
105
|
+
</div>
|
|
106
|
+
)}
|
|
107
|
+
|
|
108
|
+
{error && (
|
|
109
|
+
<p className="text-sm text-red-600">Failed to load analysis: {error}</p>
|
|
110
|
+
)}
|
|
111
|
+
|
|
112
|
+
{analysis && (
|
|
113
|
+
<>
|
|
114
|
+
{/* Root cause summary */}
|
|
115
|
+
<div className="rounded-lg border-l-4 border-l-red-500 border border-red-100 bg-red-50/30 p-3">
|
|
116
|
+
<div className="flex items-center gap-2 mb-1">
|
|
117
|
+
<Badge variant="destructive" className="text-xs">
|
|
118
|
+
{CAUSE_LABELS[analysis.rootCause.type] ?? analysis.rootCause.type}
|
|
119
|
+
</Badge>
|
|
120
|
+
</div>
|
|
121
|
+
<p className="text-sm text-muted-foreground">
|
|
122
|
+
{analysis.rootCause.summary}
|
|
123
|
+
</p>
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
{/* Step errors */}
|
|
127
|
+
{analysis.stepErrors.length > 0 && (
|
|
128
|
+
<div className="space-y-2">
|
|
129
|
+
<h4 className="text-xs font-medium text-muted-foreground uppercase tracking-wide">
|
|
130
|
+
Failed Steps
|
|
131
|
+
</h4>
|
|
132
|
+
{analysis.stepErrors.map((se) => (
|
|
133
|
+
<div key={se.stepId} className="rounded border p-2 text-xs">
|
|
134
|
+
<span className="font-medium">{se.stepName}:</span>{" "}
|
|
135
|
+
<span className="text-muted-foreground">{se.error}</span>
|
|
136
|
+
</div>
|
|
137
|
+
))}
|
|
138
|
+
</div>
|
|
139
|
+
)}
|
|
140
|
+
|
|
141
|
+
{/* Timeline */}
|
|
142
|
+
{analysis.timeline.length > 0 && (
|
|
143
|
+
<div>
|
|
144
|
+
<h4 className="text-xs font-medium text-muted-foreground uppercase tracking-wide mb-2">
|
|
145
|
+
Event Timeline
|
|
146
|
+
</h4>
|
|
147
|
+
<ErrorTimeline events={analysis.timeline} />
|
|
148
|
+
</div>
|
|
149
|
+
)}
|
|
150
|
+
|
|
151
|
+
{/* Suggestions */}
|
|
152
|
+
{analysis.suggestions.length > 0 && (
|
|
153
|
+
<div>
|
|
154
|
+
<h4 className="text-xs font-medium text-muted-foreground uppercase tracking-wide mb-2">
|
|
155
|
+
Fix Suggestions
|
|
156
|
+
</h4>
|
|
157
|
+
<div className="space-y-2">
|
|
158
|
+
{analysis.suggestions.map((s, i) => (
|
|
159
|
+
<div key={i} className={`rounded-lg border p-3 ${TIER_STYLES[s.tier]}`}>
|
|
160
|
+
<div className="flex items-center gap-2 mb-1">
|
|
161
|
+
<Badge variant="outline" className="text-[10px] uppercase">
|
|
162
|
+
{s.tier}
|
|
163
|
+
</Badge>
|
|
164
|
+
<span className="text-sm font-medium">{s.title}</span>
|
|
165
|
+
</div>
|
|
166
|
+
<p className="text-xs opacity-80">{s.description}</p>
|
|
167
|
+
</div>
|
|
168
|
+
))}
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
)}
|
|
172
|
+
|
|
173
|
+
{/* Actions */}
|
|
174
|
+
<div className="flex gap-2 pt-2 border-t">
|
|
175
|
+
<Button size="sm" onClick={handleRerun} disabled={retrying}>
|
|
176
|
+
<RefreshCw className={`h-3.5 w-3.5 mr-1.5 ${retrying ? "animate-spin" : ""}`} />
|
|
177
|
+
Re-run Workflow
|
|
178
|
+
</Button>
|
|
179
|
+
<Button size="sm" variant="outline" asChild>
|
|
180
|
+
<a href={`/workflows?id=${workflowId}&tab=logs`}>
|
|
181
|
+
<FileText className="h-3.5 w-3.5 mr-1.5" />
|
|
182
|
+
View Full Logs
|
|
183
|
+
</a>
|
|
184
|
+
</Button>
|
|
185
|
+
</div>
|
|
186
|
+
</>
|
|
187
|
+
)}
|
|
188
|
+
</div>
|
|
189
|
+
)}
|
|
190
|
+
</div>
|
|
191
|
+
);
|
|
192
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect, useRef, useCallback } from "react";
|
|
4
|
+
import { Button } from "@/components/ui/button";
|
|
5
|
+
import { Badge } from "@/components/ui/badge";
|
|
6
|
+
import { Skeleton } from "@/components/ui/skeleton";
|
|
7
|
+
import {
|
|
8
|
+
Sparkles,
|
|
9
|
+
FileText,
|
|
10
|
+
DollarSign,
|
|
11
|
+
Cpu,
|
|
12
|
+
GitBranch,
|
|
13
|
+
X,
|
|
14
|
+
} from "lucide-react";
|
|
15
|
+
import type { OptimizationSuggestion } from "@/lib/workflows/optimizer";
|
|
16
|
+
|
|
17
|
+
interface WorkflowOptimizerPanelProps {
|
|
18
|
+
definition: Record<string, unknown> | null;
|
|
19
|
+
workflowId?: string;
|
|
20
|
+
onApplySuggestion?: (suggestion: {
|
|
21
|
+
type: string;
|
|
22
|
+
payload: Record<string, unknown>;
|
|
23
|
+
}) => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const SUGGESTION_ICONS: Record<string, typeof FileText> = {
|
|
27
|
+
document_binding: FileText,
|
|
28
|
+
budget_estimate: DollarSign,
|
|
29
|
+
runtime_recommendation: Cpu,
|
|
30
|
+
pattern_insight: GitBranch,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const SUGGESTION_LABELS: Record<string, string> = {
|
|
34
|
+
document_binding: "Documents",
|
|
35
|
+
budget_estimate: "Budget",
|
|
36
|
+
runtime_recommendation: "Runtime",
|
|
37
|
+
pattern_insight: "Pattern",
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export function WorkflowOptimizerPanel({
|
|
41
|
+
definition,
|
|
42
|
+
workflowId,
|
|
43
|
+
onApplySuggestion,
|
|
44
|
+
}: WorkflowOptimizerPanelProps) {
|
|
45
|
+
const [suggestions, setSuggestions] = useState<OptimizationSuggestion[]>([]);
|
|
46
|
+
const [dismissed, setDismissed] = useState<Set<number>>(new Set());
|
|
47
|
+
const [loading, setLoading] = useState(false);
|
|
48
|
+
const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
49
|
+
|
|
50
|
+
const fetchSuggestions = useCallback(async () => {
|
|
51
|
+
if (!definition) {
|
|
52
|
+
setSuggestions([]);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
setLoading(true);
|
|
57
|
+
try {
|
|
58
|
+
const res = await fetch("/api/workflows/optimize", {
|
|
59
|
+
method: "POST",
|
|
60
|
+
headers: { "Content-Type": "application/json" },
|
|
61
|
+
body: JSON.stringify({ definition, workflowId }),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
if (res.ok) {
|
|
65
|
+
const data = await res.json();
|
|
66
|
+
setSuggestions(data.suggestions ?? []);
|
|
67
|
+
setDismissed(new Set());
|
|
68
|
+
} else {
|
|
69
|
+
setSuggestions([]);
|
|
70
|
+
}
|
|
71
|
+
} catch {
|
|
72
|
+
setSuggestions([]);
|
|
73
|
+
} finally {
|
|
74
|
+
setLoading(false);
|
|
75
|
+
}
|
|
76
|
+
}, [definition, workflowId]);
|
|
77
|
+
|
|
78
|
+
// Debounced fetch on definition changes
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
if (debounceRef.current) {
|
|
81
|
+
clearTimeout(debounceRef.current);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
debounceRef.current = setTimeout(() => {
|
|
85
|
+
fetchSuggestions();
|
|
86
|
+
}, 500);
|
|
87
|
+
|
|
88
|
+
return () => {
|
|
89
|
+
if (debounceRef.current) {
|
|
90
|
+
clearTimeout(debounceRef.current);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}, [fetchSuggestions]);
|
|
94
|
+
|
|
95
|
+
const handleDismiss = (index: number) => {
|
|
96
|
+
setDismissed((prev) => new Set(prev).add(index));
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const handleApply = (suggestion: OptimizationSuggestion) => {
|
|
100
|
+
if (suggestion.action && onApplySuggestion) {
|
|
101
|
+
onApplySuggestion({
|
|
102
|
+
type: suggestion.action.type,
|
|
103
|
+
payload: suggestion.action.payload,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const visibleSuggestions = suggestions.filter(
|
|
109
|
+
(_, i) => !dismissed.has(i)
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<div className="space-y-3">
|
|
114
|
+
{/* Header */}
|
|
115
|
+
<div className="flex items-center gap-2">
|
|
116
|
+
<Sparkles className="h-4 w-4 text-muted-foreground" />
|
|
117
|
+
<h3 className="text-sm font-medium">Optimizer</h3>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
{/* Loading state */}
|
|
121
|
+
{loading && (
|
|
122
|
+
<div className="space-y-2">
|
|
123
|
+
<Skeleton className="h-20 w-full rounded-lg" />
|
|
124
|
+
<Skeleton className="h-20 w-full rounded-lg" />
|
|
125
|
+
</div>
|
|
126
|
+
)}
|
|
127
|
+
|
|
128
|
+
{/* Empty state */}
|
|
129
|
+
{!loading && visibleSuggestions.length === 0 && (
|
|
130
|
+
<p className="text-xs text-muted-foreground py-4 text-center">
|
|
131
|
+
Run a few workflows to get optimization suggestions
|
|
132
|
+
</p>
|
|
133
|
+
)}
|
|
134
|
+
|
|
135
|
+
{/* Suggestion cards */}
|
|
136
|
+
{!loading &&
|
|
137
|
+
visibleSuggestions.map((suggestion, visibleIndex) => {
|
|
138
|
+
const originalIndex = suggestions.indexOf(suggestion);
|
|
139
|
+
const Icon = SUGGESTION_ICONS[suggestion.type] ?? Sparkles;
|
|
140
|
+
const label = SUGGESTION_LABELS[suggestion.type] ?? suggestion.type;
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<div
|
|
144
|
+
key={originalIndex}
|
|
145
|
+
className="rounded-lg border border-border bg-background p-3 space-y-2"
|
|
146
|
+
>
|
|
147
|
+
{/* Type badge + dismiss */}
|
|
148
|
+
<div className="flex items-center justify-between">
|
|
149
|
+
<div className="flex items-center gap-1.5">
|
|
150
|
+
<Icon className="h-3.5 w-3.5 text-muted-foreground" />
|
|
151
|
+
<Badge
|
|
152
|
+
variant="secondary"
|
|
153
|
+
className="text-[10px] px-1.5 py-0"
|
|
154
|
+
>
|
|
155
|
+
{label}
|
|
156
|
+
</Badge>
|
|
157
|
+
</div>
|
|
158
|
+
<button
|
|
159
|
+
type="button"
|
|
160
|
+
onClick={() => handleDismiss(originalIndex)}
|
|
161
|
+
className="text-muted-foreground hover:text-foreground transition-colors"
|
|
162
|
+
>
|
|
163
|
+
<X className="h-3.5 w-3.5" />
|
|
164
|
+
</button>
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
{/* Title */}
|
|
168
|
+
<p className="text-sm font-medium leading-tight">
|
|
169
|
+
{suggestion.title}
|
|
170
|
+
</p>
|
|
171
|
+
|
|
172
|
+
{/* Description */}
|
|
173
|
+
<p className="text-xs text-muted-foreground leading-relaxed">
|
|
174
|
+
{suggestion.description}
|
|
175
|
+
</p>
|
|
176
|
+
|
|
177
|
+
{/* Budget progress bar (budget_estimate type only) */}
|
|
178
|
+
{suggestion.type === "budget_estimate" &&
|
|
179
|
+
typeof suggestion.data.totalEstimatedCostUsd === "number" &&
|
|
180
|
+
typeof suggestion.data.totalBudgetCapUsd === "number" &&
|
|
181
|
+
(suggestion.data.totalBudgetCapUsd as number) > 0 && (
|
|
182
|
+
<div className="space-y-1">
|
|
183
|
+
<div className="h-1.5 w-full rounded-full bg-muted overflow-hidden">
|
|
184
|
+
<div
|
|
185
|
+
className={`h-full rounded-full transition-all ${
|
|
186
|
+
suggestion.data.overBudget
|
|
187
|
+
? "bg-destructive"
|
|
188
|
+
: "bg-primary"
|
|
189
|
+
}`}
|
|
190
|
+
style={{
|
|
191
|
+
width: `${Math.min(
|
|
192
|
+
100,
|
|
193
|
+
((suggestion.data.totalEstimatedCostUsd as number) /
|
|
194
|
+
(suggestion.data.totalBudgetCapUsd as number)) *
|
|
195
|
+
100
|
|
196
|
+
)}%`,
|
|
197
|
+
}}
|
|
198
|
+
/>
|
|
199
|
+
</div>
|
|
200
|
+
<div className="flex justify-between text-[10px] text-muted-foreground">
|
|
201
|
+
<span>
|
|
202
|
+
${(suggestion.data.totalEstimatedCostUsd as number).toFixed(4)}
|
|
203
|
+
</span>
|
|
204
|
+
<span>
|
|
205
|
+
${(suggestion.data.totalBudgetCapUsd as number).toFixed(2)} cap
|
|
206
|
+
</span>
|
|
207
|
+
</div>
|
|
208
|
+
</div>
|
|
209
|
+
)}
|
|
210
|
+
|
|
211
|
+
{/* Action button */}
|
|
212
|
+
{suggestion.action && onApplySuggestion && (
|
|
213
|
+
<Button
|
|
214
|
+
size="sm"
|
|
215
|
+
variant="default"
|
|
216
|
+
className="h-7 text-xs w-full"
|
|
217
|
+
onClick={() => handleApply(suggestion)}
|
|
218
|
+
>
|
|
219
|
+
{suggestion.action.label}
|
|
220
|
+
</Button>
|
|
221
|
+
)}
|
|
222
|
+
</div>
|
|
223
|
+
);
|
|
224
|
+
})}
|
|
225
|
+
</div>
|
|
226
|
+
);
|
|
227
|
+
}
|
|
@@ -454,8 +454,8 @@ export async function executeClaudeTask(taskId: string): Promise<void> {
|
|
|
454
454
|
: { type: "preset" as const, preset: "claude_code" as const },
|
|
455
455
|
// F9: Bounded turn limit from profile or default
|
|
456
456
|
maxTurns: ctx.maxTurns,
|
|
457
|
-
// F4: Per-execution budget cap
|
|
458
|
-
maxBudgetUsd: DEFAULT_MAX_BUDGET_USD,
|
|
457
|
+
// F4: Per-execution budget cap — use task-specific override if set
|
|
458
|
+
maxBudgetUsd: task.maxBudgetUsd ?? DEFAULT_MAX_BUDGET_USD,
|
|
459
459
|
...(ctx.payload?.allowedTools && { allowedTools: ctx.payload.allowedTools }),
|
|
460
460
|
...(Object.keys(mergedMcpServers).length > 0 && {
|
|
461
461
|
mcpServers: mergedMcpServers,
|
|
@@ -568,8 +568,8 @@ export async function resumeClaudeTask(taskId: string): Promise<void> {
|
|
|
568
568
|
: { type: "preset" as const, preset: "claude_code" as const },
|
|
569
569
|
// F9: Bounded turn limit from profile or default
|
|
570
570
|
maxTurns: ctx.maxTurns,
|
|
571
|
-
// F4: Per-execution budget cap
|
|
572
|
-
maxBudgetUsd: DEFAULT_MAX_BUDGET_USD,
|
|
571
|
+
// F4: Per-execution budget cap — use task-specific override if set
|
|
572
|
+
maxBudgetUsd: task.maxBudgetUsd ?? DEFAULT_MAX_BUDGET_USD,
|
|
573
573
|
...(ctx.payload?.allowedTools && { allowedTools: ctx.payload.allowedTools }),
|
|
574
574
|
...(Object.keys(mergedMcpServers).length > 0 && {
|
|
575
575
|
mcpServers: mergedMcpServers,
|
|
@@ -145,7 +145,7 @@ async function callAnthropicModel(
|
|
|
145
145
|
emitEvent: (event: AgentStreamEvent) => void,
|
|
146
146
|
options: AnthropicCallOptions = {},
|
|
147
147
|
): Promise<ModelTurnResult> {
|
|
148
|
-
const modelId = options.modelId ?? "
|
|
148
|
+
const modelId = options.modelId ?? getRuntimeCatalogEntry("anthropic-direct").models.default;
|
|
149
149
|
const maxTokens = options.maxTokens ?? 8192;
|
|
150
150
|
|
|
151
151
|
// Build system content with optional caching
|
|
@@ -315,7 +315,7 @@ async function executeAnthropicDirectTask(taskId: string, isResume = false): Pro
|
|
|
315
315
|
|
|
316
316
|
// Resolve model from settings
|
|
317
317
|
const { getSetting } = await import("@/lib/settings/helpers");
|
|
318
|
-
const modelId = (await getSetting("anthropic_direct_model")) ?? "
|
|
318
|
+
const modelId = (await getSetting("anthropic_direct_model")) ?? getRuntimeCatalogEntry("anthropic-direct").models.default;
|
|
319
319
|
|
|
320
320
|
const maxTurns = ctx.maxTurns ?? DEFAULT_MAX_TURNS;
|
|
321
321
|
|
|
@@ -407,7 +407,7 @@ async function executeAnthropicDirectTask(taskId: string, isResume = false): Pro
|
|
|
407
407
|
},
|
|
408
408
|
|
|
409
409
|
maxTurns,
|
|
410
|
-
maxBudgetUsd: DEFAULT_MAX_BUDGET_USD,
|
|
410
|
+
maxBudgetUsd: task.maxBudgetUsd ?? DEFAULT_MAX_BUDGET_USD,
|
|
411
411
|
signal: abortController.signal,
|
|
412
412
|
});
|
|
413
413
|
|
|
@@ -21,12 +21,21 @@ export interface RuntimeCapabilities {
|
|
|
21
21
|
authHealthCheck: boolean;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
export interface RuntimeModelConfig {
|
|
25
|
+
/** Default model ID for this runtime */
|
|
26
|
+
default: string;
|
|
27
|
+
/** All supported model IDs for this runtime */
|
|
28
|
+
supported: string[];
|
|
29
|
+
}
|
|
30
|
+
|
|
24
31
|
export interface RuntimeCatalogEntry {
|
|
25
32
|
id: AgentRuntimeId;
|
|
26
33
|
label: string;
|
|
27
34
|
description: string;
|
|
28
35
|
providerId: "anthropic" | "openai" | "ollama";
|
|
29
36
|
capabilities: RuntimeCapabilities;
|
|
37
|
+
/** Model catalog — default and supported model IDs for this runtime */
|
|
38
|
+
models: RuntimeModelConfig;
|
|
30
39
|
}
|
|
31
40
|
|
|
32
41
|
const RUNTIME_CATALOG: Record<AgentRuntimeId, RuntimeCatalogEntry> = {
|
|
@@ -45,6 +54,10 @@ const RUNTIME_CATALOG: Record<AgentRuntimeId, RuntimeCatalogEntry> = {
|
|
|
45
54
|
profileAssist: true,
|
|
46
55
|
authHealthCheck: true,
|
|
47
56
|
},
|
|
57
|
+
models: {
|
|
58
|
+
default: "sonnet",
|
|
59
|
+
supported: ["haiku", "sonnet", "opus"],
|
|
60
|
+
},
|
|
48
61
|
},
|
|
49
62
|
"openai-codex-app-server": {
|
|
50
63
|
id: "openai-codex-app-server",
|
|
@@ -55,12 +68,16 @@ const RUNTIME_CATALOG: Record<AgentRuntimeId, RuntimeCatalogEntry> = {
|
|
|
55
68
|
resume: true,
|
|
56
69
|
cancel: true,
|
|
57
70
|
approvals: true,
|
|
58
|
-
mcpServers: false,
|
|
71
|
+
mcpServers: false,
|
|
59
72
|
profileTests: false,
|
|
60
73
|
taskAssist: true,
|
|
61
74
|
profileAssist: false,
|
|
62
75
|
authHealthCheck: true,
|
|
63
76
|
},
|
|
77
|
+
models: {
|
|
78
|
+
default: "gpt-5.4",
|
|
79
|
+
supported: ["gpt-5.4", "gpt-5.4-mini", "gpt-5.3-codex"],
|
|
80
|
+
},
|
|
64
81
|
},
|
|
65
82
|
"anthropic-direct": {
|
|
66
83
|
id: "anthropic-direct",
|
|
@@ -77,6 +94,10 @@ const RUNTIME_CATALOG: Record<AgentRuntimeId, RuntimeCatalogEntry> = {
|
|
|
77
94
|
profileAssist: true,
|
|
78
95
|
authHealthCheck: true,
|
|
79
96
|
},
|
|
97
|
+
models: {
|
|
98
|
+
default: "claude-sonnet-4-20250514",
|
|
99
|
+
supported: ["claude-haiku-4-5-20251001", "claude-sonnet-4-20250514", "claude-opus-4-20250514"],
|
|
100
|
+
},
|
|
80
101
|
},
|
|
81
102
|
"openai-direct": {
|
|
82
103
|
id: "openai-direct",
|
|
@@ -93,6 +114,10 @@ const RUNTIME_CATALOG: Record<AgentRuntimeId, RuntimeCatalogEntry> = {
|
|
|
93
114
|
profileAssist: false,
|
|
94
115
|
authHealthCheck: true,
|
|
95
116
|
},
|
|
117
|
+
models: {
|
|
118
|
+
default: "gpt-4.1",
|
|
119
|
+
supported: ["gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano"],
|
|
120
|
+
},
|
|
96
121
|
},
|
|
97
122
|
ollama: {
|
|
98
123
|
id: "ollama",
|
|
@@ -109,6 +134,10 @@ const RUNTIME_CATALOG: Record<AgentRuntimeId, RuntimeCatalogEntry> = {
|
|
|
109
134
|
profileAssist: false,
|
|
110
135
|
authHealthCheck: true,
|
|
111
136
|
},
|
|
137
|
+
models: {
|
|
138
|
+
default: "llama3",
|
|
139
|
+
supported: [], // Dynamic — populated from Ollama API at runtime
|
|
140
|
+
},
|
|
112
141
|
},
|
|
113
142
|
};
|
|
114
143
|
|
|
@@ -85,7 +85,7 @@ async function callOpenAIModel(
|
|
|
85
85
|
emitEvent: (event: AgentStreamEvent) => void,
|
|
86
86
|
options: OpenAICallOptions = {},
|
|
87
87
|
): Promise<ModelTurnResult & { responseId?: string }> {
|
|
88
|
-
const modelId = options.modelId ?? "
|
|
88
|
+
const modelId = options.modelId ?? getRuntimeCatalogEntry("openai-direct").models.default;
|
|
89
89
|
|
|
90
90
|
// Build tool array: Stagent function tools + enabled server-side tools
|
|
91
91
|
const serverToolConfig = options.serverTools ?? { web_search_preview: true };
|
|
@@ -231,7 +231,7 @@ async function executeOpenAIDirectTask(taskId: string, isResume = false): Promis
|
|
|
231
231
|
|
|
232
232
|
// Resolve model
|
|
233
233
|
const { getSetting } = await import("@/lib/settings/helpers");
|
|
234
|
-
const modelId = (await getSetting("openai_direct_model")) ?? "
|
|
234
|
+
const modelId = (await getSetting("openai_direct_model")) ?? getRuntimeCatalogEntry("openai-direct").models.default;
|
|
235
235
|
const maxTurns = ctx.maxTurns ?? DEFAULT_MAX_TURNS;
|
|
236
236
|
|
|
237
237
|
// For resume: load previous response ID
|
|
@@ -324,7 +324,7 @@ async function executeOpenAIDirectTask(taskId: string, isResume = false): Promis
|
|
|
324
324
|
},
|
|
325
325
|
|
|
326
326
|
maxTurns,
|
|
327
|
-
maxBudgetUsd: DEFAULT_MAX_BUDGET_USD,
|
|
327
|
+
maxBudgetUsd: task.maxBudgetUsd ?? DEFAULT_MAX_BUDGET_USD,
|
|
328
328
|
signal: abortController.signal,
|
|
329
329
|
});
|
|
330
330
|
|
|
@@ -35,8 +35,8 @@ export const STRIPE_PRODUCTS: StripeProduct[] = [
|
|
|
35
35
|
annual: { id: "price_1TJ2d5RCxnzBPkIXjjiyc7lb", amount: 19000, currency: "usd" },
|
|
36
36
|
},
|
|
37
37
|
paymentLinks: {
|
|
38
|
-
monthly: "https://buy.
|
|
39
|
-
annual: "https://buy.
|
|
38
|
+
monthly: "https://buy.stagent.io/fZufZjgKC4q9azrgDzdwc06",
|
|
39
|
+
annual: "https://buy.stagent.io/bJe00l1PI7Cl7nf1IFdwc0b",
|
|
40
40
|
},
|
|
41
41
|
},
|
|
42
42
|
{
|
|
@@ -48,8 +48,8 @@ export const STRIPE_PRODUCTS: StripeProduct[] = [
|
|
|
48
48
|
annual: { id: "price_1TJ2e1RCxnzBPkIXODs5fZW2", amount: 49000, currency: "usd" },
|
|
49
49
|
},
|
|
50
50
|
paymentLinks: {
|
|
51
|
-
monthly: "https://buy.
|
|
52
|
-
annual: "https://buy.
|
|
51
|
+
monthly: "https://buy.stagent.io/aFa4gB0LE9Kt22Vevrdwc07",
|
|
52
|
+
annual: "https://buy.stagent.io/bJe6oJdyq2i1bDv1IFdwc0a",
|
|
53
53
|
},
|
|
54
54
|
},
|
|
55
55
|
{
|
|
@@ -61,8 +61,8 @@ export const STRIPE_PRODUCTS: StripeProduct[] = [
|
|
|
61
61
|
annual: { id: "price_1TJ2evRCxnzBPkIXqIRaDxQp", amount: 99000, currency: "usd" },
|
|
62
62
|
},
|
|
63
63
|
paymentLinks: {
|
|
64
|
-
monthly: "https://buy.
|
|
65
|
-
annual: "https://buy.
|
|
64
|
+
monthly: "https://buy.stagent.io/9B628t2TM5udazr72Zdwc08",
|
|
65
|
+
annual: "https://buy.stagent.io/dRmfZjbqicWF5f7873dwc09",
|
|
66
66
|
},
|
|
67
67
|
},
|
|
68
68
|
];
|
|
@@ -18,6 +18,7 @@ export const CHAPTER_SLUGS: Record<string, string> = {
|
|
|
18
18
|
"ch-10": "ch-10-the-world-model",
|
|
19
19
|
"ch-11": "ch-11-the-machine-that-builds-machines",
|
|
20
20
|
"ch-12": "ch-12-the-road-ahead",
|
|
21
|
+
"ch-13": "ch-13-the-wealth-manager",
|
|
21
22
|
};
|
|
22
23
|
|
|
23
24
|
interface ChapterMapping {
|
|
@@ -99,6 +100,11 @@ export const CHAPTER_MAPPING: Record<string, ChapterMapping> = {
|
|
|
99
100
|
sourceFiles: [],
|
|
100
101
|
caseStudies: ["stripe-minions", "ramp-background-agent", "harvey-legal-is-next", "sequoa-hierarchy-to-intelligence", "karpathy-one-gpu-research-lab", "making-machine-that-builds-machines"],
|
|
101
102
|
},
|
|
103
|
+
"ch-13": {
|
|
104
|
+
docs: ["workflows", "profiles", "schedules"],
|
|
105
|
+
sourceFiles: ["src/lib/workflows/engine.ts", "src/lib/schedules/scheduler.ts", "src/lib/agents/profiles/registry.ts"],
|
|
106
|
+
caseStudies: ["making-machine-that-builds-machines"],
|
|
107
|
+
},
|
|
102
108
|
};
|
|
103
109
|
|
|
104
110
|
/** Get related Playbook doc slugs for a chapter */
|
package/src/lib/book/content.ts
CHANGED
|
@@ -156,6 +156,16 @@ export const CHAPTERS: BookChapter[] = [
|
|
|
156
156
|
readingTime: 10,
|
|
157
157
|
sections: [],
|
|
158
158
|
},
|
|
159
|
+
{
|
|
160
|
+
id: "ch-13",
|
|
161
|
+
number: 13,
|
|
162
|
+
title: "The Wealth Manager",
|
|
163
|
+
subtitle: "When a Solo Founder Builds a Domain Application in a Day Using the Machine That Built Itself",
|
|
164
|
+
part: PARTS[3],
|
|
165
|
+
readingTime: 16,
|
|
166
|
+
relatedDocs: ["workflows", "profiles", "schedules"],
|
|
167
|
+
sections: [],
|
|
168
|
+
},
|
|
159
169
|
];
|
|
160
170
|
|
|
161
171
|
/**
|
|
@@ -39,7 +39,7 @@ export const READING_PATHS: ReadingPath[] = [
|
|
|
39
39
|
name: "Developer",
|
|
40
40
|
description: "The complete journey — every chapter, thesis to roadmap",
|
|
41
41
|
persona: "developer",
|
|
42
|
-
chapterIds: ["ch-1", "ch-2", "ch-3", "ch-4", "ch-5", "ch-6", "ch-7", "ch-8", "ch-9", "ch-10", "ch-11", "ch-12"],
|
|
42
|
+
chapterIds: ["ch-1", "ch-2", "ch-3", "ch-4", "ch-5", "ch-6", "ch-7", "ch-8", "ch-9", "ch-10", "ch-11", "ch-12", "ch-13"],
|
|
43
43
|
usageStage: "power",
|
|
44
44
|
},
|
|
45
45
|
];
|