agent-yes 1.228.0 → 1.229.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.
Files changed (45) hide show
  1. package/dist/{JsonlStore-Bm5Nj6v_.js → JsonlStore-CWOA3aDH.js} +1 -1
  2. package/dist/{SUPPORTED_CLIS-C5d64Xqj.js → SUPPORTED_CLIS-CvLtp6LS.js} +2 -2
  3. package/dist/SUPPORTED_CLIS-DJEnqsp2.js +11 -0
  4. package/dist/{agentShare-CpglhRYb.js → agentShare-CQwvWWfd.js} +4 -4
  5. package/dist/{callback-B3N2VJ1i.js → callback-BFZDARQq.js} +4 -4
  6. package/dist/{callback-CCEQtwXX.js → callback-BN-tH9dB.js} +3 -3
  7. package/dist/cli.js +9 -9
  8. package/dist/{cwdConflictWarn-CJZRp4S0.js → cwdConflictWarn-CKUsQ5_o.js} +2 -2
  9. package/dist/{globalPidIndex-CoNr7tS8.js → globalPidIndex-Ca4sTr1b.js} +1 -1
  10. package/dist/index.js +8 -8
  11. package/dist/{messageLog-Bb_Ie3Ck.js → messageLog-zVGWLX7w.js} +1 -1
  12. package/dist/{notifyDaemon-CfQN5blR.js → notifyDaemon-CENKHs1M.js} +4 -4
  13. package/dist/pidStore-Cp3HekUh.js +6 -0
  14. package/dist/{pidStore-DCqpU_bI.js → pidStore-Hjba3K6Q.js} +3 -3
  15. package/dist/{reaper-BNKvujYy.js → reaper-iQXyufgp.js} +1 -1
  16. package/dist/{reaper-BBc2AIKA.js → reaper-pJpMYKmj.js} +1 -1
  17. package/dist/{runningLock-CNMl13dC.js → runningLock-BFk3Mo8K.js} +1 -1
  18. package/dist/{rustBinary-B8IcqSw1.js → rustBinary-BjApGser.js} +2 -2
  19. package/dist/{schedule-B-RjgOOR.js → schedule-D0O3sb5A.js} +9 -9
  20. package/dist/{serve-DwF03sgB.js → serve-m-WChRrO.js} +67 -21
  21. package/dist/{setup-cZFxfS89.js → setup-fuwC5wlF.js} +2 -2
  22. package/dist/spawnGate-5jC6eiqk.js +5 -0
  23. package/dist/{spawnGate-CeFYH-Ib.js → spawnGate-CMlGqVN4.js} +2 -2
  24. package/dist/{subcommands-DSyCT0vl.js → subcommands-DzJbnYAx.js} +12 -12
  25. package/dist/{subcommands-UseeaeYM.js → subcommands-lwgJxJex.js} +3 -3
  26. package/dist/{todoAutomation-Cwnh4aWo.js → todoAutomation-x6guaQXc.js} +115 -6
  27. package/dist/{todoCli-6v1CYdxN.js → todoCli-Cjhx3DqQ.js} +38 -6
  28. package/dist/{tray-DBt8X-Qj.js → tray-C8VfnS7s.js} +2 -2
  29. package/dist/{ts-Cl3CmLH9.js → ts-oYrTk4Z_.js} +103 -20
  30. package/dist/{versionChecker-Dhjyjxs_.js → versionChecker-CgCXc8CJ.js} +2 -2
  31. package/dist/{ws-Dw2fGxTI.js → ws-D5q3R1kY.js} +4 -4
  32. package/package.json +1 -1
  33. package/ts/askApi.spec.ts +358 -0
  34. package/ts/askApi.ts +255 -0
  35. package/ts/index.ts +2 -0
  36. package/ts/serve.ts +131 -0
  37. package/ts/todoBlock.spec.ts +10 -0
  38. package/ts/todoBlock.ts +14 -2
  39. package/ts/todoCli.spec.ts +101 -0
  40. package/ts/todoCli.ts +69 -1
  41. package/ts/todoStore.spec.ts +126 -0
  42. package/ts/todoStore.ts +130 -2
  43. package/dist/SUPPORTED_CLIS-Bfresz1n.js +0 -11
  44. package/dist/pidStore-CatWvn5W.js +0 -6
  45. package/dist/spawnGate-CIC7b7nm.js +0 -5
@@ -1,7 +1,19 @@
1
- import { t as JsonlStore } from "./JsonlStore-Bm5Nj6v_.js";
1
+ import { t as JsonlStore } from "./JsonlStore-CWOA3aDH.js";
2
2
  import path from "path";
3
3
  import { lock } from "proper-lockfile";
4
4
 
5
+ //#region ts/removeControlCharacters.ts
6
+ const ESC = String.fromCharCode(27);
7
+ const C1 = String.fromCharCode(155);
8
+ const BACKSLASH = String.fromCharCode(92);
9
+ const ST_PATTERN_SOURCE = ESC + BACKSLASH + BACKSLASH;
10
+ const OSC_PATTERN = new RegExp(ESC + "][^\x07\x1B]*(?:\x07|" + ST_PATTERN_SOURCE + ")", "g");
11
+ const CSI_PATTERN = new RegExp("[" + ESC + C1 + "][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]", "g");
12
+ function removeControlCharacters(str) {
13
+ return str.replace(OSC_PATTERN, "").replace(CSI_PATTERN, "");
14
+ }
15
+
16
+ //#endregion
5
17
  //#region ts/todoLifecycle.ts
6
18
  /**
7
19
  * States considered "finished" across every kind. A finished task is never
@@ -499,7 +511,10 @@ var TodoStore = class TodoStore {
499
511
  return this.rawUpdate(id, () => ({ acceptanceCriteria: trimmed }));
500
512
  }
501
513
  setBlock(id, block) {
502
- return this.rawUpdate(id, () => ({ block }));
514
+ return this.rawUpdate(id, (fresh) => ({
515
+ block,
516
+ blockRev: (fresh.blockRev ?? 0) + 1
517
+ }));
503
518
  }
504
519
  /**
505
520
  * Clears a `waiting-on-agent` block ONLY if the FRESH record's block is
@@ -514,7 +529,101 @@ var TodoStore = class TodoStore {
514
529
  clearWaitingOnAgentBlock(id, expectedAgentId) {
515
530
  return this.rawUpdate(id, (fresh) => {
516
531
  if (fresh.block?.type !== "waiting-on-agent" || fresh.block.agentId !== expectedAgentId) throw new Error(`task ${id}: block changed since this was decided (expected waiting-on-agent for "${expectedAgentId}") — not clearing`);
517
- return { block: null };
532
+ return {
533
+ block: null,
534
+ blockRev: (fresh.blockRev ?? 0) + 1
535
+ };
536
+ });
537
+ }
538
+ /**
539
+ * Clears `block` ONLY if the FRESH record's `blockRev` still equals
540
+ * `expectedBlockRev` — the generalized form of `clearWaitingOnAgentBlock`
541
+ * above, for a caller (e.g. `askApi.ts`'s `answerAsk`) that decided to
542
+ * clear a specific `blocked-by-human` block instance from a snapshot
543
+ * taken at the start of a longer operation.
544
+ *
545
+ * Compares `blockRev`, NOT the block's own content: an earlier version of
546
+ * this method compared `JSON.stringify(fresh.block)` against the snapshot,
547
+ * which missed an ABA race — another process could replace the block with
548
+ * a byte-for-byte IDENTICAL `{type, who, question/options/actionLink}`
549
+ * value (e.g. the exact same question asked again) between the caller's
550
+ * read and this call, and the content-only check would see no difference,
551
+ * silently erasing that distinct, newer block instance as if it were the
552
+ * one the caller actually decided to answer (codex-review round-17
553
+ * Important). `blockRev` increments on every write to `block` regardless
554
+ * of whether the new value differs from the old one, so it uniquely
555
+ * identifies THIS PARTICULAR block-setting event, not just its shape.
556
+ *
557
+ * Clearing unconditionally by `id` alone (or by content alone) could
558
+ * silently erase a genuinely different, newer block that another process
559
+ * set in the meantime — the task would then look unblocked even though a
560
+ * real, later block exists (codex-review Important). Throws instead of
561
+ * silently no-op-ing so the caller can report the conflict rather than
562
+ * claim success.
563
+ */
564
+ clearBlockIfMatches(id, expectedBlockRev) {
565
+ return this.rawUpdate(id, (fresh) => {
566
+ if ((fresh.blockRev ?? 0) !== expectedBlockRev) throw new Error(`task ${id}: block changed since this was decided — not clearing`);
567
+ return {
568
+ block: null,
569
+ blockRev: (fresh.blockRev ?? 0) + 1
570
+ };
571
+ });
572
+ }
573
+ /**
574
+ * Atomically clears a `blocked-by-human` block AND, if `gate` is given,
575
+ * appends its evidence and advances the transition it names — all inside
576
+ * ONE `rawUpdate` write (one lock acquisition, one reload, one write).
577
+ *
578
+ * This exists specifically so `askApi.ts`'s `answerAsk()` never composes
579
+ * "verify blockRev, approve() a gate, transition(), clearBlockIfMatches()"
580
+ * as separate locked writes again. That composition (this method's
581
+ * predecessor) had a real gap: `approve()`/`transition()` could durably
582
+ * succeed — evidence appended, state advanced — and ONLY THEN could the
583
+ * final `clearBlockIfMatches()` fail because another process replaced the
584
+ * block in between. The task was left in a state no caller intended:
585
+ * already transitioned/evidenced for the OLD block, while `block` still
586
+ * held a DIFFERENT, newer `blocked-by-human` ask that was never gated at
587
+ * all (codex-review round-18 Important — the exact race this module's
588
+ * whole `blockRev` mechanism exists to close, just one step further out
589
+ * than `clearBlockIfMatches` alone could reach). Folding everything into
590
+ * one `rawUpdate` cycle makes the two outcomes (nothing changed / gate +
591
+ * transition + clear all changed together) the only two possible ones —
592
+ * there is no window in which one succeeded and the other didn't.
593
+ *
594
+ * `blockRev` re-validates `canTransition` against the FRESH state (not a
595
+ * value captured before this call), matching `transition()`'s own
596
+ * concurrent-state-drift defense — a state change unrelated to `block`
597
+ * (and so invisible to the `blockRev` check alone) must still be caught.
598
+ *
599
+ * Independent verification (this module's single most important
600
+ * invariant — see the file's own header comment) is re-checked here
601
+ * against the FRESH owner, exactly as `approve()` checks it: `gate`, when
602
+ * given, is never applied if its `validator` is the task's own owner.
603
+ * This method does not call `approve()` (it is not a separate write), so
604
+ * skipping this check here would silently let a human answer their own
605
+ * gated ask with no independent-verification enforcement at all.
606
+ */
607
+ answerHumanBlock(id, expectedBlockRev, gate) {
608
+ return this.rawUpdate(id, (fresh) => {
609
+ if ((fresh.blockRev ?? 0) !== expectedBlockRev) throw new Error(`task ${id}: this ask has changed since it was loaded (expected blockRev ${expectedBlockRev}, current ${fresh.blockRev ?? 0}) — refresh and try again`);
610
+ const patch = {
611
+ block: null,
612
+ blockRev: (fresh.blockRev ?? 0) + 1
613
+ };
614
+ if (gate) {
615
+ if (fresh.owner && fresh.owner.trim().toLowerCase() === gate.validator.trim().toLowerCase()) throw new Error(`task ${id}: independent verification required — validator "${gate.validator}" is the same identity as owner "${fresh.owner}"; the worker cannot certify their own work`);
616
+ if (!canTransition(fresh.kind, fresh.state, gate.toState)) throw new Error(`task ${id}: no transition ${fresh.state} -> ${gate.toState} for kind "${fresh.kind}" (state changed concurrently) — not clearing`);
617
+ const evidenceEntry = {
618
+ gate: gate.name,
619
+ passedAt: (/* @__PURE__ */ new Date()).toISOString(),
620
+ validator: gate.validator,
621
+ note: gate.note
622
+ };
623
+ patch.verifyEvidence = [...fresh.verifyEvidence, evidenceEntry];
624
+ patch.state = gate.toState;
625
+ }
626
+ return patch;
518
627
  });
519
628
  }
520
629
  /**
@@ -602,7 +711,7 @@ function monitorHint(block) {
602
711
  function describeBlock(block) {
603
712
  switch (block.type) {
604
713
  case "blocked-by-task": return `blocked by task ${block.taskId}`;
605
- case "blocked-by-human": return `waiting on ${block.who}${block.question ? `: ${block.question}` : ""}`;
714
+ case "blocked-by-human": return `waiting on ${block.who}${block.question ? `: ${block.question}` : ""}${block.actionLink ? ` (action: ${block.actionLink})` : ""}`;
606
715
  case "blocked-by-external": return `waiting on external signal: ${block.signal}`;
607
716
  case "waiting-on-agent": return `waiting on agent ${block.agentId}`;
608
717
  }
@@ -848,5 +957,5 @@ function reconcileTodos(tasks, agents, registeredGates) {
848
957
  }
849
958
 
850
959
  //#endregion
851
- export { isKnownKind as _, renderTree as a, statesOf as b, monitorHint as c, openStore as d, DONE_STATE as f, initialState as g, canTransition as h, renderDigest as i, CycleError as l, ORPHANED_STATE as m, buildTreeJSON as n, unblockedTasks as o, LIFECYCLES as p, openBlockers as r, describeBlock as s, reconcileTodos as t, TodoStore as u, nextStates as v, requiredGate as y };
852
- //# sourceMappingURL=todoAutomation-Cwnh4aWo.js.map
960
+ export { isKnownKind as _, renderTree as a, statesOf as b, monitorHint as c, openStore as d, DONE_STATE as f, initialState as g, canTransition as h, renderDigest as i, CycleError as l, ORPHANED_STATE as m, buildTreeJSON as n, unblockedTasks as o, LIFECYCLES as p, openBlockers as r, describeBlock as s, reconcileTodos as t, TodoStore as u, nextStates as v, removeControlCharacters as x, requiredGate as y };
961
+ //# sourceMappingURL=todoAutomation-x6guaQXc.js.map
@@ -1,7 +1,7 @@
1
1
  import "./logger-CDIsZ-Pp.js";
2
- import "./JsonlStore-Bm5Nj6v_.js";
3
- import { i as readGlobalPids } from "./globalPidIndex-CoNr7tS8.js";
4
- import { _ as isKnownKind, a as renderTree, d as openStore, i as renderDigest, l as CycleError, n as buildTreeJSON, o as unblockedTasks, p as LIFECYCLES, s as describeBlock, t as reconcileTodos } from "./todoAutomation-Cwnh4aWo.js";
2
+ import { _ as isKnownKind, a as renderTree, d as openStore, i as renderDigest, l as CycleError, n as buildTreeJSON, o as unblockedTasks, p as LIFECYCLES, s as describeBlock, t as reconcileTodos, x as removeControlCharacters } from "./todoAutomation-x6guaQXc.js";
3
+ import "./JsonlStore-CWOA3aDH.js";
4
+ import { i as readGlobalPids } from "./globalPidIndex-Ca4sTr1b.js";
5
5
  import yargs from "yargs";
6
6
 
7
7
  //#region ts/todoCli.ts
@@ -47,6 +47,26 @@ import yargs from "yargs";
47
47
  function fail(message) {
48
48
  throw new Error(message);
49
49
  }
50
+ /**
51
+ * Same check ask.html's own render-time guard uses — a real URL parse, not
52
+ * just a scheme-prefix regex, which could still let a malformed string
53
+ * through. Returns the parser's own normalized `.href` (not the raw input)
54
+ * on success, `null` otherwise: WHATWG URL parsing tolerates/strips some
55
+ * control characters (e.g. embedded tabs/newlines) from the input while
56
+ * still returning a valid http(s) URL, so validating against `s` but then
57
+ * STORING `s` verbatim could still persist those raw control characters
58
+ * into the store — later rendered as terminal/log text by `describeBlock()`
59
+ * (codex-review Important). Storing `.href` instead guarantees the store
60
+ * only ever holds what the parser actually validated.
61
+ */
62
+ function normalizeHttpUrl(s) {
63
+ try {
64
+ const u = new URL(s);
65
+ return u.protocol === "http:" || u.protocol === "https:" ? u.href : null;
66
+ } catch {
67
+ return null;
68
+ }
69
+ }
50
70
  function parseKind(raw) {
51
71
  if (!raw) fail(`--kind is required (one of: ${Object.keys(LIFECYCLES).join(", ")})`);
52
72
  if (!isKnownKind(raw)) fail(`unknown kind "${raw}" (one of: ${Object.keys(LIFECYCLES).join(", ")})`);
@@ -236,7 +256,11 @@ const blockCmd = {
236
256
  describe: "required for --type blocked-by-human"
237
257
  }).option("question", { type: "string" }).option("options", {
238
258
  type: "string",
239
- array: true
259
+ array: true,
260
+ describe: "choice-shape ask (/ask renders buttons)"
261
+ }).option("action-link", {
262
+ type: "string",
263
+ describe: "action-shape ask (/ask renders an 'open link, then confirm' button) — e.g. an OAuth/CAPTCHA URL"
240
264
  }).option("signal", {
241
265
  type: "string",
242
266
  describe: "required for --type blocked-by-external"
@@ -257,11 +281,19 @@ const blockCmd = {
257
281
  break;
258
282
  case "blocked-by-human":
259
283
  if (!argv.who) fail("--who <name> is required for --type blocked-by-human");
284
+ if (argv.options?.length && argv["action-link"]) fail("--options and --action-link are mutually exclusive (choice-shape vs action-shape ask)");
285
+ let normalizedActionLink;
286
+ if (argv["action-link"]) {
287
+ const normalized = normalizeHttpUrl(argv["action-link"]);
288
+ if (!normalized) fail(`--action-link must be a valid http:// or https:// URL (got "${removeControlCharacters(argv["action-link"]).replace(/[\r\n]+/g, " ")}")`);
289
+ normalizedActionLink = normalized;
290
+ }
260
291
  block = {
261
292
  type: "blocked-by-human",
262
293
  who: argv.who,
263
294
  question: argv.question,
264
- options: argv.options
295
+ options: argv.options,
296
+ actionLink: normalizedActionLink
265
297
  };
266
298
  break;
267
299
  case "blocked-by-external":
@@ -424,4 +456,4 @@ async function runTodoSubcommand(rest0) {
424
456
 
425
457
  //#endregion
426
458
  export { runTodoSubcommand };
427
- //# sourceMappingURL=todoCli-6v1CYdxN.js.map
459
+ //# sourceMappingURL=todoCli-Cjhx3DqQ.js.map
@@ -1,4 +1,4 @@
1
- import { n as getRunningAgentCount } from "./runningLock-CNMl13dC.js";
1
+ import { n as getRunningAgentCount } from "./runningLock-BFk3Mo8K.js";
2
2
  import { existsSync } from "fs";
3
3
  import { mkdir, readFile, unlink, writeFile } from "fs/promises";
4
4
  import { homedir } from "os";
@@ -187,4 +187,4 @@ async function startTray() {
187
187
 
188
188
  //#endregion
189
189
  export { ensureTray, startTray };
190
- //# sourceMappingURL=tray-DBt8X-Qj.js.map
190
+ //# sourceMappingURL=tray-C8VfnS7s.js.map
@@ -1,11 +1,12 @@
1
1
  import { n as logger, t as addTransport } from "./logger-CDIsZ-Pp.js";
2
- import { r as getInstalledPackage } from "./versionChecker-Dhjyjxs_.js";
2
+ import { r as getInstalledPackage } from "./versionChecker-CgCXc8CJ.js";
3
3
  import { t as agentYesHome } from "./agentYesHome-CtHb5b71.js";
4
- import { i as shouldUseLock, r as releaseLock, t as acquireLock } from "./runningLock-CNMl13dC.js";
5
- import { i as readGlobalPids } from "./globalPidIndex-CoNr7tS8.js";
6
- import { t as PidStore } from "./pidStore-DCqpU_bI.js";
7
- import { r as recordInbox } from "./messageLog-Bb_Ie3Ck.js";
8
- import { n as register, r as sweep } from "./reaper-BNKvujYy.js";
4
+ import { d as openStore, p as LIFECYCLES, x as removeControlCharacters } from "./todoAutomation-x6guaQXc.js";
5
+ import { i as shouldUseLock, r as releaseLock, t as acquireLock } from "./runningLock-BFk3Mo8K.js";
6
+ import { i as readGlobalPids } from "./globalPidIndex-Ca4sTr1b.js";
7
+ import { t as PidStore } from "./pidStore-Hjba3K6Q.js";
8
+ import { r as recordInbox } from "./messageLog-zVGWLX7w.js";
9
+ import { n as register, r as sweep } from "./reaper-iQXyufgp.js";
9
10
  import { arch, platform } from "process";
10
11
  import { execSync } from "child_process";
11
12
  import { closeSync, constants, createReadStream, existsSync, mkdirSync, openSync } from "fs";
@@ -23,6 +24,7 @@ import { execSync as execSync$1 } from "node:child_process";
23
24
  import os from "node:os";
24
25
  import { readFile as readFile$1 } from "node:fs/promises";
25
26
  import path$1 from "node:path";
27
+ import { existsSync as existsSync$1 } from "node:fs";
26
28
 
27
29
  //#region \0rolldown/runtime.js
28
30
  var __defProp = Object.defineProperty;
@@ -288,18 +290,6 @@ try {
288
290
  const ptyPackage = globalThis.Bun ? "bun-pty" : "node-pty";
289
291
  var pty_default = pty;
290
292
 
291
- //#endregion
292
- //#region ts/removeControlCharacters.ts
293
- const ESC = String.fromCharCode(27);
294
- const C1 = String.fromCharCode(155);
295
- const BACKSLASH = String.fromCharCode(92);
296
- const ST_PATTERN_SOURCE = ESC + BACKSLASH + BACKSLASH;
297
- const OSC_PATTERN = new RegExp(ESC + "][^\x07\x1B]*(?:\x07|" + ST_PATTERN_SOURCE + ")", "g");
298
- const CSI_PATTERN = new RegExp("[" + ESC + C1 + "][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]", "g");
299
- function removeControlCharacters(str) {
300
- return str.replace(OSC_PATTERN, "").replace(CSI_PATTERN, "");
301
- }
302
-
303
293
  //#endregion
304
294
  //#region ts/sessionEnv.ts
305
295
  const CLAUDE_SESSION_PIN_ENV = [
@@ -1200,6 +1190,99 @@ async function notifyWebhook(status, details, cwd = process.cwd()) {
1200
1190
  }
1201
1191
  }
1202
1192
 
1193
+ //#endregion
1194
+ //#region ts/askApi.ts
1195
+ /**
1196
+ * `/ask` decision panel (A7): aggregates every `blocked-by-human` task across
1197
+ * every project this host's agents have registered onto one human-facing
1198
+ * view, and closes the loop when a human answers.
1199
+ *
1200
+ * Pure store logic only — no HTTP, no live-agent notification. `ts/serve.ts`
1201
+ * wires this into `GET /api/asks` / `POST /api/asks/answer` and, as a
1202
+ * best-effort follow-up after `answerAsk()` resolves, writes the answer into
1203
+ * the owning agent's terminal via the same FIFO mechanism `/api/send`
1204
+ * already uses (see that route) — kept out of this module so it stays
1205
+ * testable without a live server or live agent processes.
1206
+ */
1207
+ /** Whether `projectRoot` has ever had an `ay todo` store created — used to skip candidate project roots (e.g. from a live-agent scan) that never called `ay todo` at all. */
1208
+ function hasTodoStore(projectRoot) {
1209
+ return existsSync$1(path$1.join(projectRoot, ".agent-yes", "todos.jsonl"));
1210
+ }
1211
+ async function listAsksForProject(projectRoot) {
1212
+ const store = await openStore(projectRoot);
1213
+ const asks = [];
1214
+ for (const t of store.all()) {
1215
+ if (t.block?.type !== "blocked-by-human") continue;
1216
+ const block = t.block;
1217
+ asks.push({
1218
+ projectRoot,
1219
+ taskId: t._id,
1220
+ summary: t.summary,
1221
+ who: block.who,
1222
+ question: block.question,
1223
+ options: block.options,
1224
+ actionLink: block.actionLink,
1225
+ shape: block.actionLink ? "action" : block.options?.length ? "choice" : "acknowledge",
1226
+ blockRev: t.blockRev ?? 0
1227
+ });
1228
+ }
1229
+ return asks;
1230
+ }
1231
+ /**
1232
+ * Aggregates asks across every project root that actually has a store —
1233
+ * candidates are supplied by the caller (`ts/serve.ts` derives them from the
1234
+ * live-agent registry's distinct `cwd`s), keeping this module itself
1235
+ * agent-registry-agnostic.
1236
+ *
1237
+ * Uses `Promise.allSettled`, not `Promise.all`: this is a cross-project
1238
+ * aggregation by design, and `Promise.all`'s fail-fast behavior meant ONE
1239
+ * project with an unreadable/corrupt store made the ENTIRE decision panel
1240
+ * return nothing (a 500 at the route level) — hiding every other project's
1241
+ * perfectly healthy asks too (codex-review Important). A failed project is
1242
+ * logged and skipped; the healthy ones are still returned.
1243
+ */
1244
+ async function listAsks(projectRoots) {
1245
+ const withStore = [...new Set(projectRoots)].filter(hasTodoStore);
1246
+ const results = await Promise.allSettled(withStore.map((root) => listAsksForProject(root)));
1247
+ const asks = [];
1248
+ for (const [i, result] of results.entries()) if (result.status === "fulfilled") asks.push(...result.value);
1249
+ else console.error(`[askApi] listAsksForProject(${withStore[i]}) failed:`, result.reason);
1250
+ return asks;
1251
+ }
1252
+ async function answerAsk(projectRoot, taskId, answer) {
1253
+ const store = await openStore(projectRoot);
1254
+ const rec = store.get(taskId);
1255
+ if (!rec) throw new Error(`no such task: ${taskId}`);
1256
+ if (rec.block?.type !== "blocked-by-human") throw new Error(`task ${taskId} is not currently blocked-by-human (already answered, or blocked on something else)`);
1257
+ const expectedBlockRev = answer.expectedBlockRev ?? rec.blockRev ?? 0;
1258
+ const block = rec.block;
1259
+ let answerText;
1260
+ if (block.actionLink) {
1261
+ if (!answer.acknowledged) throw new Error(`task ${taskId}: this ask requires { acknowledged: true } after completing ${block.actionLink}`);
1262
+ answerText = "acknowledged";
1263
+ } else if (block.options?.length) {
1264
+ if (!answer.choice) throw new Error(`task ${taskId}: this ask requires a choice (one of: ${block.options.join(", ")})`);
1265
+ if (!block.options.includes(answer.choice)) throw new Error(`task ${taskId}: "${answer.choice}" is not one of the offered options (${block.options.join(", ")})`);
1266
+ answerText = answer.choice;
1267
+ } else {
1268
+ if (!answer.acknowledged) throw new Error(`task ${taskId}: this ask requires { acknowledged: true }`);
1269
+ answerText = "acknowledged";
1270
+ }
1271
+ const gate = rec.kind === "human" || rec.kind === "decision" ? (() => {
1272
+ const primary = LIFECYCLES[rec.kind].transitions.find((tr) => tr.from === rec.state && tr.gate);
1273
+ return primary?.gate ? {
1274
+ name: primary.gate,
1275
+ toState: primary.to,
1276
+ validator: block.who,
1277
+ note: answerText
1278
+ } : null;
1279
+ })() : null;
1280
+ return {
1281
+ record: await store.answerHumanBlock(taskId, expectedBlockRev, gate),
1282
+ answerText
1283
+ };
1284
+ }
1285
+
1203
1286
  //#endregion
1204
1287
  //#region ts/index.ts
1205
1288
  const config = await import("./agent-yes.config-0G11aPWe.js").then((mod) => mod.default || mod);
@@ -1960,5 +2043,5 @@ function sleep(ms) {
1960
2043
  }
1961
2044
 
1962
2045
  //#endregion
1963
- export { CLAUDE_SESSION_PIN_ENV as a, AgentContext as i, agentYes as n, removeControlCharacters as o, config as r, CLIS_CONFIG as t };
1964
- //# sourceMappingURL=ts-Cl3CmLH9.js.map
2046
+ export { hasTodoStore as a, AgentContext as c, answerAsk as i, CLAUDE_SESSION_PIN_ENV as l, agentYes as n, listAsks as o, config as r, listAsksForProject as s, CLIS_CONFIG as t };
2047
+ //# sourceMappingURL=ts-oYrTk4Z_.js.map
@@ -7,7 +7,7 @@ import { fileURLToPath } from "url";
7
7
 
8
8
  //#region package.json
9
9
  var name = "agent-yes";
10
- var version = "1.228.0";
10
+ var version = "1.229.0";
11
11
 
12
12
  //#endregion
13
13
  //#region ts/versionChecker.ts
@@ -215,4 +215,4 @@ async function displayVersion() {
215
215
 
216
216
  //#endregion
217
217
  export { versionString as i, displayVersion as n, getInstalledPackage as r, checkAndAutoUpdate as t };
218
- //# sourceMappingURL=versionChecker-Dhjyjxs_.js.map
218
+ //# sourceMappingURL=versionChecker-CgCXc8CJ.js.map
@@ -1,8 +1,8 @@
1
1
  import "./logger-CDIsZ-Pp.js";
2
- import "./globalPidIndex-CoNr7tS8.js";
3
- import "./messageLog-Bb_Ie3Ck.js";
2
+ import "./globalPidIndex-Ca4sTr1b.js";
3
+ import "./messageLog-zVGWLX7w.js";
4
4
  import "./configShared-YKfS6NMe.js";
5
- import { S as listRecords } from "./subcommands-DSyCT0vl.js";
5
+ import { S as listRecords } from "./subcommands-DzJbnYAx.js";
6
6
  import "./e2e-DB7BDdEA.js";
7
7
  import "./webrtcLink-CjckgIZe.js";
8
8
  import "./remotes-ZqD0FHl_.js";
@@ -418,4 +418,4 @@ async function cmdWs(args) {
418
418
 
419
419
  //#endregion
420
420
  export { WS_JSON_SCHEMA, cmdWs, collectWorkspaces, isPathInside, loadProvision, workspaceStatus };
421
- //# sourceMappingURL=ws-Dw2fGxTI.js.map
421
+ //# sourceMappingURL=ws-D5q3R1kY.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-yes",
3
- "version": "1.228.0",
3
+ "version": "1.229.0",
4
4
  "description": "A wrapper tool that automates interactions with various AI CLI tools by automatically handling common prompts and responses.",
5
5
  "keywords": [
6
6
  "ai",