@a-t-h-i/bot-lobby 0.6.45 → 0.6.46

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a-t-h-i/bot-lobby",
3
- "version": "0.6.45",
3
+ "version": "0.6.46",
4
4
  "description": "Structured multi-agent software engineering orchestrator for Pi",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -0,0 +1,27 @@
1
+ import { resolve } from "node:path";
2
+
3
+ const executions = new Map<string, Set<AbortController>>();
4
+ const key = (root: string, configDir: string, taskId: string) => JSON.stringify([resolve(root), configDir, taskId]);
5
+
6
+ /** Scope an orchestration action and its workers/children to one project task. */
7
+ export function taskExecution(root: string, configDir: string, taskId: string, parent?: AbortSignal) {
8
+ const id = key(root, configDir, taskId);
9
+ const controller = new AbortController();
10
+ const running = executions.get(id) ?? new Set<AbortController>();
11
+ running.add(controller);
12
+ executions.set(id, running);
13
+ return {
14
+ signal: parent ? AbortSignal.any([parent, controller.signal]) : controller.signal,
15
+ release: () => {
16
+ running.delete(controller);
17
+ if (!running.size) executions.delete(id);
18
+ },
19
+ };
20
+ }
21
+
22
+ /** Abort only this task's orchestration and the subprocesses inheriting its signal. */
23
+ export function cancelTaskExecutions(root: string, configDir: string, taskId: string): void {
24
+ for (const controller of executions.get(key(root, configDir, taskId)) ?? []) {
25
+ controller.abort(new Error("Task abandoned by the user"));
26
+ }
27
+ }
package/src/lobby/host.ts CHANGED
@@ -173,6 +173,10 @@ export interface LobbyService {
173
173
  * background session. A notice, and the background session's key.
174
174
  */
175
175
  resumeTask?(taskId: string): Promise<{ notice: string; key?: string }>;
176
+ acceptTask?(taskId: string): Promise<{ notice: string }>;
177
+ forceCloseTask?(taskId: string, title: string): Promise<{ notice: string }>;
178
+ canForceCloseTask?(taskId: string): boolean;
179
+ resolveTaskApproval?(taskId: string, approvalId: string, decision: "approved" | "rejected", note?: string): Promise<{ notice: string }>;
176
180
  /** Leave a message for a session running in another terminal; it delivers it within seconds. Returns a notice. */
177
181
  sendToSession(sessionId: string, text: string): string;
178
182
  /** Sessions running in this project, other than this window's, from their heartbeats. */
@@ -4,7 +4,7 @@
4
4
  * `node:*`): the web page imports this file directly.
5
5
  */
6
6
 
7
- export const TAB_IDS = ["lobby", "tasks", "plan", "quickfix", "git", "knowledge", "metrics"] as const;
7
+ export const TAB_IDS = ["lobby", "tasks", "plan", "quickfix", "git", "knowledge"] as const;
8
8
  export type TabId = (typeof TAB_IDS)[number];
9
9
 
10
10
  export const TAB_LABELS: Record<TabId, string> = {
@@ -12,7 +12,6 @@ export const TAB_LABELS: Record<TabId, string> = {
12
12
  tasks: "Tasks",
13
13
  plan: "Plan",
14
14
  quickfix: "Quick fix",
15
- metrics: "Metrics",
16
15
  git: "Git",
17
16
  knowledge: "Knowledge",
18
17
  };
@@ -9,6 +9,8 @@ import { updateBlockingRequest } from "../state/blocking-requests.ts";
9
9
  import { randomUUID } from "node:crypto";
10
10
  import { loadTask, ownedTask, peekOwnedTask, peekTasks, saveTask } from "../state/persistence.ts";
11
11
  import { taskWork } from "../state/work-time.ts";
12
+ import { transition } from "../state/task-state.ts";
13
+ import { cancelTaskExecutions } from "../execution/task-cancellation.ts";
12
14
  import { refreshReview, deferReview } from "../delivery/review.ts";
13
15
  import { deliver, type DeliveryRequest } from "../delivery/operations.ts";
14
16
  import { deliveryStore, deliveryTask } from "../delivery/store.ts";
@@ -18,7 +20,7 @@ import { loadConfig, readDataRoots, saveConfig as writeConfig } from "../state/p
18
20
  import { knowledgeQuestion } from "./knowledge.ts";
19
21
  import { addPlanComment, editComment, readPlanComments, type PlanComment } from "../state/comments.ts";
20
22
  import { isAutoMode } from "../state/auto.ts";
21
- import { sendToInbox, sendToSession as leaveForSession } from "../state/inbox.ts";
23
+ import { markInboxDelivered, sendToInbox, sendToSession as leaveForSession } from "../state/inbox.ts";
22
24
  import { livePresence } from "../state/presence.ts";
23
25
  import { archiveTask as archiveTaskOnDisk, deleteTask as deleteTaskOnDisk, listArchivedTasks, restoreTask as restoreTaskOnDisk } from "../state/archive.ts";
24
26
  import { discardPlannedTask, listPlannedTasks, type PlannedTask } from "../state/backlog.ts";
@@ -50,6 +52,9 @@ import type { LiveSession, LobbyService, SwitchTarget } from "./host.ts";
50
52
  import { pushNotice, type NoticeLevel } from "../webui/notices.ts";
51
53
  import type { Runtime } from "./runtime.ts";
52
54
  import { findLinters } from "../execution/lint.ts";
55
+ import { handleResolveApproval, lastQaAsks, waiveLint, waiveQa } from "../workflow/workflow.ts";
56
+ import { recordDecision } from "../master/decisions.ts";
57
+ import { fail } from "../webui/api/index.ts";
53
58
 
54
59
  /** The state background callbacks report to; set while the lobby service runs. */
55
60
  let serviceState: Runtime | undefined;
@@ -384,6 +389,73 @@ async function resumeTask(state: Runtime, taskId: string): Promise<{ notice: str
384
389
  }
385
390
  }
386
391
 
392
+ function mutableTask(state: Runtime, taskId: string): Task {
393
+ if (!/^[A-Za-z0-9][A-Za-z0-9_.-]{0,199}$/.test(taskId)) fail(400, "bad_request", "invalid task ID");
394
+ const task = loadTask(state.root, state.configDir, taskId);
395
+ if (!task) fail(404, "not_found", "task not found in this project");
396
+ if (TERMINAL_STATES.includes(task.state)) fail(400, "bad_request", "task is terminal");
397
+ return task;
398
+ }
399
+
400
+ async function wakeTask(state: Runtime, task: Task, message: string): Promise<string> {
401
+ const inbox = sendToInbox(state.root, state.configDir, task.id, message, state.ctx.sessionManager.getSessionId());
402
+ const route = resumeRoute(task, taskDrivers(state));
403
+ if (route.kind === "stopped" || route.kind === "paused") return (await resumeTask(state, task.id)).notice;
404
+ if (task.ownerSessionId === state.ctx.sessionManager.getSessionId()) {
405
+ state.pi.sendUserMessage(message, state.ctx.isIdle() ? undefined : { deliverAs: "followUp" });
406
+ markInboxDelivered(state.root, state.configDir, task.id, [inbox.id]);
407
+ } else {
408
+ const session = backgroundSessions().find((entry) => entry.alive && entry.projectRoot === state.root && entry.sessionId === task.ownerSessionId);
409
+ if (session) {
410
+ session.send(message);
411
+ markInboxDelivered(state.root, state.configDir, task.id, [inbox.id]);
412
+ }
413
+ }
414
+ return "the owning oracle has been notified";
415
+ }
416
+
417
+ function canForceCloseTask(state: Runtime, taskId: string): boolean {
418
+ const task = loadTask(state.root, state.configDir, taskId);
419
+ return Boolean(task && !TERMINAL_STATES.includes(task.state) && (!task.ownerSessionId || task.ownerSessionId === state.ctx.sessionManager.getSessionId()));
420
+ }
421
+
422
+ async function forceCloseTask(state: Runtime, taskId: string, title: string): Promise<{ notice: string }> {
423
+ const task = mutableTask(state, taskId);
424
+ if (title !== task.title) fail(400, "bad_request", "the title must match exactly");
425
+ if (!canForceCloseTask(state, taskId)) fail(400, "bad_request", "cancel this task in its owning session; remote cancellation is unavailable");
426
+ recordDecision(task, "The user force-closed the task from the Tasks screen; work abandoned, code and worktree retained.");
427
+ transition(task, "abandoned");
428
+ saveTask(state.root, state.configDir, task);
429
+ cancelTaskExecutions(state.root, state.configDir, task.id);
430
+ if (task.ownerSessionId === state.ctx.sessionManager.getSessionId()) state.ctx.abort();
431
+ lobbyTopics.bump("tasks");
432
+ return { notice: `${task.id} abandoned; code, worktree and history retained` };
433
+ }
434
+
435
+ async function acceptTask(state: Runtime, taskId: string): Promise<{ notice: string }> {
436
+ const task = mutableTask(state, taskId);
437
+ if (!["implementing", "reviewing", "blocked"].includes(task.state)) fail(400, "bad_request", "there is no work to accept yet");
438
+ const asks = lastQaAsks(task);
439
+ if (task.qaVerdict !== "pass" && !task.qaWaiver) waiveQa(task, asks, "from the Tasks screen");
440
+ if (task.lint?.state === "failing") waiveLint(task, "from the Tasks screen");
441
+ recordDecision(task, "The user accepted the work as it is from the Tasks screen; the oracle was asked to complete it.");
442
+ saveTask(state.root, state.configDir, task);
443
+ lobbyTopics.bump("tasks");
444
+ const wake = await wakeTask(state, task, `The user accepted ${task.id}'s work as it is. Call orchestrate action=complete now with a short summary naming outstanding QA/lint asks: ${asks.join("; ") || "none recorded"}. Read the persisted gate waivers first. Do not merge automatically.`);
445
+ return { notice: `${task.id} accepted as it is; ${wake}` };
446
+ }
447
+
448
+ async function resolveTaskApproval(state: Runtime, taskId: string, approvalId: string, decision: "approved" | "rejected", note?: string): Promise<{ notice: string }> {
449
+ const task = mutableTask(state, taskId);
450
+ if (!task.approvals.some((entry) => entry.id === approvalId && entry.status === "pending")) fail(409, "conflict", "approval is no longer pending");
451
+ let message: string;
452
+ try { message = handleResolveApproval(task, { action: "resolve_approval", approvalId, decision, note }); }
453
+ catch (error) { return fail(400, "bad_request", (error as Error).message); }
454
+ saveTask(state.root, state.configDir, task);
455
+ lobbyTopics.bump("tasks");
456
+ return { notice: `${message}; ${await wakeTask(state, task, message)}` };
457
+ }
458
+
387
459
  /** Leave a message for a task another terminal's session drives; its owner passes it to the oracle. */
388
460
  function sendToTask(state: Runtime, taskId: string, text: string): string {
389
461
  const task = loadTask(state.root, state.configDir, taskId);
@@ -826,6 +898,10 @@ export function createLobbyService(state: Runtime): LobbyService {
826
898
  setAuto: (taskId, on) => switchAuto(state, taskId, on),
827
899
  sendToTask: (taskId, text) => sendToTask(state, taskId, text),
828
900
  resumeTask: (taskId) => resumeTask(state, taskId),
901
+ acceptTask: (taskId) => acceptTask(state, taskId),
902
+ forceCloseTask: (taskId, title) => forceCloseTask(state, taskId, title),
903
+ canForceCloseTask: (taskId) => canForceCloseTask(state, taskId),
904
+ resolveTaskApproval: (taskId, approvalId, decision, note) => resolveTaskApproval(state, taskId, approvalId, decision, note),
829
905
  sendToSession: (sessionId, text) => sendToSession(state, sessionId, text),
830
906
  liveSessions: () => liveSessions(state),
831
907
  switchTo: (target) => switchTo(state, target),
@@ -89,13 +89,38 @@ export function readTaskArtifact(
89
89
 
90
90
  export function saveTask(root: string, configDir: string, task: Task): void {
91
91
  const latest = loadTask(root, configDir, task.id);
92
- if (latest) mergePhaseTiming(task, timingBaselines.get(task), latest);
92
+ // Abandonment is final: an in-flight action must not overwrite its audit or resurrect it.
93
+ if (latest?.state === "abandoned") {
94
+ Object.assign(task, latest);
95
+ return;
96
+ }
97
+ if (latest) {
98
+ preserveUserInterventions(task, latest);
99
+ mergePhaseTiming(task, timingBaselines.get(task), latest);
100
+ }
93
101
  const path = join(taskDir(dataRoot(root, configDir), task.id), "state.json");
94
102
  forgetCached(path);
95
103
  writeFileEnsured(path, JSON.stringify(task, null, 2));
96
104
  rememberTiming(task);
97
105
  }
98
106
 
107
+ /** Explicit user waivers and resolved approvals must survive an in-flight worker's snapshot. */
108
+ function preserveUserInterventions(task: Task, latest: Task): void {
109
+ const newlyWaived = !task.qaWaiver && latest.qaWaiver;
110
+ task.qaWaiver ??= latest.qaWaiver;
111
+ task.lintWaiver ??= latest.lintWaiver;
112
+ if (newlyWaived && !TERMINAL_STATES.includes(task.state)) {
113
+ task.blockers = [];
114
+ if (["implementing", "blocked"].includes(task.state)) task.state = "reviewing";
115
+ }
116
+ for (const approval of task.approvals) {
117
+ const resolved = latest.approvals.find((entry) => entry.id === approval.id && entry.status !== "pending");
118
+ if (approval.status === "pending" && resolved) Object.assign(approval, resolved);
119
+ }
120
+ const keys = new Set(task.decisions.map((entry) => JSON.stringify(entry)));
121
+ task.decisions.push(...latest.decisions.filter((entry) => !keys.has(JSON.stringify(entry))));
122
+ }
123
+
99
124
  /** Read a task state: the bot-lobby copy wins, else the newest pre-rename copy. */
100
125
  export function loadTask(root: string, configDir: string, taskId: string): Task | undefined {
101
126
  for (const dr of readDataRoots(root, configDir)) {
@@ -15,8 +15,7 @@ import { lobbyAbort, lobbyHistory, lobbySend, lobbySnapshot } from "./lobby.ts";
15
15
  import { promptsAnswer, promptsDismiss, promptsList } from "./prompts.ts";
16
16
  import { plannerAnswer, plannerCommentLine, plannerGet, plannerNew, plannerRetry, plannerSend, plannerEditMessage, plannerToggleSeat, plannerSave, plannerPrevious, plannerPreviousArchive, plannerPreviousDelete, plannerPreviousGet, plannerPreviousOpen } from "./planner.ts";
17
17
  import { quickfixCancel, quickfixList, quickfixMovedToTask, quickfixRunAnyway, quickfixSubmit } from "./quickfix.ts";
18
- import { tasksArchive, tasksArchived, tasksAuto, tasksComment, tasksComments, tasksDelete, tasksEditComment, tasksGet, tasksList, tasksMessage, tasksRestore, tasksOpen, tasksResume, tasksDeliveryReview, tasksDeliveryDefer, tasksDeliver } from "./tasks.ts";
19
- import { metricsGet } from "./metrics.ts";
18
+ import { tasksArchive, tasksArchived, tasksAuto, tasksComment, tasksComments, tasksDelete, tasksEditComment, tasksGet, tasksList, tasksMessage, tasksRestore, tasksOpen, tasksResume, tasksDeliveryReview, tasksDeliveryDefer, tasksDeliver, tasksAccept, tasksForceClose, tasksResolveApproval } from "./tasks.ts";
20
19
  import { knowledgeAdd, knowledgeAsk, knowledgeComment, knowledgeEdit, knowledgeFiles, knowledgeOpen, knowledgeRemove, knowledgeReplaceFile, knowledgeUnnote } from "./knowledge.ts";
21
20
  import { plansDiscard, plansGet, plansStart } from "./plans.ts";
22
21
  import { gitCancelReview, gitJev, gitPull, gitPulls, gitReview } from "./git.ts";
@@ -131,6 +130,18 @@ function buildRoutes(): Record<string, Route> {
131
130
  schema: Type.Object({ taskId: TaskId }, { additionalProperties: false }),
132
131
  run: (body, ctx) => tasksResume(body as { taskId: string }, ctx),
133
132
  };
133
+ routes["tasks.accept"] = {
134
+ schema: Type.Object({ taskId: TaskId }, { additionalProperties: false }),
135
+ run: (body, ctx) => tasksAccept(body as { taskId: string }, ctx),
136
+ };
137
+ routes["tasks.forceClose"] = {
138
+ schema: Type.Object({ taskId: TaskId, title: Type.String({ maxLength: 20_000 }) }, { additionalProperties: false }),
139
+ run: (body, ctx) => tasksForceClose(body as { taskId: string; title: string }, ctx),
140
+ };
141
+ routes["tasks.resolveApproval"] = {
142
+ schema: Type.Object({ taskId: TaskId, approvalId: TaskId, decision: Type.Union([Type.Literal("approved"), Type.Literal("rejected")]), note: Type.Optional(NoticeText) }, { additionalProperties: false }),
143
+ run: (body, ctx) => tasksResolveApproval(body as Parameters<typeof tasksResolveApproval>[0], ctx),
144
+ };
134
145
  routes["tasks.deliveryReview"] = {
135
146
  schema: Type.Object({ taskId: TaskId }, { additionalProperties: false }),
136
147
  run: (body, ctx) => tasksDeliveryReview(body as { taskId: string }, ctx),
@@ -268,10 +279,6 @@ function buildRoutes(): Record<string, Route> {
268
279
  schema: Type.Object({ id: TaskId }, { additionalProperties: false }),
269
280
  run: (body, ctx) => quickfixMovedToTask(body as { id: string }, ctx),
270
281
  };
271
- routes["metrics.get"] = {
272
- schema: Type.Object({ groupBy: Type.Union([Type.Literal("model"), Type.Literal("model-kind")]), query: Type.Optional(Type.String({ maxLength: 500 })), from: Type.Optional(Type.String({ pattern: "^\\d{4}-\\d{2}-\\d{2}$" })), to: Type.Optional(Type.String({ pattern: "^\\d{4}-\\d{2}-\\d{2}$" })), timeZone: Type.Optional(Type.String({ maxLength: 100 })) }, { additionalProperties: false }),
273
- run: (body, ctx) => metricsGet(body as Parameters<typeof metricsGet>[0], ctx),
274
- };
275
282
  routes["knowledge.files"] = { schema: Empty, run: (_body, ctx) => knowledgeFiles(ctx) };
276
283
  routes["knowledge.ask"] = {
277
284
  schema: Type.Object({ text: NoticeText, attachments: Attachments }, { additionalProperties: false }),
@@ -282,17 +282,52 @@ export function tasksOpen(body: { taskId: string }, ctx: ApiContext): { sessionI
282
282
  return { notice: "The task's owning session has ended or is unavailable. No replacement session was created." };
283
283
  }
284
284
 
285
+ /** Interventions only apply to an active task in this project, never an archive. */
286
+ function interventionTask(taskId: string, ctx: ApiContext): Task {
287
+ if (!/^[A-Za-z0-9][A-Za-z0-9_.-]{0,199}$/.test(taskId)) fail(400, "bad_request", "invalid task ID");
288
+ const task = ctx.service.tasks().find((entry) => entry.id === taskId);
289
+ if (!task) fail(404, "not_found", "task not found in this project");
290
+ if (TERMINAL_STATES.includes(task.state)) fail(400, "bad_request", "this task is already finished");
291
+ return task;
292
+ }
293
+
294
+ function acceptable(task: Task): boolean {
295
+ return ["implementing", "reviewing", "blocked"].includes(task.state);
296
+ }
297
+
298
+ export async function tasksAccept(body: { taskId: string }, ctx: ApiContext): Promise<{ notice: string }> {
299
+ const task = interventionTask(body.taskId, ctx);
300
+ if (!acceptable(task)) fail(400, "bad_request", "only implementing, reviewing or blocked tasks can be accepted");
301
+ if (!ctx.service.acceptTask) fail(400, "bad_request", "accepting tasks is unavailable");
302
+ return ctx.service.acceptTask(body.taskId);
303
+ }
304
+
305
+ export async function tasksForceClose(body: { taskId: string; title: string }, ctx: ApiContext): Promise<{ notice: string }> {
306
+ const task = interventionTask(body.taskId, ctx);
307
+ if (body.title !== task.title) fail(400, "bad_request", "the title must match exactly");
308
+ if (!ctx.service.forceCloseTask) fail(400, "bad_request", "force closing tasks is unavailable");
309
+ return ctx.service.forceCloseTask(body.taskId, body.title);
310
+ }
311
+
312
+ export async function tasksResolveApproval(body: { taskId: string; approvalId: string; decision: "approved" | "rejected"; note?: string }, ctx: ApiContext): Promise<{ notice: string }> {
313
+ const task = interventionTask(body.taskId, ctx);
314
+ if (!pendingApprovals(task).some((approval) => approval.id === body.approvalId)) fail(409, "conflict", "approval is no longer pending; refresh the task");
315
+ if (!ctx.service.resolveTaskApproval) fail(400, "bad_request", "resolving task approvals is unavailable");
316
+ return ctx.service.resolveTaskApproval(body.taskId, body.approvalId, body.decision, body.note);
317
+ }
318
+
285
319
  /** How many recent runs the detail lists (the terminal's count). */
286
320
  const DETAIL_RUNS = 6;
287
321
 
288
322
  /** The terminal's "Waiting on" approvals: `kind` plus `for <domain>: <detail>`. */
289
323
  function waitingOf(task: Task): TaskDetail["waiting"] {
290
- return pendingApprovals(task).map((approval) => ({ kind: approval.kind, detail: `for ${approval.domain}: ${approval.detail}` }));
324
+ return pendingApprovals(task).map((approval) => ({ id: approval.id, domain: approval.domain, kind: approval.kind, detail: `for ${approval.domain}: ${approval.detail}` }));
291
325
  }
292
326
 
293
327
  /** One task read whole: request, plan or proposal, step checklist, waits and recent runs. */
294
328
  export function tasksGet(body: { taskId: string }, ctx: ApiContext): TaskDetail {
295
- const task = [...ctx.service.tasks(), ...ctx.service.archivedTasks()].find((entry) => entry.id === body.taskId);
329
+ const active = ctx.service.tasks().find((entry) => entry.id === body.taskId);
330
+ const task = active ?? ctx.service.archivedTasks().find((entry) => entry.id === body.taskId);
296
331
  if (!task) fail(404, "not_found", `no task ${body.taskId}`);
297
332
  const found = task as Task;
298
333
  const request = taskRequest(found);
@@ -301,6 +336,8 @@ export function tasksGet(body: { taskId: string }, ctx: ApiContext): TaskDetail
301
336
  const clock = clockOf(found, work);
302
337
  const details = found.plan ? planDetails(found.plan) : undefined;
303
338
  return {
339
+ canAccept: Boolean(active && ctx.service.acceptTask) && acceptable(found),
340
+ canForceClose: Boolean(active && ctx.service.forceCloseTask) && !TERMINAL_STATES.includes(found.state) && (ctx.service.canForceCloseTask?.(found.id) ?? true),
304
341
  timing: projectPhaseTiming(found, new Date(now).toISOString()),
305
342
  ...(clock ? { work: clock } : {}),
306
343
  ...(found.delivery ? { delivery: found.delivery } : {}),
@@ -174,10 +174,11 @@ export const DEFAULT_ID = "mist"
174
174
 
175
175
  /**
176
176
  * The themes that come with the page. The first is the page's own colours and
177
- * sets nothing; the rest are T3 Code's built-in themes (MIT, Copyright (c) 2026
177
+ * sets nothing; Blossom through Iris are T3 Code's built-in themes (MIT, Copyright (c) 2026
178
178
  * T3 Tools Inc., packages/shared/src/themePalettes.ts), their roles mapped onto
179
179
  * the shadcn variables: canvas is the background, surface the card, accent the
180
- * primary, accent surface the accent, error the destructive colour.
180
+ * primary, accent surface the accent, error the destructive colour. Slate,
181
+ * Sand, Rosewood, Aurora and Midnight are original complementary palettes.
181
182
  */
182
183
  export const PRESETS: readonly Palette[] = [
183
184
  { id: DEFAULT_ID, name: "Default" },
@@ -411,6 +412,236 @@ export const PRESETS: readonly Palette[] = [
411
412
  "chart-1": "oklch(0.671712 0.169136 293.929)",
412
413
  },
413
414
  },
415
+ {
416
+ id: "slate",
417
+ name: "Slate",
418
+ light: {
419
+ "background": "#f5f7fa",
420
+ "foreground": "#202b3b",
421
+ "card": "#ffffff",
422
+ "card-foreground": "#202b3b",
423
+ "popover": "#ffffff",
424
+ "popover-foreground": "#202b3b",
425
+ "primary": "#465b78",
426
+ "primary-foreground": "#ffffff",
427
+ "secondary": "#e7ecf2",
428
+ "secondary-foreground": "#202b3b",
429
+ "muted": "#edf1f6",
430
+ "muted-foreground": "#526177",
431
+ "accent": "#e0e7ef",
432
+ "accent-foreground": "#202b3b",
433
+ "destructive": "#a52a34",
434
+ "border": "#cad3df",
435
+ "input": "#b9c5d4",
436
+ "ring": "#465b78",
437
+ "chart-1": "#465b78",
438
+ },
439
+ dark: {
440
+ "background": "#141b26",
441
+ "foreground": "#e8edf5",
442
+ "card": "#1c2533",
443
+ "card-foreground": "#e8edf5",
444
+ "popover": "#202b3b",
445
+ "popover-foreground": "#e8edf5",
446
+ "primary": "#adc0dc",
447
+ "primary-foreground": "#141b26",
448
+ "secondary": "#2a3648",
449
+ "secondary-foreground": "#e8edf5",
450
+ "muted": "#243043",
451
+ "muted-foreground": "#afbdd0",
452
+ "accent": "#33445d",
453
+ "accent-foreground": "#e8edf5",
454
+ "destructive": "#fb8990",
455
+ "border": "#43536a",
456
+ "input": "#53647d",
457
+ "ring": "#adc0dc",
458
+ "chart-1": "#adc0dc",
459
+ },
460
+ },
461
+ {
462
+ id: "sand",
463
+ name: "Sand",
464
+ light: {
465
+ "background": "#faf7f1",
466
+ "foreground": "#352b22",
467
+ "card": "#fffdf8",
468
+ "card-foreground": "#352b22",
469
+ "popover": "#fffdf8",
470
+ "popover-foreground": "#352b22",
471
+ "primary": "#765536",
472
+ "primary-foreground": "#ffffff",
473
+ "secondary": "#eee6da",
474
+ "secondary-foreground": "#352b22",
475
+ "muted": "#f2ece2",
476
+ "muted-foreground": "#6c5b49",
477
+ "accent": "#e9ddcb",
478
+ "accent-foreground": "#352b22",
479
+ "destructive": "#a52a34",
480
+ "border": "#d9cbb8",
481
+ "input": "#c9b79e",
482
+ "ring": "#765536",
483
+ "chart-1": "#765536",
484
+ },
485
+ dark: {
486
+ "background": "#211c17",
487
+ "foreground": "#f4ece0",
488
+ "card": "#2c251e",
489
+ "card-foreground": "#f4ece0",
490
+ "popover": "#352c23",
491
+ "popover-foreground": "#f4ece0",
492
+ "primary": "#dcbc92",
493
+ "primary-foreground": "#211c17",
494
+ "secondary": "#3c3227",
495
+ "secondary-foreground": "#f4ece0",
496
+ "muted": "#352d24",
497
+ "muted-foreground": "#c9b9a5",
498
+ "accent": "#4a3b2b",
499
+ "accent-foreground": "#f4ece0",
500
+ "destructive": "#fb8990",
501
+ "border": "#64503e",
502
+ "input": "#79614b",
503
+ "ring": "#dcbc92",
504
+ "chart-1": "#dcbc92",
505
+ },
506
+ },
507
+ {
508
+ id: "rosewood",
509
+ name: "Rosewood",
510
+ light: {
511
+ "background": "#fbf6f5",
512
+ "foreground": "#3b262c",
513
+ "card": "#fffafa",
514
+ "card-foreground": "#3b262c",
515
+ "popover": "#fffafa",
516
+ "popover-foreground": "#3b262c",
517
+ "primary": "#854252",
518
+ "primary-foreground": "#ffffff",
519
+ "secondary": "#f0e2e5",
520
+ "secondary-foreground": "#3b262c",
521
+ "muted": "#f5eaec",
522
+ "muted-foreground": "#76525d",
523
+ "accent": "#edd6dd",
524
+ "accent-foreground": "#3b262c",
525
+ "destructive": "#a52a34",
526
+ "border": "#dfc5cc",
527
+ "input": "#d0afb9",
528
+ "ring": "#854252",
529
+ "chart-1": "#854252",
530
+ },
531
+ dark: {
532
+ "background": "#241a20",
533
+ "foreground": "#f6e8ed",
534
+ "card": "#30222a",
535
+ "card-foreground": "#f6e8ed",
536
+ "popover": "#3a2932",
537
+ "popover-foreground": "#f6e8ed",
538
+ "primary": "#e3a5b7",
539
+ "primary-foreground": "#241a20",
540
+ "secondary": "#442e38",
541
+ "secondary-foreground": "#f6e8ed",
542
+ "muted": "#3a2932",
543
+ "muted-foreground": "#d2b4c0",
544
+ "accent": "#553441",
545
+ "accent-foreground": "#f6e8ed",
546
+ "destructive": "#fb8990",
547
+ "border": "#70505d",
548
+ "input": "#856170",
549
+ "ring": "#e3a5b7",
550
+ "chart-1": "#e3a5b7",
551
+ },
552
+ },
553
+ {
554
+ id: "aurora",
555
+ name: "Aurora",
556
+ light: {
557
+ "background": "#f2faf8",
558
+ "foreground": "#193a37",
559
+ "card": "#fafffe",
560
+ "card-foreground": "#193a37",
561
+ "popover": "#fafffe",
562
+ "popover-foreground": "#193a37",
563
+ "primary": "#216c63",
564
+ "primary-foreground": "#ffffff",
565
+ "secondary": "#def0eb",
566
+ "secondary-foreground": "#193a37",
567
+ "muted": "#e6f2ef",
568
+ "muted-foreground": "#456c66",
569
+ "accent": "#eee5f6",
570
+ "accent-foreground": "#193a37",
571
+ "destructive": "#a52a34",
572
+ "border": "#bbd7d1",
573
+ "input": "#a7c8c0",
574
+ "ring": "#216c63",
575
+ "chart-1": "#216c63",
576
+ },
577
+ dark: {
578
+ "background": "#142321",
579
+ "foreground": "#e1f5ef",
580
+ "card": "#1d302d",
581
+ "card-foreground": "#e1f5ef",
582
+ "popover": "#253d38",
583
+ "popover-foreground": "#e1f5ef",
584
+ "primary": "#87d5c1",
585
+ "primary-foreground": "#142321",
586
+ "secondary": "#29443e",
587
+ "secondary-foreground": "#e1f5ef",
588
+ "muted": "#243c36",
589
+ "muted-foreground": "#a9cfc3",
590
+ "accent": "#3d3352",
591
+ "accent-foreground": "#e1f5ef",
592
+ "destructive": "#fb8990",
593
+ "border": "#466b60",
594
+ "input": "#568172",
595
+ "ring": "#87d5c1",
596
+ "chart-1": "#87d5c1",
597
+ },
598
+ },
599
+ {
600
+ id: "midnight",
601
+ name: "Midnight",
602
+ light: {
603
+ "background": "#f5f6fb",
604
+ "foreground": "#282e48",
605
+ "card": "#fcfcff",
606
+ "card-foreground": "#282e48",
607
+ "popover": "#fcfcff",
608
+ "popover-foreground": "#282e48",
609
+ "primary": "#4f5f96",
610
+ "primary-foreground": "#ffffff",
611
+ "secondary": "#e5e8f4",
612
+ "secondary-foreground": "#282e48",
613
+ "muted": "#eceef7",
614
+ "muted-foreground": "#565f7d",
615
+ "accent": "#dfe4f6",
616
+ "accent-foreground": "#282e48",
617
+ "destructive": "#a52a34",
618
+ "border": "#c7cee5",
619
+ "input": "#b4bfdc",
620
+ "ring": "#4f5f96",
621
+ "chart-1": "#4f5f96",
622
+ },
623
+ dark: {
624
+ "background": "#151a2b",
625
+ "foreground": "#e8ecfa",
626
+ "card": "#1f263b",
627
+ "card-foreground": "#e8ecfa",
628
+ "popover": "#29314a",
629
+ "popover-foreground": "#e8ecfa",
630
+ "primary": "#acbbed",
631
+ "primary-foreground": "#151a2b",
632
+ "secondary": "#303954",
633
+ "secondary-foreground": "#e8ecfa",
634
+ "muted": "#28314b",
635
+ "muted-foreground": "#b1bedf",
636
+ "accent": "#3a466a",
637
+ "accent-foreground": "#e8ecfa",
638
+ "destructive": "#fb8990",
639
+ "border": "#48577d",
640
+ "input": "#596990",
641
+ "ring": "#acbbed",
642
+ "chart-1": "#acbbed",
643
+ },
644
+ },
414
645
  ]
415
646
 
416
647
  /** Colours for a preview swatch, per mode: the default's own values by hand, the rest from the theme. */