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

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.
@@ -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. */
@@ -276,78 +276,6 @@ export interface PlannerSeedInfo {
276
276
  body: string;
277
277
  }
278
278
 
279
- /** The Metrics tab's KPI row as data: runs, success, average run, cost, tasks. */
280
- export interface MetricsTiles {
281
- runs: number;
282
- successes: number;
283
- stalls: number;
284
- avgMs: number;
285
- p90Ms: number;
286
- cost: number;
287
- completed: number;
288
- active: number;
289
- avgCompleteMs: number;
290
- }
291
-
292
- /** One per-model group as `metrics.get` carries it. */
293
- export interface MetricGroupInfo {
294
- model: string;
295
- thinking: string;
296
- kinds: string[];
297
- runs: number;
298
- successes: number;
299
- timeouts: number;
300
- avgMs: number;
301
- p50Ms: number;
302
- p90Ms: number;
303
- avgTurns: number;
304
- avgTools: number;
305
- avgTokens: number;
306
- avgCost: number;
307
- totalCost: number;
308
- tokensPerSecond: number;
309
- }
310
-
311
- /** One agent's share of all run time. */
312
- export interface MetricsAgentShare {
313
- agent: string;
314
- runs: number;
315
- ms: number;
316
- share: number;
317
- }
318
-
319
- /** Completed tasks' request-to-done time by the oracle's model and thinking. */
320
- export interface MetricsTaskTime {
321
- model: string;
322
- thinking: string;
323
- tasks: number;
324
- avgMs: number;
325
- }
326
-
327
- /** What the classifier did and spared, when it has run. */
328
- export interface MetricsClassifier {
329
- calls: number;
330
- ok: number;
331
- p50Ms: number;
332
- p90Ms: number;
333
- input: number;
334
- byPurpose: Record<string, number>;
335
- seatRunsSkipped: number;
336
- questionsAnswered: number;
337
- quickFixesHeld: number;
338
- routed: number;
339
- routedOk: number;
340
- }
341
-
342
- /** `metrics.get`: the dashboard figures for one grouping and search. */
343
- export interface MetricsData {
344
- tiles: MetricsTiles;
345
- groups: MetricGroupInfo[];
346
- daily: Array<{ date: string; runs: number; successes: number; cost: number }>;
347
- timeShare: { byAgent: MetricsAgentShare[]; taskTimes: MetricsTaskTime[] };
348
- classifier?: MetricsClassifier;
349
- }
350
-
351
279
  /** Who a knowledge file belongs to. */
352
280
  export type KnowledgeAgentName = "master" | "designer" | "backend" | "qa";
353
281
 
@@ -395,6 +323,8 @@ export interface EntryRefInfo {
395
323
 
396
324
  /** `tasks.get`: one task read whole, in the terminal's wording. */
397
325
  export interface TaskDetail {
326
+ canAccept: boolean;
327
+ canForceClose: boolean;
398
328
  timing?: TimingProjection;
399
329
  delivery?: Delivery;
400
330
  /** The full request, when it says more than the title. */
@@ -414,7 +344,7 @@ export interface TaskDetail {
414
344
  work?: WorkClock;
415
345
  amendments: string[];
416
346
  /** Approvals waiting on you: `kind` plus `for <domain>: <detail>`. */
417
- waiting: Array<{ kind: string; detail: string }>;
347
+ waiting: Array<{ id: string; domain: string; kind: string; detail: string }>;
418
348
  blockers: Array<{ reason: string; need: string }>;
419
349
  /** The last six runs, one line each (`✓ DEV worker · 3m 12s · …`). */
420
350
  runs: string[];
@@ -589,6 +519,9 @@ export interface Api {
589
519
  "tasks.get": { request: { taskId: string }; result: TaskDetail };
590
520
  "tasks.open": { request: { taskId: string }; result: { sessionId?: string; key?: string; notice?: string } };
591
521
  "tasks.resume": { request: { taskId: string }; result: { notice: string; key?: string } };
522
+ "tasks.accept": { request: { taskId: string }; result: { notice: string } };
523
+ "tasks.forceClose": { request: { taskId: string; title: string }; result: { notice: string } };
524
+ "tasks.resolveApproval": { request: { taskId: string; approvalId: string; decision: "approved" | "rejected"; note?: string }; result: { notice: string } };
592
525
  "tasks.deliveryReview": { request: { taskId: string }; result: { delivery: Delivery } };
593
526
  "tasks.deliveryDefer": { request: { taskId: string; reviewId: string }; result: { delivery: Delivery } };
594
527
  "tasks.deliver": { request: { taskId: string; reviewId: string; action: DeliveryAction; confirmMain?: boolean }; result: { delivery: Delivery } };
@@ -624,7 +557,6 @@ export interface Api {
624
557
  "quickfix.cancel": { request: { id: string }; result: { notice: string } };
625
558
  "quickfix.runAnyway": { request: { id: string }; result: { notice: string } };
626
559
  "quickfix.movedToTask": { request: { id: string }; result: { notice: string; key?: string } };
627
- "metrics.get": { request: { groupBy: "model" | "model-kind"; query?: string; from?: string; to?: string; timeZone?: string }; result: MetricsData };
628
560
  "projects.browse": { request: { path?: string }; result: FolderListing };
629
561
  "projects.open": { request: { path: string }; result: { project: ProjectInfo } };
630
562
  "knowledge.files": { request: Record<string, never>; result: { files: KnowledgeFileInfo[]; models?: Partial<Record<KnowledgeAgentName, string>> } };
@@ -28,6 +28,7 @@ import { spawnPiProcess, type ProcessRunner, type RelayAsk } from "../execution/
28
28
  import { previewDir } from "../ask/relay.ts";
29
29
  import type { AskQuestion, AskResult } from "../ask/types.ts";
30
30
  import { mapConcurrent } from "../execution/agent-runner.ts";
31
+ import { taskExecution } from "../execution/task-cancellation.ts";
31
32
  import { parseWorkerResult } from "../roles/worker.ts";
32
33
  import { autoNote, DESK_TOOLS, DeskSession } from "../desk/session.ts";
33
34
  import type { Handover } from "../desk/desk.ts";
@@ -1171,7 +1172,7 @@ function handoverLog(handovers: readonly Handover[]): string {
1171
1172
  return `File handovers:\n${lines.join("\n")}`;
1172
1173
  }
1173
1174
 
1174
- function handleResolveApproval(task: Task, params: OrchestrateParams): string {
1175
+ export function handleResolveApproval(task: Task, params: OrchestrateParams): string {
1175
1176
  const id = params.approvalId?.trim();
1176
1177
  const decision = params.decision;
1177
1178
  if (!id || (decision !== "approved" && decision !== "rejected")) {
@@ -1288,7 +1289,9 @@ function qaReport(header: string, result: ReviewResult, decision: LoopDecision,
1288
1289
  : decision === "waived"
1289
1290
  ? "The user accepted the work as it is, without a QA pass. Call action=complete now with a short summary that names what QA still asked for."
1290
1291
  : decision === "iterate"
1291
- ? "The QA gate did not pass: delegate the required changes to the owning domain, then re-run action=qa."
1292
+ ? result.unread
1293
+ ? "QA returned no readable verdict, so nothing is known about the work yet: re-run action=qa; there is nothing to fix first."
1294
+ : "The QA gate did not pass: delegate the required changes to the owning domain, then re-run action=qa."
1292
1295
  : "The review limit is reached and the user did not accept the work: mark the task blocked and tell the user what QA still asks for. They can accept it with /bot-lobby accept.",
1293
1296
  result.pushback ? pushbackLine(result.pushback, "qa") : "",
1294
1297
  issues.length > 0 ? `Issues: ${issues.join("; ")}` : "",
@@ -1557,7 +1560,9 @@ async function handleComplete(task: Task, params: OrchestrateParams, deps: Workf
1557
1560
  // The gate refuses before any side effect, so a refusal leaves the task untouched.
1558
1561
  const oversized = overThreshold(readDataRoots(deps.root, deps.configDir), deps.config.knowledge.compactionThreshold);
1559
1562
  if (oversized.length > 0) throw new Error(`cannot complete: knowledge files over the compaction threshold: ${oversized.map((entry) => `${entry.agent}/${entry.file} (${entry.chars})`).join(", ")}. First record domain-relevant facts where they belong with action=knowledge (domain=designer|backend|qa), then rewrite each oversized file with action=compact, then call complete again.`);
1563
+ deps.signal?.throwIfAborted();
1560
1564
  const committed = await commitTaskWork(task, params, deps);
1565
+ deps.signal?.throwIfAborted();
1561
1566
  const summary = params.text?.trim() || task.proposal || task.title;
1562
1567
  recordCompletion(deps, task, summary);
1563
1568
  removeTaskScratchpads(deps.root, deps.configDir, task.id);
@@ -1598,6 +1603,7 @@ async function commitTaskWork(task: Task, params: OrchestrateParams, deps: Workf
1598
1603
  }
1599
1604
  let result: Awaited<ReturnType<typeof commitFiles>>;
1600
1605
  try {
1606
+ deps.signal?.throwIfAborted();
1601
1607
  result = await commitFiles(state.top, dirty, message);
1602
1608
  } catch (error) {
1603
1609
  throw new Error(`cannot complete: git refused the commit: ${(error as Error).message}`);
@@ -2098,14 +2104,19 @@ export async function runWorkflowAction(params: OrchestrateParams, deps: Workflo
2098
2104
  }
2099
2105
  const gone = missingWorktree(task);
2100
2106
  if (gone && params.action !== "status") return { ok: false, taskId: task.id, state: task.state, message: `Rejected: ${gone}` };
2107
+ const execution = taskExecution(deps.root, deps.configDir, task.id, deps.signal);
2101
2108
  const finished = new Map<string, AgentRun>();
2102
2109
  const identity = { root: deps.root, configDir: deps.configDir, taskId: task.id, sessionId: task.ownerSessionId };
2103
- const wait = <T>(run: () => Promise<T>) => {
2110
+ const wait = async <T>(run: () => Promise<T>) => {
2111
+ execution.signal.throwIfAborted();
2104
2112
  saveTask(deps.root, deps.configDir, task);
2105
- return withBlockingRequest(identity, run);
2113
+ const result = await withBlockingRequest(identity, run);
2114
+ execution.signal.throwIfAborted();
2115
+ return result;
2106
2116
  };
2107
2117
  const tracked: WorkflowDeps = {
2108
2118
  ...deps,
2119
+ signal: execution.signal,
2109
2120
  choose: (...args) => wait(() => deps.choose(...args)),
2110
2121
  ask: (...args) => wait(() => deps.ask(...args)),
2111
2122
  ...(deps.askQuestions ? { askQuestions: (...args: Parameters<NonNullable<WorkflowDeps["askQuestions"]>>) => wait(() => deps.askQuestions!(...args)) } : {}),
@@ -2113,6 +2124,7 @@ export async function runWorkflowAction(params: OrchestrateParams, deps: Workflo
2113
2124
  cwd: taskCwd(task, deps.cwd),
2114
2125
  // An agent can work for an hour: what the action has done so far (implementing, reviewing) is saved before it starts, so every window reads it.
2115
2126
  runProcess: (args, options) => {
2127
+ execution.signal.throwIfAborted();
2116
2128
  saveTask(deps.root, deps.configDir, task);
2117
2129
  return (deps.runProcess ?? spawnPiProcess)(args, options);
2118
2130
  },
@@ -2134,6 +2146,7 @@ export async function runWorkflowAction(params: OrchestrateParams, deps: Workflo
2134
2146
  const working = new Set<string>();
2135
2147
  try {
2136
2148
  const message = await handler(task, params, tracked);
2149
+ execution.signal.throwIfAborted();
2137
2150
  const runs = recordRunLog(task, finished, deps);
2138
2151
  saveTask(deps.root, deps.configDir, task);
2139
2152
  return { ok: true, taskId: task.id, state: task.state, message: `${message}${runsFooter(runs)}${budgetFooter(task, deps)}`, runs };
@@ -2142,6 +2155,7 @@ export async function runWorkflowAction(params: OrchestrateParams, deps: Workflo
2142
2155
  saveTask(deps.root, deps.configDir, task);
2143
2156
  return { ok: false, taskId: task.id, state: task.state, message: `Rejected: ${(error as Error).message}${budgetFooter(task, deps)}`, runs };
2144
2157
  } finally {
2158
+ execution.release();
2145
2159
  for (const runId of working) noteWorkerRun(deps.root, deps.configDir, task.id, { runId, startedAt: "", status: "cancelled" });
2146
2160
  }
2147
2161
  }