stagent 0.9.5 → 0.10.0
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 +5 -42
- package/dist/cli.js +42 -18
- package/docs/.coverage-gaps.json +13 -55
- package/docs/.last-generated +1 -1
- package/docs/features/provider-runtimes.md +4 -0
- package/docs/features/schedules.md +32 -4
- package/docs/features/settings.md +28 -5
- package/docs/features/tables.md +9 -2
- package/docs/features/workflows.md +10 -4
- package/docs/journeys/developer.md +15 -1
- package/docs/journeys/personal-use.md +21 -4
- package/docs/superpowers/plans/2026-04-07-instance-bootstrap.md +1691 -0
- package/docs/superpowers/plans/2026-04-08-schedule-orchestration.md +2983 -0
- package/docs/superpowers/plans/2026-04-11-schedule-maxturns-api-control.md +551 -0
- package/docs/superpowers/plans/2026-04-11-task-create-profile-validation.md +864 -0
- package/docs/superpowers/plans/2026-04-11-task-runtime-stagent-mcp-injection.md +739 -0
- package/docs/superpowers/specs/2026-04-08-chat-sse-resilience-hotfix-design.md +201 -0
- package/docs/superpowers/specs/2026-04-08-schedule-orchestration-design.md +371 -0
- package/docs/superpowers/specs/2026-04-08-swarm-visibility-design.md +213 -0
- package/package.json +3 -2
- package/src/__tests__/instrumentation-smoke.test.ts +15 -0
- package/src/app/analytics/page.tsx +1 -21
- package/src/app/api/chat/conversations/[id]/messages/route.ts +22 -1
- package/src/app/api/diagnostics/chat-streams/route.ts +65 -0
- package/src/app/api/instance/config/route.ts +41 -0
- package/src/app/api/instance/init/route.ts +34 -0
- package/src/app/api/instance/upgrade/check/route.ts +26 -0
- package/src/app/api/instance/upgrade/route.ts +96 -0
- package/src/app/api/instance/upgrade/status/route.ts +35 -0
- package/src/app/api/memory/route.ts +0 -11
- package/src/app/api/notifications/route.ts +4 -2
- package/src/app/api/projects/[id]/route.ts +5 -155
- package/src/app/api/projects/__tests__/delete-project.test.ts +10 -19
- package/src/app/api/schedules/[id]/execute/route.ts +111 -0
- package/src/app/api/schedules/[id]/route.ts +9 -1
- package/src/app/api/schedules/__tests__/execute-route.test.ts +118 -0
- package/src/app/api/schedules/route.ts +3 -12
- package/src/app/api/settings/openai/login/route.ts +22 -0
- package/src/app/api/settings/openai/logout/route.ts +7 -0
- package/src/app/api/settings/openai/route.ts +21 -1
- package/src/app/api/settings/providers/route.ts +35 -8
- package/src/app/api/tables/[id]/enrich/__tests__/route.test.ts +153 -0
- package/src/app/api/tables/[id]/enrich/plan/route.ts +98 -0
- package/src/app/api/tables/[id]/enrich/route.ts +147 -0
- package/src/app/api/tables/[id]/enrich/runs/route.ts +25 -0
- package/src/app/api/tasks/[id]/execute/route.ts +0 -21
- package/src/app/api/workflows/[id]/resume/route.ts +59 -0
- package/src/app/api/workflows/[id]/status/route.ts +22 -8
- package/src/app/api/workspace/context/route.ts +2 -0
- package/src/app/api/workspace/fix-data-dir/route.ts +81 -0
- package/src/app/chat/page.tsx +11 -0
- package/src/app/inbox/page.tsx +12 -5
- package/src/app/layout.tsx +42 -21
- package/src/app/page.tsx +0 -2
- package/src/app/settings/page.tsx +6 -9
- package/src/components/chat/__tests__/chat-session-provider.test.tsx +408 -0
- package/src/components/chat/chat-command-popover.tsx +2 -2
- package/src/components/chat/chat-input.tsx +2 -3
- package/src/components/chat/chat-session-provider.tsx +720 -0
- package/src/components/chat/chat-shell.tsx +92 -401
- package/src/components/instance/__tests__/instance-section.test.tsx +125 -0
- package/src/components/instance/instance-section.tsx +382 -0
- package/src/components/instance/upgrade-badge.tsx +219 -0
- package/src/components/notifications/__tests__/batch-proposal-review.test.tsx +95 -0
- package/src/components/notifications/__tests__/notification-item.test.tsx +106 -0
- package/src/components/notifications/batch-proposal-review.tsx +20 -5
- package/src/components/notifications/inbox-list.tsx +11 -2
- package/src/components/notifications/notification-item.tsx +56 -2
- package/src/components/notifications/pending-approval-host.tsx +56 -37
- package/src/components/schedules/schedule-create-sheet.tsx +19 -1
- package/src/components/schedules/schedule-edit-sheet.tsx +20 -1
- package/src/components/schedules/schedule-form.tsx +31 -0
- package/src/components/settings/__tests__/providers-runtimes-section.test.tsx +149 -0
- package/src/components/settings/auth-method-selector.tsx +19 -4
- package/src/components/settings/auth-status-badge.tsx +28 -3
- package/src/components/settings/openai-chatgpt-auth-control.tsx +278 -0
- package/src/components/settings/openai-runtime-section.tsx +7 -1
- package/src/components/settings/providers-runtimes-section.tsx +138 -19
- package/src/components/shared/app-sidebar.tsx +4 -3
- package/src/components/shared/command-palette.tsx +4 -5
- package/src/components/shared/theme-toggle.tsx +5 -24
- package/src/components/shared/workspace-indicator.tsx +61 -2
- package/src/components/tables/__tests__/table-enrichment-sheet.test.tsx +130 -0
- package/src/components/tables/table-create-sheet.tsx +4 -0
- package/src/components/tables/table-enrichment-runs.tsx +103 -0
- package/src/components/tables/table-enrichment-sheet.tsx +538 -0
- package/src/components/tables/table-spreadsheet.tsx +29 -5
- package/src/components/tables/table-toolbar.tsx +10 -1
- package/src/components/tasks/kanban-board.tsx +1 -0
- package/src/components/tasks/kanban-column.tsx +53 -14
- package/src/components/tasks/task-bento-grid.tsx +19 -0
- package/src/components/tasks/task-card.tsx +26 -3
- package/src/components/tasks/task-chip-bar.tsx +24 -0
- package/src/components/tasks/task-result-renderer.tsx +1 -1
- package/src/components/workflows/delay-step-body.tsx +109 -0
- package/src/components/workflows/hooks/use-workflow-status.ts +50 -0
- package/src/components/workflows/loop-status-view.tsx +1 -1
- package/src/components/workflows/shared/step-result.tsx +78 -0
- package/src/components/workflows/shared/workflow-header.tsx +141 -0
- package/src/components/workflows/shared/workflow-loading-skeleton.tsx +36 -0
- package/src/components/workflows/swarm-dashboard.tsx +2 -15
- package/src/components/workflows/views/loop-pattern-view.tsx +137 -0
- package/src/components/workflows/views/sequence-pattern-view.tsx +511 -0
- package/src/components/workflows/workflow-form-view.tsx +133 -16
- package/src/components/workflows/workflow-status-view.tsx +30 -740
- package/src/instrumentation-node.ts +94 -0
- package/src/instrumentation.ts +4 -48
- package/src/lib/agents/__tests__/claude-agent.test.ts +199 -0
- package/src/lib/agents/__tests__/execution-manager.test.ts +1 -27
- package/src/lib/agents/__tests__/failure-reason.test.ts +68 -0
- package/src/lib/agents/__tests__/learned-context.test.ts +0 -11
- package/src/lib/agents/__tests__/learning-session.test.ts +158 -0
- package/src/lib/agents/__tests__/pattern-extractor.test.ts +48 -0
- package/src/lib/agents/claude-agent.ts +155 -18
- package/src/lib/agents/execution-manager.ts +0 -35
- package/src/lib/agents/learned-context.ts +0 -12
- package/src/lib/agents/learning-session.ts +18 -5
- package/src/lib/agents/profiles/__tests__/registry.test.ts +6 -4
- package/src/lib/agents/profiles/builtins/upgrade-assistant/SKILL.md +70 -0
- package/src/lib/agents/profiles/builtins/upgrade-assistant/profile.yaml +32 -0
- package/src/lib/agents/runtime/__tests__/openai-codex-auth.test.ts +118 -0
- package/src/lib/agents/runtime/codex-app-server-client.ts +11 -5
- package/src/lib/agents/runtime/openai-codex-auth.ts +389 -0
- package/src/lib/agents/runtime/openai-codex.ts +29 -60
- package/src/lib/agents/runtime/types.ts +8 -0
- package/src/lib/book/chapter-mapping.ts +11 -0
- package/src/lib/book/content.ts +10 -0
- package/src/lib/chat/__tests__/active-streams.test.ts +49 -0
- package/src/lib/chat/__tests__/finalize-safety-net.test.ts +139 -0
- package/src/lib/chat/__tests__/reconcile.test.ts +137 -0
- package/src/lib/chat/__tests__/stream-telemetry.test.ts +151 -0
- package/src/lib/chat/active-streams.ts +27 -0
- package/src/lib/chat/codex-engine.ts +16 -17
- package/src/lib/chat/context-builder.ts +5 -3
- package/src/lib/chat/engine.ts +50 -3
- package/src/lib/chat/reconcile.ts +117 -0
- package/src/lib/chat/stagent-tools.ts +1 -0
- package/src/lib/chat/stream-telemetry.ts +132 -0
- package/src/lib/chat/suggested-prompts.ts +28 -1
- package/src/lib/chat/system-prompt.ts +26 -1
- package/src/lib/chat/tool-catalog.ts +2 -1
- package/src/lib/chat/tools/__tests__/enrich-table-tool.test.ts +127 -0
- package/src/lib/chat/tools/__tests__/schedule-tools.test.ts +261 -0
- package/src/lib/chat/tools/__tests__/task-tools.test.ts +352 -0
- package/src/lib/chat/tools/__tests__/workflow-tools-dedup.test.ts +217 -0
- package/src/lib/chat/tools/document-tools.ts +29 -13
- package/src/lib/chat/tools/helpers.ts +39 -0
- package/src/lib/chat/tools/notification-tools.ts +9 -5
- package/src/lib/chat/tools/project-tools.ts +33 -0
- package/src/lib/chat/tools/schedule-tools.ts +44 -11
- package/src/lib/chat/tools/table-tools.ts +71 -0
- package/src/lib/chat/tools/task-tools.ts +84 -20
- package/src/lib/chat/tools/workflow-tools.ts +234 -32
- package/src/lib/constants/settings.ts +8 -18
- package/src/lib/data/__tests__/clear.test.ts +56 -2
- package/src/lib/data/clear.ts +20 -15
- package/src/lib/data/delete-project.ts +171 -0
- package/src/lib/db/__tests__/bootstrap.test.ts +1 -1
- package/src/lib/db/bootstrap.ts +45 -16
- package/src/lib/db/index.ts +5 -0
- package/src/lib/db/migrations/0009_add_app_instances.sql +25 -0
- package/src/lib/db/migrations/0024_add_workflow_resume_at.sql +10 -0
- package/src/lib/db/migrations/0025_drop_app_instances.sql +3 -0
- package/src/lib/db/migrations/0026_drop_license.sql +3 -0
- package/src/lib/db/migrations/meta/_journal.json +21 -0
- package/src/lib/db/schema.ts +68 -23
- package/src/lib/environment/workspace-context.ts +13 -1
- package/src/lib/import/dedup.ts +4 -54
- package/src/lib/instance/__tests__/bootstrap.test.ts +362 -0
- package/src/lib/instance/__tests__/detect.test.ts +115 -0
- package/src/lib/instance/__tests__/fingerprint.test.ts +48 -0
- package/src/lib/instance/__tests__/git-ops.test.ts +95 -0
- package/src/lib/instance/__tests__/settings.test.ts +83 -0
- package/src/lib/instance/__tests__/upgrade-poller.test.ts +131 -0
- package/src/lib/instance/bootstrap.ts +270 -0
- package/src/lib/instance/detect.ts +49 -0
- package/src/lib/instance/fingerprint.ts +78 -0
- package/src/lib/instance/git-ops.ts +95 -0
- package/src/lib/instance/settings.ts +61 -0
- package/src/lib/instance/types.ts +77 -0
- package/src/lib/instance/upgrade-poller.ts +153 -0
- package/src/lib/notifications/__tests__/visibility.test.ts +51 -0
- package/src/lib/notifications/visibility.ts +33 -0
- package/src/lib/schedules/__tests__/collision-check.test.ts +93 -0
- package/src/lib/schedules/__tests__/config.test.ts +62 -0
- package/src/lib/schedules/__tests__/firing-metrics.test.ts +99 -0
- package/src/lib/schedules/__tests__/integration.test.ts +82 -0
- package/src/lib/schedules/__tests__/slot-claim.test.ts +242 -0
- package/src/lib/schedules/__tests__/tick-scheduler.test.ts +102 -0
- package/src/lib/schedules/__tests__/turn-budget.test.ts +228 -0
- package/src/lib/schedules/collision-check.ts +105 -0
- package/src/lib/schedules/config.ts +53 -0
- package/src/lib/schedules/scheduler.ts +232 -13
- package/src/lib/schedules/slot-claim.ts +105 -0
- package/src/lib/settings/__tests__/openai-auth.test.ts +101 -0
- package/src/lib/settings/__tests__/openai-login-manager.test.ts +64 -0
- package/src/lib/settings/__tests__/runtime-setup.test.ts +33 -0
- package/src/lib/settings/openai-auth.ts +105 -10
- package/src/lib/settings/openai-login-manager.ts +260 -0
- package/src/lib/settings/runtime-setup.ts +14 -4
- package/src/lib/tables/__tests__/enrichment-planner.test.ts +124 -0
- package/src/lib/tables/__tests__/enrichment.test.ts +147 -0
- package/src/lib/tables/enrichment-planner.ts +454 -0
- package/src/lib/tables/enrichment.ts +328 -0
- package/src/lib/tables/query-builder.ts +5 -2
- package/src/lib/tables/trigger-evaluator.ts +3 -2
- package/src/lib/theme.ts +71 -0
- package/src/lib/usage/ledger.ts +2 -18
- package/src/lib/util/__tests__/similarity.test.ts +106 -0
- package/src/lib/util/similarity.ts +77 -0
- package/src/lib/utils/format-timestamp.ts +24 -0
- package/src/lib/utils/stagent-paths.ts +12 -0
- package/src/lib/validators/__tests__/blueprint.test.ts +172 -0
- package/src/lib/validators/__tests__/settings.test.ts +10 -0
- package/src/lib/validators/blueprint.ts +70 -9
- package/src/lib/validators/profile.ts +2 -2
- package/src/lib/validators/settings.ts +3 -1
- package/src/lib/workflows/__tests__/delay.test.ts +196 -0
- package/src/lib/workflows/__tests__/engine.test.ts +8 -0
- package/src/lib/workflows/__tests__/loop-executor.test.ts +54 -0
- package/src/lib/workflows/__tests__/post-action.test.ts +108 -0
- package/src/lib/workflows/blueprints/instantiator.ts +22 -1
- package/src/lib/workflows/blueprints/types.ts +10 -2
- package/src/lib/workflows/delay.ts +106 -0
- package/src/lib/workflows/engine.ts +207 -4
- package/src/lib/workflows/loop-executor.ts +349 -24
- package/src/lib/workflows/post-action.ts +91 -0
- package/src/lib/workflows/types.ts +166 -1
- package/src/app/api/license/checkout/route.ts +0 -28
- package/src/app/api/license/portal/route.ts +0 -26
- package/src/app/api/license/route.ts +0 -89
- package/src/app/api/license/usage/route.ts +0 -63
- package/src/app/api/marketplace/browse/route.ts +0 -15
- package/src/app/api/marketplace/import/route.ts +0 -28
- package/src/app/api/marketplace/publish/route.ts +0 -40
- package/src/app/api/onboarding/email/route.ts +0 -53
- package/src/app/api/settings/telemetry/route.ts +0 -14
- package/src/app/api/sync/export/route.ts +0 -54
- package/src/app/api/sync/restore/route.ts +0 -37
- package/src/app/api/sync/sessions/route.ts +0 -24
- package/src/app/auth/callback/route.ts +0 -73
- package/src/app/marketplace/page.tsx +0 -19
- package/src/components/analytics/analytics-gate-card.tsx +0 -101
- package/src/components/marketplace/blueprint-card.tsx +0 -61
- package/src/components/marketplace/marketplace-browser.tsx +0 -131
- package/src/components/onboarding/email-capture-card.tsx +0 -104
- package/src/components/settings/activation-form.tsx +0 -95
- package/src/components/settings/cloud-account-section.tsx +0 -147
- package/src/components/settings/cloud-sync-section.tsx +0 -155
- package/src/components/settings/subscription-section.tsx +0 -410
- package/src/components/settings/telemetry-section.tsx +0 -80
- package/src/components/shared/premium-gate-overlay.tsx +0 -50
- package/src/components/shared/schedule-gate-dialog.tsx +0 -64
- package/src/components/shared/upgrade-banner.tsx +0 -112
- package/src/hooks/use-supabase-auth.ts +0 -79
- package/src/lib/billing/email.ts +0 -54
- package/src/lib/billing/products.ts +0 -80
- package/src/lib/billing/stripe.ts +0 -101
- package/src/lib/cloud/supabase-browser.ts +0 -32
- package/src/lib/cloud/supabase-client.ts +0 -56
- package/src/lib/license/__tests__/features.test.ts +0 -56
- package/src/lib/license/__tests__/key-format.test.ts +0 -88
- package/src/lib/license/__tests__/manager.test.ts +0 -64
- package/src/lib/license/__tests__/tier-limits.test.ts +0 -79
- package/src/lib/license/cloud-validation.ts +0 -60
- package/src/lib/license/features.ts +0 -44
- package/src/lib/license/key-format.ts +0 -101
- package/src/lib/license/limit-check.ts +0 -111
- package/src/lib/license/limit-queries.ts +0 -51
- package/src/lib/license/manager.ts +0 -345
- package/src/lib/license/notifications.ts +0 -59
- package/src/lib/license/tier-limits.ts +0 -71
- package/src/lib/marketplace/marketplace-client.ts +0 -107
- package/src/lib/sync/cloud-sync.ts +0 -235
- package/src/lib/telemetry/conversion-events.ts +0 -71
- package/src/lib/telemetry/queue.ts +0 -122
- package/src/lib/validators/license.ts +0 -33
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import Link from "next/link";
|
|
4
4
|
import { usePathname, useRouter } from "next/navigation";
|
|
5
|
-
import { useEffect, useMemo, useRef, useState } from "react";
|
|
5
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
6
6
|
import {
|
|
7
7
|
ArrowUpRight,
|
|
8
8
|
Inbox,
|
|
@@ -108,12 +108,14 @@ function PendingApprovalDetail({
|
|
|
108
108
|
selected,
|
|
109
109
|
overflow,
|
|
110
110
|
onResponded,
|
|
111
|
+
onRequestFailed,
|
|
111
112
|
onOpenInbox,
|
|
112
113
|
onSelect,
|
|
113
114
|
}: {
|
|
114
115
|
selected: PendingApprovalPayload;
|
|
115
116
|
overflow: PendingApprovalPayload[];
|
|
116
117
|
onResponded: () => void;
|
|
118
|
+
onRequestFailed: () => void;
|
|
117
119
|
onOpenInbox: () => void;
|
|
118
120
|
onSelect: (notificationId: string) => void;
|
|
119
121
|
}) {
|
|
@@ -172,6 +174,7 @@ function PendingApprovalDetail({
|
|
|
172
174
|
profileIds={parsed.profileIds}
|
|
173
175
|
body={selected.body ?? ""}
|
|
174
176
|
onResponded={onResponded}
|
|
177
|
+
onRequestFailed={onRequestFailed}
|
|
175
178
|
/>
|
|
176
179
|
);
|
|
177
180
|
})()
|
|
@@ -264,6 +267,33 @@ export function PendingApprovalHost() {
|
|
|
264
267
|
const router = useRouter();
|
|
265
268
|
const pathname = usePathname();
|
|
266
269
|
|
|
270
|
+
const applySnapshot = useCallback((snapshot: PendingApprovalPayload[]) => {
|
|
271
|
+
const nextItems = dedupePendingApprovals(snapshot);
|
|
272
|
+
const previousIds = new Set(knownIdsRef.current);
|
|
273
|
+
const newestNew = nextItems.find(
|
|
274
|
+
(item) => !previousIds.has(item.notificationId)
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
if (newestNew) {
|
|
278
|
+
setAnnouncement(
|
|
279
|
+
`Permission required for ${buildContextLabel(newestNew)}. ${newestNew.compactSummary}`
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
knownIdsRef.current = nextItems.map((item) => item.notificationId);
|
|
284
|
+
setItems(nextItems);
|
|
285
|
+
}, []);
|
|
286
|
+
|
|
287
|
+
const refreshApprovals = useCallback(async () => {
|
|
288
|
+
const res = await fetch("/api/notifications/pending-approvals", {
|
|
289
|
+
cache: "no-store",
|
|
290
|
+
});
|
|
291
|
+
if (!res.ok) return;
|
|
292
|
+
|
|
293
|
+
const snapshot = (await res.json()) as PendingApprovalPayload[];
|
|
294
|
+
applySnapshot(snapshot);
|
|
295
|
+
}, [applySnapshot]);
|
|
296
|
+
|
|
267
297
|
const primary = items[0] ?? null;
|
|
268
298
|
const selected = useMemo(() => {
|
|
269
299
|
if (!items.length) return null;
|
|
@@ -287,51 +317,25 @@ export function PendingApprovalHost() {
|
|
|
287
317
|
let pollId: ReturnType<typeof setInterval> | null = null;
|
|
288
318
|
let eventSource: EventSource | null = null;
|
|
289
319
|
|
|
290
|
-
function applySnapshot(snapshot: PendingApprovalPayload[]) {
|
|
291
|
-
if (cancelled) return;
|
|
292
|
-
|
|
293
|
-
const nextItems = dedupePendingApprovals(snapshot);
|
|
294
|
-
const previousIds = new Set(knownIdsRef.current);
|
|
295
|
-
const newestNew = nextItems.find(
|
|
296
|
-
(item) => !previousIds.has(item.notificationId)
|
|
297
|
-
);
|
|
298
|
-
|
|
299
|
-
if (newestNew) {
|
|
300
|
-
setAnnouncement(
|
|
301
|
-
`Permission required for ${buildContextLabel(newestNew)}. ${newestNew.compactSummary}`
|
|
302
|
-
);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
knownIdsRef.current = nextItems.map((item) => item.notificationId);
|
|
306
|
-
setItems(nextItems);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
async function refresh() {
|
|
310
|
-
try {
|
|
311
|
-
const res = await fetch("/api/notifications/pending-approvals", {
|
|
312
|
-
cache: "no-store",
|
|
313
|
-
});
|
|
314
|
-
if (!res.ok) return;
|
|
315
|
-
|
|
316
|
-
const snapshot = (await res.json()) as PendingApprovalPayload[];
|
|
317
|
-
applySnapshot(snapshot);
|
|
318
|
-
} catch {
|
|
319
|
-
// Fallback refresh should fail quietly.
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
320
|
const startPolling = () => {
|
|
324
321
|
if (pollId) return;
|
|
325
|
-
pollId = setInterval(
|
|
322
|
+
pollId = setInterval(() => {
|
|
323
|
+
refreshApprovals().catch(() => {
|
|
324
|
+
// Fallback refresh should fail quietly.
|
|
325
|
+
});
|
|
326
|
+
}, 15_000);
|
|
326
327
|
};
|
|
327
328
|
|
|
328
|
-
|
|
329
|
+
refreshApprovals().catch(() => {
|
|
330
|
+
// Initial refresh should fail quietly.
|
|
331
|
+
});
|
|
329
332
|
|
|
330
333
|
try {
|
|
331
334
|
eventSource = new EventSource("/api/notifications/pending-approvals/stream");
|
|
332
335
|
eventSource.onmessage = (event) => {
|
|
333
336
|
try {
|
|
334
337
|
const snapshot = JSON.parse(event.data) as PendingApprovalPayload[];
|
|
338
|
+
if (cancelled) return;
|
|
335
339
|
applySnapshot(snapshot);
|
|
336
340
|
} catch {
|
|
337
341
|
startPolling();
|
|
@@ -351,7 +355,7 @@ export function PendingApprovalHost() {
|
|
|
351
355
|
if (pollId) clearInterval(pollId);
|
|
352
356
|
eventSource?.close();
|
|
353
357
|
};
|
|
354
|
-
}, []);
|
|
358
|
+
}, [applySnapshot, refreshApprovals]);
|
|
355
359
|
|
|
356
360
|
function removeNotification(notificationId: string) {
|
|
357
361
|
setItems((current) =>
|
|
@@ -453,6 +457,11 @@ export function PendingApprovalHost() {
|
|
|
453
457
|
profileIds={parsed.profileIds}
|
|
454
458
|
body={primary.body ?? ""}
|
|
455
459
|
onResponded={() => removeNotification(primary.notificationId)}
|
|
460
|
+
onRequestFailed={() => {
|
|
461
|
+
refreshApprovals().catch(() => {
|
|
462
|
+
// Refresh failures are surfaced by the batch review toast.
|
|
463
|
+
});
|
|
464
|
+
}}
|
|
456
465
|
compact
|
|
457
466
|
/>
|
|
458
467
|
);
|
|
@@ -518,6 +527,11 @@ export function PendingApprovalHost() {
|
|
|
518
527
|
selected={selected}
|
|
519
528
|
overflow={overflowItems}
|
|
520
529
|
onResponded={() => removeNotification(selected.notificationId)}
|
|
530
|
+
onRequestFailed={() => {
|
|
531
|
+
refreshApprovals().catch(() => {
|
|
532
|
+
// Refresh failures are surfaced by the batch review toast.
|
|
533
|
+
});
|
|
534
|
+
}}
|
|
521
535
|
onOpenInbox={handleOpenInbox}
|
|
522
536
|
onSelect={setSelectedId}
|
|
523
537
|
/>
|
|
@@ -545,6 +559,11 @@ export function PendingApprovalHost() {
|
|
|
545
559
|
selected={selected}
|
|
546
560
|
overflow={overflowItems}
|
|
547
561
|
onResponded={() => removeNotification(selected.notificationId)}
|
|
562
|
+
onRequestFailed={() => {
|
|
563
|
+
refreshApprovals().catch(() => {
|
|
564
|
+
// Refresh failures are surfaced by the batch review toast.
|
|
565
|
+
});
|
|
566
|
+
}}
|
|
548
567
|
onOpenInbox={handleOpenInbox}
|
|
549
568
|
onSelect={setSelectedId}
|
|
550
569
|
/>
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import { Calendar } from "lucide-react";
|
|
12
12
|
import { toast } from "sonner";
|
|
13
13
|
import { ScheduleForm, type ScheduleFormValues } from "./schedule-form";
|
|
14
|
+
import type { CronCollisionWarning } from "@/lib/schedules/collision-check";
|
|
14
15
|
|
|
15
16
|
interface ScheduleCreateSheetProps {
|
|
16
17
|
projects: { id: string; name: string }[];
|
|
@@ -27,6 +28,7 @@ export function ScheduleCreateSheet({
|
|
|
27
28
|
}: ScheduleCreateSheetProps) {
|
|
28
29
|
const [loading, setLoading] = useState(false);
|
|
29
30
|
const [error, setError] = useState<string | null>(null);
|
|
31
|
+
const [warnings, setWarnings] = useState<CronCollisionWarning[]>([]);
|
|
30
32
|
|
|
31
33
|
async function handleSubmit(values: ScheduleFormValues) {
|
|
32
34
|
setLoading(true);
|
|
@@ -58,10 +60,15 @@ export function ScheduleCreateSheet({
|
|
|
58
60
|
});
|
|
59
61
|
|
|
60
62
|
if (res.ok) {
|
|
63
|
+
const { warnings: newWarnings } = await res.json();
|
|
61
64
|
setError(null);
|
|
62
|
-
|
|
65
|
+
setWarnings(newWarnings ?? []);
|
|
63
66
|
toast.success("Schedule created");
|
|
64
67
|
onCreated();
|
|
68
|
+
if (!newWarnings || newWarnings.length === 0) {
|
|
69
|
+
onOpenChange(false);
|
|
70
|
+
}
|
|
71
|
+
// Keep sheet open if there are warnings so the user sees the banner
|
|
65
72
|
} else {
|
|
66
73
|
const data = await res.json().catch(() => null);
|
|
67
74
|
setError(data?.error ?? `Failed to create schedule (${res.status})`);
|
|
@@ -89,6 +96,17 @@ export function ScheduleCreateSheet({
|
|
|
89
96
|
|
|
90
97
|
{/* Body — px-6 pb-6 per project convention (SheetContent has NO body padding) */}
|
|
91
98
|
<div className="px-6 pb-6 overflow-y-auto">
|
|
99
|
+
{warnings.length > 0 && (
|
|
100
|
+
<div className="mb-4 rounded-lg border border-amber-500/40 bg-amber-50 p-3 text-sm">
|
|
101
|
+
<p className="font-medium text-amber-900">
|
|
102
|
+
Overlap detected with: {warnings[0].overlappingSchedules.join(", ")}
|
|
103
|
+
</p>
|
|
104
|
+
<p className="text-amber-800">
|
|
105
|
+
Combined load: ~{warnings[0].estimatedConcurrentSteps} agent steps.
|
|
106
|
+
Schedules will take turns; the last to run may be delayed.
|
|
107
|
+
</p>
|
|
108
|
+
</div>
|
|
109
|
+
)}
|
|
92
110
|
<ScheduleForm
|
|
93
111
|
projects={projects}
|
|
94
112
|
onSubmit={handleSubmit}
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
type ScheduleFormValues,
|
|
17
17
|
type ScheduleFormInitialValues,
|
|
18
18
|
} from "./schedule-form";
|
|
19
|
+
import type { CronCollisionWarning } from "@/lib/schedules/collision-check";
|
|
19
20
|
|
|
20
21
|
interface ScheduleEditSheetProps {
|
|
21
22
|
scheduleId: string | null;
|
|
@@ -49,6 +50,7 @@ export function ScheduleEditSheet({
|
|
|
49
50
|
const [loaded, setLoaded] = useState(false);
|
|
50
51
|
const [loading, setLoading] = useState(false);
|
|
51
52
|
const [error, setError] = useState<string | null>(null);
|
|
53
|
+
const [warnings, setWarnings] = useState<CronCollisionWarning[]>([]);
|
|
52
54
|
|
|
53
55
|
const fetchSchedule = useCallback(async () => {
|
|
54
56
|
if (!scheduleId) return;
|
|
@@ -63,6 +65,7 @@ export function ScheduleEditSheet({
|
|
|
63
65
|
setSchedule(null);
|
|
64
66
|
setLoaded(false);
|
|
65
67
|
setError(null);
|
|
68
|
+
setWarnings([]);
|
|
66
69
|
return;
|
|
67
70
|
}
|
|
68
71
|
fetchSchedule();
|
|
@@ -102,9 +105,14 @@ export function ScheduleEditSheet({
|
|
|
102
105
|
});
|
|
103
106
|
|
|
104
107
|
if (res.ok) {
|
|
108
|
+
const { warnings: newWarnings } = await res.json();
|
|
109
|
+
setWarnings(newWarnings ?? []);
|
|
105
110
|
toast.success("Schedule updated");
|
|
106
|
-
onOpenChange(false);
|
|
107
111
|
onUpdated();
|
|
112
|
+
if (!newWarnings || newWarnings.length === 0) {
|
|
113
|
+
onOpenChange(false);
|
|
114
|
+
}
|
|
115
|
+
// Keep sheet open if there are warnings so the user sees the banner
|
|
108
116
|
} else {
|
|
109
117
|
const data = await res.json().catch(() => null);
|
|
110
118
|
setError(data?.error ?? `Failed to update schedule (${res.status})`);
|
|
@@ -131,6 +139,17 @@ export function ScheduleEditSheet({
|
|
|
131
139
|
|
|
132
140
|
{/* Body — px-6 pb-6 per project convention (SheetContent has NO body padding) */}
|
|
133
141
|
<div className="px-6 pb-6 overflow-y-auto">
|
|
142
|
+
{warnings.length > 0 && (
|
|
143
|
+
<div className="mb-4 rounded-lg border border-amber-500/40 bg-amber-50 p-3 text-sm">
|
|
144
|
+
<p className="font-medium text-amber-900">
|
|
145
|
+
Overlap detected with: {warnings[0].overlappingSchedules.join(", ")}
|
|
146
|
+
</p>
|
|
147
|
+
<p className="text-amber-800">
|
|
148
|
+
Combined load: ~{warnings[0].estimatedConcurrentSteps} agent steps.
|
|
149
|
+
Schedules will take turns; the last to run may be delayed.
|
|
150
|
+
</p>
|
|
151
|
+
</div>
|
|
152
|
+
)}
|
|
134
153
|
{!loaded ? (
|
|
135
154
|
<div className="space-y-4">
|
|
136
155
|
<Skeleton className="h-8 w-full" />
|
|
@@ -63,6 +63,7 @@ export interface ScheduleFormValues {
|
|
|
63
63
|
activeTimezone: string;
|
|
64
64
|
heartbeatBudgetPerDay: number | "";
|
|
65
65
|
documentIds: string[];
|
|
66
|
+
maxTurns: number | null;
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
export interface ScheduleFormInitialValues {
|
|
@@ -76,6 +77,7 @@ export interface ScheduleFormInitialValues {
|
|
|
76
77
|
recurs: boolean;
|
|
77
78
|
maxFirings: number | null;
|
|
78
79
|
expiresAt: string | null;
|
|
80
|
+
maxTurns?: number | null;
|
|
79
81
|
}
|
|
80
82
|
|
|
81
83
|
interface ScheduleFormProps {
|
|
@@ -140,6 +142,7 @@ export function ScheduleForm({
|
|
|
140
142
|
const [expiresInHours, setExpiresInHours] = useState<number | "">(
|
|
141
143
|
initialValues ? "" : ""
|
|
142
144
|
);
|
|
145
|
+
const [maxTurns, setMaxTurns] = useState<number | null>(initialValues?.maxTurns ?? null);
|
|
143
146
|
const [profiles, setProfiles] = useState<ProfileOption[]>([]);
|
|
144
147
|
|
|
145
148
|
// NL schedule input state
|
|
@@ -279,6 +282,7 @@ export function ScheduleForm({
|
|
|
279
282
|
activeTimezone,
|
|
280
283
|
heartbeatBudgetPerDay,
|
|
281
284
|
documentIds: [...selectedDocIds],
|
|
285
|
+
maxTurns,
|
|
282
286
|
});
|
|
283
287
|
}
|
|
284
288
|
|
|
@@ -507,6 +511,13 @@ export function ScheduleForm({
|
|
|
507
511
|
? "Extra instructions appended to the heartbeat evaluation"
|
|
508
512
|
: "Instructions for each execution"}
|
|
509
513
|
</p>
|
|
514
|
+
{scheduleType === "scheduled" && (
|
|
515
|
+
<p className="text-muted-foreground text-xs">
|
|
516
|
+
Note: writing "MAX N turns" in your prompt is a hint to the model,
|
|
517
|
+
not a runtime limit. Use <strong>Max agent steps</strong> below to enforce
|
|
518
|
+
a budget.
|
|
519
|
+
</p>
|
|
520
|
+
)}
|
|
510
521
|
</div>
|
|
511
522
|
|
|
512
523
|
{/* Natural Language Schedule Input */}
|
|
@@ -640,6 +651,26 @@ export function ScheduleForm({
|
|
|
640
651
|
)}
|
|
641
652
|
</div>
|
|
642
653
|
|
|
654
|
+
{/* Max agent steps */}
|
|
655
|
+
<div className="space-y-2">
|
|
656
|
+
<Label htmlFor="max-turns">Max agent steps per run</Label>
|
|
657
|
+
<Input
|
|
658
|
+
id="max-turns"
|
|
659
|
+
type="number"
|
|
660
|
+
min={1}
|
|
661
|
+
max={10000}
|
|
662
|
+
placeholder="Inherits global default"
|
|
663
|
+
value={maxTurns ?? ""}
|
|
664
|
+
onChange={(e) =>
|
|
665
|
+
setMaxTurns(e.target.value ? parseInt(e.target.value, 10) : null)
|
|
666
|
+
}
|
|
667
|
+
/>
|
|
668
|
+
<p className="text-muted-foreground text-xs">
|
|
669
|
+
One step = one agent action (message, tool call, or sub-response). Most
|
|
670
|
+
schedules use 50–500 steps; heavy research runs 2,000+.
|
|
671
|
+
</p>
|
|
672
|
+
</div>
|
|
673
|
+
|
|
643
674
|
{/* Project */}
|
|
644
675
|
{projects.length > 0 && (
|
|
645
676
|
<div className="space-y-2">
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { render, screen, waitFor } from "@testing-library/react";
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
|
|
4
|
+
import { ProvidersAndRuntimesSection } from "@/components/settings/providers-runtimes-section";
|
|
5
|
+
|
|
6
|
+
describe("providers and runtimes section", () => {
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
vi.clearAllMocks();
|
|
9
|
+
vi.stubGlobal("open", vi.fn());
|
|
10
|
+
vi.stubGlobal(
|
|
11
|
+
"fetch",
|
|
12
|
+
vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
13
|
+
const url = String(input);
|
|
14
|
+
const method = init?.method ?? "GET";
|
|
15
|
+
|
|
16
|
+
if (url === "/api/settings/providers" && method === "GET") {
|
|
17
|
+
return {
|
|
18
|
+
ok: true,
|
|
19
|
+
json: async () => ({
|
|
20
|
+
providers: {
|
|
21
|
+
anthropic: {
|
|
22
|
+
configured: false,
|
|
23
|
+
authMethod: "api_key",
|
|
24
|
+
hasKey: false,
|
|
25
|
+
apiKeySource: "unknown",
|
|
26
|
+
dualBilling: false,
|
|
27
|
+
runtimes: [
|
|
28
|
+
{
|
|
29
|
+
runtimeId: "claude-code",
|
|
30
|
+
label: "Claude Code",
|
|
31
|
+
providerId: "anthropic",
|
|
32
|
+
configured: false,
|
|
33
|
+
authMethod: "none",
|
|
34
|
+
apiKeySource: "unknown",
|
|
35
|
+
billingMode: "usage",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
runtimeId: "anthropic-direct",
|
|
39
|
+
label: "Anthropic Direct API",
|
|
40
|
+
providerId: "anthropic",
|
|
41
|
+
configured: false,
|
|
42
|
+
authMethod: "none",
|
|
43
|
+
apiKeySource: "unknown",
|
|
44
|
+
billingMode: "usage",
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
openai: {
|
|
49
|
+
configured: true,
|
|
50
|
+
authMethod: "oauth",
|
|
51
|
+
hasKey: true,
|
|
52
|
+
apiKeySource: "env",
|
|
53
|
+
oauthConnected: false,
|
|
54
|
+
account: null,
|
|
55
|
+
rateLimits: null,
|
|
56
|
+
login: {
|
|
57
|
+
phase: "idle",
|
|
58
|
+
loginId: null,
|
|
59
|
+
authUrl: null,
|
|
60
|
+
account: null,
|
|
61
|
+
rateLimits: null,
|
|
62
|
+
error: null,
|
|
63
|
+
startedAt: null,
|
|
64
|
+
updatedAt: new Date("2026-04-10T15:00:00.000Z").toISOString(),
|
|
65
|
+
},
|
|
66
|
+
dualBilling: false,
|
|
67
|
+
runtimes: [
|
|
68
|
+
{
|
|
69
|
+
runtimeId: "openai-codex-app-server",
|
|
70
|
+
label: "OpenAI Codex App Server",
|
|
71
|
+
providerId: "openai",
|
|
72
|
+
configured: false,
|
|
73
|
+
authMethod: "oauth",
|
|
74
|
+
apiKeySource: "oauth",
|
|
75
|
+
billingMode: "usage",
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
runtimeId: "openai-direct",
|
|
79
|
+
label: "OpenAI Direct API",
|
|
80
|
+
providerId: "openai",
|
|
81
|
+
configured: true,
|
|
82
|
+
authMethod: "api_key",
|
|
83
|
+
apiKeySource: "env",
|
|
84
|
+
billingMode: "usage",
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
routingPreference: "quality",
|
|
90
|
+
configuredProviderCount: 1,
|
|
91
|
+
}),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (url === "/api/settings/openai/login" && method === "POST") {
|
|
96
|
+
return {
|
|
97
|
+
ok: true,
|
|
98
|
+
json: async () => ({
|
|
99
|
+
phase: "pending",
|
|
100
|
+
loginId: "login-1",
|
|
101
|
+
authUrl: "https://auth.openai.com/log-in",
|
|
102
|
+
account: null,
|
|
103
|
+
rateLimits: null,
|
|
104
|
+
error: null,
|
|
105
|
+
startedAt: new Date("2026-04-10T15:01:00.000Z").toISOString(),
|
|
106
|
+
updatedAt: new Date("2026-04-10T15:01:00.000Z").toISOString(),
|
|
107
|
+
}),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
throw new Error(`Unexpected fetch: ${url}`);
|
|
112
|
+
})
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
afterEach(() => {
|
|
117
|
+
vi.unstubAllGlobals();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("shows partial OpenAI setup state when ChatGPT auth is selected but not connected", async () => {
|
|
121
|
+
render(<ProvidersAndRuntimesSection />);
|
|
122
|
+
|
|
123
|
+
await waitFor(() => {
|
|
124
|
+
expect(screen.getByText("Direct API only")).toBeInTheDocument();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
expect(
|
|
128
|
+
screen.getByText("Codex App Server needs ChatGPT sign-in. OpenAI Direct API remains active.")
|
|
129
|
+
).toBeInTheDocument();
|
|
130
|
+
expect(screen.getAllByText("Sign in with ChatGPT")).toHaveLength(2);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("updates the provider row immediately when ChatGPT sign-in starts", async () => {
|
|
134
|
+
render(<ProvidersAndRuntimesSection />);
|
|
135
|
+
|
|
136
|
+
const signInButton = await screen.findByRole("button", {
|
|
137
|
+
name: "Sign in with ChatGPT",
|
|
138
|
+
});
|
|
139
|
+
signInButton.click();
|
|
140
|
+
|
|
141
|
+
await waitFor(() => {
|
|
142
|
+
expect(
|
|
143
|
+
screen.getByText("Waiting for ChatGPT sign-in. OpenAI Direct API remains active.")
|
|
144
|
+
).toBeInTheDocument();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
expect(screen.getAllByText("Waiting for ChatGPT sign-in")).toHaveLength(2);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
@@ -4,13 +4,22 @@ import { Key, Shield } from "lucide-react";
|
|
|
4
4
|
import { cn } from "@/lib/utils";
|
|
5
5
|
import type { AuthMethod } from "@/lib/constants/settings";
|
|
6
6
|
|
|
7
|
+
interface AuthMethodOption {
|
|
8
|
+
id: AuthMethod;
|
|
9
|
+
icon: typeof Key;
|
|
10
|
+
title: string;
|
|
11
|
+
description: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
7
14
|
interface AuthMethodSelectorProps {
|
|
8
15
|
value: AuthMethod;
|
|
9
16
|
onChange: (method: AuthMethod) => void;
|
|
10
17
|
recommendedMethod?: AuthMethod | null;
|
|
18
|
+
label?: string;
|
|
19
|
+
options?: AuthMethodOption[];
|
|
11
20
|
}
|
|
12
21
|
|
|
13
|
-
const
|
|
22
|
+
const defaultMethods = [
|
|
14
23
|
{
|
|
15
24
|
id: "api_key" as const,
|
|
16
25
|
icon: Key,
|
|
@@ -25,12 +34,18 @@ const methods = [
|
|
|
25
34
|
},
|
|
26
35
|
];
|
|
27
36
|
|
|
28
|
-
export function AuthMethodSelector({
|
|
37
|
+
export function AuthMethodSelector({
|
|
38
|
+
value,
|
|
39
|
+
onChange,
|
|
40
|
+
recommendedMethod,
|
|
41
|
+
label = "Authentication Method",
|
|
42
|
+
options = defaultMethods,
|
|
43
|
+
}: AuthMethodSelectorProps) {
|
|
29
44
|
return (
|
|
30
45
|
<div className="space-y-2">
|
|
31
|
-
<p className="text-sm font-medium">
|
|
46
|
+
<p className="text-sm font-medium">{label}</p>
|
|
32
47
|
<div className="grid grid-cols-2 gap-3">
|
|
33
|
-
{
|
|
48
|
+
{options.map((method) => {
|
|
34
49
|
const Icon = method.icon;
|
|
35
50
|
const isSelected = value === method.id;
|
|
36
51
|
return (
|
|
@@ -1,21 +1,30 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { Badge } from "@/components/ui/badge";
|
|
4
|
-
import type { ApiKeySource } from "@/lib/constants/settings";
|
|
4
|
+
import type { ApiKeySource, AuthMethod } from "@/lib/constants/settings";
|
|
5
5
|
|
|
6
6
|
interface AuthStatusBadgeProps {
|
|
7
7
|
connected: boolean;
|
|
8
8
|
apiKeySource: ApiKeySource;
|
|
9
|
+
authMethod?: AuthMethod | "none";
|
|
10
|
+
oauthLabel?: string;
|
|
11
|
+
oauthConnected?: boolean;
|
|
9
12
|
}
|
|
10
13
|
|
|
11
14
|
const sourceLabels: Record<ApiKeySource, string> = {
|
|
12
15
|
db: "Managed API Key",
|
|
13
16
|
env: "Environment Variable",
|
|
14
|
-
oauth: "OAuth
|
|
17
|
+
oauth: "OAuth",
|
|
15
18
|
unknown: "Unknown",
|
|
16
19
|
};
|
|
17
20
|
|
|
18
|
-
export function AuthStatusBadge({
|
|
21
|
+
export function AuthStatusBadge({
|
|
22
|
+
connected,
|
|
23
|
+
apiKeySource,
|
|
24
|
+
authMethod,
|
|
25
|
+
oauthLabel = "OAuth",
|
|
26
|
+
oauthConnected,
|
|
27
|
+
}: AuthStatusBadgeProps) {
|
|
19
28
|
if (!connected && apiKeySource === "unknown") {
|
|
20
29
|
return (
|
|
21
30
|
<Badge variant="outline" className="border-warning/50 text-warning">
|
|
@@ -32,6 +41,22 @@ export function AuthStatusBadge({ connected, apiKeySource }: AuthStatusBadgeProp
|
|
|
32
41
|
);
|
|
33
42
|
}
|
|
34
43
|
|
|
44
|
+
if (connected && authMethod === "oauth" && oauthConnected === false) {
|
|
45
|
+
return (
|
|
46
|
+
<Badge variant="outline" className="border-status-warning/50 text-status-warning">
|
|
47
|
+
Direct API only
|
|
48
|
+
</Badge>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (connected && authMethod === "oauth" && (oauthConnected ?? true)) {
|
|
53
|
+
return (
|
|
54
|
+
<Badge variant="outline" className="border-success/50 text-success">
|
|
55
|
+
Connected via {oauthLabel}
|
|
56
|
+
</Badge>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
35
60
|
if (apiKeySource === "unknown") {
|
|
36
61
|
return (
|
|
37
62
|
<Badge variant="outline" className="border-success/50 text-success">
|