@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.
@@ -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")) {
@@ -1557,7 +1558,9 @@ async function handleComplete(task: Task, params: OrchestrateParams, deps: Workf
1557
1558
  // The gate refuses before any side effect, so a refusal leaves the task untouched.
1558
1559
  const oversized = overThreshold(readDataRoots(deps.root, deps.configDir), deps.config.knowledge.compactionThreshold);
1559
1560
  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.`);
1561
+ deps.signal?.throwIfAborted();
1560
1562
  const committed = await commitTaskWork(task, params, deps);
1563
+ deps.signal?.throwIfAborted();
1561
1564
  const summary = params.text?.trim() || task.proposal || task.title;
1562
1565
  recordCompletion(deps, task, summary);
1563
1566
  removeTaskScratchpads(deps.root, deps.configDir, task.id);
@@ -1598,6 +1601,7 @@ async function commitTaskWork(task: Task, params: OrchestrateParams, deps: Workf
1598
1601
  }
1599
1602
  let result: Awaited<ReturnType<typeof commitFiles>>;
1600
1603
  try {
1604
+ deps.signal?.throwIfAborted();
1601
1605
  result = await commitFiles(state.top, dirty, message);
1602
1606
  } catch (error) {
1603
1607
  throw new Error(`cannot complete: git refused the commit: ${(error as Error).message}`);
@@ -2098,14 +2102,19 @@ export async function runWorkflowAction(params: OrchestrateParams, deps: Workflo
2098
2102
  }
2099
2103
  const gone = missingWorktree(task);
2100
2104
  if (gone && params.action !== "status") return { ok: false, taskId: task.id, state: task.state, message: `Rejected: ${gone}` };
2105
+ const execution = taskExecution(deps.root, deps.configDir, task.id, deps.signal);
2101
2106
  const finished = new Map<string, AgentRun>();
2102
2107
  const identity = { root: deps.root, configDir: deps.configDir, taskId: task.id, sessionId: task.ownerSessionId };
2103
- const wait = <T>(run: () => Promise<T>) => {
2108
+ const wait = async <T>(run: () => Promise<T>) => {
2109
+ execution.signal.throwIfAborted();
2104
2110
  saveTask(deps.root, deps.configDir, task);
2105
- return withBlockingRequest(identity, run);
2111
+ const result = await withBlockingRequest(identity, run);
2112
+ execution.signal.throwIfAborted();
2113
+ return result;
2106
2114
  };
2107
2115
  const tracked: WorkflowDeps = {
2108
2116
  ...deps,
2117
+ signal: execution.signal,
2109
2118
  choose: (...args) => wait(() => deps.choose(...args)),
2110
2119
  ask: (...args) => wait(() => deps.ask(...args)),
2111
2120
  ...(deps.askQuestions ? { askQuestions: (...args: Parameters<NonNullable<WorkflowDeps["askQuestions"]>>) => wait(() => deps.askQuestions!(...args)) } : {}),
@@ -2113,6 +2122,7 @@ export async function runWorkflowAction(params: OrchestrateParams, deps: Workflo
2113
2122
  cwd: taskCwd(task, deps.cwd),
2114
2123
  // 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
2124
  runProcess: (args, options) => {
2125
+ execution.signal.throwIfAborted();
2116
2126
  saveTask(deps.root, deps.configDir, task);
2117
2127
  return (deps.runProcess ?? spawnPiProcess)(args, options);
2118
2128
  },
@@ -2134,6 +2144,7 @@ export async function runWorkflowAction(params: OrchestrateParams, deps: Workflo
2134
2144
  const working = new Set<string>();
2135
2145
  try {
2136
2146
  const message = await handler(task, params, tracked);
2147
+ execution.signal.throwIfAborted();
2137
2148
  const runs = recordRunLog(task, finished, deps);
2138
2149
  saveTask(deps.root, deps.configDir, task);
2139
2150
  return { ok: true, taskId: task.id, state: task.state, message: `${message}${runsFooter(runs)}${budgetFooter(task, deps)}`, runs };
@@ -2142,6 +2153,7 @@ export async function runWorkflowAction(params: OrchestrateParams, deps: Workflo
2142
2153
  saveTask(deps.root, deps.configDir, task);
2143
2154
  return { ok: false, taskId: task.id, state: task.state, message: `Rejected: ${(error as Error).message}${budgetFooter(task, deps)}`, runs };
2144
2155
  } finally {
2156
+ execution.release();
2145
2157
  for (const runId of working) noteWorkerRun(deps.root, deps.configDir, task.id, { runId, startedAt: "", status: "cancelled" });
2146
2158
  }
2147
2159
  }