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.
Files changed (112) hide show
  1. package/README.md +58 -27
  2. package/package.json +3 -3
  3. package/src/__tests__/e2e/blueprint.test.ts +63 -0
  4. package/src/__tests__/e2e/cross-runtime.test.ts +77 -0
  5. package/src/__tests__/e2e/helpers.ts +286 -0
  6. package/src/__tests__/e2e/parallel-workflow.test.ts +120 -0
  7. package/src/__tests__/e2e/sequence-workflow.test.ts +109 -0
  8. package/src/__tests__/e2e/setup.ts +156 -0
  9. package/src/__tests__/e2e/single-task.test.ts +170 -0
  10. package/src/app/api/command-palette/recent/route.ts +41 -18
  11. package/src/app/api/context/batch/route.ts +44 -0
  12. package/src/app/api/permissions/presets/route.ts +80 -0
  13. package/src/app/api/playbook/status/route.ts +15 -0
  14. package/src/app/api/profiles/route.ts +23 -21
  15. package/src/app/api/settings/pricing/route.ts +15 -0
  16. package/src/app/costs/page.tsx +53 -43
  17. package/src/app/globals.css +0 -5
  18. package/src/app/playbook/[slug]/page.tsx +76 -0
  19. package/src/app/playbook/page.tsx +54 -0
  20. package/src/app/profiles/page.tsx +7 -4
  21. package/src/app/settings/page.tsx +2 -2
  22. package/src/app/tasks/page.tsx +5 -0
  23. package/src/components/costs/cost-dashboard.tsx +226 -320
  24. package/src/components/dashboard/activity-feed.tsx +6 -2
  25. package/src/components/notifications/batch-proposal-review.tsx +150 -0
  26. package/src/components/notifications/notification-item.tsx +6 -3
  27. package/src/components/notifications/pending-approval-host.tsx +57 -11
  28. package/src/components/playbook/adoption-heatmap.tsx +69 -0
  29. package/src/components/playbook/journey-card.tsx +110 -0
  30. package/src/components/playbook/playbook-action-button.tsx +22 -0
  31. package/src/components/playbook/playbook-browser.tsx +143 -0
  32. package/src/components/playbook/playbook-card.tsx +102 -0
  33. package/src/components/playbook/playbook-detail-view.tsx +223 -0
  34. package/src/components/playbook/playbook-homepage.tsx +142 -0
  35. package/src/components/playbook/playbook-toc.tsx +90 -0
  36. package/src/components/playbook/playbook-updated-badge.tsx +23 -0
  37. package/src/components/playbook/related-docs.tsx +30 -0
  38. package/src/components/profiles/__tests__/learned-context-panel.test.tsx +175 -0
  39. package/src/components/profiles/context-proposal-review.tsx +7 -3
  40. package/src/components/profiles/learned-context-panel.tsx +116 -8
  41. package/src/components/profiles/profile-detail-view.tsx +7 -19
  42. package/src/components/profiles/profile-form-view.tsx +0 -22
  43. package/src/components/settings/__tests__/auth-config-section.test.tsx +147 -0
  44. package/src/components/settings/api-key-form.tsx +5 -43
  45. package/src/components/settings/auth-config-section.tsx +10 -6
  46. package/src/components/settings/auth-status-badge.tsx +8 -0
  47. package/src/components/settings/budget-guardrails-section.tsx +403 -620
  48. package/src/components/settings/connection-test-control.tsx +63 -0
  49. package/src/components/settings/permissions-section.tsx +85 -75
  50. package/src/components/settings/permissions-sections.tsx +24 -0
  51. package/src/components/settings/presets-section.tsx +159 -0
  52. package/src/components/settings/pricing-registry-panel.tsx +164 -0
  53. package/src/components/shared/app-sidebar.tsx +2 -0
  54. package/src/components/shared/command-palette.tsx +30 -0
  55. package/src/components/shared/light-markdown.tsx +134 -0
  56. package/src/components/workflows/loop-status-view.tsx +8 -4
  57. package/src/components/workflows/workflow-status-view.tsx +16 -9
  58. package/src/lib/agents/__tests__/claude-agent.test.ts +7 -2
  59. package/src/lib/agents/__tests__/learned-context.test.ts +500 -0
  60. package/src/lib/agents/__tests__/pattern-extractor.test.ts +243 -0
  61. package/src/lib/agents/__tests__/sweep.test.ts +202 -0
  62. package/src/lib/agents/claude-agent.ts +104 -78
  63. package/src/lib/agents/learned-context.ts +32 -28
  64. package/src/lib/agents/learning-session.ts +234 -0
  65. package/src/lib/agents/pattern-extractor.ts +34 -64
  66. package/src/lib/agents/profiles/__tests__/sort.test.ts +42 -0
  67. package/src/lib/agents/profiles/builtins/code-reviewer/profile.yaml +0 -1
  68. package/src/lib/agents/profiles/builtins/data-analyst/profile.yaml +0 -1
  69. package/src/lib/agents/profiles/builtins/devops-engineer/profile.yaml +0 -1
  70. package/src/lib/agents/profiles/builtins/document-writer/profile.yaml +0 -1
  71. package/src/lib/agents/profiles/builtins/general/profile.yaml +0 -1
  72. package/src/lib/agents/profiles/builtins/health-fitness-coach/profile.yaml +0 -1
  73. package/src/lib/agents/profiles/builtins/learning-coach/profile.yaml +0 -1
  74. package/src/lib/agents/profiles/builtins/project-manager/profile.yaml +0 -1
  75. package/src/lib/agents/profiles/builtins/researcher/profile.yaml +0 -1
  76. package/src/lib/agents/profiles/builtins/shopping-assistant/profile.yaml +0 -1
  77. package/src/lib/agents/profiles/builtins/sweep/profile.yaml +0 -1
  78. package/src/lib/agents/profiles/builtins/technical-writer/profile.yaml +0 -1
  79. package/src/lib/agents/profiles/builtins/travel-planner/profile.yaml +0 -1
  80. package/src/lib/agents/profiles/builtins/wealth-manager/profile.yaml +0 -1
  81. package/src/lib/agents/profiles/registry.ts +0 -1
  82. package/src/lib/agents/profiles/sort.ts +7 -0
  83. package/src/lib/agents/profiles/types.ts +0 -1
  84. package/src/lib/agents/runtime/catalog.ts +1 -1
  85. package/src/lib/agents/runtime/claude.ts +66 -0
  86. package/src/lib/constants/settings.ts +1 -0
  87. package/src/lib/constants/task-status.ts +6 -0
  88. package/src/lib/data/seed-data/profiles.ts +0 -3
  89. package/src/lib/db/schema.ts +3 -0
  90. package/src/lib/docs/adoption.ts +105 -0
  91. package/src/lib/docs/journey-tracker.ts +21 -0
  92. package/src/lib/docs/reader.ts +102 -0
  93. package/src/lib/docs/types.ts +54 -0
  94. package/src/lib/docs/usage-stage.ts +60 -0
  95. package/src/lib/notifications/actionable.ts +18 -10
  96. package/src/lib/settings/__tests__/budget-guardrails.test.ts +86 -24
  97. package/src/lib/settings/budget-guardrails.ts +213 -85
  98. package/src/lib/settings/permission-presets.ts +150 -0
  99. package/src/lib/settings/runtime-setup.ts +71 -0
  100. package/src/lib/usage/__tests__/ledger.test.ts +29 -5
  101. package/src/lib/usage/__tests__/pricing-registry.test.ts +78 -0
  102. package/src/lib/usage/ledger.ts +4 -2
  103. package/src/lib/usage/pricing-registry.ts +570 -0
  104. package/src/lib/usage/pricing.ts +15 -41
  105. package/src/lib/utils/__tests__/learned-context-history.test.ts +171 -0
  106. package/src/lib/utils/learned-context-history.ts +150 -0
  107. package/src/lib/validators/__tests__/profile.test.ts +0 -15
  108. package/src/lib/validators/__tests__/settings.test.ts +23 -16
  109. package/src/lib/validators/profile.ts +0 -1
  110. package/src/lib/validators/settings.ts +3 -9
  111. package/src/lib/workflows/__tests__/engine.test.ts +2 -0
  112. 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
- <p className="text-xs text-muted-foreground whitespace-pre-wrap line-clamp-6">
237
- {iter.result.slice(0, 1000)}
238
- {iter.result.length > 1000 ? "..." : ""}
239
- </p>
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
- <p className="mt-2 text-xs text-muted-foreground line-clamp-4">
367
- {step.state.result.slice(0, 260)}
368
- </p>
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
- <p className="mt-2 text-xs text-muted-foreground line-clamp-4">
413
- {synthesisStep.state.result.slice(0, 260)}
414
- </p>
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
- <p className="text-xs text-muted-foreground mt-1 line-clamp-2">
452
- {step.state.result.slice(0, 200)}
453
- </p>
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
- // query prompt should include output instructions and fall back to the title
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: "Write outputs to /tmp/stagent-outputs/task-1\n\nTest Task",
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