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
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import {
|
|
5
|
+
CircleSlash,
|
|
6
|
+
ExternalLink,
|
|
7
|
+
Loader2,
|
|
8
|
+
LogOut,
|
|
9
|
+
RefreshCw,
|
|
10
|
+
ShieldCheck,
|
|
11
|
+
XCircle,
|
|
12
|
+
} from "lucide-react";
|
|
13
|
+
import { Button } from "@/components/ui/button";
|
|
14
|
+
import type {
|
|
15
|
+
OpenAIAccountInfo,
|
|
16
|
+
OpenAIRateLimitInfo,
|
|
17
|
+
} from "@/lib/settings/openai-auth";
|
|
18
|
+
import type { OpenAILoginState } from "@/lib/settings/openai-login-manager";
|
|
19
|
+
import type { RuntimeConnectionResult } from "@/lib/agents/runtime/types";
|
|
20
|
+
|
|
21
|
+
interface OpenAIChatGPTAuthControlProps {
|
|
22
|
+
connected: boolean;
|
|
23
|
+
account: OpenAIAccountInfo | null;
|
|
24
|
+
rateLimits: OpenAIRateLimitInfo | null;
|
|
25
|
+
initialLoginState: OpenAILoginState;
|
|
26
|
+
onChanged: () => Promise<void>;
|
|
27
|
+
onLoginStateChange?: (state: OpenAILoginState) => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function formatResetAt(value: number | null | undefined) {
|
|
31
|
+
if (!value) return null;
|
|
32
|
+
return new Date(value * 1000).toLocaleString(undefined, {
|
|
33
|
+
dateStyle: "medium",
|
|
34
|
+
timeStyle: "short",
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function formatPlanType(value: string | null | undefined) {
|
|
39
|
+
if (!value) return "Unknown";
|
|
40
|
+
|
|
41
|
+
switch (value.toLowerCase()) {
|
|
42
|
+
case "prolite":
|
|
43
|
+
case "pro":
|
|
44
|
+
return "Pro";
|
|
45
|
+
case "plus":
|
|
46
|
+
return "Plus";
|
|
47
|
+
case "business":
|
|
48
|
+
return "Business";
|
|
49
|
+
case "enterprise":
|
|
50
|
+
return "Enterprise";
|
|
51
|
+
case "edu":
|
|
52
|
+
case "education":
|
|
53
|
+
return "Education";
|
|
54
|
+
default:
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function OpenAIChatGPTAuthControl({
|
|
60
|
+
connected,
|
|
61
|
+
account,
|
|
62
|
+
rateLimits,
|
|
63
|
+
initialLoginState,
|
|
64
|
+
onChanged,
|
|
65
|
+
onLoginStateChange,
|
|
66
|
+
}: OpenAIChatGPTAuthControlProps) {
|
|
67
|
+
const [loginState, setLoginState] = useState<OpenAILoginState>(initialLoginState);
|
|
68
|
+
const [testResult, setTestResult] = useState<RuntimeConnectionResult | null>(null);
|
|
69
|
+
const [testing, setTesting] = useState(false);
|
|
70
|
+
const [signingOut, setSigningOut] = useState(false);
|
|
71
|
+
|
|
72
|
+
function updateLoginState(next: OpenAILoginState) {
|
|
73
|
+
setLoginState(next);
|
|
74
|
+
onLoginStateChange?.(next);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
updateLoginState(initialLoginState);
|
|
79
|
+
}, [initialLoginState]);
|
|
80
|
+
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
if (loginState.phase !== "pending") return;
|
|
83
|
+
|
|
84
|
+
const interval = window.setInterval(async () => {
|
|
85
|
+
const res = await fetch("/api/settings/openai/login");
|
|
86
|
+
if (!res.ok) return;
|
|
87
|
+
const next = (await res.json()) as OpenAILoginState;
|
|
88
|
+
updateLoginState(next);
|
|
89
|
+
if (next.phase !== "pending") {
|
|
90
|
+
window.clearInterval(interval);
|
|
91
|
+
await onChanged();
|
|
92
|
+
}
|
|
93
|
+
}, 1500);
|
|
94
|
+
|
|
95
|
+
return () => window.clearInterval(interval);
|
|
96
|
+
}, [loginState.phase, onChanged]);
|
|
97
|
+
|
|
98
|
+
async function handleStartLogin() {
|
|
99
|
+
setTestResult(null);
|
|
100
|
+
const res = await fetch("/api/settings/openai/login", { method: "POST" });
|
|
101
|
+
const next = (await res.json()) as OpenAILoginState;
|
|
102
|
+
updateLoginState(next);
|
|
103
|
+
|
|
104
|
+
if (next.authUrl) {
|
|
105
|
+
window.open(next.authUrl, "_blank", "noopener,noreferrer");
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function handleCancelLogin() {
|
|
110
|
+
const res = await fetch("/api/settings/openai/login", { method: "DELETE" });
|
|
111
|
+
const next = (await res.json()) as OpenAILoginState;
|
|
112
|
+
updateLoginState(next);
|
|
113
|
+
await onChanged();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function handleLogout() {
|
|
117
|
+
setSigningOut(true);
|
|
118
|
+
try {
|
|
119
|
+
await fetch("/api/settings/openai/logout", { method: "POST" });
|
|
120
|
+
updateLoginState({
|
|
121
|
+
phase: "idle",
|
|
122
|
+
loginId: null,
|
|
123
|
+
authUrl: null,
|
|
124
|
+
account: null,
|
|
125
|
+
rateLimits: null,
|
|
126
|
+
error: null,
|
|
127
|
+
startedAt: null,
|
|
128
|
+
updatedAt: new Date().toISOString(),
|
|
129
|
+
});
|
|
130
|
+
setTestResult(null);
|
|
131
|
+
await onChanged();
|
|
132
|
+
} finally {
|
|
133
|
+
setSigningOut(false);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function handleTestConnection() {
|
|
138
|
+
setTesting(true);
|
|
139
|
+
setTestResult(null);
|
|
140
|
+
try {
|
|
141
|
+
const res = await fetch("/api/settings/test", {
|
|
142
|
+
method: "POST",
|
|
143
|
+
headers: { "Content-Type": "application/json" },
|
|
144
|
+
body: JSON.stringify({ runtime: "openai-codex-app-server" }),
|
|
145
|
+
});
|
|
146
|
+
const result = (await res.json()) as RuntimeConnectionResult;
|
|
147
|
+
setTestResult(result);
|
|
148
|
+
if (result.connected) {
|
|
149
|
+
await onChanged();
|
|
150
|
+
}
|
|
151
|
+
} finally {
|
|
152
|
+
setTesting(false);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const visibleAccount = account ?? loginState.account;
|
|
157
|
+
const visibleRateLimits = rateLimits ?? loginState.rateLimits;
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<div className="space-y-3">
|
|
161
|
+
<p className="text-sm text-muted-foreground">
|
|
162
|
+
ChatGPT mode uses Codex App Server's browser sign-in flow and keeps the session
|
|
163
|
+
in Stagent's isolated Codex home so it does not touch your normal `~/.codex` login.
|
|
164
|
+
</p>
|
|
165
|
+
|
|
166
|
+
{visibleAccount?.email && (
|
|
167
|
+
<div className="rounded-xl border border-border/60 bg-background/40 px-3 py-2">
|
|
168
|
+
<div className="flex items-center gap-2 text-sm font-medium">
|
|
169
|
+
<ShieldCheck className="h-4 w-4 text-success" />
|
|
170
|
+
<span>{visibleAccount.email}</span>
|
|
171
|
+
</div>
|
|
172
|
+
<p className="mt-1 text-xs text-muted-foreground">
|
|
173
|
+
Plan: {formatPlanType(visibleAccount.planType)}
|
|
174
|
+
</p>
|
|
175
|
+
</div>
|
|
176
|
+
)}
|
|
177
|
+
|
|
178
|
+
{visibleRateLimits?.primary && (
|
|
179
|
+
<div className="rounded-xl border border-border/60 bg-background/40 px-3 py-2">
|
|
180
|
+
<p className="text-sm font-medium">Codex rate limits</p>
|
|
181
|
+
<p className="mt-1 text-xs text-muted-foreground">
|
|
182
|
+
{visibleRateLimits.primary.usedPercent ?? 0}% used in the current{" "}
|
|
183
|
+
{visibleRateLimits.primary.windowDurationMins ?? "?"}-minute window
|
|
184
|
+
{formatResetAt(visibleRateLimits.primary.resetsAt)
|
|
185
|
+
? ` • resets ${formatResetAt(visibleRateLimits.primary.resetsAt)}`
|
|
186
|
+
: ""}
|
|
187
|
+
</p>
|
|
188
|
+
</div>
|
|
189
|
+
)}
|
|
190
|
+
|
|
191
|
+
{loginState.phase === "pending" ? (
|
|
192
|
+
<div className="rounded-xl border border-primary/20 bg-primary/5 px-3 py-3">
|
|
193
|
+
<div className="flex items-center gap-2 text-sm font-medium text-foreground">
|
|
194
|
+
<Loader2 className="h-4 w-4 animate-spin text-primary" />
|
|
195
|
+
Waiting for ChatGPT sign-in
|
|
196
|
+
</div>
|
|
197
|
+
<p className="mt-1 text-xs text-muted-foreground">
|
|
198
|
+
Complete the browser flow, then return here. This page will update automatically.
|
|
199
|
+
</p>
|
|
200
|
+
<div className="mt-3 flex flex-wrap items-center gap-2">
|
|
201
|
+
{loginState.authUrl && (
|
|
202
|
+
<Button asChild variant="outline" size="sm">
|
|
203
|
+
<a href={loginState.authUrl} target="_blank" rel="noreferrer">
|
|
204
|
+
Open login page
|
|
205
|
+
<ExternalLink className="ml-1 h-3.5 w-3.5" />
|
|
206
|
+
</a>
|
|
207
|
+
</Button>
|
|
208
|
+
)}
|
|
209
|
+
<Button variant="ghost" size="sm" onClick={handleCancelLogin}>
|
|
210
|
+
Cancel sign-in
|
|
211
|
+
</Button>
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
) : (
|
|
215
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
216
|
+
{!connected && (
|
|
217
|
+
<Button size="sm" onClick={handleStartLogin}>
|
|
218
|
+
Sign in with ChatGPT
|
|
219
|
+
</Button>
|
|
220
|
+
)}
|
|
221
|
+
<Button
|
|
222
|
+
variant="outline"
|
|
223
|
+
size="sm"
|
|
224
|
+
onClick={handleTestConnection}
|
|
225
|
+
disabled={testing}
|
|
226
|
+
>
|
|
227
|
+
{testing && <Loader2 className="mr-1 h-3 w-3 animate-spin" />}
|
|
228
|
+
Test connection
|
|
229
|
+
</Button>
|
|
230
|
+
{connected && (
|
|
231
|
+
<Button
|
|
232
|
+
variant="ghost"
|
|
233
|
+
size="sm"
|
|
234
|
+
onClick={handleLogout}
|
|
235
|
+
disabled={signingOut}
|
|
236
|
+
>
|
|
237
|
+
{signingOut ? (
|
|
238
|
+
<Loader2 className="mr-1 h-3 w-3 animate-spin" />
|
|
239
|
+
) : (
|
|
240
|
+
<LogOut className="mr-1 h-3.5 w-3.5" />
|
|
241
|
+
)}
|
|
242
|
+
Sign out
|
|
243
|
+
</Button>
|
|
244
|
+
)}
|
|
245
|
+
</div>
|
|
246
|
+
)}
|
|
247
|
+
|
|
248
|
+
{loginState.phase === "cancelled" && (
|
|
249
|
+
<p className="flex items-center gap-1.5 text-sm text-muted-foreground">
|
|
250
|
+
<CircleSlash className="h-4 w-4" />
|
|
251
|
+
<span>ChatGPT sign-in cancelled.</span>
|
|
252
|
+
</p>
|
|
253
|
+
)}
|
|
254
|
+
|
|
255
|
+
{loginState.phase === "failed" && loginState.error && (
|
|
256
|
+
<p className="flex items-center gap-1.5 text-sm text-status-failed">
|
|
257
|
+
<XCircle className="h-4 w-4" />
|
|
258
|
+
<span>{loginState.error}</span>
|
|
259
|
+
</p>
|
|
260
|
+
)}
|
|
261
|
+
|
|
262
|
+
{testResult && (
|
|
263
|
+
<p
|
|
264
|
+
className={`flex items-center gap-1.5 text-sm ${
|
|
265
|
+
testResult.connected ? "text-success" : "text-status-failed"
|
|
266
|
+
}`}
|
|
267
|
+
>
|
|
268
|
+
{testResult.connected ? (
|
|
269
|
+
<ShieldCheck className="h-4 w-4" />
|
|
270
|
+
) : (
|
|
271
|
+
<RefreshCw className="h-4 w-4" />
|
|
272
|
+
)}
|
|
273
|
+
<span>{testResult.connected ? "Connected" : testResult.error ?? "Connection failed"}</span>
|
|
274
|
+
</p>
|
|
275
|
+
)}
|
|
276
|
+
</div>
|
|
277
|
+
);
|
|
278
|
+
}
|
|
@@ -13,12 +13,15 @@ import { ApiKeyForm } from "./api-key-form";
|
|
|
13
13
|
import { AuthStatusBadge } from "./auth-status-badge";
|
|
14
14
|
|
|
15
15
|
interface OpenAISettings {
|
|
16
|
+
method: "api_key" | "oauth";
|
|
16
17
|
hasKey: boolean;
|
|
17
18
|
apiKeySource: "db" | "env" | "unknown";
|
|
19
|
+
oauthConnected?: boolean;
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
export function OpenAIRuntimeSection() {
|
|
21
23
|
const [settings, setSettings] = useState<OpenAISettings>({
|
|
24
|
+
method: "api_key",
|
|
22
25
|
hasKey: false,
|
|
23
26
|
apiKeySource: "unknown",
|
|
24
27
|
});
|
|
@@ -41,7 +44,7 @@ export function OpenAIRuntimeSection() {
|
|
|
41
44
|
const res = await fetch("/api/settings/openai", {
|
|
42
45
|
method: "POST",
|
|
43
46
|
headers: { "Content-Type": "application/json" },
|
|
44
|
-
body: JSON.stringify({ apiKey }),
|
|
47
|
+
body: JSON.stringify({ method: "api_key", apiKey }),
|
|
45
48
|
});
|
|
46
49
|
if (res.ok) {
|
|
47
50
|
const data = (await res.json()) as OpenAISettings;
|
|
@@ -80,6 +83,9 @@ export function OpenAIRuntimeSection() {
|
|
|
80
83
|
<AuthStatusBadge
|
|
81
84
|
connected={connected}
|
|
82
85
|
apiKeySource={settings.apiKeySource}
|
|
86
|
+
authMethod={settings.method}
|
|
87
|
+
oauthConnected={settings.oauthConnected}
|
|
88
|
+
oauthLabel="ChatGPT"
|
|
83
89
|
/>
|
|
84
90
|
</div>
|
|
85
91
|
</CardHeader>
|
|
@@ -17,8 +17,11 @@ import { AuthMethodSelector } from "./auth-method-selector";
|
|
|
17
17
|
import { ApiKeyForm } from "./api-key-form";
|
|
18
18
|
import { AuthStatusBadge } from "./auth-status-badge";
|
|
19
19
|
import { ConnectionTestControl } from "./connection-test-control";
|
|
20
|
+
import { OpenAIChatGPTAuthControl } from "./openai-chatgpt-auth-control";
|
|
20
21
|
import type { AuthMethod, ApiKeySource, RoutingPreference } from "@/lib/constants/settings";
|
|
21
22
|
import type { RuntimeSetupState } from "@/lib/settings/runtime-setup";
|
|
23
|
+
import type { OpenAIAccountInfo, OpenAIRateLimitInfo } from "@/lib/settings/openai-auth";
|
|
24
|
+
import type { OpenAILoginState } from "@/lib/settings/openai-login-manager";
|
|
22
25
|
|
|
23
26
|
// ── Types ────────────────────────────────────────────────────────────
|
|
24
27
|
|
|
@@ -27,6 +30,10 @@ interface ProviderState {
|
|
|
27
30
|
authMethod?: AuthMethod;
|
|
28
31
|
hasKey: boolean;
|
|
29
32
|
apiKeySource: ApiKeySource;
|
|
33
|
+
oauthConnected?: boolean;
|
|
34
|
+
account?: OpenAIAccountInfo | null;
|
|
35
|
+
rateLimits?: OpenAIRateLimitInfo | null;
|
|
36
|
+
login?: OpenAILoginState;
|
|
30
37
|
dualBilling: boolean;
|
|
31
38
|
runtimes: RuntimeSetupState[];
|
|
32
39
|
}
|
|
@@ -99,6 +106,7 @@ const BILLING_LABELS: Record<string, string> = {
|
|
|
99
106
|
|
|
100
107
|
function ProviderRow({
|
|
101
108
|
name,
|
|
109
|
+
oauthLabel,
|
|
102
110
|
provider,
|
|
103
111
|
defaultOpen,
|
|
104
112
|
open: controlledOpen,
|
|
@@ -106,6 +114,7 @@ function ProviderRow({
|
|
|
106
114
|
children,
|
|
107
115
|
}: {
|
|
108
116
|
name: string;
|
|
117
|
+
oauthLabel?: string;
|
|
109
118
|
provider: ProviderState;
|
|
110
119
|
defaultOpen: boolean;
|
|
111
120
|
open?: boolean;
|
|
@@ -125,10 +134,19 @@ function ProviderRow({
|
|
|
125
134
|
const activeRuntimes = provider.runtimes.filter((r) => r.configured);
|
|
126
135
|
const activeCount = activeRuntimes.length;
|
|
127
136
|
const activeLabels = activeRuntimes.map((r) => r.label).join(", ");
|
|
137
|
+
const openAIOAuthPending = provider.authMethod === "oauth" && provider.oauthConnected === false;
|
|
138
|
+
const openAILoginPending = provider.login?.phase === "pending";
|
|
128
139
|
|
|
129
140
|
let statusLine: string;
|
|
130
141
|
if (!provider.configured) {
|
|
131
|
-
statusLine =
|
|
142
|
+
statusLine =
|
|
143
|
+
provider.authMethod === "oauth"
|
|
144
|
+
? "Sign in with ChatGPT to enable Codex App Server"
|
|
145
|
+
: "Add an API key to enable runtimes";
|
|
146
|
+
} else if (openAIOAuthPending && activeCount > 0) {
|
|
147
|
+
statusLine = openAILoginPending
|
|
148
|
+
? `Waiting for ${oauthLabel ?? "OAuth"} sign-in. ${activeLabels} remains active.`
|
|
149
|
+
: `Codex App Server needs ${oauthLabel ?? "OAuth"} sign-in. ${activeLabels} remains active.`;
|
|
132
150
|
} else if (activeCount === 2) {
|
|
133
151
|
statusLine = `2 runtimes active: ${activeLabels}`;
|
|
134
152
|
} else if (activeCount === 1) {
|
|
@@ -157,6 +175,9 @@ function ProviderRow({
|
|
|
157
175
|
<AuthStatusBadge
|
|
158
176
|
connected={provider.configured}
|
|
159
177
|
apiKeySource={provider.apiKeySource}
|
|
178
|
+
authMethod={provider.authMethod}
|
|
179
|
+
oauthLabel={oauthLabel}
|
|
180
|
+
oauthConnected={provider.oauthConnected}
|
|
160
181
|
/>
|
|
161
182
|
</div>
|
|
162
183
|
<p className="text-xs text-muted-foreground mt-0.5">{statusLine}</p>
|
|
@@ -178,8 +199,10 @@ function ProviderRow({
|
|
|
178
199
|
<div className="rounded-xl border border-primary/20 bg-primary/5 px-3 py-2">
|
|
179
200
|
<p className="text-xs text-muted-foreground">
|
|
180
201
|
<span className="font-medium text-foreground">Two billing modes active.</span>{" "}
|
|
181
|
-
|
|
182
|
-
|
|
202
|
+
{name === "Anthropic"
|
|
203
|
+
? "Claude Code uses your Max/Pro subscription. Anthropic Direct API uses pay-as-you-go API billing."
|
|
204
|
+
: "Codex App Server uses your ChatGPT plan. OpenAI Direct uses pay-as-you-go API billing."}{" "}
|
|
205
|
+
Budget guardrails track each separately.
|
|
183
206
|
</p>
|
|
184
207
|
</div>
|
|
185
208
|
)}
|
|
@@ -192,6 +215,14 @@ function ProviderRow({
|
|
|
192
215
|
<div className="grid gap-2 sm:grid-cols-2">
|
|
193
216
|
{provider.runtimes.map((runtime) => {
|
|
194
217
|
const isActive = runtime.configured;
|
|
218
|
+
const inactiveDescription = runtime.runtimeId.includes("direct")
|
|
219
|
+
? "Requires API key"
|
|
220
|
+
: runtime.runtimeId === "openai-codex-app-server" &&
|
|
221
|
+
provider.authMethod === "oauth"
|
|
222
|
+
? provider.login?.phase === "pending"
|
|
223
|
+
? "Waiting for ChatGPT sign-in"
|
|
224
|
+
: "Sign in with ChatGPT"
|
|
225
|
+
: "Requires CLI or API key";
|
|
195
226
|
return (
|
|
196
227
|
<div
|
|
197
228
|
key={runtime.runtimeId}
|
|
@@ -212,9 +243,7 @@ function ProviderRow({
|
|
|
212
243
|
<p className="text-xs text-muted-foreground mt-0.5">
|
|
213
244
|
{isActive
|
|
214
245
|
? (RUNTIME_DESCRIPTIONS[runtime.runtimeId] ?? "Active")
|
|
215
|
-
:
|
|
216
|
-
? "Requires API key"
|
|
217
|
-
: "Requires CLI or API key"}
|
|
246
|
+
: inactiveDescription}
|
|
218
247
|
</p>
|
|
219
248
|
</div>
|
|
220
249
|
);
|
|
@@ -233,6 +262,7 @@ export function ProvidersAndRuntimesSection() {
|
|
|
233
262
|
const [data, setData] = useState<ProvidersPayload | null>(null);
|
|
234
263
|
const [loading, setLoading] = useState(true);
|
|
235
264
|
const [anthropicOpen, setAnthropicOpen] = useState(false);
|
|
265
|
+
const [openAILoginState, setOpenAILoginState] = useState<OpenAILoginState | null>(null);
|
|
236
266
|
|
|
237
267
|
const fetchData = useCallback(async () => {
|
|
238
268
|
try {
|
|
@@ -256,6 +286,7 @@ export function ProvidersAndRuntimesSection() {
|
|
|
256
286
|
if (none || !data.providers.anthropic.configured) {
|
|
257
287
|
setAnthropicOpen(true);
|
|
258
288
|
}
|
|
289
|
+
setOpenAILoginState(data.providers.openai.login ?? null);
|
|
259
290
|
}
|
|
260
291
|
}, [data?.configuredProviderCount, data?.providers.anthropic.configured]);
|
|
261
292
|
|
|
@@ -292,7 +323,16 @@ export function ProvidersAndRuntimesSection() {
|
|
|
292
323
|
const res = await fetch("/api/settings/openai", {
|
|
293
324
|
method: "POST",
|
|
294
325
|
headers: { "Content-Type": "application/json" },
|
|
295
|
-
body: JSON.stringify({ apiKey }),
|
|
326
|
+
body: JSON.stringify({ method: "api_key", apiKey }),
|
|
327
|
+
});
|
|
328
|
+
if (res.ok) fetchData();
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async function handleOpenAIMethodChange(method: AuthMethod) {
|
|
332
|
+
const res = await fetch("/api/settings/openai", {
|
|
333
|
+
method: "POST",
|
|
334
|
+
headers: { "Content-Type": "application/json" },
|
|
335
|
+
body: JSON.stringify({ method }),
|
|
296
336
|
});
|
|
297
337
|
if (res.ok) fetchData();
|
|
298
338
|
}
|
|
@@ -308,6 +348,17 @@ export function ProvidersAndRuntimesSection() {
|
|
|
308
348
|
return result;
|
|
309
349
|
}
|
|
310
350
|
|
|
351
|
+
async function handleOpenAIDirectTest() {
|
|
352
|
+
const res = await fetch("/api/settings/test", {
|
|
353
|
+
method: "POST",
|
|
354
|
+
headers: { "Content-Type": "application/json" },
|
|
355
|
+
body: JSON.stringify({ runtime: "openai-direct" }),
|
|
356
|
+
});
|
|
357
|
+
const result = await res.json();
|
|
358
|
+
fetchData();
|
|
359
|
+
return result;
|
|
360
|
+
}
|
|
361
|
+
|
|
311
362
|
// ── Routing preference handler ───────────────────────────────────
|
|
312
363
|
|
|
313
364
|
async function handleRoutingChange(value: RoutingPreference) {
|
|
@@ -349,6 +400,10 @@ export function ProvidersAndRuntimesSection() {
|
|
|
349
400
|
}
|
|
350
401
|
|
|
351
402
|
const { providers, routingPreference, configuredProviderCount } = data;
|
|
403
|
+
const openAIProvider: ProviderState = {
|
|
404
|
+
...providers.openai,
|
|
405
|
+
login: openAILoginState ?? providers.openai.login,
|
|
406
|
+
};
|
|
352
407
|
const noneConfigured = configuredProviderCount === 0;
|
|
353
408
|
const recommendedAuth = recommendedAuthForRouting(routingPreference);
|
|
354
409
|
|
|
@@ -430,7 +485,7 @@ export function ProvidersAndRuntimesSection() {
|
|
|
430
485
|
<p className="text-xs text-primary/70">
|
|
431
486
|
{recommendedAuth === "api_key"
|
|
432
487
|
? "This preference works best with an API key configured below."
|
|
433
|
-
: "This preference works well with
|
|
488
|
+
: "This preference works well with subscription-backed auth configured below."}
|
|
434
489
|
</p>
|
|
435
490
|
)}
|
|
436
491
|
</div>
|
|
@@ -440,6 +495,7 @@ export function ProvidersAndRuntimesSection() {
|
|
|
440
495
|
{/* Anthropic provider — controlled open state */}
|
|
441
496
|
<ProviderRow
|
|
442
497
|
name="Anthropic"
|
|
498
|
+
oauthLabel="Claude Max/Pro"
|
|
443
499
|
provider={providers.anthropic}
|
|
444
500
|
defaultOpen={false}
|
|
445
501
|
open={anthropicOpen}
|
|
@@ -479,19 +535,82 @@ export function ProvidersAndRuntimesSection() {
|
|
|
479
535
|
{/* OpenAI provider — uncontrolled */}
|
|
480
536
|
<ProviderRow
|
|
481
537
|
name="OpenAI"
|
|
482
|
-
|
|
483
|
-
|
|
538
|
+
oauthLabel="ChatGPT"
|
|
539
|
+
provider={openAIProvider}
|
|
540
|
+
defaultOpen={
|
|
541
|
+
noneConfigured ||
|
|
542
|
+
!openAIProvider.configured ||
|
|
543
|
+
((openAIProvider.authMethod ?? "api_key") === "oauth" &&
|
|
544
|
+
!(openAIProvider.oauthConnected ?? false))
|
|
545
|
+
}
|
|
484
546
|
>
|
|
485
|
-
<
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
547
|
+
<AuthMethodSelector
|
|
548
|
+
value={openAIProvider.authMethod ?? "api_key"}
|
|
549
|
+
onChange={handleOpenAIMethodChange}
|
|
550
|
+
recommendedMethod={recommendedAuth}
|
|
551
|
+
label="Codex App Server Authentication"
|
|
552
|
+
options={[
|
|
553
|
+
{
|
|
554
|
+
id: "api_key",
|
|
555
|
+
icon: Zap,
|
|
556
|
+
title: "API Key",
|
|
557
|
+
description: "Use an OpenAI API key for Codex App Server",
|
|
558
|
+
},
|
|
559
|
+
{
|
|
560
|
+
id: "oauth",
|
|
561
|
+
icon: Crown,
|
|
562
|
+
title: "ChatGPT",
|
|
563
|
+
description: "Use your ChatGPT plan with browser sign-in",
|
|
564
|
+
},
|
|
565
|
+
]}
|
|
494
566
|
/>
|
|
567
|
+
|
|
568
|
+
{(openAIProvider.authMethod ?? "api_key") === "oauth" ? (
|
|
569
|
+
<OpenAIChatGPTAuthControl
|
|
570
|
+
connected={openAIProvider.oauthConnected ?? false}
|
|
571
|
+
account={openAIProvider.account ?? null}
|
|
572
|
+
rateLimits={openAIProvider.rateLimits ?? null}
|
|
573
|
+
initialLoginState={
|
|
574
|
+
openAIProvider.login ?? {
|
|
575
|
+
phase: "idle",
|
|
576
|
+
loginId: null,
|
|
577
|
+
authUrl: null,
|
|
578
|
+
account: null,
|
|
579
|
+
rateLimits: null,
|
|
580
|
+
error: null,
|
|
581
|
+
startedAt: null,
|
|
582
|
+
updatedAt: new Date().toISOString(),
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
onChanged={fetchData}
|
|
586
|
+
onLoginStateChange={setOpenAILoginState}
|
|
587
|
+
/>
|
|
588
|
+
) : (
|
|
589
|
+
<p className="text-sm text-muted-foreground">
|
|
590
|
+
API key mode authenticates Codex App Server directly and also powers OpenAI Direct.
|
|
591
|
+
</p>
|
|
592
|
+
)}
|
|
593
|
+
|
|
594
|
+
<Separator />
|
|
595
|
+
|
|
596
|
+
<div className="space-y-4">
|
|
597
|
+
<div>
|
|
598
|
+
<p className="text-sm font-medium">OpenAI Direct API Key</p>
|
|
599
|
+
<p className="mt-1 text-xs text-muted-foreground">
|
|
600
|
+
Used by the OpenAI Direct runtime. If Codex App Server is in API key mode, it shares this key.
|
|
601
|
+
</p>
|
|
602
|
+
</div>
|
|
603
|
+
<ApiKeyForm
|
|
604
|
+
hasKey={providers.openai.hasKey}
|
|
605
|
+
onSave={handleOpenAISaveKey}
|
|
606
|
+
onTest={handleOpenAIDirectTest}
|
|
607
|
+
keyPrefix="sk-"
|
|
608
|
+
placeholder="sk-..."
|
|
609
|
+
maskedPrefix="sk-••••••"
|
|
610
|
+
envVarName="OPENAI_API_KEY"
|
|
611
|
+
testButtonLabel="Test OpenAI Direct"
|
|
612
|
+
/>
|
|
613
|
+
</div>
|
|
495
614
|
</ProviderRow>
|
|
496
615
|
</CardContent>
|
|
497
616
|
</Card>
|
|
@@ -21,7 +21,6 @@ import {
|
|
|
21
21
|
MessageCircle,
|
|
22
22
|
Table2,
|
|
23
23
|
BarChart3,
|
|
24
|
-
Store,
|
|
25
24
|
ChevronDown,
|
|
26
25
|
} from "lucide-react";
|
|
27
26
|
import { cn } from "@/lib/utils";
|
|
@@ -43,6 +42,7 @@ import {
|
|
|
43
42
|
import { ThemeToggle } from "@/components/shared/theme-toggle";
|
|
44
43
|
import { TrustTierBadge } from "@/components/shared/trust-tier-badge";
|
|
45
44
|
import { UnreadBadge } from "@/components/notifications/unread-badge";
|
|
45
|
+
import { UpgradeBadge } from "@/components/instance/upgrade-badge";
|
|
46
46
|
import { AuthStatusDot } from "@/components/settings/auth-status-dot";
|
|
47
47
|
import { StagentLogo } from "@/components/shared/stagent-logo";
|
|
48
48
|
import { WorkspaceIndicator } from "@/components/shared/workspace-indicator";
|
|
@@ -65,7 +65,6 @@ const workItems: NavItem[] = [
|
|
|
65
65
|
{ title: "Workflows", href: "/workflows", icon: Workflow },
|
|
66
66
|
{ title: "Documents", href: "/documents", icon: FileText },
|
|
67
67
|
{ title: "Tables", href: "/tables", icon: Table2, alsoMatches: ["/tables/"] },
|
|
68
|
-
{ title: "Marketplace", href: "/marketplace", icon: Store },
|
|
69
68
|
];
|
|
70
69
|
|
|
71
70
|
const manageItems: NavItem[] = [
|
|
@@ -199,7 +198,6 @@ function NavGroup({
|
|
|
199
198
|
|
|
200
199
|
export function AppSidebar() {
|
|
201
200
|
const pathname = usePathname();
|
|
202
|
-
|
|
203
201
|
// Determine which group owns the current route
|
|
204
202
|
const activeGroup = useMemo(() => {
|
|
205
203
|
for (const group of groupMap) {
|
|
@@ -248,6 +246,9 @@ export function AppSidebar() {
|
|
|
248
246
|
))}
|
|
249
247
|
</SidebarContent>
|
|
250
248
|
<SidebarFooter className="px-4 py-3">
|
|
249
|
+
<div className="group-data-[collapsible=icon]:hidden mb-2 empty:hidden">
|
|
250
|
+
<UpgradeBadge />
|
|
251
|
+
</div>
|
|
251
252
|
<div className="group-data-[collapsible=icon]:hidden mb-2">
|
|
252
253
|
<WorkspaceIndicator variant="sidebar" />
|
|
253
254
|
</div>
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
BookOpen,
|
|
24
24
|
} from "lucide-react";
|
|
25
25
|
import { navigationItems, createItems } from "@/lib/chat/command-data";
|
|
26
|
+
import { toggleTheme } from "@/lib/theme";
|
|
26
27
|
|
|
27
28
|
interface RecentProject {
|
|
28
29
|
id: string;
|
|
@@ -115,11 +116,9 @@ export function CommandPalette() {
|
|
|
115
116
|
[router]
|
|
116
117
|
);
|
|
117
118
|
|
|
118
|
-
function
|
|
119
|
+
function handleToggleTheme() {
|
|
119
120
|
setOpen(false);
|
|
120
|
-
|
|
121
|
-
document.documentElement.classList.toggle("dark");
|
|
122
|
-
localStorage.setItem("stagent-theme", isDark ? "light" : "dark");
|
|
121
|
+
toggleTheme();
|
|
123
122
|
}
|
|
124
123
|
|
|
125
124
|
async function markAllRead() {
|
|
@@ -239,7 +238,7 @@ export function CommandPalette() {
|
|
|
239
238
|
|
|
240
239
|
{/* Utility */}
|
|
241
240
|
<CommandGroup heading="Utility">
|
|
242
|
-
<CommandItem onSelect={
|
|
241
|
+
<CommandItem onSelect={handleToggleTheme} value="Toggle Theme" keywords={["dark", "light", "mode"]}>
|
|
243
242
|
<Sun className="h-4 w-4 dark:hidden" />
|
|
244
243
|
<Moon className="h-4 w-4 hidden dark:block" />
|
|
245
244
|
Toggle Theme
|