stagent 0.1.10 → 0.1.12
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 +58 -27
- package/package.json +3 -3
- package/src/__tests__/e2e/blueprint.test.ts +63 -0
- package/src/__tests__/e2e/cross-runtime.test.ts +77 -0
- package/src/__tests__/e2e/helpers.ts +286 -0
- package/src/__tests__/e2e/parallel-workflow.test.ts +120 -0
- package/src/__tests__/e2e/sequence-workflow.test.ts +109 -0
- package/src/__tests__/e2e/setup.ts +156 -0
- package/src/__tests__/e2e/single-task.test.ts +170 -0
- package/src/app/api/command-palette/recent/route.ts +41 -18
- package/src/app/api/context/batch/route.ts +44 -0
- package/src/app/api/permissions/presets/route.ts +80 -0
- package/src/app/api/playbook/status/route.ts +15 -0
- package/src/app/api/profiles/route.ts +23 -21
- package/src/app/api/settings/pricing/route.ts +15 -0
- package/src/app/costs/page.tsx +53 -43
- package/src/app/globals.css +0 -5
- package/src/app/playbook/[slug]/page.tsx +76 -0
- package/src/app/playbook/page.tsx +54 -0
- package/src/app/profiles/page.tsx +7 -4
- package/src/app/settings/page.tsx +2 -2
- package/src/app/tasks/page.tsx +5 -0
- package/src/components/costs/cost-dashboard.tsx +226 -320
- package/src/components/dashboard/activity-feed.tsx +6 -2
- package/src/components/notifications/batch-proposal-review.tsx +150 -0
- package/src/components/notifications/notification-item.tsx +6 -3
- package/src/components/notifications/pending-approval-host.tsx +57 -11
- package/src/components/playbook/adoption-heatmap.tsx +69 -0
- package/src/components/playbook/journey-card.tsx +110 -0
- package/src/components/playbook/playbook-action-button.tsx +22 -0
- package/src/components/playbook/playbook-browser.tsx +143 -0
- package/src/components/playbook/playbook-card.tsx +102 -0
- package/src/components/playbook/playbook-detail-view.tsx +223 -0
- package/src/components/playbook/playbook-homepage.tsx +142 -0
- package/src/components/playbook/playbook-toc.tsx +90 -0
- package/src/components/playbook/playbook-updated-badge.tsx +23 -0
- package/src/components/playbook/related-docs.tsx +30 -0
- package/src/components/profiles/__tests__/learned-context-panel.test.tsx +175 -0
- package/src/components/profiles/context-proposal-review.tsx +7 -3
- package/src/components/profiles/learned-context-panel.tsx +116 -8
- package/src/components/profiles/profile-detail-view.tsx +7 -19
- package/src/components/profiles/profile-form-view.tsx +0 -22
- package/src/components/settings/__tests__/auth-config-section.test.tsx +147 -0
- package/src/components/settings/api-key-form.tsx +5 -43
- package/src/components/settings/auth-config-section.tsx +10 -6
- package/src/components/settings/auth-status-badge.tsx +8 -0
- package/src/components/settings/budget-guardrails-section.tsx +403 -620
- package/src/components/settings/connection-test-control.tsx +63 -0
- package/src/components/settings/permissions-section.tsx +85 -75
- package/src/components/settings/permissions-sections.tsx +24 -0
- package/src/components/settings/presets-section.tsx +159 -0
- package/src/components/settings/pricing-registry-panel.tsx +164 -0
- package/src/components/shared/app-sidebar.tsx +2 -0
- package/src/components/shared/command-palette.tsx +30 -0
- package/src/components/shared/light-markdown.tsx +134 -0
- package/src/components/workflows/loop-status-view.tsx +8 -4
- package/src/components/workflows/workflow-status-view.tsx +16 -9
- 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 +32 -28
- package/src/lib/agents/learning-session.ts +234 -0
- package/src/lib/agents/pattern-extractor.ts +34 -64
- package/src/lib/agents/profiles/__tests__/sort.test.ts +42 -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/sort.ts +7 -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 +66 -0
- package/src/lib/constants/settings.ts +1 -0
- package/src/lib/constants/task-status.ts +6 -0
- package/src/lib/data/seed-data/profiles.ts +0 -3
- package/src/lib/db/schema.ts +3 -0
- package/src/lib/docs/adoption.ts +105 -0
- package/src/lib/docs/journey-tracker.ts +21 -0
- package/src/lib/docs/reader.ts +102 -0
- package/src/lib/docs/types.ts +54 -0
- package/src/lib/docs/usage-stage.ts +60 -0
- package/src/lib/notifications/actionable.ts +18 -10
- package/src/lib/settings/__tests__/budget-guardrails.test.ts +86 -24
- package/src/lib/settings/budget-guardrails.ts +213 -85
- package/src/lib/settings/permission-presets.ts +150 -0
- package/src/lib/settings/runtime-setup.ts +71 -0
- package/src/lib/usage/__tests__/ledger.test.ts +29 -5
- package/src/lib/usage/__tests__/pricing-registry.test.ts +78 -0
- package/src/lib/usage/ledger.ts +4 -2
- package/src/lib/usage/pricing-registry.ts +570 -0
- package/src/lib/usage/pricing.ts +15 -41
- package/src/lib/utils/__tests__/learned-context-history.test.ts +171 -0
- package/src/lib/utils/learned-context-history.ts +150 -0
- package/src/lib/validators/__tests__/profile.test.ts +0 -15
- package/src/lib/validators/__tests__/settings.test.ts +23 -16
- package/src/lib/validators/profile.ts +0 -1
- package/src/lib/validators/settings.ts +3 -9
- package/src/lib/workflows/__tests__/engine.test.ts +2 -0
- package/src/lib/workflows/engine.ts +20 -1
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
interface LightMarkdownProps {
|
|
4
|
+
content: string;
|
|
5
|
+
className?: string;
|
|
6
|
+
maxHeight?: string;
|
|
7
|
+
lineClamp?: number;
|
|
8
|
+
textSize?: "xs" | "sm";
|
|
9
|
+
stripBracketTags?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Lightweight markdown renderer for short agent output.
|
|
14
|
+
* Handles: headers, bullet lists, paragraphs, **bold**, `code`.
|
|
15
|
+
* For full markdown (tables, code fences, GFM), use ReactMarkdown instead.
|
|
16
|
+
*/
|
|
17
|
+
export function LightMarkdown({
|
|
18
|
+
content,
|
|
19
|
+
className = "",
|
|
20
|
+
maxHeight,
|
|
21
|
+
lineClamp,
|
|
22
|
+
textSize = "xs",
|
|
23
|
+
stripBracketTags = false,
|
|
24
|
+
}: LightMarkdownProps) {
|
|
25
|
+
const sizeClass = textSize === "sm" ? "text-sm" : "text-xs";
|
|
26
|
+
|
|
27
|
+
const blocks = content.split(/\n{2,}/);
|
|
28
|
+
|
|
29
|
+
const rendered = blocks.map((block, i) => {
|
|
30
|
+
let text = block.trim();
|
|
31
|
+
if (!text) return null;
|
|
32
|
+
|
|
33
|
+
if (stripBracketTags) {
|
|
34
|
+
text = text.replace(/\s*\[.*?\]\s*/g, " ").trim();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Header: ### Title
|
|
38
|
+
const headerMatch = text.match(/^(#{1,4})\s+(.+)/);
|
|
39
|
+
if (headerMatch) {
|
|
40
|
+
return (
|
|
41
|
+
<p key={i} className="font-semibold text-foreground">
|
|
42
|
+
{formatInline(headerMatch[2])}
|
|
43
|
+
</p>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Bullet list: lines starting with - or *
|
|
48
|
+
const lines = text.split("\n");
|
|
49
|
+
const isList = lines.every(
|
|
50
|
+
(l) => /^\s*[-*]\s+/.test(l) || l.trim() === ""
|
|
51
|
+
);
|
|
52
|
+
if (isList && lines.some((l) => /^\s*[-*]\s+/.test(l))) {
|
|
53
|
+
return (
|
|
54
|
+
<ul key={i} className="list-disc pl-4 space-y-0.5 text-muted-foreground">
|
|
55
|
+
{lines
|
|
56
|
+
.filter((l) => /^\s*[-*]\s+/.test(l))
|
|
57
|
+
.map((l, j) => (
|
|
58
|
+
<li key={j}>{formatInline(l.replace(/^\s*[-*]\s+/, ""))}</li>
|
|
59
|
+
))}
|
|
60
|
+
</ul>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Paragraph
|
|
65
|
+
return (
|
|
66
|
+
<p key={i} className="leading-relaxed text-muted-foreground">
|
|
67
|
+
{formatInline(text)}
|
|
68
|
+
</p>
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Overflow / clamp styles
|
|
73
|
+
let containerClass = `${sizeClass} space-y-2 ${className}`;
|
|
74
|
+
const style: React.CSSProperties = {};
|
|
75
|
+
|
|
76
|
+
if (lineClamp) {
|
|
77
|
+
// Approximate: each "line" ~1.25rem at text-xs, ~1.5rem at text-sm
|
|
78
|
+
const lineHeight = textSize === "sm" ? 1.5 : 1.25;
|
|
79
|
+
style.maxHeight = `${lineClamp * lineHeight}rem`;
|
|
80
|
+
style.overflow = "hidden";
|
|
81
|
+
} else if (maxHeight) {
|
|
82
|
+
containerClass += ` ${maxHeight} overflow-auto`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<div className={containerClass} style={style}>
|
|
87
|
+
{rendered}
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Format inline **bold** and `code` spans */
|
|
93
|
+
function formatInline(text: string): ReactNode {
|
|
94
|
+
// Split on **bold** and `code` patterns
|
|
95
|
+
const parts: ReactNode[] = [];
|
|
96
|
+
const regex = /(\*\*(.+?)\*\*|`([^`]+)`)/g;
|
|
97
|
+
let lastIndex = 0;
|
|
98
|
+
let match: RegExpExecArray | null;
|
|
99
|
+
|
|
100
|
+
while ((match = regex.exec(text)) !== null) {
|
|
101
|
+
// Text before the match
|
|
102
|
+
if (match.index > lastIndex) {
|
|
103
|
+
parts.push(text.slice(lastIndex, match.index));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (match[2]) {
|
|
107
|
+
// **bold**
|
|
108
|
+
parts.push(
|
|
109
|
+
<strong key={match.index} className="font-semibold text-foreground">
|
|
110
|
+
{match[2]}
|
|
111
|
+
</strong>
|
|
112
|
+
);
|
|
113
|
+
} else if (match[3]) {
|
|
114
|
+
// `code`
|
|
115
|
+
parts.push(
|
|
116
|
+
<code
|
|
117
|
+
key={match.index}
|
|
118
|
+
className="rounded bg-muted px-1 py-0.5 font-mono text-[0.85em]"
|
|
119
|
+
>
|
|
120
|
+
{match[3]}
|
|
121
|
+
</code>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
lastIndex = match.index + match[0].length;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Remaining text
|
|
129
|
+
if (lastIndex < text.length) {
|
|
130
|
+
parts.push(text.slice(lastIndex));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return parts.length === 1 ? parts[0] : parts;
|
|
134
|
+
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { useState } from "react";
|
|
4
4
|
import { Badge } from "@/components/ui/badge";
|
|
5
5
|
import { Button } from "@/components/ui/button";
|
|
6
|
+
import { LightMarkdown } from "@/components/shared/light-markdown";
|
|
6
7
|
import {
|
|
7
8
|
CheckCircle,
|
|
8
9
|
Circle,
|
|
@@ -233,10 +234,13 @@ export function LoopStatusView({
|
|
|
233
234
|
<p className="text-xs text-destructive">{iter.error}</p>
|
|
234
235
|
)}
|
|
235
236
|
{iter.result && (
|
|
236
|
-
<
|
|
237
|
-
{
|
|
238
|
-
|
|
239
|
-
|
|
237
|
+
<LightMarkdown
|
|
238
|
+
content={
|
|
239
|
+
iter.result.slice(0, 1000) +
|
|
240
|
+
(iter.result.length > 1000 ? "..." : "")
|
|
241
|
+
}
|
|
242
|
+
lineClamp={6}
|
|
243
|
+
/>
|
|
240
244
|
)}
|
|
241
245
|
{iter.taskId && (
|
|
242
246
|
<a
|
|
@@ -25,6 +25,7 @@ import { toast } from "sonner";
|
|
|
25
25
|
import { workflowStatusVariant, patternLabels } from "@/lib/constants/status-colors";
|
|
26
26
|
import { LoopStatusView } from "./loop-status-view";
|
|
27
27
|
import { ConfirmDialog } from "@/components/shared/confirm-dialog";
|
|
28
|
+
import { LightMarkdown } from "@/components/shared/light-markdown";
|
|
28
29
|
import { SwarmDashboard } from "./swarm-dashboard";
|
|
29
30
|
import type { LoopState, LoopConfig, SwarmConfig } from "@/lib/workflows/types";
|
|
30
31
|
|
|
@@ -363,9 +364,11 @@ export function WorkflowStatusView({ workflowId }: WorkflowStatusViewProps) {
|
|
|
363
364
|
</p>
|
|
364
365
|
)}
|
|
365
366
|
{step.state.result && step.state.status === "completed" && (
|
|
366
|
-
<
|
|
367
|
-
{step.state.result.slice(0,
|
|
368
|
-
|
|
367
|
+
<LightMarkdown
|
|
368
|
+
content={step.state.result.slice(0, 500)}
|
|
369
|
+
lineClamp={4}
|
|
370
|
+
className="mt-2"
|
|
371
|
+
/>
|
|
369
372
|
)}
|
|
370
373
|
</div>
|
|
371
374
|
</div>
|
|
@@ -409,9 +412,11 @@ export function WorkflowStatusView({ workflowId }: WorkflowStatusViewProps) {
|
|
|
409
412
|
)}
|
|
410
413
|
{synthesisStep.state.result &&
|
|
411
414
|
synthesisStep.state.status === "completed" && (
|
|
412
|
-
<
|
|
413
|
-
{synthesisStep.state.result.slice(0,
|
|
414
|
-
|
|
415
|
+
<LightMarkdown
|
|
416
|
+
content={synthesisStep.state.result.slice(0, 500)}
|
|
417
|
+
lineClamp={4}
|
|
418
|
+
className="mt-2"
|
|
419
|
+
/>
|
|
415
420
|
)}
|
|
416
421
|
</div>
|
|
417
422
|
</div>
|
|
@@ -448,9 +453,11 @@ export function WorkflowStatusView({ workflowId }: WorkflowStatusViewProps) {
|
|
|
448
453
|
</p>
|
|
449
454
|
)}
|
|
450
455
|
{step.state.result && step.state.status === "completed" && (
|
|
451
|
-
<
|
|
452
|
-
{step.state.result.slice(0,
|
|
453
|
-
|
|
456
|
+
<LightMarkdown
|
|
457
|
+
content={step.state.result.slice(0, 500)}
|
|
458
|
+
lineClamp={2}
|
|
459
|
+
className="mt-1"
|
|
460
|
+
/>
|
|
454
461
|
)}
|
|
455
462
|
</div>
|
|
456
463
|
</div>
|
|
@@ -315,12 +315,17 @@ describe("executeClaudeTask", () => {
|
|
|
315
315
|
|
|
316
316
|
await executeClaudeTask("task-1");
|
|
317
317
|
|
|
318
|
-
//
|
|
318
|
+
// F1: prompt contains only user task text (title fallback); system instructions in systemPrompt
|
|
319
319
|
expect(mockQuery).toHaveBeenCalledWith(
|
|
320
320
|
expect.objectContaining({
|
|
321
|
-
prompt: "
|
|
321
|
+
prompt: "Test Task",
|
|
322
322
|
})
|
|
323
323
|
);
|
|
324
|
+
// System instructions (including output instructions) are in the systemPrompt option
|
|
325
|
+
const callOptions = mockQuery.mock.calls[0][0].options;
|
|
326
|
+
expect(callOptions.systemPrompt).toBeDefined();
|
|
327
|
+
expect(callOptions.maxTurns).toBeDefined();
|
|
328
|
+
expect(callOptions.maxBudgetUsd).toBeDefined();
|
|
324
329
|
});
|
|
325
330
|
});
|
|
326
331
|
|