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,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E2E: Single task execution across profiles and runtimes.
|
|
3
|
+
*
|
|
4
|
+
* Tests that individual tasks execute and produce results via both
|
|
5
|
+
* Claude Code and Codex runtimes with different agent profiles.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
setupE2E,
|
|
10
|
+
teardownE2E,
|
|
11
|
+
testProjectId,
|
|
12
|
+
claudeAvailable,
|
|
13
|
+
codexAvailable,
|
|
14
|
+
} from "./setup";
|
|
15
|
+
import {
|
|
16
|
+
createTask,
|
|
17
|
+
executeTask,
|
|
18
|
+
pollTaskUntilDone,
|
|
19
|
+
updateTask,
|
|
20
|
+
} from "./helpers";
|
|
21
|
+
|
|
22
|
+
beforeAll(async () => {
|
|
23
|
+
await setupE2E();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
afterAll(async () => {
|
|
27
|
+
await teardownE2E();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// Claude Code runtime
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
describe("Single Task — Claude Code", () => {
|
|
35
|
+
beforeAll(() => {
|
|
36
|
+
if (!claudeAvailable) {
|
|
37
|
+
console.warn("Skipping Claude Code tests — runtime not available");
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it.skipIf(!claudeAvailable)(
|
|
42
|
+
"general profile describes code",
|
|
43
|
+
async () => {
|
|
44
|
+
const { ok, data: task } = await createTask({
|
|
45
|
+
title: "Describe the TypeScript code in src/",
|
|
46
|
+
description:
|
|
47
|
+
"Read the TypeScript files in the project and describe what the code does.",
|
|
48
|
+
projectId: testProjectId,
|
|
49
|
+
agentProfile: "general",
|
|
50
|
+
});
|
|
51
|
+
expect(ok).toBe(true);
|
|
52
|
+
|
|
53
|
+
// Queue → execute
|
|
54
|
+
await updateTask(task!.id, { status: "queued" });
|
|
55
|
+
const exec = await executeTask(task!.id);
|
|
56
|
+
expect(exec.status).toBe(202);
|
|
57
|
+
|
|
58
|
+
// Poll until done
|
|
59
|
+
const result = await pollTaskUntilDone(task!.id);
|
|
60
|
+
expect(result.status).toBe("completed");
|
|
61
|
+
expect(result.result).toBeTruthy();
|
|
62
|
+
expect(result.result!.length).toBeGreaterThan(50);
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
it.skipIf(!claudeAvailable)(
|
|
67
|
+
"code-reviewer profile finds bugs",
|
|
68
|
+
async () => {
|
|
69
|
+
const { ok, data: task } = await createTask({
|
|
70
|
+
title: "Review code for bugs",
|
|
71
|
+
description:
|
|
72
|
+
"Review all TypeScript files in the project. Find bugs and report them with severity levels.",
|
|
73
|
+
projectId: testProjectId,
|
|
74
|
+
agentProfile: "code-reviewer",
|
|
75
|
+
});
|
|
76
|
+
expect(ok).toBe(true);
|
|
77
|
+
|
|
78
|
+
await updateTask(task!.id, { status: "queued" });
|
|
79
|
+
const exec = await executeTask(task!.id);
|
|
80
|
+
expect(exec.status).toBe(202);
|
|
81
|
+
|
|
82
|
+
const result = await pollTaskUntilDone(task!.id);
|
|
83
|
+
expect(result.status).toBe("completed");
|
|
84
|
+
expect(result.result).toBeTruthy();
|
|
85
|
+
// Code reviewer should find at least some issues
|
|
86
|
+
expect(result.result!.length).toBeGreaterThan(100);
|
|
87
|
+
}
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
it.skipIf(!claudeAvailable)(
|
|
91
|
+
"document-writer profile generates overview",
|
|
92
|
+
async () => {
|
|
93
|
+
const { ok, data: task } = await createTask({
|
|
94
|
+
title: "Write a technical overview document",
|
|
95
|
+
description:
|
|
96
|
+
"Generate a technical overview of this project including structure, modules, and dependencies.",
|
|
97
|
+
projectId: testProjectId,
|
|
98
|
+
agentProfile: "document-writer",
|
|
99
|
+
});
|
|
100
|
+
expect(ok).toBe(true);
|
|
101
|
+
|
|
102
|
+
await updateTask(task!.id, { status: "queued" });
|
|
103
|
+
const exec = await executeTask(task!.id);
|
|
104
|
+
expect(exec.status).toBe(202);
|
|
105
|
+
|
|
106
|
+
const result = await pollTaskUntilDone(task!.id);
|
|
107
|
+
expect(result.status).toBe("completed");
|
|
108
|
+
expect(result.result).toBeTruthy();
|
|
109
|
+
}
|
|
110
|
+
);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
// Codex runtime
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
describe("Single Task — Codex", () => {
|
|
118
|
+
beforeAll(() => {
|
|
119
|
+
if (!codexAvailable) {
|
|
120
|
+
console.warn("Skipping Codex tests — runtime not available");
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it.skipIf(!codexAvailable)(
|
|
125
|
+
"general profile describes code via Codex",
|
|
126
|
+
async () => {
|
|
127
|
+
const { ok, data: task } = await createTask({
|
|
128
|
+
title: "Describe the TypeScript code in src/",
|
|
129
|
+
description:
|
|
130
|
+
"Read the TypeScript files in the project and describe what the code does.",
|
|
131
|
+
projectId: testProjectId,
|
|
132
|
+
assignedAgent: "codex",
|
|
133
|
+
agentProfile: "general",
|
|
134
|
+
});
|
|
135
|
+
expect(ok).toBe(true);
|
|
136
|
+
|
|
137
|
+
await updateTask(task!.id, { status: "queued" });
|
|
138
|
+
const exec = await executeTask(task!.id);
|
|
139
|
+
expect(exec.status).toBe(202);
|
|
140
|
+
|
|
141
|
+
const result = await pollTaskUntilDone(task!.id);
|
|
142
|
+
expect(result.status).toBe("completed");
|
|
143
|
+
expect(result.result).toBeTruthy();
|
|
144
|
+
expect(result.result!.length).toBeGreaterThan(50);
|
|
145
|
+
}
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
it.skipIf(!codexAvailable)(
|
|
149
|
+
"code-reviewer profile finds bugs via Codex",
|
|
150
|
+
async () => {
|
|
151
|
+
const { ok, data: task } = await createTask({
|
|
152
|
+
title: "Review code for bugs",
|
|
153
|
+
description:
|
|
154
|
+
"Review all TypeScript files in the project. Find bugs and report them with severity levels.",
|
|
155
|
+
projectId: testProjectId,
|
|
156
|
+
assignedAgent: "codex",
|
|
157
|
+
agentProfile: "code-reviewer",
|
|
158
|
+
});
|
|
159
|
+
expect(ok).toBe(true);
|
|
160
|
+
|
|
161
|
+
await updateTask(task!.id, { status: "queued" });
|
|
162
|
+
const exec = await executeTask(task!.id);
|
|
163
|
+
expect(exec.status).toBe(202);
|
|
164
|
+
|
|
165
|
+
const result = await pollTaskUntilDone(task!.id);
|
|
166
|
+
expect(result.status).toBe("completed");
|
|
167
|
+
expect(result.result).toBeTruthy();
|
|
168
|
+
}
|
|
169
|
+
);
|
|
170
|
+
});
|
|
@@ -2,30 +2,53 @@ import { NextResponse } from "next/server";
|
|
|
2
2
|
import { db } from "@/lib/db";
|
|
3
3
|
import { projects, tasks } from "@/lib/db/schema";
|
|
4
4
|
import { desc } from "drizzle-orm";
|
|
5
|
+
import { getManifest } from "@/lib/docs/reader";
|
|
5
6
|
|
|
6
7
|
export async function GET() {
|
|
7
|
-
const recentProjects = await
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
8
|
+
const [recentProjects, recentTasks] = await Promise.all([
|
|
9
|
+
db
|
|
10
|
+
.select({
|
|
11
|
+
id: projects.id,
|
|
12
|
+
name: projects.name,
|
|
13
|
+
status: projects.status,
|
|
14
|
+
})
|
|
15
|
+
.from(projects)
|
|
16
|
+
.orderBy(desc(projects.updatedAt))
|
|
17
|
+
.limit(5),
|
|
18
|
+
db
|
|
19
|
+
.select({
|
|
20
|
+
id: tasks.id,
|
|
21
|
+
title: tasks.title,
|
|
22
|
+
status: tasks.status,
|
|
23
|
+
})
|
|
24
|
+
.from(tasks)
|
|
25
|
+
.orderBy(desc(tasks.updatedAt))
|
|
26
|
+
.limit(5),
|
|
27
|
+
]);
|
|
16
28
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
29
|
+
// Read playbook items from manifest
|
|
30
|
+
let playbook: { slug: string; title: string; tags: string[] }[] = [];
|
|
31
|
+
try {
|
|
32
|
+
const manifest = getManifest();
|
|
33
|
+
playbook = [
|
|
34
|
+
...manifest.sections.map((s) => ({
|
|
35
|
+
slug: s.slug,
|
|
36
|
+
title: s.title,
|
|
37
|
+
tags: s.tags,
|
|
38
|
+
})),
|
|
39
|
+
...manifest.journeys.map((j) => ({
|
|
40
|
+
slug: j.slug,
|
|
41
|
+
title: j.title,
|
|
42
|
+
tags: [j.persona, j.difficulty],
|
|
43
|
+
})),
|
|
44
|
+
];
|
|
45
|
+
} catch {
|
|
46
|
+
// docs/manifest.json may not exist — graceful fallback
|
|
47
|
+
}
|
|
26
48
|
|
|
27
49
|
return NextResponse.json({
|
|
28
50
|
projects: recentProjects,
|
|
29
51
|
tasks: recentTasks,
|
|
52
|
+
playbook,
|
|
30
53
|
});
|
|
31
54
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import {
|
|
4
|
+
batchApproveProposals,
|
|
5
|
+
batchRejectProposals,
|
|
6
|
+
} from "@/lib/agents/learning-session";
|
|
7
|
+
|
|
8
|
+
const batchSchema = z.object({
|
|
9
|
+
proposalIds: z.array(z.string().min(1)).min(1),
|
|
10
|
+
action: z.enum(["approve", "reject"]),
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* POST /api/context/batch — batch approve or reject context proposals.
|
|
15
|
+
*
|
|
16
|
+
* Used by the batch proposal review UI after workflow completion.
|
|
17
|
+
* Accepts an array of learned_context row IDs and an action.
|
|
18
|
+
*/
|
|
19
|
+
export async function POST(req: NextRequest) {
|
|
20
|
+
try {
|
|
21
|
+
const body = await req.json();
|
|
22
|
+
const parsed = batchSchema.safeParse(body);
|
|
23
|
+
|
|
24
|
+
if (!parsed.success) {
|
|
25
|
+
return NextResponse.json(
|
|
26
|
+
{ error: "proposalIds (string[]) and action ('approve'|'reject') are required" },
|
|
27
|
+
{ status: 400 }
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const { proposalIds, action } = parsed.data;
|
|
32
|
+
|
|
33
|
+
const count =
|
|
34
|
+
action === "approve"
|
|
35
|
+
? await batchApproveProposals(proposalIds)
|
|
36
|
+
: await batchRejectProposals(proposalIds);
|
|
37
|
+
|
|
38
|
+
return NextResponse.json({ success: true, action, count });
|
|
39
|
+
} catch (err: unknown) {
|
|
40
|
+
const message =
|
|
41
|
+
err instanceof Error ? err.message : "Batch operation failed";
|
|
42
|
+
return NextResponse.json({ error: message }, { status: 500 });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import {
|
|
4
|
+
getPreset,
|
|
5
|
+
getActivePresets,
|
|
6
|
+
applyPreset,
|
|
7
|
+
removePreset,
|
|
8
|
+
PRESETS,
|
|
9
|
+
} from "@/lib/settings/permission-presets";
|
|
10
|
+
|
|
11
|
+
const presetSchema = z.object({
|
|
12
|
+
presetId: z.string().min(1),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* GET /api/permissions/presets — list all presets with their active status.
|
|
17
|
+
*/
|
|
18
|
+
export async function GET() {
|
|
19
|
+
const activeIds = await getActivePresets();
|
|
20
|
+
const activeSet = new Set(activeIds);
|
|
21
|
+
|
|
22
|
+
const presets = PRESETS.map((p) => ({
|
|
23
|
+
...p,
|
|
24
|
+
active: activeSet.has(p.id),
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
return NextResponse.json({ presets });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* POST /api/permissions/presets — enable a preset (adds all its patterns).
|
|
32
|
+
*/
|
|
33
|
+
export async function POST(req: NextRequest) {
|
|
34
|
+
const body = await req.json();
|
|
35
|
+
const parsed = presetSchema.safeParse(body);
|
|
36
|
+
|
|
37
|
+
if (!parsed.success) {
|
|
38
|
+
return NextResponse.json(
|
|
39
|
+
{ error: "presetId (string) is required" },
|
|
40
|
+
{ status: 400 }
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const preset = getPreset(parsed.data.presetId);
|
|
45
|
+
if (!preset) {
|
|
46
|
+
return NextResponse.json(
|
|
47
|
+
{ error: `Unknown preset: ${parsed.data.presetId}` },
|
|
48
|
+
{ status: 404 }
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
await applyPreset(parsed.data.presetId);
|
|
53
|
+
return NextResponse.json({ success: true });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* DELETE /api/permissions/presets — disable a preset (removes unique patterns).
|
|
58
|
+
*/
|
|
59
|
+
export async function DELETE(req: NextRequest) {
|
|
60
|
+
const body = await req.json();
|
|
61
|
+
const parsed = presetSchema.safeParse(body);
|
|
62
|
+
|
|
63
|
+
if (!parsed.success) {
|
|
64
|
+
return NextResponse.json(
|
|
65
|
+
{ error: "presetId (string) is required" },
|
|
66
|
+
{ status: 400 }
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const preset = getPreset(parsed.data.presetId);
|
|
71
|
+
if (!preset) {
|
|
72
|
+
return NextResponse.json(
|
|
73
|
+
{ error: `Unknown preset: ${parsed.data.presetId}` },
|
|
74
|
+
{ status: 404 }
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
await removePreset(parsed.data.presetId);
|
|
79
|
+
return NextResponse.json({ success: true });
|
|
80
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { getDocsLastGenerated } from "@/lib/docs/reader";
|
|
3
|
+
import { getSetting } from "@/lib/settings/helpers";
|
|
4
|
+
|
|
5
|
+
export async function GET() {
|
|
6
|
+
const lastGenerated = getDocsLastGenerated();
|
|
7
|
+
const lastVisit = await getSetting("lastPlaybookVisit");
|
|
8
|
+
|
|
9
|
+
const hasUpdates =
|
|
10
|
+
lastGenerated != null &&
|
|
11
|
+
lastVisit != null &&
|
|
12
|
+
new Date(lastGenerated) > new Date(lastVisit);
|
|
13
|
+
|
|
14
|
+
return NextResponse.json({ hasUpdates });
|
|
15
|
+
}
|
|
@@ -1,29 +1,31 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from "next/server";
|
|
2
2
|
import { listProfiles, createProfile, isBuiltin } from "@/lib/agents/profiles/registry";
|
|
3
|
+
import { sortProfilesByName } from "@/lib/agents/profiles/sort";
|
|
3
4
|
import { ProfileConfigSchema } from "@/lib/validators/profile";
|
|
4
5
|
|
|
5
6
|
export async function GET() {
|
|
6
|
-
const profiles =
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
7
|
+
const profiles = sortProfilesByName(
|
|
8
|
+
listProfiles().map((p) => ({
|
|
9
|
+
id: p.id,
|
|
10
|
+
name: p.name,
|
|
11
|
+
description: p.description,
|
|
12
|
+
domain: p.domain,
|
|
13
|
+
tags: p.tags,
|
|
14
|
+
skillMd: p.skillMd,
|
|
15
|
+
allowedTools: p.allowedTools,
|
|
16
|
+
mcpServers: p.mcpServers,
|
|
17
|
+
canUseToolPolicy: p.canUseToolPolicy,
|
|
18
|
+
maxTurns: p.maxTurns,
|
|
19
|
+
outputFormat: p.outputFormat,
|
|
20
|
+
version: p.version,
|
|
21
|
+
author: p.author,
|
|
22
|
+
source: p.source,
|
|
23
|
+
tests: p.tests,
|
|
24
|
+
supportedRuntimes: p.supportedRuntimes,
|
|
25
|
+
runtimeOverrides: p.runtimeOverrides,
|
|
26
|
+
isBuiltin: isBuiltin(p.id),
|
|
27
|
+
}))
|
|
28
|
+
);
|
|
27
29
|
|
|
28
30
|
return NextResponse.json(profiles);
|
|
29
31
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import {
|
|
3
|
+
getPricingRegistrySnapshot,
|
|
4
|
+
refreshPricingRegistry,
|
|
5
|
+
} from "@/lib/usage/pricing-registry";
|
|
6
|
+
|
|
7
|
+
export async function GET() {
|
|
8
|
+
const snapshot = await getPricingRegistrySnapshot();
|
|
9
|
+
return NextResponse.json(snapshot);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function POST() {
|
|
13
|
+
const snapshot = await refreshPricingRegistry();
|
|
14
|
+
return NextResponse.json(snapshot);
|
|
15
|
+
}
|
package/src/app/costs/page.tsx
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { listRuntimeCatalog } from "@/lib/agents/runtime/catalog";
|
|
2
1
|
import { CostDashboard } from "@/components/costs/cost-dashboard";
|
|
3
2
|
import { getBudgetGuardrailSnapshot } from "@/lib/settings/budget-guardrails";
|
|
4
3
|
import {
|
|
@@ -13,8 +12,6 @@ import {
|
|
|
13
12
|
|
|
14
13
|
export const dynamic = "force-dynamic";
|
|
15
14
|
|
|
16
|
-
const runtimeCatalog = listRuntimeCatalog();
|
|
17
|
-
const validRuntimeIds = new Set<string>(runtimeCatalog.map((runtime) => runtime.id));
|
|
18
15
|
const validDateRanges = new Set(["7d", "30d", "90d", "all"]);
|
|
19
16
|
const validStatuses = new Set<UsageLedgerStatus>([
|
|
20
17
|
"completed",
|
|
@@ -40,10 +37,6 @@ function resolveDateRange(value: string | undefined) {
|
|
|
40
37
|
return value && validDateRanges.has(value) ? value : "30d";
|
|
41
38
|
}
|
|
42
39
|
|
|
43
|
-
function resolveRuntime(value: string | undefined) {
|
|
44
|
-
return value && validRuntimeIds.has(value) ? value : "all";
|
|
45
|
-
}
|
|
46
|
-
|
|
47
40
|
function resolveStatus(value: string | undefined) {
|
|
48
41
|
return value && validStatuses.has(value as UsageLedgerStatus) ? value : "all";
|
|
49
42
|
}
|
|
@@ -102,25 +95,6 @@ function fillSeries<T extends { day: string }>(
|
|
|
102
95
|
return keys.map((key) => values.get(key) ?? 0);
|
|
103
96
|
}
|
|
104
97
|
|
|
105
|
-
function findOverallSpend(
|
|
106
|
-
statuses: Array<{
|
|
107
|
-
scopeId: string;
|
|
108
|
-
window: string;
|
|
109
|
-
metric: string;
|
|
110
|
-
currentValue: number;
|
|
111
|
-
}>,
|
|
112
|
-
window: "daily" | "monthly"
|
|
113
|
-
) {
|
|
114
|
-
return (
|
|
115
|
-
statuses.find(
|
|
116
|
-
(status) =>
|
|
117
|
-
status.scopeId === "overall" &&
|
|
118
|
-
status.window === window &&
|
|
119
|
-
status.metric === "spend"
|
|
120
|
-
)?.currentValue ?? 0
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
98
|
function buildRuntimeBreakdown(
|
|
125
99
|
rows: ProviderModelBreakdownEntry[]
|
|
126
100
|
): Array<{
|
|
@@ -149,9 +123,7 @@ function buildRuntimeBreakdown(
|
|
|
149
123
|
for (const row of rows) {
|
|
150
124
|
const current = totals.get(row.runtimeId) ?? {
|
|
151
125
|
runtimeId: row.runtimeId,
|
|
152
|
-
label:
|
|
153
|
-
runtimeCatalog.find((runtime) => runtime.id === row.runtimeId)?.label ??
|
|
154
|
-
row.runtimeId,
|
|
126
|
+
label: row.runtimeId,
|
|
155
127
|
providerId: row.providerId,
|
|
156
128
|
costMicros: 0,
|
|
157
129
|
totalTokens: 0,
|
|
@@ -191,22 +163,34 @@ export default async function CostsPage({
|
|
|
191
163
|
}) {
|
|
192
164
|
const params = await searchParams;
|
|
193
165
|
const dateRange = resolveDateRange(toScalar(params.range));
|
|
194
|
-
const runtimeId = resolveRuntime(toScalar(params.runtime));
|
|
195
166
|
const status = resolveStatus(toScalar(params.status));
|
|
196
167
|
const activityType = resolveActivityType(toScalar(params.activity));
|
|
197
|
-
|
|
198
168
|
const rangeStart = getRangeStart(dateRange);
|
|
199
|
-
|
|
169
|
+
|
|
170
|
+
const budgetSnapshot = await getBudgetGuardrailSnapshot();
|
|
171
|
+
const configuredRuntimeIds = Object.values(budgetSnapshot.runtimeStates)
|
|
172
|
+
.filter((runtime) => runtime.configured)
|
|
173
|
+
.map((runtime) => runtime.runtimeId);
|
|
174
|
+
const requestedRuntime = toScalar(params.runtime);
|
|
175
|
+
const runtimeId =
|
|
176
|
+
requestedRuntime && configuredRuntimeIds.includes(requestedRuntime as never)
|
|
177
|
+
? requestedRuntime
|
|
178
|
+
: "all";
|
|
179
|
+
|
|
180
|
+
const [spendRows30, tokenRows30, monthBreakdown, filteredBreakdown, auditEntries] =
|
|
200
181
|
await Promise.all([
|
|
201
182
|
getDailySpendTotals(30),
|
|
202
183
|
getDailyTokenTotals(30),
|
|
203
184
|
getProviderModelBreakdown({ startedAt: startOfCurrentMonth() }),
|
|
204
|
-
getProviderModelBreakdown(
|
|
205
|
-
rangeStart ? { startedAt: rangeStart } : undefined
|
|
206
|
-
),
|
|
185
|
+
getProviderModelBreakdown(rangeStart ? { startedAt: rangeStart } : undefined),
|
|
207
186
|
listUsageAuditEntries({
|
|
208
187
|
limit: 100,
|
|
209
|
-
runtimeIds:
|
|
188
|
+
runtimeIds:
|
|
189
|
+
runtimeId === "all"
|
|
190
|
+
? configuredRuntimeIds.length > 0
|
|
191
|
+
? configuredRuntimeIds
|
|
192
|
+
: undefined
|
|
193
|
+
: [runtimeId],
|
|
210
194
|
statuses: status === "all" ? undefined : [status as UsageLedgerStatus],
|
|
211
195
|
activityTypes:
|
|
212
196
|
activityType === "all"
|
|
@@ -214,17 +198,38 @@ export default async function CostsPage({
|
|
|
214
198
|
: [activityType as UsageActivityType],
|
|
215
199
|
startedAt: rangeStart,
|
|
216
200
|
}),
|
|
217
|
-
getBudgetGuardrailSnapshot(),
|
|
218
201
|
]);
|
|
219
202
|
|
|
203
|
+
const configuredBreakdown = filteredBreakdown.filter((row) =>
|
|
204
|
+
configuredRuntimeIds.length > 0
|
|
205
|
+
? configuredRuntimeIds.includes(row.runtimeId as never)
|
|
206
|
+
: true
|
|
207
|
+
);
|
|
208
|
+
const configuredMonthBreakdown = monthBreakdown.filter((row) =>
|
|
209
|
+
configuredRuntimeIds.length > 0
|
|
210
|
+
? configuredRuntimeIds.includes(row.runtimeId as never)
|
|
211
|
+
: true
|
|
212
|
+
);
|
|
213
|
+
|
|
220
214
|
const spendSeries30 = fillSeries(30, spendRows30, (row) => row.costMicros);
|
|
221
215
|
const tokenSeries30 = fillSeries(30, tokenRows30, (row) => row.totalTokens);
|
|
222
|
-
const runtimeBreakdown = buildRuntimeBreakdown(
|
|
223
|
-
|
|
216
|
+
const runtimeBreakdown = buildRuntimeBreakdown(configuredBreakdown).map((row) => ({
|
|
217
|
+
...row,
|
|
218
|
+
label: budgetSnapshot.runtimeStates[row.runtimeId as keyof typeof budgetSnapshot.runtimeStates]
|
|
219
|
+
?.label ?? row.runtimeId,
|
|
220
|
+
}));
|
|
221
|
+
const monthTokens = configuredMonthBreakdown.reduce(
|
|
224
222
|
(sum, row) => sum + row.totalTokens,
|
|
225
223
|
0
|
|
226
224
|
);
|
|
227
225
|
|
|
226
|
+
const overallDaily = budgetSnapshot.statuses.find(
|
|
227
|
+
(status) => status.scopeId === "overall" && status.window === "daily"
|
|
228
|
+
);
|
|
229
|
+
const overallMonthly = budgetSnapshot.statuses.find(
|
|
230
|
+
(status) => status.scopeId === "overall" && status.window === "monthly"
|
|
231
|
+
);
|
|
232
|
+
|
|
228
233
|
return (
|
|
229
234
|
<div className="gradient-neutral min-h-screen p-6">
|
|
230
235
|
<CostDashboard
|
|
@@ -235,9 +240,12 @@ export default async function CostsPage({
|
|
|
235
240
|
activityType,
|
|
236
241
|
}}
|
|
237
242
|
summary={{
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
243
|
+
monthSpendMicros: overallMonthly?.currentValue ?? 0,
|
|
244
|
+
derivedDailyBudgetMicros: overallDaily?.limitValue ?? 0,
|
|
245
|
+
remainingMonthlyHeadroomMicros: Math.max(
|
|
246
|
+
(overallMonthly?.limitValue ?? 0) - (overallMonthly?.currentValue ?? 0),
|
|
247
|
+
0
|
|
248
|
+
),
|
|
241
249
|
monthTokens,
|
|
242
250
|
}}
|
|
243
251
|
trendSeries={{
|
|
@@ -247,8 +255,10 @@ export default async function CostsPage({
|
|
|
247
255
|
tokens30: tokenSeries30,
|
|
248
256
|
}}
|
|
249
257
|
budgetStatuses={budgetSnapshot.statuses}
|
|
258
|
+
runtimeStates={budgetSnapshot.runtimeStates}
|
|
259
|
+
pricing={budgetSnapshot.pricing}
|
|
250
260
|
runtimeBreakdown={runtimeBreakdown}
|
|
251
|
-
modelBreakdown={
|
|
261
|
+
modelBreakdown={configuredBreakdown}
|
|
252
262
|
auditEntries={auditEntries}
|
|
253
263
|
/>
|
|
254
264
|
</div>
|
package/src/app/globals.css
CHANGED
|
@@ -446,11 +446,6 @@
|
|
|
446
446
|
box-shadow: var(--glass-shadow-sm);
|
|
447
447
|
}
|
|
448
448
|
|
|
449
|
-
/* Temperature slider gradient track */
|
|
450
|
-
.slider-temperature [data-slot="slider-range"] {
|
|
451
|
-
background: linear-gradient(90deg, oklch(0.6 0.18 260), oklch(0.7 0.15 55));
|
|
452
|
-
}
|
|
453
|
-
|
|
454
449
|
[data-slot="popover-content"],
|
|
455
450
|
[data-slot="dropdown-menu-content"],
|
|
456
451
|
[data-slot="select-content"] {
|