pi-orchestrator-config 4.20.1 → 4.21.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 +8 -0
- package/extensions/coms/coms-p2p.ts +44 -1
- package/extensions/coms/coms-shared.ts +35 -0
- package/extensions/orchestrator/enforcement.ts +16 -8
- package/extensions/orchestrator/index.ts +32 -0
- package/extensions/orchestrator/subagent-tool.ts +7 -1
- package/extensions/pidash/browser-ops.ts +231 -0
- package/extensions/pidash/pidash-ui/dist/assets/index-BKsBLUuf.css +2 -0
- package/extensions/pidash/pidash-ui/dist/assets/index-DzT6_qJc.js +23 -0
- package/extensions/pidash/pidash-ui/dist/index.html +2 -2
- package/extensions/pidash/pidash-ui/src/App.tsx +81 -15
- package/extensions/pidash/pidash-ui/src/components/ComsPanel.tsx +162 -0
- package/extensions/pidash/pidash-ui/src/components/InfoBar.tsx +162 -70
- package/extensions/pidash/pidash-ui/src/components/InputBar.tsx +5 -5
- package/extensions/pidash/pidash-ui/src/components/KeybindingSettings.tsx +3 -0
- package/extensions/pidash/pidash-ui/src/components/McpPanel.tsx +102 -0
- package/extensions/pidash/pidash-ui/src/components/MemoryPanel.tsx +68 -0
- package/extensions/pidash/pidash-ui/src/components/MessageList.tsx +103 -54
- package/extensions/pidash/pidash-ui/src/components/NewSessionDialog.tsx +102 -0
- package/extensions/pidash/pidash-ui/src/components/SessionSidebar.tsx +243 -83
- package/extensions/pidash/pidash-ui/src/components/SettingsPanel.tsx +310 -0
- package/extensions/pidash/pidash-ui/src/components/SidePanel.tsx +164 -0
- package/extensions/pidash/pidash-ui/src/components/TasksPanel.tsx +119 -0
- package/extensions/pidash/pidash-ui/src/hooks/useKeybindings.ts +24 -3
- package/extensions/pidash/pidash-ui/src/hooks/useKeyboardNavigation.ts +21 -4
- package/extensions/pidash/pidash-ui/src/hooks/useMessageHandler.ts +78 -8
- package/extensions/pidash/pidash-ui/src/lib/settings-edit.ts +73 -0
- package/extensions/pidash/pidash-ui/src/lib/theme.ts +53 -0
- package/extensions/pidash/pidash-ui/src/main.tsx +9 -0
- package/extensions/pidash/pidash-ui/src/types.ts +58 -0
- package/extensions/pidash/pidash.ts +161 -0
- package/extensions/pitasks/index.ts +60 -9
- package/extensions/pitasks/task-store.ts +37 -12
- package/extensions/shared/ui-bridge.ts +232 -0
- package/package.json +1 -1
- package/scripts/pidash-server.ts +8 -0
- package/extensions/pidash/pidash-ui/dist/assets/index-CI6UL4g4.js +0 -23
- package/extensions/pidash/pidash-ui/dist/assets/index-hM6gC5Yg.css +0 -2
package/README.md
CHANGED
|
@@ -661,6 +661,14 @@ Pidash is a web-based dashboard that runs alongside the TUI, accessible from any
|
|
|
661
661
|
- Per-project diff viewer (pidiff) — opens in browser via clickable `pi-diff` status-line link, powered by `@pierre/diffs` + `@pierre/trees`, with review comments
|
|
662
662
|
- Collapsible thinking and tool blocks with copy buttons
|
|
663
663
|
- ask_user tool bridging (answer from browser or TUI)
|
|
664
|
+
- **Full TUI parity (#968)** — every interactive prompt and surface is answerable/operable from either TUI or browser, first answer wins:
|
|
665
|
+
- Dangerous-command approvals, commit-trailer selection, project-agent confirmations, and `/tasks` menus render in both surfaces
|
|
666
|
+
- Tasks panel (view/create/start/complete/delete/clear) driven through the live TaskStore with live updates
|
|
667
|
+
- Coms peer list (busy state, queue depth, context %) + send message to a peer (same delivery path as `coms_send`)
|
|
668
|
+
- New-session creation (name + initial model) and `/compact` from the browser
|
|
669
|
+
- Memory status (usage, topics, situation report)
|
|
670
|
+
- MCP server management (list/login/logout)
|
|
671
|
+
- Full settings page — every key from `settings-keys.json`, project + global scopes, typed editors, written through the live settings module (same semantics as `/pi-config-settings`)
|
|
664
672
|
- Mobile responsive
|
|
665
673
|
- Event buffering for message replay on refresh
|
|
666
674
|
- Browser push notifications for background events (configurable per event type)
|
|
@@ -19,7 +19,8 @@ import type { AgentToolResult, ExtensionAPI, ExtensionContext, Theme } from "@ea
|
|
|
19
19
|
import { Text, truncateToWidth } from "@earendil-works/pi-tui";
|
|
20
20
|
import { Type } from "typebox";
|
|
21
21
|
import { applyExtensionDefaults } from "./themeMap.js";
|
|
22
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
buildComsPeersPayload, ulid, hexFg, isValidHex, fallbackColor, comsParseYamlFrontmatter as parseFrontmatter, nowIso, abbreviateModel, findSystemPromptPath, readFrontmatterFromArgv, readTaskSummary, buildInboundContent, renderTasksPart, renderQueuePart, formatQueueStr, formatComsResponseText, formatComsResponseType, formatComsResponseBody, formatComsInboundType, sanitizeComsName, createComsInboundTasks, shouldEvictOnLeaving, shouldAnnouncePeerLeft, isSessionStillRegistered, FALLBACK_PALETTE, type TasksSummary, insertInboundByPriority, shouldAcceptSteer, summarizeBroadcast, formatBusyStr, selectSenderBundle, type BroadcastTargetResult } from "./coms-shared.js";
|
|
23
24
|
import { appendHistoryRecord, queryHistoryRecords, truncatePreview, type HistoryRecord } from "./coms-history.js";
|
|
24
25
|
import { openListDetailOverlay, OverlayScrollDetail } from "../orchestrator/overlay-dashboard.js";
|
|
25
26
|
import * as net from "node:net";
|
|
@@ -785,6 +786,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
785
786
|
const key = `pending=${pc}|busy=${busyKey}|prog=${progressKey}`;
|
|
786
787
|
if (key === lastPoolSnapshot) return;
|
|
787
788
|
lastPoolSnapshot = key;
|
|
789
|
+
emitComsPeers();
|
|
788
790
|
try { installPoolWidget(currentCtx); } catch {}
|
|
789
791
|
}
|
|
790
792
|
|
|
@@ -1803,6 +1805,10 @@ Do not respond to this message.`;
|
|
|
1803
1805
|
existing.staleCount = 0;
|
|
1804
1806
|
existing.model = entry.model ?? existing.model;
|
|
1805
1807
|
log.debug("fs_watch_heartbeat", entry.name, "ctx", entry.context_used_pct, "busy", existing.busy);
|
|
1808
|
+
// Heartbeats mutate peer fields without touching the widget
|
|
1809
|
+
// refresh snapshot key — re-emit so browser peer cards stay
|
|
1810
|
+
// current (Qodo R1: stale peer status otherwise lingers).
|
|
1811
|
+
emitComsPeers();
|
|
1806
1812
|
} else if (entry && entry.coms_session_id && !peerCards.has(entry.coms_session_id)) {
|
|
1807
1813
|
const card: AgentCard & { staleCount: number } = {
|
|
1808
1814
|
name: entry.name, purpose: entry.purpose, model: entry.model,
|
|
@@ -3613,6 +3619,43 @@ Do not respond to this message.`;
|
|
|
3613
3619
|
handler: comsRefreshHandler,
|
|
3614
3620
|
});
|
|
3615
3621
|
|
|
3622
|
+
// ━━ #968 part C: pidash browser bridge ─━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
3623
|
+
// Peer list + messaging from the browser, routed through the SAME
|
|
3624
|
+
// resolveTarget/deliverPrompt path as coms_send — identical delivery
|
|
3625
|
+
// semantics, browser is just another transport.
|
|
3626
|
+
function emitComsPeers(): void {
|
|
3627
|
+
try {
|
|
3628
|
+
pi.events.emit("pidash:coms-peers", buildComsPeersPayload(
|
|
3629
|
+
peerCards.values(),
|
|
3630
|
+
identity ? { name: identity.name, purpose: identity.purpose, project: identity.project } : null,
|
|
3631
|
+
));
|
|
3632
|
+
} catch { /* pidash not loaded */ }
|
|
3633
|
+
}
|
|
3634
|
+
pi.events.on("pidash:coms-peers-request", () => emitComsPeers());
|
|
3635
|
+
pi.events.on("pidash:coms-send", async (data: unknown) => {
|
|
3636
|
+
const d = data as { requestId?: string; target?: string; prompt?: string; steer?: boolean; priority?: boolean };
|
|
3637
|
+
if (!d?.target || !d?.prompt) {
|
|
3638
|
+
pi.events.emit("pidash:coms-send-result", { requestId: d?.requestId, ok: false, error: "target and prompt required" });
|
|
3639
|
+
return;
|
|
3640
|
+
}
|
|
3641
|
+
try {
|
|
3642
|
+
if (!identity) throw new Error("coms not initialised");
|
|
3643
|
+
const target = await resolveTarget(d.target);
|
|
3644
|
+
if (!target) throw new Error(`peer not found: ${d.target}`);
|
|
3645
|
+
const { msg_id } = await deliverPrompt(target, {
|
|
3646
|
+
prompt: d.prompt,
|
|
3647
|
+
...(d.steer ? { steer: true } : {}),
|
|
3648
|
+
...(d.priority ? { priority: true } : {}),
|
|
3649
|
+
});
|
|
3650
|
+
log.info("pidash_coms_send", { target: target.name, msg_id, steer: !!d.steer });
|
|
3651
|
+
pi.events.emit("pidash:coms-send-result", { requestId: d.requestId, ok: true, msg_id, target: target.name });
|
|
3652
|
+
} catch (e) {
|
|
3653
|
+
const error = e instanceof Error ? e.message : String(e);
|
|
3654
|
+
log.warn("pidash_coms_send_failed", { target: d.target, error });
|
|
3655
|
+
pi.events.emit("pidash:coms-send-result", { requestId: d.requestId, ok: false, error });
|
|
3656
|
+
}
|
|
3657
|
+
});
|
|
3658
|
+
|
|
3616
3659
|
// ━━ /coms-queue command — view and kill queued messages ━━━━━━━━━━━━━━━
|
|
3617
3660
|
let comsQueueCmdHandler: any;
|
|
3618
3661
|
pi.registerCommand("coms-queue", {
|
|
@@ -551,6 +551,41 @@ export function formatComsResponseText(targetName: string, response: any, error:
|
|
|
551
551
|
: `[coms response from ${targetName}${arrow}${queueNote}] ${JSON.stringify(response, null, 2)}`;
|
|
552
552
|
}
|
|
553
553
|
|
|
554
|
+
/** #968 C: pidash browser peer-list payload — pure projection of live cards. */
|
|
555
|
+
export interface ComsPeerCard {
|
|
556
|
+
name: string;
|
|
557
|
+
purpose: string;
|
|
558
|
+
model: string;
|
|
559
|
+
color: string;
|
|
560
|
+
context_used_pct?: number;
|
|
561
|
+
queue_depth?: number;
|
|
562
|
+
busy?: boolean;
|
|
563
|
+
tasks_summary?: unknown;
|
|
564
|
+
staleCount: number;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
export function buildComsPeersPayload(
|
|
568
|
+
cards: Iterable<ComsPeerCard>,
|
|
569
|
+
self: { name: string; purpose?: string; project?: string } | null,
|
|
570
|
+
): { self: { name: string; purpose: string; project: string } | null; peers: Array<Record<string, unknown>> } {
|
|
571
|
+
const peers: Array<Record<string, unknown>> = [];
|
|
572
|
+
for (const card of cards) {
|
|
573
|
+
peers.push({
|
|
574
|
+
name: card.name,
|
|
575
|
+
purpose: card.purpose,
|
|
576
|
+
model: card.model,
|
|
577
|
+
busy: card.busy ?? false,
|
|
578
|
+
queueDepth: card.queue_depth ?? 0,
|
|
579
|
+
contextUsedPct: card.context_used_pct ?? 0,
|
|
580
|
+
tasksSummary: card.tasks_summary ?? null,
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
return {
|
|
584
|
+
self: self ? { name: self.name, purpose: self.purpose || "", project: self.project || "" } : null,
|
|
585
|
+
peers,
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
|
|
554
589
|
/** Sanitize a coms identity name for safe embedding in header strings.
|
|
555
590
|
* Strips all control characters (C0/C1), brackets; replaces whitespace with hyphens. */
|
|
556
591
|
export function sanitizeComsName(name: string): string {
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { createLogger } from "../shared/logger.js";
|
|
9
|
+
import { createBridgedUi } from "../shared/ui-bridge.js";
|
|
9
10
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
10
11
|
import { isToolCallEventType } from "@earendil-works/pi-coding-agent";
|
|
11
12
|
import { Type } from "typebox";
|
|
@@ -119,6 +120,9 @@ function commitTrailerModelId(ctx: any): string {
|
|
|
119
120
|
}
|
|
120
121
|
|
|
121
122
|
/** Inject commit trailer into command (Assisted-by, Co-authored-by, etc.) */
|
|
123
|
+
/** #968 B: set by registerEnforcement; null → plain ctx.ui prompts (tests, unregistered). */
|
|
124
|
+
let bridgedUi: ReturnType<typeof createBridgedUi> | null = null;
|
|
125
|
+
|
|
122
126
|
async function handleCommitTrailer(command: string, event: any, ctx: any, gitCwd: string): Promise<EnforcementResult> {
|
|
123
127
|
// #932: rewrite a literal `$PI_MODEL` in the trailer identity — an agent
|
|
124
128
|
// following the git-expert trailer instruction may write the placeholder
|
|
@@ -159,10 +163,9 @@ async function handleCommitTrailer(command: string, event: any, ctx: any, gitCwd
|
|
|
159
163
|
// No UI available — default to first option
|
|
160
164
|
trailerName = options[0];
|
|
161
165
|
} else {
|
|
162
|
-
const selected =
|
|
163
|
-
"Select commit trailer:",
|
|
164
|
-
options
|
|
165
|
-
);
|
|
166
|
+
const selected = bridgedUi
|
|
167
|
+
? await bridgedUi.select(ctx, "Select commit trailer:", options)
|
|
168
|
+
: await ctx.ui.select("Select commit trailer:", options);
|
|
166
169
|
if (!selected || !options.includes(selected)) {
|
|
167
170
|
return {
|
|
168
171
|
block: true,
|
|
@@ -579,10 +582,10 @@ async function checkDangerousCommands(command: string, cwd: string, ctx: any): P
|
|
|
579
582
|
: []),
|
|
580
583
|
"No",
|
|
581
584
|
];
|
|
582
|
-
const
|
|
583
|
-
|
|
584
|
-
options
|
|
585
|
-
|
|
585
|
+
const promptTitle = `⚠️ Dangerous statement${remaining.length > 1 ? "s" : ""} (of the full command):\n\n${list}${scopeNote}\n\nAllow?`;
|
|
586
|
+
const ok = bridgedUi
|
|
587
|
+
? await bridgedUi.select(ctx, promptTitle, options)
|
|
588
|
+
: await ctx.ui.select(promptTitle, options);
|
|
586
589
|
if (ok === "No" || ok === undefined)
|
|
587
590
|
return { block: true, reason: "Blocked by user. Do NOT retry with an equivalent command (e.g., find -delete, perl, python os.remove). Stop and report this block to the user." };
|
|
588
591
|
if (ok === "Yes — run once" || ok.startsWith("Always allow all of the above")) {
|
|
@@ -639,6 +642,11 @@ async function checkDangerousCommands(command: string, cwd: string, ctx: any): P
|
|
|
639
642
|
}
|
|
640
643
|
|
|
641
644
|
export function registerEnforcement(pi: ExtensionAPI, inContainer?: boolean): void {
|
|
645
|
+
// #968 part B: enforcement prompts (dangerous-command approvals, trailer
|
|
646
|
+
// selection) are bridged — answerable from TUI or pidash browser, first
|
|
647
|
+
// answer wins, same enforcement semantics either way. Module-level check
|
|
648
|
+
// functions read this ref; null (unregistered / tests) falls back to plain ui.
|
|
649
|
+
bridgedUi = createBridgedUi(pi);
|
|
642
650
|
|
|
643
651
|
pi.on("session_start", (_event, ctx) => {
|
|
644
652
|
cachedTrailerSelection = null;
|
|
@@ -34,6 +34,8 @@ import { registerMemoryTools } from "./memory-tools.js";
|
|
|
34
34
|
import { registerReviewUI } from "./review-ui.js";
|
|
35
35
|
import { registerSessionSearch } from "./session-search.js";
|
|
36
36
|
import { registerSettingsTui } from "./settings-tui.js";
|
|
37
|
+
import { buildSituationReport } from "./situation-report.js";
|
|
38
|
+
import { listTopics } from "./memory-tree.js";
|
|
37
39
|
import { registerMarkdownTransformer } from "./markdown-transformer.js";
|
|
38
40
|
import { registerGraft } from "./graft.js";
|
|
39
41
|
import { registerReviewLearning } from "./review-learning.js";
|
|
@@ -89,6 +91,36 @@ export default function (pi: ExtensionAPI) {
|
|
|
89
91
|
}
|
|
90
92
|
});
|
|
91
93
|
|
|
94
|
+
// ── #968 part E: memory status surface for pidash ──────────────────
|
|
95
|
+
// Read-only snapshot through the live orchestrator memory modules —
|
|
96
|
+
// situation report, topic hotness, entry counts, budget usage.
|
|
97
|
+
// The requesting session's cwd comes in the event payload (the pidash
|
|
98
|
+
// browser may be watching a session in a different project than this
|
|
99
|
+
// process's cwd).
|
|
100
|
+
pi.events.on("pidash:memory-status-request", (data: unknown) => {
|
|
101
|
+
try {
|
|
102
|
+
const payload = (typeof data === "object" && data !== null ? data : {}) as { cwd?: unknown };
|
|
103
|
+
const cwd = typeof payload.cwd === "string" && payload.cwd ? payload.cwd : process.cwd();
|
|
104
|
+
const report = buildSituationReport(cwd);
|
|
105
|
+
const topics = listTopics(cwd).map((t) => ({
|
|
106
|
+
name: t.name,
|
|
107
|
+
entryCount: t.entryCount,
|
|
108
|
+
hotness: Math.round(t.hotness),
|
|
109
|
+
newestEntry: t.newestEntry,
|
|
110
|
+
}));
|
|
111
|
+
const usageMatch = report.match(/# Project Memory \[([0-9]+)%[^\]]*\]/);
|
|
112
|
+
pi.events.emit("pidash:memory-status", {
|
|
113
|
+
topics,
|
|
114
|
+
usagePct: usageMatch ? Number(usageMatch[1]) : null,
|
|
115
|
+
report,
|
|
116
|
+
});
|
|
117
|
+
log.info("pidash_memory_status_sent", { topics: topics.length });
|
|
118
|
+
} catch (e: any) {
|
|
119
|
+
log.warn("pidash_memory_status_failed", { error: e?.message });
|
|
120
|
+
pi.events.emit("pidash:memory-status", { topics: [], usagePct: null, report: "", error: e?.message });
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
92
124
|
// Extended autocomplete must register FIRST — it wraps registerCommand
|
|
93
125
|
// to inject getArgumentCompletions before other modules register their commands.
|
|
94
126
|
registerExtendedAutocomplete(pi);
|
|
@@ -17,6 +17,7 @@ import type { Message } from "@earendil-works/pi-ai";
|
|
|
17
17
|
import { StringEnum } from "@earendil-works/pi-ai";
|
|
18
18
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
19
19
|
import { createLogger } from "../shared/logger.js";
|
|
20
|
+
import { createBridgedUi } from "../shared/ui-bridge.js";
|
|
20
21
|
import {
|
|
21
22
|
getMarkdownTheme,
|
|
22
23
|
withFileMutationQueue,
|
|
@@ -602,6 +603,10 @@ export function registerSubagentTool(
|
|
|
602
603
|
// Child processes set PI_SUBAGENT_CHILD=1 to prevent infinite recursion.
|
|
603
604
|
if (process.env.PI_SUBAGENT_CHILD === "1") return;
|
|
604
605
|
|
|
606
|
+
// #968 part B: the project-agent approval is bridged — answerable from
|
|
607
|
+
// TUI or pidash browser, first answer wins.
|
|
608
|
+
const bridgedUi = createBridgedUi(pi);
|
|
609
|
+
|
|
605
610
|
// ── Extracted execute helpers (closure over pi, spawnAsyncAgent, killAsyncAgent) ──
|
|
606
611
|
|
|
607
612
|
// asyncKill is an LLM-issued decision, not a user action.
|
|
@@ -1627,7 +1632,8 @@ export function registerSubagentTool(
|
|
|
1627
1632
|
.map((n) => agents.find((a) => a.name === n))
|
|
1628
1633
|
.filter((a): a is AgentConfig => a?.source === "project");
|
|
1629
1634
|
if (projAgents.length > 0) {
|
|
1630
|
-
const ok = await
|
|
1635
|
+
const ok = await bridgedUi.confirm(
|
|
1636
|
+
ctx,
|
|
1631
1637
|
"Run project-local agents?",
|
|
1632
1638
|
`Agents: ${projAgents.map((a) => a.name).join(", ")}\nSource: ${discovery.projectAgentsDir}`,
|
|
1633
1639
|
);
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* browser-ops — pidash browser operations that need config/CLI access (#968 D/F/G).
|
|
3
|
+
*
|
|
4
|
+
* Settings: read/write through the SAME project-settings modules the TUI uses
|
|
5
|
+
* (settings-keys.json is the single source of truth; writeSettingsFile is the
|
|
6
|
+
* atomic writer). Never a direct ad-hoc file write from the daemon.
|
|
7
|
+
* MCP: `pi mcp` CLI (list --json / login / logout).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { execFile } from "node:child_process";
|
|
11
|
+
import { promisify } from "node:util";
|
|
12
|
+
|
|
13
|
+
const execFileAsync = promisify(execFile);
|
|
14
|
+
import { createLogger } from "../shared/logger.js";
|
|
15
|
+
import {
|
|
16
|
+
SETTINGS_KEYS,
|
|
17
|
+
clearSettingsCache,
|
|
18
|
+
getGlobalSettingsPath,
|
|
19
|
+
getSettingsPath,
|
|
20
|
+
loadGlobalSettings,
|
|
21
|
+
loadProjectSettings,
|
|
22
|
+
parseSettingsFile,
|
|
23
|
+
} from "../orchestrator/project-settings.js";
|
|
24
|
+
import { readSettingsFile, writeSettingsFile } from "../orchestrator/settings-tui-helpers.js";
|
|
25
|
+
|
|
26
|
+
const log = createLogger("pidash-browser-ops");
|
|
27
|
+
|
|
28
|
+
export type SettingsScope = "project" | "global";
|
|
29
|
+
|
|
30
|
+
export interface SettingsKeyPayload {
|
|
31
|
+
key: string;
|
|
32
|
+
description: string;
|
|
33
|
+
type: string;
|
|
34
|
+
env?: string;
|
|
35
|
+
default: unknown;
|
|
36
|
+
group: string;
|
|
37
|
+
enum?: string[];
|
|
38
|
+
min?: number;
|
|
39
|
+
max?: number;
|
|
40
|
+
/** Value stored in the scope's file (undefined = not set there). */
|
|
41
|
+
stored: unknown;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface SettingsPayload {
|
|
45
|
+
scope: SettingsScope;
|
|
46
|
+
keys: SettingsKeyPayload[];
|
|
47
|
+
error?: string;
|
|
48
|
+
/** Resolved settings file path (project scope) — lets browsers decide
|
|
49
|
+
* whether a broadcast describes THEIR settings file even when their session
|
|
50
|
+
* cwd is a different worktree/subdir of the same repo (Qodo R5). */
|
|
51
|
+
settingsPath?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function isSettingsScope(v: unknown): v is SettingsScope {
|
|
55
|
+
return v === "project" || v === "global";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Snapshot of every settings key for a scope, with the value stored in that scope's file. */
|
|
59
|
+
export function buildSettingsPayload(cwd: string, scope: unknown): SettingsPayload {
|
|
60
|
+
if (!isSettingsScope(scope)) return { scope: "project", keys: [], error: "invalid scope" };
|
|
61
|
+
try {
|
|
62
|
+
clearSettingsCache();
|
|
63
|
+
let stored: Record<string, unknown> = {};
|
|
64
|
+
if (scope === "project") {
|
|
65
|
+
stored = parseSettingsFile(getSettingsPath(cwd)) as unknown as Record<string, unknown>;
|
|
66
|
+
} else {
|
|
67
|
+
stored = loadGlobalSettings() as unknown as Record<string, unknown>;
|
|
68
|
+
}
|
|
69
|
+
const keys: SettingsKeyPayload[] = Object.entries(SETTINGS_KEYS).map(([key, def]) => ({
|
|
70
|
+
key,
|
|
71
|
+
description: def.description,
|
|
72
|
+
type: def.type,
|
|
73
|
+
env: def.env,
|
|
74
|
+
default: def.default,
|
|
75
|
+
group: (def as any).group ?? "General",
|
|
76
|
+
enum: def.enum,
|
|
77
|
+
min: def.min,
|
|
78
|
+
max: def.max,
|
|
79
|
+
stored: key in stored ? stored[key] : undefined,
|
|
80
|
+
}));
|
|
81
|
+
keys.sort((a, b) => (a.group === b.group ? a.key.localeCompare(b.key) : a.group.localeCompare(b.group)));
|
|
82
|
+
log.debug("settings_payload_built", { scope, keys: keys.length });
|
|
83
|
+
// Resolved file path (project scope only) — identity of the settings
|
|
84
|
+
// file being described, independent of the session's cwd (Qodo R5).
|
|
85
|
+
return { scope, keys, settingsPath: scope === "project" ? getSettingsPath(cwd) : undefined };
|
|
86
|
+
} catch (e: any) {
|
|
87
|
+
log.warn("settings_payload_failed", { scope, error: e?.message });
|
|
88
|
+
return { scope, keys: [], error: e?.message };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Validate a browser-supplied value against the settings-keys schema.
|
|
94
|
+
* Mirrors the checks `parseSettingsFile` applies on read, so the browser can
|
|
95
|
+
* never persist a value the terminal settings editor would reject or drop.
|
|
96
|
+
* Returns an error string, or null when the value is valid.
|
|
97
|
+
*/
|
|
98
|
+
export function validateSettingValue(key: string, value: unknown): string | null {
|
|
99
|
+
const def = (SETTINGS_KEYS as Record<string, any>)[key];
|
|
100
|
+
if (!def) return `unknown setting key: ${key}`;
|
|
101
|
+
// Security-sensitive: the dangerous-command allowlist has a dedicated
|
|
102
|
+
// writer with trust/escaping checks (settings-tui) — never accept it over
|
|
103
|
+
// the browser websocket.
|
|
104
|
+
if (key === "dangerous_command_allowlist") {
|
|
105
|
+
return "dangerous_command_allowlist is managed by the terminal settings editor only";
|
|
106
|
+
}
|
|
107
|
+
if (value === null) return null; // clear — removing the key is always valid
|
|
108
|
+
const t = def.type as string;
|
|
109
|
+
const isStr = (v: unknown) => typeof v === "string" && v.trim().length > 0;
|
|
110
|
+
switch (t) {
|
|
111
|
+
case "bool":
|
|
112
|
+
case "bool_enable":
|
|
113
|
+
return typeof value === "boolean" ? null : `${key} must be a boolean`;
|
|
114
|
+
case "int":
|
|
115
|
+
if (typeof value !== "number" || !Number.isInteger(value)) return `${key} must be an integer`;
|
|
116
|
+
if (def.min !== undefined && value < def.min) return `${key} must be >= ${def.min}`;
|
|
117
|
+
if (def.max !== undefined && value > def.max) return `${key} must be <= ${def.max}`;
|
|
118
|
+
return null;
|
|
119
|
+
case "number":
|
|
120
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return `${key} must be a finite number`;
|
|
121
|
+
if (def.min !== undefined && value < def.min) return `${key} must be >= ${def.min}`;
|
|
122
|
+
if (def.max !== undefined && value > def.max) return `${key} must be <= ${def.max}`;
|
|
123
|
+
return null;
|
|
124
|
+
case "port":
|
|
125
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0 && value <= 65535
|
|
126
|
+
? null
|
|
127
|
+
: `${key} must be an integer between 1 and 65535`;
|
|
128
|
+
case "string":
|
|
129
|
+
case "enum":
|
|
130
|
+
case "model":
|
|
131
|
+
case "provider":
|
|
132
|
+
if (!isStr(value)) return `${key} must be a non-empty string`;
|
|
133
|
+
if (Array.isArray(def.enum) && def.enum.length > 0 && !def.enum.includes(value)) {
|
|
134
|
+
return `${key} must be one of: ${def.enum.join(", ")}`;
|
|
135
|
+
}
|
|
136
|
+
return null;
|
|
137
|
+
case "bool_or_string":
|
|
138
|
+
return typeof value === "boolean" || isStr(value) ? null : `${key} must be a boolean or non-empty string`;
|
|
139
|
+
case "string_list":
|
|
140
|
+
if (!Array.isArray(value) || !value.every((v: unknown) => typeof v === "string")) {
|
|
141
|
+
return `${key} must be an array of strings`;
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
case "agent_list":
|
|
145
|
+
if (typeof value === "string" || (Array.isArray(value) && value.every((v: unknown) => typeof v === "string"))) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
return `${key} must be a string or array of strings`;
|
|
149
|
+
case "agent_overrides":
|
|
150
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
151
|
+
return `${key} must be an object mapping agent name to {provider?, model?}`;
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
default:
|
|
155
|
+
return `${key} has unsupported type ${t} for browser edits`;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Apply one setting to a scope's file. value === null removes the key. */
|
|
160
|
+
export function applySetting(
|
|
161
|
+
cwd: string,
|
|
162
|
+
scope: unknown,
|
|
163
|
+
key: string,
|
|
164
|
+
value: unknown,
|
|
165
|
+
): { ok: boolean; error?: string } {
|
|
166
|
+
if (!isSettingsScope(scope)) return { ok: false, error: "invalid scope" };
|
|
167
|
+
if (!(key in SETTINGS_KEYS)) return { ok: false, error: `unknown setting key: ${key}` };
|
|
168
|
+
const validationError = validateSettingValue(key, value);
|
|
169
|
+
if (validationError) {
|
|
170
|
+
log.warn("browser_setting_rejected", { scope, key, error: validationError });
|
|
171
|
+
return { ok: false, error: validationError };
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
clearSettingsCache();
|
|
175
|
+
const filePath = scope === "project" ? getSettingsPath(cwd) : getGlobalSettingsPath();
|
|
176
|
+
// Read the RAW file — parseSettingsFile drops unknown keys and invalid
|
|
177
|
+
// values, so a read-modify-write through it would rewrite the file
|
|
178
|
+
// without them; and for malformed JSON it returns {} which would replace
|
|
179
|
+
// the user's file with a one-key file. readSettingsFile preserves every
|
|
180
|
+
// key and returns null for malformed files — refuse to save instead.
|
|
181
|
+
const data = readSettingsFile(filePath);
|
|
182
|
+
if (data === null) {
|
|
183
|
+
log.warn("browser_setting_save_refused_malformed_file", { scope, key, filePath });
|
|
184
|
+
return { ok: false, error: `${filePath} is malformed (invalid JSON or not an object) — fix it before saving from the browser` };
|
|
185
|
+
}
|
|
186
|
+
if (value === null) delete data[key];
|
|
187
|
+
else data[key] = value;
|
|
188
|
+
writeSettingsFile(filePath, data);
|
|
189
|
+
clearSettingsCache();
|
|
190
|
+
log.info("browser_setting_applied", { scope, key, cleared: value === null });
|
|
191
|
+
return { ok: true };
|
|
192
|
+
} catch (e: any) {
|
|
193
|
+
log.error("browser_setting_apply_failed", { scope, key, error: e?.message });
|
|
194
|
+
return { ok: false, error: e?.message };
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Run `pi mcp <args>` and resolve stdout (rejects with .stdout attached on failure).
|
|
200
|
+
*
|
|
201
|
+
* Async on purpose: this runs inside the live Pi process's websocket handler,
|
|
202
|
+
* and `pi mcp login` can wait on OAuth browser completion for minutes — a
|
|
203
|
+
* synchronous exec would block the event loop (TUI, agent streaming, timers,
|
|
204
|
+
* every pidash socket) for the whole timeout window.
|
|
205
|
+
*/
|
|
206
|
+
export async function runMcp(args: string[], cwd: string, timeoutMs = 60000): Promise<string> {
|
|
207
|
+
const { stdout } = await execFileAsync("pi", ["mcp", ...args], {
|
|
208
|
+
cwd,
|
|
209
|
+
encoding: "utf-8",
|
|
210
|
+
timeout: timeoutMs,
|
|
211
|
+
env: { ...process.env, PI_MCP_NO_BROWSER: process.env.PI_MCP_NO_BROWSER },
|
|
212
|
+
});
|
|
213
|
+
return stdout;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** Parse `pi mcp list --json` output. Returns servers array or error. */
|
|
217
|
+
export async function mcpList(cwd: string): Promise<{ servers?: unknown[]; raw?: string; error?: string }> {
|
|
218
|
+
try {
|
|
219
|
+
const out = await runMcp(["list", "--json"], cwd);
|
|
220
|
+
try {
|
|
221
|
+
const parsed = JSON.parse(out);
|
|
222
|
+
return { servers: Array.isArray(parsed) ? parsed : parsed?.servers ?? [parsed] };
|
|
223
|
+
} catch {
|
|
224
|
+
// Not JSON — return raw text for display
|
|
225
|
+
return { raw: out };
|
|
226
|
+
}
|
|
227
|
+
} catch (e: any) {
|
|
228
|
+
log.warn("mcp_list_failed", { error: e?.message });
|
|
229
|
+
return { error: (e?.stderr || e?.message || String(e)).slice(0, 2000) };
|
|
230
|
+
}
|
|
231
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
|
|
2
|
+
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){:root,:host{--shimmer-angle:20deg}*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-animation-delay:0s;--tw-animation-direction:normal;--tw-animation-duration:initial;--tw-animation-fill-mode:none;--tw-animation-iteration-count:1;--tw-enter-blur:0;--tw-enter-opacity:1;--tw-enter-rotate:0;--tw-enter-scale:1;--tw-enter-translate-x:0;--tw-enter-translate-y:0;--tw-exit-blur:0;--tw-exit-opacity:1;--tw-exit-rotate:0;--tw-exit-scale:1;--tw-exit-translate-x:0;--tw-exit-translate-y:0;--scroll-fade-t:0px;--scroll-fade-b:0px;--scroll-fade-s:0px;--scroll-fade-e:0px;--scroll-fade-mask:initial;--shimmer-image:initial;--shimmer-text-fill:initial}}}@layer theme{:root,:host{--font-sans:"Geist Variable", sans-serif;--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-orange-400:oklch(75% .183 55.934);--color-orange-500:oklch(70.5% .213 47.604);--color-yellow-300:oklch(90.5% .182 98.111);--color-yellow-400:oklch(85.2% .199 91.936);--color-yellow-500:oklch(79.5% .184 86.047);--color-yellow-600:oklch(68.1% .162 75.834);--color-green-400:oklch(79.2% .209 151.711);--color-green-500:oklch(72.3% .219 149.579);--color-green-600:oklch(62.7% .194 149.214);--color-cyan-300:oklch(86.5% .127 207.078);--color-cyan-400:oklch(78.9% .154 211.53);--color-cyan-500:oklch(71.5% .143 215.221);--color-cyan-700:oklch(52% .105 223.128);--color-blue-300:oklch(80.9% .105 251.813);--color-blue-400:oklch(70.7% .165 254.624);--color-purple-400:oklch(71.4% .203 305.504);--color-purple-500:oklch(62.7% .265 303.9);--color-pink-400:oklch(71.8% .202 349.761);--color-black:#000;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-normal:0em;--tracking-wider:.05em;--animate-ping:ping 1s cubic-bezier(0, 0, .2, 1) infinite;--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--animate-bounce:bounce 1s infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:"Geist Variable", sans-serif;--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}*{border-color:var(--border);outline-color:var(--ring)}@supports (color:color-mix(in lab, red, red)){*{outline-color:color-mix(in oklab, var(--ring) 50%, transparent)}}body{background-color:var(--background);color:var(--foreground)}html{font-family:Geist Variable,sans-serif}}@layer components;@layer utilities{.pointer-events-none{pointer-events:none}.collapse{visibility:collapse}.invisible{visibility:hidden}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.inset-0{inset:0}.-top-1{top:calc(var(--spacing) * -1)}.top-0{top:0}.top-1\.5{top:calc(var(--spacing) * 1.5)}.-right-1{right:calc(var(--spacing) * -1)}.right-0{right:0}.right-1\.5{right:calc(var(--spacing) * 1.5)}.bottom-0{bottom:0}.bottom-full{bottom:100%}.left-0{left:0}.z-10{z-index:10}.z-50{z-index:50}.container{width:100%}@media (width>=40rem){.container{max-width:40rem}}@media (width>=48rem){.container{max-width:48rem}}@media (width>=64rem){.container{max-width:64rem}}@media (width>=80rem){.container{max-width:80rem}}@media (width>=96rem){.container{max-width:96rem}}.mx-0\.5{margin-inline:calc(var(--spacing) * .5)}.mx-3{margin-inline:calc(var(--spacing) * 3)}.-mt-0\.5{margin-top:calc(var(--spacing) * -.5)}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:var(--spacing)}.mt-1\.5{margin-top:calc(var(--spacing) * 1.5)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-6{margin-top:calc(var(--spacing) * 6)}.mr-1\.5{margin-right:calc(var(--spacing) * 1.5)}.mb-0\.5{margin-bottom:calc(var(--spacing) * .5)}.mb-1{margin-bottom:var(--spacing)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.ml-0\.5{margin-left:calc(var(--spacing) * .5)}.ml-1{margin-left:var(--spacing)}.ml-2{margin-left:calc(var(--spacing) * 2)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.h-1\.5{height:calc(var(--spacing) * 1.5)}.h-2{height:calc(var(--spacing) * 2)}.h-2\.5{height:calc(var(--spacing) * 2.5)}.h-3{height:calc(var(--spacing) * 3)}.h-3\.5{height:calc(var(--spacing) * 3.5)}.h-4{height:calc(var(--spacing) * 4)}.h-6{height:calc(var(--spacing) * 6)}.h-10{height:calc(var(--spacing) * 10)}.h-16{height:calc(var(--spacing) * 16)}.h-\[38px\]{height:38px}.h-full{height:100%}.max-h-32{max-height:calc(var(--spacing) * 32)}.max-h-48{max-height:calc(var(--spacing) * 48)}.max-h-60{max-height:calc(var(--spacing) * 60)}.max-h-72{max-height:calc(var(--spacing) * 72)}.max-h-\[75vh\]{max-height:75vh}.max-h-\[300px\]{max-height:300px}.min-h-0{min-height:0}.min-h-\[38px\]{min-height:38px}.min-h-\[60px\]{min-height:60px}.w-1{width:var(--spacing)}.w-1\.5{width:calc(var(--spacing) * 1.5)}.w-2{width:calc(var(--spacing) * 2)}.w-2\.5{width:calc(var(--spacing) * 2.5)}.w-3{width:calc(var(--spacing) * 3)}.w-3\.5{width:calc(var(--spacing) * 3.5)}.w-4{width:calc(var(--spacing) * 4)}.w-6{width:calc(var(--spacing) * 6)}.w-12{width:calc(var(--spacing) * 12)}.w-16{width:calc(var(--spacing) * 16)}.w-56{width:calc(var(--spacing) * 56)}.w-72{width:calc(var(--spacing) * 72)}.w-\[6\.5rem\]{width:6.5rem}.w-\[8\.5rem\]{width:8.5rem}.w-\[38px\]{width:38px}.w-\[130px\]{width:130px}.w-\[320px\]{width:320px}.w-\[380px\]{width:380px}.w-\[520px\]{width:520px}.w-\[600px\]{width:600px}.w-\[var\(--panel-w\)\]{width:var(--panel-w)}.w-full{width:100%}.w-max{width:max-content}.w-screen{width:100vw}.max-w-\[5rem\]{max-width:5rem}.max-w-\[6\.5rem\]{max-width:6.5rem}.max-w-\[22rem\]{max-width:22rem}.max-w-\[90vw\]{max-width:90vw}.max-w-\[200px\]{max-width:200px}.min-w-0{min-width:0}.min-w-28{min-width:calc(var(--spacing) * 28)}.min-w-\[3\.5em\]{min-width:3.5em}.min-w-\[4\.75rem\]{min-width:4.75rem}.min-w-\[9\.5em\]{min-width:9.5em}.min-w-\[9rem\]{min-width:9rem}.min-w-\[200px\]{min-width:200px}.flex-1{flex:1}.flex-shrink-0{flex-shrink:0}.shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.animate-bounce{animation:var(--animate-bounce)}.animate-ping{animation:var(--animate-ping)}.animate-pulse{animation:var(--animate-pulse)}.cursor-col-resize{cursor:col-resize}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.resize{resize:both}.resize-none{resize:none}.resize-y{resize:vertical}.scrollbar-none{scrollbar-width:none}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing) * var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-1\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1.5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1.5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-border>:not(:last-child)){border-color:var(--border)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:2147483647px}.rounded-lg{border-radius:var(--radius)}.rounded-md{border-radius:calc(var(--radius) * .8)}.rounded-sm{border-radius:calc(var(--radius) * .6)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-l-2{border-left-style:var(--tw-border-style);border-left-width:2px}.border-l-3,.border-l-\[3px\]{border-left-style:var(--tw-border-style);border-left-width:3px}.border-border,.border-border\/30{border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){.border-border\/30{border-color:color-mix(in oklab, var(--border) 30%, transparent)}}.border-border\/50{border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){.border-border\/50{border-color:color-mix(in oklab, var(--border) 50%, transparent)}}.border-cyan-400{border-color:var(--color-cyan-400)}.border-green-500\/30{border-color:#00c7584d}@supports (color:color-mix(in lab, red, red)){.border-green-500\/30{border-color:color-mix(in oklab, var(--color-green-500) 30%, transparent)}}.border-green-500\/40{border-color:#00c75866}@supports (color:color-mix(in lab, red, red)){.border-green-500\/40{border-color:color-mix(in oklab, var(--color-green-500) 40%, transparent)}}.border-green-600{border-color:var(--color-green-600)}.border-orange-500{border-color:var(--color-orange-500)}.border-primary{border-color:var(--primary)}.border-purple-500{border-color:var(--color-purple-500)}.border-red-500{border-color:var(--color-red-500)}.border-red-500\/30{border-color:#fb2c364d}@supports (color:color-mix(in lab, red, red)){.border-red-500\/30{border-color:color-mix(in oklab, var(--color-red-500) 30%, transparent)}}.border-yellow-500\/20{border-color:#edb20033}@supports (color:color-mix(in lab, red, red)){.border-yellow-500\/20{border-color:color-mix(in oklab, var(--color-yellow-500) 20%, transparent)}}.border-l-primary{border-left-color:var(--primary)}.border-l-transparent{border-left-color:#0000}.bg-accent,.bg-accent\/30{background-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.bg-accent\/30{background-color:color-mix(in oklab, var(--accent) 30%, transparent)}}.bg-accent\/50{background-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.bg-accent\/50{background-color:color-mix(in oklab, var(--accent) 50%, transparent)}}.bg-background{background-color:var(--background)}.bg-black\/50{background-color:#00000080}@supports (color:color-mix(in lab, red, red)){.bg-black\/50{background-color:color-mix(in oklab, var(--color-black) 50%, transparent)}}.bg-black\/60{background-color:#0009}@supports (color:color-mix(in lab, red, red)){.bg-black\/60{background-color:color-mix(in oklab, var(--color-black) 60%, transparent)}}.bg-card{background-color:var(--card)}.bg-cyan-400{background-color:var(--color-cyan-400)}.bg-cyan-500\/15{background-color:#00b7d726}@supports (color:color-mix(in lab, red, red)){.bg-cyan-500\/15{background-color:color-mix(in oklab, var(--color-cyan-500) 15%, transparent)}}.bg-destructive{background-color:var(--destructive)}.bg-green-400{background-color:var(--color-green-400)}.bg-green-500{background-color:var(--color-green-500)}.bg-green-500\/10{background-color:#00c7581a}@supports (color:color-mix(in lab, red, red)){.bg-green-500\/10{background-color:color-mix(in oklab, var(--color-green-500) 10%, transparent)}}.bg-green-500\/15{background-color:#00c75826}@supports (color:color-mix(in lab, red, red)){.bg-green-500\/15{background-color:color-mix(in oklab, var(--color-green-500) 15%, transparent)}}.bg-green-500\/20{background-color:#00c75833}@supports (color:color-mix(in lab, red, red)){.bg-green-500\/20{background-color:color-mix(in oklab, var(--color-green-500) 20%, transparent)}}.bg-muted,.bg-muted\/40{background-color:var(--muted)}@supports (color:color-mix(in lab, red, red)){.bg-muted\/40{background-color:color-mix(in oklab, var(--muted) 40%, transparent)}}.bg-muted\/50{background-color:var(--muted)}@supports (color:color-mix(in lab, red, red)){.bg-muted\/50{background-color:color-mix(in oklab, var(--muted) 50%, transparent)}}.bg-orange-400{background-color:var(--color-orange-400)}.bg-orange-400\/15{background-color:#ff8b1a26}@supports (color:color-mix(in lab, red, red)){.bg-orange-400\/15{background-color:color-mix(in oklab, var(--color-orange-400) 15%, transparent)}}.bg-primary{background-color:var(--primary)}.bg-red-500{background-color:var(--color-red-500)}.bg-red-500\/10{background-color:#fb2c361a}@supports (color:color-mix(in lab, red, red)){.bg-red-500\/10{background-color:color-mix(in oklab, var(--color-red-500) 10%, transparent)}}.bg-red-500\/20{background-color:#fb2c3633}@supports (color:color-mix(in lab, red, red)){.bg-red-500\/20{background-color:color-mix(in oklab, var(--color-red-500) 20%, transparent)}}.bg-transparent{background-color:#0000}.bg-yellow-400{background-color:var(--color-yellow-400)}.bg-yellow-500\/10{background-color:#edb2001a}@supports (color:color-mix(in lab, red, red)){.bg-yellow-500\/10{background-color:color-mix(in oklab, var(--color-yellow-500) 10%, transparent)}}.bg-yellow-500\/15{background-color:#edb20026}@supports (color:color-mix(in lab, red, red)){.bg-yellow-500\/15{background-color:color-mix(in oklab, var(--color-yellow-500) 15%, transparent)}}.bg-yellow-500\/20{background-color:#edb20033}@supports (color:color-mix(in lab, red, red)){.bg-yellow-500\/20{background-color:color-mix(in oklab, var(--color-yellow-500) 20%, transparent)}}.bg-yellow-500\/30{background-color:#edb2004d}@supports (color:color-mix(in lab, red, red)){.bg-yellow-500\/30{background-color:color-mix(in oklab, var(--color-yellow-500) 30%, transparent)}}.fill-current{fill:currentColor}.object-cover{object-fit:cover}.p-0{padding:0}.p-1{padding:var(--spacing)}.p-1\.5{padding:calc(var(--spacing) * 1.5)}.p-2{padding:calc(var(--spacing) * 2)}.p-2\.5{padding:calc(var(--spacing) * 2.5)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-8{padding:calc(var(--spacing) * 8)}.px-0\.5{padding-inline:calc(var(--spacing) * .5)}.px-1{padding-inline:var(--spacing)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.py-0{padding-block:0}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:var(--spacing)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-4{padding-block:calc(var(--spacing) * 4)}.pt-2{padding-top:calc(var(--spacing) * 2)}.pt-\[10vh\]{padding-top:10vh}.pt-\[15vh\]{padding-top:15vh}.pt-\[env\(safe-area-inset-top\,12px\)\]{padding-top:env(safe-area-inset-top,12px)}.pr-3{padding-right:calc(var(--spacing) * 3)}.pb-1\.5{padding-bottom:calc(var(--spacing) * 1.5)}.pl-5{padding-left:calc(var(--spacing) * 5)}.pl-7{padding-left:calc(var(--spacing) * 7)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.font-mono{font-family:var(--font-mono)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[8px\]{font-size:8px}.text-\[9px\]{font-size:9px}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[13px\]{font-size:13px}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-normal{--tw-tracking:var(--tracking-normal);letter-spacing:var(--tracking-normal)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.break-words{overflow-wrap:break-word}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-blue-400{color:var(--color-blue-400)}.text-border{color:var(--border)}.text-cyan-300{color:var(--color-cyan-300)}.text-cyan-400{color:var(--color-cyan-400)}.text-cyan-700{color:var(--color-cyan-700)}.text-foreground{color:var(--foreground)}.text-green-400{color:var(--color-green-400)}.text-green-500{color:var(--color-green-500)}.text-green-600{color:var(--color-green-600)}.text-inherit{color:inherit}.text-muted-foreground,.text-muted-foreground\/50{color:var(--muted-foreground)}@supports (color:color-mix(in lab, red, red)){.text-muted-foreground\/50{color:color-mix(in oklab, var(--muted-foreground) 50%, transparent)}}.text-muted-foreground\/70{color:var(--muted-foreground)}@supports (color:color-mix(in lab, red, red)){.text-muted-foreground\/70{color:color-mix(in oklab, var(--muted-foreground) 70%, transparent)}}.text-orange-400{color:var(--color-orange-400)}.text-pink-400{color:var(--color-pink-400)}.text-primary{color:var(--primary)}.text-primary-foreground{color:var(--primary-foreground)}.text-purple-400{color:var(--color-purple-400)}.text-red-400{color:var(--color-red-400)}.text-red-500{color:var(--color-red-500)}.text-yellow-300{color:var(--color-yellow-300)}.text-yellow-400{color:var(--color-yellow-400)}.text-yellow-400\/80{color:#fac800cc}@supports (color:color-mix(in lab, red, red)){.text-yellow-400\/80{color:color-mix(in oklab, var(--color-yellow-400) 80%, transparent)}}.text-yellow-500{color:var(--color-yellow-500)}.text-yellow-600{color:var(--color-yellow-600)}.normal-case{text-transform:none}.uppercase{text-transform:uppercase}.italic{font-style:italic}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.line-through{text-decoration-line:line-through}.accent-primary{accent-color:var(--primary)}.opacity-0{opacity:0}.opacity-35{opacity:.35}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.opacity-75{opacity:.75}.shadow-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}.running{animation-play-state:running}@media (hover:hover){.group-hover\:opacity-100:is(:where(.group):hover *),.group-hover\/session\:opacity-100:is(:where(.group\/session):hover *){opacity:1}}.placeholder\:text-muted-foreground::placeholder{color:var(--muted-foreground)}@media (hover:hover){.hover\:border-primary\/50:hover{border-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.hover\:border-primary\/50:hover{border-color:color-mix(in oklab, var(--primary) 50%, transparent)}}.hover\:bg-accent:hover,.hover\:bg-accent\/20:hover{background-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-accent\/20:hover{background-color:color-mix(in oklab, var(--accent) 20%, transparent)}}.hover\:bg-accent\/30:hover{background-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-accent\/30:hover{background-color:color-mix(in oklab, var(--accent) 30%, transparent)}}.hover\:bg-accent\/50:hover{background-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-accent\/50:hover{background-color:color-mix(in oklab, var(--accent) 50%, transparent)}}.hover\:bg-green-500\/40:hover{background-color:#00c75866}@supports (color:color-mix(in lab, red, red)){.hover\:bg-green-500\/40:hover{background-color:color-mix(in oklab, var(--color-green-500) 40%, transparent)}}.hover\:bg-muted:hover{background-color:var(--muted)}.hover\:bg-primary\/30:hover{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-primary\/30:hover{background-color:color-mix(in oklab, var(--primary) 30%, transparent)}}.hover\:bg-primary\/40:hover{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-primary\/40:hover{background-color:color-mix(in oklab, var(--primary) 40%, transparent)}}.hover\:bg-primary\/90:hover{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-primary\/90:hover{background-color:color-mix(in oklab, var(--primary) 90%, transparent)}}.hover\:bg-red-500\/40:hover{background-color:#fb2c3666}@supports (color:color-mix(in lab, red, red)){.hover\:bg-red-500\/40:hover{background-color:color-mix(in oklab, var(--color-red-500) 40%, transparent)}}.hover\:bg-yellow-500\/40:hover{background-color:#edb20066}@supports (color:color-mix(in lab, red, red)){.hover\:bg-yellow-500\/40:hover{background-color:color-mix(in oklab, var(--color-yellow-500) 40%, transparent)}}.hover\:text-blue-300:hover{color:var(--color-blue-300)}.hover\:text-foreground:hover{color:var(--foreground)}.hover\:text-muted-foreground:hover{color:var(--muted-foreground)}.hover\:text-primary:hover,.hover\:text-primary\/80:hover{color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.hover\:text-primary\/80:hover{color:color-mix(in oklab, var(--primary) 80%, transparent)}}.hover\:text-red-400:hover{color:var(--color-red-400)}.hover\:text-yellow-300:hover{color:var(--color-yellow-300)}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-80:hover{opacity:.8}}.focus\:border-primary:focus{border-color:var(--primary)}.focus\:bg-accent:focus{background-color:var(--accent)}.focus\:ring-1:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus\:ring-primary:focus{--tw-ring-color:var(--primary)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.focus\:ring-inset:focus{--tw-ring-inset:inset}.disabled\:opacity-50:disabled{opacity:.5}@media not all and (width>=48rem){.max-md\:absolute{position:absolute}.max-md\:fixed{position:fixed}.max-md\:inset-0{inset:0}.max-md\:inset-y-0{inset-block:0}.max-md\:left-0{left:0}.max-md\:z-20{z-index:20}.max-md\:z-30{z-index:30}.max-md\:block{display:block}.max-md\:hidden{display:none}.max-md\:w-\[85vw\]{width:85vw}.max-md\:w-full{width:100%}.max-md\:max-w-\[90px\]{max-width:90px}.max-md\:max-w-\[320px\]{max-width:320px}.max-md\:min-w-0{min-width:0}.max-md\:gap-1\.5{gap:calc(var(--spacing) * 1.5)}.max-md\:px-2{padding-inline:calc(var(--spacing) * 2)}.max-md\:text-\[10px\]{font-size:10px}}@media (width>=48rem){.md\:w-10{width:calc(var(--spacing) * 10)}.md\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}}.dark\:text-cyan-400\/90:is(.dark *){color:#00d2efe6}@supports (color:color-mix(in lab, red, red)){.dark\:text-cyan-400\/90:is(.dark *){color:color-mix(in oklab, var(--color-cyan-400) 90%, transparent)}}.dark\:text-green-400:is(.dark *){color:var(--color-green-400)}.dark\:text-yellow-400:is(.dark *){color:var(--color-yellow-400)}}@property --tw-animation-delay{syntax:"*";inherits:false;initial-value:0s}@property --tw-animation-direction{syntax:"*";inherits:false;initial-value:normal}@property --tw-animation-duration{syntax:"*";inherits:false}@property --tw-animation-fill-mode{syntax:"*";inherits:false;initial-value:none}@property --tw-animation-iteration-count{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-blur{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-blur{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-translate-y{syntax:"*";inherits:false;initial-value:0}@property --scroll-fade-t{syntax:"<length-percentage>";inherits:false;initial-value:0}@property --scroll-fade-b{syntax:"<length-percentage>";inherits:false;initial-value:0}@property --scroll-fade-s{syntax:"<length-percentage>";inherits:false;initial-value:0}@property --scroll-fade-e{syntax:"<length-percentage>";inherits:false;initial-value:0}@property --scroll-fade-mask{syntax:"*";inherits:false}@property --shimmer-angle{syntax:"<angle>";inherits:true;initial-value:20deg}@property --shimmer-image{syntax:"*";inherits:false}@property --shimmer-text-fill{syntax:"*";inherits:false}@media (prefers-reduced-motion:reduce){.shimmer{-webkit-text-fill-color:currentColor;background-image:none;animation:none}}@font-face{font-family:Geist Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2)format("woff2-variations");unicode-range:U+460-52F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Geist Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2)format("woff2-variations");unicode-range:U+301,U+400-45F,U+490-491,U+4B0-4B1,U+2116}@font-face{font-family:Geist Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2)format("woff2-variations");unicode-range:U+102-103,U+110-111,U+128-129,U+168-169,U+1A0-1A1,U+1AF-1B0,U+300-301,U+303-304,U+308-309,U+323,U+329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Geist Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2)format("woff2-variations");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Geist Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/assets/geist-latin-wght-normal-BgDaEnEv.woff2)format("woff2-variations");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}:root{--background:oklch(100% 0 0);--foreground:oklch(14.5% 0 0);--card:oklch(100% 0 0);--card-foreground:oklch(14.5% 0 0);--popover:oklch(100% 0 0);--popover-foreground:oklch(14.5% 0 0);--primary:oklch(20.5% 0 0);--primary-foreground:oklch(98.5% 0 0);--secondary:oklch(97% 0 0);--secondary-foreground:oklch(20.5% 0 0);--muted:oklch(97% 0 0);--muted-foreground:oklch(55.6% 0 0);--accent:oklch(97% 0 0);--accent-foreground:oklch(20.5% 0 0);--destructive:oklch(57.7% .245 27.325);--border:oklch(92.2% 0 0);--input:oklch(92.2% 0 0);--ring:oklch(70.8% 0 0);--chart-1:oklch(87% 0 0);--chart-2:oklch(55.6% 0 0);--chart-3:oklch(43.9% 0 0);--chart-4:oklch(37.1% 0 0);--chart-5:oklch(26.9% 0 0);--radius:.625rem;--sidebar:oklch(98.5% 0 0);--sidebar-foreground:oklch(14.5% 0 0);--sidebar-primary:oklch(20.5% 0 0);--sidebar-primary-foreground:oklch(98.5% 0 0);--sidebar-accent:oklch(97% 0 0);--sidebar-accent-foreground:oklch(20.5% 0 0);--sidebar-border:oklch(92.2% 0 0);--sidebar-ring:oklch(70.8% 0 0)}.dark{--background:oklch(14.5% 0 0);--foreground:oklch(98.5% 0 0);--card:oklch(20.5% 0 0);--card-foreground:oklch(98.5% 0 0);--popover:oklch(20.5% 0 0);--popover-foreground:oklch(98.5% 0 0);--primary:oklch(92.2% 0 0);--primary-foreground:oklch(20.5% 0 0);--secondary:oklch(26.9% 0 0);--secondary-foreground:oklch(98.5% 0 0);--muted:oklch(26.9% 0 0);--muted-foreground:oklch(70.8% 0 0);--accent:oklch(26.9% 0 0);--accent-foreground:oklch(98.5% 0 0);--destructive:oklch(70.4% .191 22.216);--border:oklch(100% 0 0/.1);--input:oklch(100% 0 0/.15);--ring:oklch(55.6% 0 0);--chart-1:oklch(87% 0 0);--chart-2:oklch(55.6% 0 0);--chart-3:oklch(43.9% 0 0);--chart-4:oklch(37.1% 0 0);--chart-5:oklch(26.9% 0 0);--sidebar:oklch(20.5% 0 0);--sidebar-foreground:oklch(98.5% 0 0);--sidebar-primary:oklch(48.8% .243 264.376);--sidebar-primary-foreground:oklch(98.5% 0 0);--sidebar-accent:oklch(26.9% 0 0);--sidebar-accent-foreground:oklch(98.5% 0 0);--sidebar-border:oklch(100% 0 0/.1);--sidebar-ring:oklch(55.6% 0 0)}body{font-family:SF Mono,Fira Code,JetBrains Mono,Cascadia Code,monospace!important}@media (width<=768px){body{font-size:16px}.body{padding:10px 12px;font-size:15px}.role,.think-toggle{font-size:12px}}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:0 0}::-webkit-scrollbar-thumb{background:oklch(40% 0 0);border-radius:3px}::-webkit-scrollbar-thumb:hover{background:oklch(50% 0 0)}*{scrollbar-width:thin;scrollbar-color:oklch(40% 0 0) transparent}html,body,#root{height:100vh}@supports (height:100dvh){html,body,#root{height:100dvh}}.scrollbar-none{-ms-overflow-style:none;scrollbar-width:none}.scrollbar-none::-webkit-scrollbar{display:none}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@keyframes ping{75%,to{opacity:0;transform:scale(2)}}@keyframes pulse{50%{opacity:.5}}@keyframes bounce{0%,to{animation-timing-function:cubic-bezier(.8,0,1,1);transform:translateY(-25%)}50%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:none}}
|