stagent 0.1.11 → 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 +35 -4
- package/package.json +3 -2
- 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 -20
- package/src/app/api/settings/pricing/route.ts +15 -0
- package/src/app/costs/page.tsx +53 -43
- 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/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 +6 -3
- 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/learned-context.ts +27 -15
- package/src/lib/agents/learning-session.ts +234 -0
- package/src/lib/agents/pattern-extractor.ts +19 -0
- package/src/lib/agents/profiles/__tests__/sort.test.ts +42 -0
- package/src/lib/agents/profiles/sort.ts +7 -0
- package/src/lib/constants/settings.ts +1 -0
- 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 +2 -2
- package/src/lib/usage/__tests__/pricing-registry.test.ts +78 -0
- package/src/lib/usage/ledger.ts +1 -1
- package/src/lib/usage/pricing-registry.ts +570 -0
- package/src/lib/usage/pricing.ts +15 -95
- 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__/settings.test.ts +23 -16
- package/src/lib/validators/settings.ts +3 -9
- package/src/lib/workflows/engine.ts +18 -0
|
@@ -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,28 +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
|
-
|
|
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
|
+
);
|
|
26
29
|
|
|
27
30
|
return NextResponse.json(profiles);
|
|
28
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>
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { notFound } from "next/navigation";
|
|
2
|
+
import { getDocBySlug, getManifest } from "@/lib/docs/reader";
|
|
3
|
+
import { getAdoptionMap } from "@/lib/docs/adoption";
|
|
4
|
+
import { PlaybookDetailView } from "@/components/playbook/playbook-detail-view";
|
|
5
|
+
|
|
6
|
+
export const dynamic = "force-dynamic";
|
|
7
|
+
|
|
8
|
+
interface PlaybookDetailProps {
|
|
9
|
+
params: Promise<{ slug: string }>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function generateMetadata({ params }: PlaybookDetailProps) {
|
|
13
|
+
const { slug } = await params;
|
|
14
|
+
const doc = getDocBySlug(slug);
|
|
15
|
+
return {
|
|
16
|
+
title: doc
|
|
17
|
+
? `${(doc.frontmatter.title as string) || slug} | Playbook | Stagent`
|
|
18
|
+
: "Not Found | Playbook",
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default async function PlaybookDetailPage({
|
|
23
|
+
params,
|
|
24
|
+
}: PlaybookDetailProps) {
|
|
25
|
+
const { slug } = await params;
|
|
26
|
+
const [doc, manifest, adoptionMap] = await Promise.all([
|
|
27
|
+
getDocBySlug(slug),
|
|
28
|
+
getManifest(),
|
|
29
|
+
getAdoptionMap(),
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
if (!doc) notFound();
|
|
33
|
+
|
|
34
|
+
// Find related sections from manifest
|
|
35
|
+
const allSections = [...manifest.sections, ...manifest.journeys];
|
|
36
|
+
const currentSection = allSections.find((s) => s.slug === slug);
|
|
37
|
+
|
|
38
|
+
// Find related docs by shared tags
|
|
39
|
+
const currentTags = new Set(
|
|
40
|
+
(currentSection && "tags" in currentSection
|
|
41
|
+
? (currentSection as { tags: string[] }).tags
|
|
42
|
+
: (doc.frontmatter.tags as string[]) || []
|
|
43
|
+
).map((t) => t.toLowerCase())
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const relatedSections = manifest.sections
|
|
47
|
+
.filter(
|
|
48
|
+
(s) =>
|
|
49
|
+
s.slug !== slug &&
|
|
50
|
+
s.tags.some((t) => currentTags.has(t.toLowerCase()))
|
|
51
|
+
)
|
|
52
|
+
.slice(0, 4);
|
|
53
|
+
|
|
54
|
+
const adoption = Object.fromEntries(adoptionMap);
|
|
55
|
+
|
|
56
|
+
// Collect all known doc slugs so markdown links resolve correctly
|
|
57
|
+
const allSlugs = [
|
|
58
|
+
...manifest.sections.map((s) => s.slug),
|
|
59
|
+
...manifest.journeys.map((j) => j.slug),
|
|
60
|
+
"getting-started",
|
|
61
|
+
"index",
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<div className="gradient-twilight min-h-[100dvh] p-4 sm:p-6">
|
|
66
|
+
<div className="surface-page rounded-[28px] border border-border/60 p-6 shadow-[0_18px_48px_oklch(0.12_0.02_260_/_0.08)]">
|
|
67
|
+
<PlaybookDetailView
|
|
68
|
+
doc={doc}
|
|
69
|
+
relatedSections={relatedSections}
|
|
70
|
+
adoption={adoption}
|
|
71
|
+
allSlugs={allSlugs}
|
|
72
|
+
/>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { getManifest, getDocsLastGenerated } from "@/lib/docs/reader";
|
|
2
|
+
import { getUsageStage } from "@/lib/docs/usage-stage";
|
|
3
|
+
import { getAdoptionMap } from "@/lib/docs/adoption";
|
|
4
|
+
import { getJourneyCompletions } from "@/lib/docs/journey-tracker";
|
|
5
|
+
import { getSetting, setSetting } from "@/lib/settings/helpers";
|
|
6
|
+
import { PlaybookHomepage } from "@/components/playbook/playbook-homepage";
|
|
7
|
+
|
|
8
|
+
export const dynamic = "force-dynamic";
|
|
9
|
+
|
|
10
|
+
export const metadata = {
|
|
11
|
+
title: "Playbook | Stagent",
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default async function PlaybookPage() {
|
|
15
|
+
const [manifest, stage, adoptionMap, lastGenerated, lastVisit] =
|
|
16
|
+
await Promise.all([
|
|
17
|
+
getManifest(),
|
|
18
|
+
getUsageStage(),
|
|
19
|
+
getAdoptionMap(),
|
|
20
|
+
getDocsLastGenerated(),
|
|
21
|
+
getSetting("lastPlaybookVisit"),
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
const journeyCompletions = getJourneyCompletions(
|
|
25
|
+
manifest.journeys,
|
|
26
|
+
adoptionMap
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
// Update last visit timestamp
|
|
30
|
+
await setSetting("lastPlaybookVisit", new Date().toISOString());
|
|
31
|
+
|
|
32
|
+
// Serialize maps for client component
|
|
33
|
+
const adoption = Object.fromEntries(adoptionMap);
|
|
34
|
+
const completions = Object.fromEntries(journeyCompletions);
|
|
35
|
+
|
|
36
|
+
const hasUpdates =
|
|
37
|
+
lastGenerated != null &&
|
|
38
|
+
lastVisit != null &&
|
|
39
|
+
new Date(lastGenerated) > new Date(lastVisit);
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div className="gradient-twilight min-h-[100dvh] p-4 sm:p-6">
|
|
43
|
+
<div className="surface-page rounded-[28px] border border-border/60 p-6 shadow-[0_18px_48px_oklch(0.12_0.02_260_/_0.08)]">
|
|
44
|
+
<PlaybookHomepage
|
|
45
|
+
manifest={manifest}
|
|
46
|
+
stage={stage}
|
|
47
|
+
adoption={adoption}
|
|
48
|
+
journeyCompletions={completions}
|
|
49
|
+
hasUpdates={hasUpdates}
|
|
50
|
+
/>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { listProfiles, isBuiltin } from "@/lib/agents/profiles/registry";
|
|
2
|
+
import { sortProfilesByName } from "@/lib/agents/profiles/sort";
|
|
2
3
|
import { ProfileBrowser } from "@/components/profiles/profile-browser";
|
|
3
4
|
|
|
4
5
|
export const dynamic = "force-dynamic";
|
|
5
6
|
|
|
6
7
|
export default async function ProfilesPage() {
|
|
7
|
-
const profiles =
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
const profiles = sortProfilesByName(
|
|
9
|
+
listProfiles().map((p) => ({
|
|
10
|
+
...p,
|
|
11
|
+
isBuiltin: isBuiltin(p.id),
|
|
12
|
+
}))
|
|
13
|
+
);
|
|
11
14
|
|
|
12
15
|
return (
|
|
13
16
|
<div className="gradient-ocean-mist min-h-[100dvh] p-4 sm:p-6">
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AuthConfigSection } from "@/components/settings/auth-config-section";
|
|
2
2
|
import { OpenAIRuntimeSection } from "@/components/settings/openai-runtime-section";
|
|
3
|
-
import {
|
|
3
|
+
import { PermissionsSections } from "@/components/settings/permissions-sections";
|
|
4
4
|
import { DataManagementSection } from "@/components/settings/data-management-section";
|
|
5
5
|
import { BudgetGuardrailsSection } from "@/components/settings/budget-guardrails-section";
|
|
6
6
|
|
|
@@ -19,7 +19,7 @@ export default function SettingsPage() {
|
|
|
19
19
|
<AuthConfigSection />
|
|
20
20
|
<OpenAIRuntimeSection />
|
|
21
21
|
<BudgetGuardrailsSection />
|
|
22
|
-
<
|
|
22
|
+
<PermissionsSections />
|
|
23
23
|
<DataManagementSection />
|
|
24
24
|
</div>
|
|
25
25
|
</div>
|