indusagi-coding-agent 0.2.8 → 0.2.10
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/CHANGELOG.md +7 -0
- package/dist/entry.js +4 -4
- package/dist/index.js +4 -4
- package/package.json +3 -2
- package/src/_decls/entry.ts +18 -0
- package/src/_decls/guardrails.ts +35 -0
- package/src/_decls/index.ts +26 -0
- package/src/addons/contract.ts +236 -0
- package/src/addons/dispatch/event-dispatcher.ts +164 -0
- package/src/addons/dispatch/index.ts +25 -0
- package/src/addons/dispatch/tool-interceptor.ts +208 -0
- package/src/addons/host.ts +225 -0
- package/src/addons/index.ts +112 -0
- package/src/addons/manifest.ts +158 -0
- package/src/addons/sandbox.ts +170 -0
- package/src/addons/surface.ts +78 -0
- package/src/boot/auth-vault.ts +195 -0
- package/src/boot/boot.ts +138 -0
- package/src/boot/contract.ts +238 -0
- package/src/boot/heap.ts +59 -0
- package/src/boot/index.ts +28 -0
- package/src/boot/invocation.ts +93 -0
- package/src/boot/runners/addon-wiring.ts +153 -0
- package/src/boot/runners/checkpoint.ts +169 -0
- package/src/boot/runners/delegate-runner.ts +294 -0
- package/src/boot/runners/index.ts +13 -0
- package/src/boot/runners/link-runner.ts +45 -0
- package/src/boot/runners/memdir.ts +168 -0
- package/src/boot/runners/oneshot-runner.ts +58 -0
- package/src/boot/runners/read-state.ts +90 -0
- package/src/boot/runners/registry.ts +42 -0
- package/src/boot/runners/repl-runner.ts +143 -0
- package/src/boot/runners/server-mode.ts +121 -0
- package/src/boot/runners/session.ts +641 -0
- package/src/boot/server-token.ts +148 -0
- package/src/boot/stages.ts +167 -0
- package/src/boot/upgrade/apply.ts +94 -0
- package/src/boot/upgrade/index.ts +13 -0
- package/src/boot/upgrade/upgrades.ts +289 -0
- package/src/briefing/compose.ts +150 -0
- package/src/briefing/context-docs.ts +19 -0
- package/src/briefing/contract.ts +717 -0
- package/src/briefing/index.ts +31 -0
- package/src/briefing/macros.ts +97 -0
- package/src/briefing/skills.ts +47 -0
- package/src/capability-deck/bridge-ledger/index.ts +27 -0
- package/src/capability-deck/bridge-ledger/key.ts +67 -0
- package/src/capability-deck/bridge-ledger/ledger.ts +131 -0
- package/src/capability-deck/bridge-ledger/network.ts +117 -0
- package/src/capability-deck/builtin-bridge.ts +312 -0
- package/src/capability-deck/cards/bg-process-card.ts +335 -0
- package/src/capability-deck/cards/index.ts +115 -0
- package/src/capability-deck/cards/memory-card.ts +146 -0
- package/src/capability-deck/cards/plan-file.ts +97 -0
- package/src/capability-deck/cards/plan-tools.ts +185 -0
- package/src/capability-deck/cards/saas-card.ts +183 -0
- package/src/capability-deck/cards/task-card.ts +207 -0
- package/src/capability-deck/cards/todo-card.ts +168 -0
- package/src/capability-deck/cards/workflow-card.ts +247 -0
- package/src/capability-deck/contract.ts +388 -0
- package/src/capability-deck/index.ts +48 -0
- package/src/capability-deck/manifest.ts +109 -0
- package/src/capability-deck/provision.ts +169 -0
- package/src/channels/contract.ts +191 -0
- package/src/channels/framer.ts +50 -0
- package/src/channels/index.ts +101 -0
- package/src/channels/link/dialog.ts +129 -0
- package/src/channels/link/driver.ts +190 -0
- package/src/channels/link/index.ts +34 -0
- package/src/channels/link/server.ts +134 -0
- package/src/channels/oneshot.ts +90 -0
- package/src/channels/ops.ts +65 -0
- package/src/channels/session-ops.ts +81 -0
- package/src/conductor/bash-guard.ts +599 -0
- package/src/conductor/catalog/catalog.ts +116 -0
- package/src/conductor/catalog/index.ts +8 -0
- package/src/conductor/catalog/matcher.ts +134 -0
- package/src/conductor/conductor.ts +234 -0
- package/src/conductor/contract.ts +842 -0
- package/src/conductor/diagnostics.ts +227 -0
- package/src/conductor/index.ts +33 -0
- package/src/conductor/permissions.ts +588 -0
- package/src/conductor/quota-error.ts +49 -0
- package/src/conductor/signal-hub/hub.ts +46 -0
- package/src/conductor/signal-hub/index.ts +2 -0
- package/src/conductor/signal-hub/translate.test.ts +81 -0
- package/src/conductor/signal-hub/translate.ts +74 -0
- package/src/conductor/skill-parse/index.ts +2 -0
- package/src/conductor/skill-parse/parse.ts +108 -0
- package/src/conductor/transcript-store/index.ts +22 -0
- package/src/conductor/transcript-store/serialize.ts +116 -0
- package/src/conductor/transcript-store/store.ts +205 -0
- package/src/console/auth-status.ts +56 -0
- package/src/console/components/AgentsView.ts +165 -0
- package/src/console/components/BackgroundAgents.ts +155 -0
- package/src/console/components/Banner.ts +334 -0
- package/src/console/components/Composer.ts +94 -0
- package/src/console/components/StatusBar.ts +49 -0
- package/src/console/components/TerminalConsole.ts +1090 -0
- package/src/console/components/WorkingIndicator.ts +98 -0
- package/src/console/components/banner-sweep.ts +24 -0
- package/src/console/components/welcome.ts +74 -0
- package/src/console/contract.ts +630 -0
- package/src/console/index.ts +34 -0
- package/src/console/input/complete.ts +127 -0
- package/src/console/input/dir-reader.ts +34 -0
- package/src/console/input/index.ts +23 -0
- package/src/console/input/keymap.ts +159 -0
- package/src/console/input/paste.ts +104 -0
- package/src/console/mount.ts +56 -0
- package/src/console/overlays/approval-queue.ts +57 -0
- package/src/console/overlays/approval.ts +130 -0
- package/src/console/overlays/auth.ts +342 -0
- package/src/console/overlays/boards.ts +308 -0
- package/src/console/overlays/host.ts +36 -0
- package/src/console/overlays/index.ts +26 -0
- package/src/console/overlays/pickers.ts +258 -0
- package/src/console/overlays/sessions.ts +190 -0
- package/src/console/reducer.ts +182 -0
- package/src/console/slash/builtins.ts +81 -0
- package/src/console/slash/commands/dynamic.ts +83 -0
- package/src/console/slash/commands/integrations.ts +695 -0
- package/src/console/slash/commands/shared.ts +75 -0
- package/src/console/slash/commands/transcript.ts +263 -0
- package/src/console/slash/commands/workbench.ts +246 -0
- package/src/console/slash/index.ts +15 -0
- package/src/console/slash/registry.ts +70 -0
- package/src/console/slash/resolve.ts +63 -0
- package/src/console/startup.ts +209 -0
- package/src/console/theme/adapter.ts +45 -0
- package/src/console/theme/index.ts +7 -0
- package/src/console/theme/palette.ts +68 -0
- package/src/console/theme/resolve.ts +39 -0
- package/src/console/theme/tokens.ts +71 -0
- package/src/entry.ts +55 -0
- package/src/guardrails.ts +37 -0
- package/src/index.ts +18 -0
- package/src/insight/channel.ts +88 -0
- package/src/insight/contract.ts +185 -0
- package/src/insight/index.ts +110 -0
- package/src/insight/recorder.ts +213 -0
- package/src/insight/redaction.ts +157 -0
- package/src/insight/replay.ts +158 -0
- package/src/insight/sampling.ts +70 -0
- package/src/insight/serialize.ts +50 -0
- package/src/insight/sinks/console.ts +64 -0
- package/src/insight/sinks/file.ts +40 -0
- package/src/insight/sinks/index.ts +24 -0
- package/src/insight/sinks/stream.ts +54 -0
- package/src/integrations/sarvam/attach.ts +239 -0
- package/src/integrations/sarvam/config.ts +156 -0
- package/src/integrations/sarvam/index.ts +25 -0
- package/src/integrations/sarvam/sarvam.test.ts +60 -0
- package/src/integrations/sarvam/types.ts +27 -0
- package/src/integrations/zoho/attach.ts +342 -0
- package/src/integrations/zoho/config.ts +125 -0
- package/src/integrations/zoho/index.ts +27 -0
- package/src/integrations/zoho/types.ts +21 -0
- package/src/integrations/zoho/zoho.test.ts +50 -0
- package/src/kit/clipboard-image.ts +107 -0
- package/src/kit/external-editor.ts +48 -0
- package/src/kit/image.ts +59 -0
- package/src/kit/index.ts +51 -0
- package/src/kit/shell.ts +19 -0
- package/src/kit/tool-fetch.ts +85 -0
- package/src/launch/catalog.ts +148 -0
- package/src/launch/contract.ts +187 -0
- package/src/launch/credentials.ts +625 -0
- package/src/launch/index.ts +98 -0
- package/src/launch/invocation/attachments.ts +179 -0
- package/src/launch/invocation/flags.ts +196 -0
- package/src/launch/invocation/index.ts +25 -0
- package/src/launch/invocation/read.ts +260 -0
- package/src/launch/invocation/usage.ts +67 -0
- package/src/launch/login.ts +324 -0
- package/src/launch/oauth.test.ts +18 -0
- package/src/launch/oauth.ts +203 -0
- package/src/launch/packages.ts +194 -0
- package/src/launch/pickers.ts +189 -0
- package/src/runtime-bridge/bridges/_drive.ts +96 -0
- package/src/runtime-bridge/bridges/builtins.ts +68 -0
- package/src/runtime-bridge/bridges/claude-cli.ts +123 -0
- package/src/runtime-bridge/bridges/codex-cli.ts +142 -0
- package/src/runtime-bridge/bridges/index.ts +33 -0
- package/src/runtime-bridge/bridges/indusagi-cli.ts +155 -0
- package/src/runtime-bridge/broker.ts +227 -0
- package/src/runtime-bridge/contract.ts +122 -0
- package/src/runtime-bridge/index.ts +79 -0
- package/src/runtime-bridge/sink.ts +180 -0
- package/src/sessions/contract.ts +81 -0
- package/src/sessions/index.ts +13 -0
- package/src/sessions/library.ts +229 -0
- package/src/settings/contract.ts +114 -0
- package/src/settings/index.ts +32 -0
- package/src/settings/manager.ts +117 -0
- package/src/transcript-export/index.ts +45 -0
- package/src/transcript-export/publish.ts +260 -0
- package/src/transcript-export/sgr.ts +315 -0
- package/src/transcript-export/template.ts +272 -0
- package/src/transcript-export/theme-bridge.ts +150 -0
- package/src/window-budget/budget/estimate.ts +135 -0
- package/src/window-budget/budget/gate.ts +33 -0
- package/src/window-budget/budget/index.ts +16 -0
- package/src/window-budget/budget/slice.ts +56 -0
- package/src/window-budget/condenser.ts +58 -0
- package/src/window-budget/contract.ts +184 -0
- package/src/window-budget/index.ts +19 -0
- package/src/window-budget/microcompact.ts +95 -0
- package/src/window-budget/rehydrate.ts +136 -0
- package/src/window-budget/summarize/condense.ts +103 -0
- package/src/window-budget/summarize/index.ts +14 -0
- package/src/window-budget/summarize/prompt.ts +149 -0
- package/src/workflow-engine/agent-runner.ts +181 -0
- package/src/workflow-engine/display.ts +224 -0
- package/src/workflow-engine/engine.ts +294 -0
- package/src/workflow-engine/index.ts +23 -0
- package/src/workflow-engine/parse.ts +172 -0
- package/src/workflow-engine/structured-output.ts +35 -0
- package/src/workspace/brand.ts +29 -0
- package/src/workspace/index.ts +18 -0
- package/src/workspace/locator.ts +103 -0
- package/src/workspace/runtime-detect.ts +64 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth overlays — the sign-in / sign-out and plugin dialogs.
|
|
3
|
+
*/
|
|
4
|
+
import React, { useEffect, useState } from "indusagi/react-host";
|
|
5
|
+
import { Box, Text, useInput } from "indusagi/react-host/ink";
|
|
6
|
+
import { DialogFrame, LoginDialog, OAuthDialog } from "indusagi/react-ink";
|
|
7
|
+
import { jsx } from "indusagi/react-host/jsx-runtime";
|
|
8
|
+
import { clearServerToken, getServerToken } from "../../boot/server-token.js";
|
|
9
|
+
import { isGatewayEligibleProvider } from "../../boot/runners/server-mode.js";
|
|
10
|
+
import type { OverlayGroupProps } from "./pickers.js";
|
|
11
|
+
|
|
12
|
+
const { useRef } = React;
|
|
13
|
+
|
|
14
|
+
function SignInDialog(props: OverlayGroupProps & { requestedProvider?: string }) {
|
|
15
|
+
const { services, dispatch, closeModal, requestedProvider } = props;
|
|
16
|
+
const providers = (() => {
|
|
17
|
+
try {
|
|
18
|
+
return services!.listLoginProviders().map((entry: any) => ({
|
|
19
|
+
id: entry.id,
|
|
20
|
+
label: entry.label,
|
|
21
|
+
authKind: entry.authKind
|
|
22
|
+
}));
|
|
23
|
+
} catch {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
})();
|
|
27
|
+
const direct = requestedProvider !== void 0 ? providers.find((p) => p.id === requestedProvider) : void 0;
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (direct === void 0) return;
|
|
30
|
+
dispatch({
|
|
31
|
+
type: "modal:open",
|
|
32
|
+
kind: "oauth",
|
|
33
|
+
payload: {
|
|
34
|
+
providerId: direct.id,
|
|
35
|
+
mode: direct.authKind === "oauth" ? "oauth" : "apiKey"
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}, []);
|
|
39
|
+
if (direct !== void 0) return null;
|
|
40
|
+
return jsx(LoginDialog, {
|
|
41
|
+
mode: "login",
|
|
42
|
+
providers,
|
|
43
|
+
onClose: closeModal,
|
|
44
|
+
onSelect: (provider: any) => {
|
|
45
|
+
dispatch({
|
|
46
|
+
type: "modal:open",
|
|
47
|
+
kind: "oauth",
|
|
48
|
+
payload: {
|
|
49
|
+
providerId: provider.id,
|
|
50
|
+
mode: provider.authKind === "oauth" ? "oauth" : "apiKey"
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const SERVER_TIER_ACCOUNT_ID = "server-session";
|
|
58
|
+
const SERVER_TIER_PROVIDER_ID = "indus-server";
|
|
59
|
+
|
|
60
|
+
function SignOutDialog(props: OverlayGroupProps) {
|
|
61
|
+
const { services, closeModal } = props;
|
|
62
|
+
const [accounts, setAccounts] = useState<any[]>([]);
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
let live = true;
|
|
65
|
+
void (async () => {
|
|
66
|
+
const rows: any[] = [];
|
|
67
|
+
try {
|
|
68
|
+
const token = await getServerToken();
|
|
69
|
+
if (token) {
|
|
70
|
+
rows.push({
|
|
71
|
+
provider: SERVER_TIER_PROVIDER_ID,
|
|
72
|
+
accountId: SERVER_TIER_ACCOUNT_ID,
|
|
73
|
+
label: "Indus Server · signed in"
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
} catch {
|
|
77
|
+
// ignore
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
for (const provider of services!.listLoginProviders()) {
|
|
81
|
+
let names: string[] = [];
|
|
82
|
+
try {
|
|
83
|
+
names = await services!.vault.listAccounts(provider.id);
|
|
84
|
+
} catch {
|
|
85
|
+
names = [];
|
|
86
|
+
}
|
|
87
|
+
for (const accountId of names) {
|
|
88
|
+
rows.push({
|
|
89
|
+
provider: provider.id,
|
|
90
|
+
accountId,
|
|
91
|
+
label: `${provider.label} · ${accountId}`
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
} catch {
|
|
96
|
+
// ignore
|
|
97
|
+
}
|
|
98
|
+
if (live) setAccounts(rows);
|
|
99
|
+
})();
|
|
100
|
+
return () => {
|
|
101
|
+
live = false;
|
|
102
|
+
};
|
|
103
|
+
}, []);
|
|
104
|
+
return jsx(LoginDialog, {
|
|
105
|
+
mode: "logout",
|
|
106
|
+
accounts,
|
|
107
|
+
onClose: closeModal,
|
|
108
|
+
onSelect: async (account: any) => {
|
|
109
|
+
try {
|
|
110
|
+
if (account.provider === SERVER_TIER_PROVIDER_ID) {
|
|
111
|
+
await clearServerToken();
|
|
112
|
+
} else {
|
|
113
|
+
await services!.vault.remove(account.provider, account.accountId);
|
|
114
|
+
}
|
|
115
|
+
} catch {
|
|
116
|
+
// ignore
|
|
117
|
+
}
|
|
118
|
+
closeModal();
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function seedOAuthState(providerId: string, providerName: string, mode: string) {
|
|
124
|
+
return {
|
|
125
|
+
kind: "oauth",
|
|
126
|
+
providerId,
|
|
127
|
+
providerName,
|
|
128
|
+
mode,
|
|
129
|
+
inputLabel: mode === "apiKey" ? `${providerName} API key` : "Authorization code or redirect URL",
|
|
130
|
+
inputValue: "",
|
|
131
|
+
accountId: "default",
|
|
132
|
+
accountName: "Default"
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function selectProviderModel(services: any, providerId: string) {
|
|
137
|
+
try {
|
|
138
|
+
const cards = services.conductor.availableModels().filter((c: any) => c.provider === providerId);
|
|
139
|
+
const chosen = cards[cards.length - 1];
|
|
140
|
+
if (chosen) services.conductor.selectModel(chosen.id);
|
|
141
|
+
} catch {
|
|
142
|
+
// ignore
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function OAuthFlowDialog(props: OverlayGroupProps & { providerId: string; mode: "oauth" | "apiKey" }) {
|
|
147
|
+
const { services, providerId, mode, dispatch, closeModal } = props;
|
|
148
|
+
const providerName = (() => {
|
|
149
|
+
try {
|
|
150
|
+
const match = services!.listLoginProviders().find((p: any) => p.id === providerId);
|
|
151
|
+
return match?.label ?? providerId;
|
|
152
|
+
} catch {
|
|
153
|
+
return providerId;
|
|
154
|
+
}
|
|
155
|
+
})();
|
|
156
|
+
const [state, setState] = useState(() => seedOAuthState(providerId, providerName, mode));
|
|
157
|
+
const pending = useRef<((value: string) => void) | null>(null);
|
|
158
|
+
const merge = (next: any) => setState((prev) => ({ ...prev, ...next }));
|
|
159
|
+
useEffect(() => {
|
|
160
|
+
if (mode !== "oauth") return;
|
|
161
|
+
let live = true;
|
|
162
|
+
const safeMerge = (next: any) => {
|
|
163
|
+
if (live) merge(next);
|
|
164
|
+
};
|
|
165
|
+
const callbacks = {
|
|
166
|
+
onAuth: (info: any) => {
|
|
167
|
+
safeMerge({ authInfo: info, progress: "Opening your browser to sign in…" });
|
|
168
|
+
void services!.openLoginUrl(info.url).then((opened: boolean) => {
|
|
169
|
+
safeMerge({
|
|
170
|
+
progress: opened
|
|
171
|
+
? "Opened the login URL in your browser. Finish there, then paste the code if prompted."
|
|
172
|
+
: "Could not open a browser automatically — open the URL above, then paste the code if prompted."
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
},
|
|
176
|
+
onPrompt: (prompt: any) =>
|
|
177
|
+
new Promise<string>((resolve) => {
|
|
178
|
+
pending.current = resolve;
|
|
179
|
+
safeMerge({ prompt, inputLabel: prompt.message, inputValue: "" });
|
|
180
|
+
}),
|
|
181
|
+
onManualCodeInput: () =>
|
|
182
|
+
new Promise<string>((resolve) => {
|
|
183
|
+
pending.current = resolve;
|
|
184
|
+
safeMerge({
|
|
185
|
+
prompt: { message: "Paste the authorization code or complete redirect URL:" },
|
|
186
|
+
inputLabel: "Authorization code or redirect URL",
|
|
187
|
+
inputValue: ""
|
|
188
|
+
});
|
|
189
|
+
}),
|
|
190
|
+
onProgress: (message: string) => {
|
|
191
|
+
safeMerge({ progress: message });
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
void (async () => {
|
|
195
|
+
try {
|
|
196
|
+
await services!.startOAuthLogin(providerId, callbacks, services!.vault);
|
|
197
|
+
await services!.refreshGatewayRouting();
|
|
198
|
+
selectProviderModel(services!, providerId);
|
|
199
|
+
if (live) {
|
|
200
|
+
dispatch({
|
|
201
|
+
type: "status:set",
|
|
202
|
+
status: { kind: "success", text: `Signed in to ${providerName}.` }
|
|
203
|
+
});
|
|
204
|
+
closeModal();
|
|
205
|
+
}
|
|
206
|
+
} catch {
|
|
207
|
+
safeMerge({ progress: "Sign-in did not complete." });
|
|
208
|
+
}
|
|
209
|
+
})();
|
|
210
|
+
return () => {
|
|
211
|
+
live = false;
|
|
212
|
+
};
|
|
213
|
+
}, []);
|
|
214
|
+
const saveApiKey = () => {
|
|
215
|
+
const key = (state as any).inputValue.trim();
|
|
216
|
+
if (key.length === 0) return;
|
|
217
|
+
const account = (state as any).accountId ?? "default";
|
|
218
|
+
void (async () => {
|
|
219
|
+
try {
|
|
220
|
+
await services!.vault.putApiKey(providerId, account, key, true);
|
|
221
|
+
selectProviderModel(services!, providerId);
|
|
222
|
+
dispatch({
|
|
223
|
+
type: "status:set",
|
|
224
|
+
status: { kind: "success", text: `Saved your ${providerName} API key.` }
|
|
225
|
+
});
|
|
226
|
+
} catch {
|
|
227
|
+
dispatch({
|
|
228
|
+
type: "status:set",
|
|
229
|
+
status: { kind: "error", text: `Could not save the ${providerName} API key.` }
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
closeModal();
|
|
233
|
+
})();
|
|
234
|
+
};
|
|
235
|
+
return jsx(OAuthDialog, {
|
|
236
|
+
state,
|
|
237
|
+
onChange: (next: any) => merge(next),
|
|
238
|
+
onSubmit: () => {
|
|
239
|
+
if (mode === "apiKey") {
|
|
240
|
+
saveApiKey();
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const resolve = pending.current;
|
|
244
|
+
if (resolve === null) return;
|
|
245
|
+
pending.current = null;
|
|
246
|
+
const value = (state as any).inputValue;
|
|
247
|
+
merge({ inputValue: "", prompt: void 0 });
|
|
248
|
+
resolve(value);
|
|
249
|
+
},
|
|
250
|
+
onClose: closeModal
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function readPluginRequest(payload: unknown) {
|
|
255
|
+
if (payload && typeof payload === "object") {
|
|
256
|
+
const record = payload as any;
|
|
257
|
+
return {
|
|
258
|
+
surface: typeof record.surface === "string" ? record.surface : "plugin",
|
|
259
|
+
...(typeof record.title === "string" ? { title: record.title } : {}),
|
|
260
|
+
...(typeof record.text === "string" ? { text: record.text } : {})
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
return { surface: "plugin" };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function PluginDialog(props: { request: ReturnType<typeof readPluginRequest>; closeModal: () => void }) {
|
|
267
|
+
const { request, closeModal } = props;
|
|
268
|
+
const body = request.text ?? `No data available for the ${request.surface} surface.`;
|
|
269
|
+
const lines = body.split("\n");
|
|
270
|
+
useInput((input, key) => {
|
|
271
|
+
if (key.escape || key.return || input === "q" || input === "x") {
|
|
272
|
+
closeModal?.();
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
return jsx(DialogFrame, {
|
|
276
|
+
title: request.title ?? "Plugin surface",
|
|
277
|
+
subtitle: request.surface,
|
|
278
|
+
footer: jsx(Text, { children: "esc / enter to close" }),
|
|
279
|
+
children: jsx(Box, {
|
|
280
|
+
flexDirection: "column",
|
|
281
|
+
children: lines.map((line, index) => jsx(Text, { children: line.length === 0 ? " " : line }, index))
|
|
282
|
+
})
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function readRequestedProvider(payload: unknown): string | undefined {
|
|
287
|
+
if (payload && typeof payload === "object" && "providerId" in (payload as any)) {
|
|
288
|
+
const id = (payload as any).providerId;
|
|
289
|
+
if (typeof id === "string" && id.length > 0) return id;
|
|
290
|
+
}
|
|
291
|
+
return void 0;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function readProviderId(payload: unknown): string {
|
|
295
|
+
if (payload && typeof payload === "object" && "providerId" in (payload as any)) {
|
|
296
|
+
const id = (payload as any).providerId;
|
|
297
|
+
if (typeof id === "string") return id;
|
|
298
|
+
}
|
|
299
|
+
return "";
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function readEntryMode(payload: unknown): "apiKey" | "oauth" {
|
|
303
|
+
if (payload && typeof payload === "object" && "mode" in (payload as any)) {
|
|
304
|
+
const mode = (payload as any).mode;
|
|
305
|
+
if (mode === "apiKey") return "apiKey";
|
|
306
|
+
}
|
|
307
|
+
return "oauth";
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export function AuthOverlays(props: OverlayGroupProps) {
|
|
311
|
+
const { modal, services, dispatch, closeModal } = props;
|
|
312
|
+
if (modal.kind === "plugin") {
|
|
313
|
+
return jsx(PluginDialog, { request: readPluginRequest(modal.payload), closeModal });
|
|
314
|
+
}
|
|
315
|
+
if (services === void 0) return null;
|
|
316
|
+
switch (modal.kind) {
|
|
317
|
+
case "signIn":
|
|
318
|
+
return jsx(SignInDialog, {
|
|
319
|
+
...props,
|
|
320
|
+
services,
|
|
321
|
+
dispatch,
|
|
322
|
+
closeModal,
|
|
323
|
+
requestedProvider: readRequestedProvider(modal.payload)
|
|
324
|
+
});
|
|
325
|
+
case "signOut":
|
|
326
|
+
return jsx(SignOutDialog, { ...props, services, closeModal });
|
|
327
|
+
case "oauth": {
|
|
328
|
+
const providerId = readProviderId(modal.payload);
|
|
329
|
+
const mode = readEntryMode(modal.payload);
|
|
330
|
+
return jsx(OAuthFlowDialog, {
|
|
331
|
+
...props,
|
|
332
|
+
services,
|
|
333
|
+
providerId,
|
|
334
|
+
mode,
|
|
335
|
+
dispatch,
|
|
336
|
+
closeModal
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
default:
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Saffron picker boards — the themed `/settings` and `/model` overlays.
|
|
3
|
+
*/
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import { readFileSync } from "node:fs";
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { useEffect, useMemo, useState } from "indusagi/react-host";
|
|
9
|
+
import { Box, Text, useInput } from "indusagi/react-host/ink";
|
|
10
|
+
import {
|
|
11
|
+
matchesSearchQuery,
|
|
12
|
+
wrapSelectionIndex,
|
|
13
|
+
type InkThemeAdapter,
|
|
14
|
+
type SettingsDialogItem,
|
|
15
|
+
type Model
|
|
16
|
+
} from "indusagi/react-ink";
|
|
17
|
+
import { Fragment, jsx, jsxs } from "indusagi/react-host/jsx-runtime";
|
|
18
|
+
import { isGatewayEligibleProvider } from "../../boot/runners/server-mode.js";
|
|
19
|
+
|
|
20
|
+
function readServerTierInfo() {
|
|
21
|
+
let signedIn = false;
|
|
22
|
+
try {
|
|
23
|
+
const file = join(homedir(), ".indusagi", "agent", "server-token.json");
|
|
24
|
+
const token = JSON.parse(readFileSync(file, "utf8"));
|
|
25
|
+
signedIn = typeof token.expires_at === "string" && new Date(token.expires_at).getTime() - Date.now() > 5 * 60 * 1e3;
|
|
26
|
+
} catch {
|
|
27
|
+
signedIn = false;
|
|
28
|
+
}
|
|
29
|
+
const localKeys = new Set<string>();
|
|
30
|
+
try {
|
|
31
|
+
const file = join(homedir(), ".indusagi", "agent", "auth.json");
|
|
32
|
+
const vault = JSON.parse(readFileSync(file, "utf8"));
|
|
33
|
+
for (const [provider, entry] of Object.entries(vault)) {
|
|
34
|
+
if (entry !== null && typeof entry === "object" && Object.keys(entry).length > 0) {
|
|
35
|
+
localKeys.add(provider);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
} catch {
|
|
39
|
+
// ignore
|
|
40
|
+
}
|
|
41
|
+
return { signedIn, localKeys };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function truncate(text: string, width: number): string {
|
|
45
|
+
if (text.length <= width) return text;
|
|
46
|
+
if (width <= 1) return text.slice(0, width);
|
|
47
|
+
return `${text.slice(0, width - 1)}…`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function tabPill(theme: InkThemeAdapter, text: string): string {
|
|
51
|
+
const accent = theme.colors["accent"] ?? "#f7a23f";
|
|
52
|
+
try {
|
|
53
|
+
return chalk.bgHex(accent).hex("#1a120a").bold(` ${text} `);
|
|
54
|
+
} catch {
|
|
55
|
+
return theme.color("accent", text);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function paintValue(theme: InkThemeAdapter, value: string): string {
|
|
60
|
+
const v = value.trim().toLowerCase();
|
|
61
|
+
if (v === "on" || v === "true" || v === "yes" || v === "enabled") {
|
|
62
|
+
return theme.color("success", value);
|
|
63
|
+
}
|
|
64
|
+
if (v === "off" || v === "false" || v === "no" || v === "disabled") {
|
|
65
|
+
return theme.dim(value);
|
|
66
|
+
}
|
|
67
|
+
return theme.color("customMessage", value);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function visibleRowBudget(): number {
|
|
71
|
+
return Math.max(6, (process.stdout.rows ?? 24) - 18);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function windowAround(total: number, selectedIndex: number) {
|
|
75
|
+
const budget = visibleRowBudget();
|
|
76
|
+
const start = Math.max(0, Math.min(selectedIndex - Math.floor(budget / 2), Math.max(0, total - budget)));
|
|
77
|
+
const below = Math.max(0, total - (start + budget));
|
|
78
|
+
return { start, budget, below };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function SearchBox({ theme, query, placeholder }: { theme: InkThemeAdapter; query: string; placeholder: string }) {
|
|
82
|
+
return jsxs(Box, {
|
|
83
|
+
borderStyle: "round",
|
|
84
|
+
borderColor: theme.colors["customMessage"],
|
|
85
|
+
paddingX: 1,
|
|
86
|
+
marginBottom: 1,
|
|
87
|
+
children: [
|
|
88
|
+
jsx(Text, { children: theme.color("customMessage", "⌕ ") }),
|
|
89
|
+
query.length > 0
|
|
90
|
+
? jsxs(Fragment, {
|
|
91
|
+
children: [
|
|
92
|
+
jsx(Text, { children: theme.color("text", query) }),
|
|
93
|
+
jsx(Text, { inverse: true, children: " " })
|
|
94
|
+
]
|
|
95
|
+
})
|
|
96
|
+
: jsxs(Fragment, {
|
|
97
|
+
children: [
|
|
98
|
+
jsx(Text, { inverse: true, children: " " }),
|
|
99
|
+
jsx(Text, { children: theme.dim(` ${placeholder}`) })
|
|
100
|
+
]
|
|
101
|
+
})
|
|
102
|
+
]
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function BoardFooter({ theme, hint, detail }: { theme: InkThemeAdapter; hint: string; detail?: string }) {
|
|
107
|
+
return jsxs(Box, {
|
|
108
|
+
flexDirection: "column",
|
|
109
|
+
marginTop: 1,
|
|
110
|
+
children: [
|
|
111
|
+
detail ? jsx(Box, { marginBottom: 1, children: jsx(Text, { children: theme.muted(detail) }) }) : null,
|
|
112
|
+
jsx(Box, {
|
|
113
|
+
borderStyle: "single",
|
|
114
|
+
borderColor: theme.colors["borderMuted"],
|
|
115
|
+
borderTop: true,
|
|
116
|
+
borderBottom: false,
|
|
117
|
+
borderLeft: false,
|
|
118
|
+
borderRight: false,
|
|
119
|
+
paddingTop: 0,
|
|
120
|
+
children: jsx(Text, { children: theme.dim(hint) })
|
|
121
|
+
})
|
|
122
|
+
]
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function BoardShell({ theme, title, query, placeholder, below, hint, detail, children }: any) {
|
|
127
|
+
return jsxs(Box, {
|
|
128
|
+
flexDirection: "column",
|
|
129
|
+
borderStyle: "round",
|
|
130
|
+
borderColor: theme.colors["accent"],
|
|
131
|
+
paddingX: 1,
|
|
132
|
+
marginTop: 1,
|
|
133
|
+
children: [
|
|
134
|
+
jsx(Box, { marginBottom: 1, children: jsx(Text, { children: tabPill(theme, title) }) }),
|
|
135
|
+
jsx(SearchBox, { theme, query, placeholder }),
|
|
136
|
+
children,
|
|
137
|
+
below > 0 ? jsx(Box, { marginTop: 0, children: jsx(Text, { children: theme.dim(`↓ ${below} more below`) }) }) : null,
|
|
138
|
+
jsx(BoardFooter, { theme, hint, detail })
|
|
139
|
+
]
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const SETTINGS_LABEL_WIDTH = 34;
|
|
144
|
+
|
|
145
|
+
function nextValue(values: readonly string[], current: string | undefined, direction: number): string | undefined {
|
|
146
|
+
if (values.length === 0) return void 0;
|
|
147
|
+
const at = Math.max(0, values.indexOf(current as string));
|
|
148
|
+
return values[(at + direction + values.length) % values.length];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface SettingsBoardProps {
|
|
152
|
+
readonly theme: InkThemeAdapter;
|
|
153
|
+
readonly items: readonly SettingsDialogItem[];
|
|
154
|
+
readonly onClose: () => void;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function SettingsBoard({ theme, items, onClose }: SettingsBoardProps) {
|
|
158
|
+
const [query, setQuery] = useState("");
|
|
159
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
160
|
+
const filtered = useMemo(
|
|
161
|
+
() =>
|
|
162
|
+
items.filter((item) =>
|
|
163
|
+
matchesSearchQuery(`${item.label} ${item.description} ${item.value} ${item.values.join(" ")}`, query)
|
|
164
|
+
),
|
|
165
|
+
[items, query]
|
|
166
|
+
);
|
|
167
|
+
useEffect(() => setSelectedIndex(0), [query]);
|
|
168
|
+
useEffect(() => {
|
|
169
|
+
if (selectedIndex >= filtered.length) setSelectedIndex(0);
|
|
170
|
+
}, [filtered.length, selectedIndex]);
|
|
171
|
+
const cycle = (direction: number) => {
|
|
172
|
+
const item = filtered[selectedIndex];
|
|
173
|
+
if (!item) return;
|
|
174
|
+
const value = nextValue(item.values, item.value, direction);
|
|
175
|
+
if (value && value !== item.value) void item.onChange(value);
|
|
176
|
+
};
|
|
177
|
+
useInput((input, key) => {
|
|
178
|
+
if (key.escape) return onClose();
|
|
179
|
+
if (key.upArrow) return setSelectedIndex((c) => wrapSelectionIndex(c, -1, filtered.length));
|
|
180
|
+
if (key.downArrow) return setSelectedIndex((c) => wrapSelectionIndex(c, 1, filtered.length));
|
|
181
|
+
if (key.backspace || key.delete) return setQuery((c) => c.slice(0, -1));
|
|
182
|
+
if (key.ctrl && input === "u") return setQuery("");
|
|
183
|
+
if (key.leftArrow) return cycle(-1);
|
|
184
|
+
if (key.rightArrow || key.return) return cycle(1);
|
|
185
|
+
if (!key.ctrl && !key.meta && input) setQuery((c) => c + input);
|
|
186
|
+
});
|
|
187
|
+
const { start, budget, below } = windowAround(filtered.length, selectedIndex);
|
|
188
|
+
const visible = filtered.slice(start, start + budget);
|
|
189
|
+
const selected = filtered[selectedIndex];
|
|
190
|
+
const detail = selected ? `${selected.description} · ${selected.values.join(" / ")}` : void 0;
|
|
191
|
+
const list =
|
|
192
|
+
items.length === 0
|
|
193
|
+
? jsx(Text, { children: theme.muted("No settings are available.") })
|
|
194
|
+
: filtered.length === 0
|
|
195
|
+
? jsx(Text, { children: theme.muted("No settings matched the search.") })
|
|
196
|
+
: jsx(Box, {
|
|
197
|
+
flexDirection: "column",
|
|
198
|
+
children: visible.map((item, index) => {
|
|
199
|
+
const isSelected = start + index === selectedIndex;
|
|
200
|
+
const labelText = truncate(item.label, SETTINGS_LABEL_WIDTH - 1).padEnd(SETTINGS_LABEL_WIDTH);
|
|
201
|
+
return jsxs(
|
|
202
|
+
Box,
|
|
203
|
+
{
|
|
204
|
+
children: [
|
|
205
|
+
jsx(Box, { width: 2, children: jsx(Text, { children: isSelected ? theme.color("accent", "›") : " " }) }),
|
|
206
|
+
jsx(Box, {
|
|
207
|
+
width: SETTINGS_LABEL_WIDTH,
|
|
208
|
+
children: jsx(Text, { bold: isSelected, children: theme.color("text", labelText) })
|
|
209
|
+
}),
|
|
210
|
+
jsx(Text, { children: paintValue(theme, item.value) })
|
|
211
|
+
]
|
|
212
|
+
},
|
|
213
|
+
item.id
|
|
214
|
+
);
|
|
215
|
+
})
|
|
216
|
+
});
|
|
217
|
+
return jsx(BoardShell, {
|
|
218
|
+
theme,
|
|
219
|
+
title: "Settings",
|
|
220
|
+
query,
|
|
221
|
+
placeholder: "Search settings…",
|
|
222
|
+
below,
|
|
223
|
+
hint: "Type to filter · ↑/↓ select · ←/→ or ↵ change · Ctrl+U clear · Esc close",
|
|
224
|
+
detail,
|
|
225
|
+
children: list
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function modelMatches(model: Model<any>, query: string) {
|
|
230
|
+
return matchesSearchQuery(`${model.provider} ${model.id} ${model.name ?? ""}`, query);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export interface ModelBoardProps {
|
|
234
|
+
readonly theme: InkThemeAdapter;
|
|
235
|
+
readonly models: readonly Model<any>[];
|
|
236
|
+
readonly onClose: () => void;
|
|
237
|
+
readonly onSelect: (model: Model<any>) => void | Promise<void>;
|
|
238
|
+
readonly search?: string;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function ModelBoard({ theme, models, onClose, onSelect, search }: ModelBoardProps) {
|
|
242
|
+
const [query, setQuery] = useState(search ?? "");
|
|
243
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
244
|
+
useEffect(() => setQuery(search ?? ""), [search]);
|
|
245
|
+
const serverTier = useMemo(readServerTierInfo, []);
|
|
246
|
+
const isServerTier = (provider: string) =>
|
|
247
|
+
serverTier.signedIn && isGatewayEligibleProvider(provider) && !serverTier.localKeys.has(provider);
|
|
248
|
+
const available = useMemo(
|
|
249
|
+
() => models.filter((model) => serverTier.localKeys.has(model.provider) || isServerTier(model.provider)),
|
|
250
|
+
[models, serverTier]
|
|
251
|
+
);
|
|
252
|
+
const filtered = useMemo(() => available.filter((model) => modelMatches(model, query)), [available, query]);
|
|
253
|
+
useEffect(() => setSelectedIndex(0), [query]);
|
|
254
|
+
useEffect(() => {
|
|
255
|
+
if (selectedIndex >= filtered.length) setSelectedIndex(0);
|
|
256
|
+
}, [filtered.length, selectedIndex]);
|
|
257
|
+
useInput((input, key) => {
|
|
258
|
+
if (key.escape) return onClose();
|
|
259
|
+
if (key.upArrow) return setSelectedIndex((c) => wrapSelectionIndex(c, -1, filtered.length));
|
|
260
|
+
if (key.downArrow) return setSelectedIndex((c) => wrapSelectionIndex(c, 1, filtered.length));
|
|
261
|
+
if (key.backspace || key.delete) return setQuery((c) => c.slice(0, -1));
|
|
262
|
+
if (key.ctrl && input === "u") return setQuery("");
|
|
263
|
+
if (key.return) {
|
|
264
|
+
const model = filtered[selectedIndex];
|
|
265
|
+
if (model) void onSelect(model);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (!key.ctrl && !key.meta && input) setQuery((c) => c + input);
|
|
269
|
+
});
|
|
270
|
+
const { start, budget, below } = windowAround(filtered.length, selectedIndex);
|
|
271
|
+
const visible = filtered.slice(start, start + budget);
|
|
272
|
+
const list =
|
|
273
|
+
filtered.length === 0
|
|
274
|
+
? jsx(Text, {
|
|
275
|
+
children: theme.muted(
|
|
276
|
+
available.length === 0
|
|
277
|
+
? "No models available — sign in with a provider key or run /login to use the server tier."
|
|
278
|
+
: "No models matched the search."
|
|
279
|
+
)
|
|
280
|
+
})
|
|
281
|
+
: jsx(Box, {
|
|
282
|
+
flexDirection: "column",
|
|
283
|
+
children: visible.map((model, index) => {
|
|
284
|
+
const isSelected = start + index === selectedIndex;
|
|
285
|
+
return jsxs(
|
|
286
|
+
Box,
|
|
287
|
+
{
|
|
288
|
+
children: [
|
|
289
|
+
jsx(Box, { width: 2, children: jsx(Text, { children: isSelected ? theme.color("accent", "›") : " " }) }),
|
|
290
|
+
jsx(Text, { bold: isSelected, children: theme.color("text", model.id) }),
|
|
291
|
+
model.name ? jsx(Text, { children: theme.dim(` ${model.name}`) }) : null,
|
|
292
|
+
isServerTier(model.provider) ? jsx(Text, { children: theme.color("accent", " ⫡ server") }) : null
|
|
293
|
+
]
|
|
294
|
+
},
|
|
295
|
+
model.id
|
|
296
|
+
);
|
|
297
|
+
})
|
|
298
|
+
});
|
|
299
|
+
return jsx(BoardShell, {
|
|
300
|
+
theme,
|
|
301
|
+
title: "Models",
|
|
302
|
+
query,
|
|
303
|
+
placeholder: "Search models by provider, id, or name…",
|
|
304
|
+
below,
|
|
305
|
+
hint: "Type to filter · ↑/↓ select · ↵ choose · Ctrl+U clear · Esc close",
|
|
306
|
+
children: list
|
|
307
|
+
});
|
|
308
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OverlayHost — the single mount point for every modal overlay.
|
|
3
|
+
*/
|
|
4
|
+
import { Fragment, jsx, jsxs } from "indusagi/react-host/jsx-runtime";
|
|
5
|
+
import type { ConsoleDispatch, InkThemeAdapter, ModalState, OverlayServices } from "../contract.js";
|
|
6
|
+
import { PickerOverlays } from "./pickers.js";
|
|
7
|
+
import { SessionOverlays } from "./sessions.js";
|
|
8
|
+
import { AuthOverlays } from "./auth.js";
|
|
9
|
+
import { ApprovalOverlays } from "./approval.js";
|
|
10
|
+
|
|
11
|
+
export interface OverlayHostProps {
|
|
12
|
+
readonly modal: ModalState;
|
|
13
|
+
readonly services?: OverlayServices;
|
|
14
|
+
readonly theme: InkThemeAdapter;
|
|
15
|
+
readonly dispatch: ConsoleDispatch;
|
|
16
|
+
readonly closeModal: () => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function OverlayHost(props: OverlayHostProps) {
|
|
20
|
+
if (props.modal.kind === "none") return null;
|
|
21
|
+
const forwarded = {
|
|
22
|
+
modal: props.modal,
|
|
23
|
+
services: props.services,
|
|
24
|
+
theme: props.theme,
|
|
25
|
+
dispatch: props.dispatch,
|
|
26
|
+
closeModal: props.closeModal
|
|
27
|
+
};
|
|
28
|
+
return jsxs(Fragment, {
|
|
29
|
+
children: [
|
|
30
|
+
jsx(PickerOverlays, { ...forwarded }),
|
|
31
|
+
jsx(SessionOverlays, { ...forwarded }),
|
|
32
|
+
jsx(AuthOverlays, { ...forwarded }),
|
|
33
|
+
jsx(ApprovalOverlays, { ...forwarded })
|
|
34
|
+
]
|
|
35
|
+
});
|
|
36
|
+
}
|