agent-yes 1.267.2 → 1.269.1

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 (108) hide show
  1. package/README.md +73 -0
  2. package/dist/{SUPPORTED_CLIS-CpWKDCtL.js → SUPPORTED_CLIS-Bne04-66.js} +2 -2
  3. package/dist/SUPPORTED_CLIS-CdyjhpZP.js +12 -0
  4. package/dist/{agent-yes.config-DzNini0E.js → agent-yes.config-2xKNJM5Y.js} +2 -2
  5. package/dist/{agentShare-D0reU6LW.js → agentShare-BI126Ej9.js} +8 -8
  6. package/dist/askCli-Bbxt9Umz.js +272 -0
  7. package/dist/{browser-DSduVZeZ.js → browser-CorYp8Yu.js} +1 -1
  8. package/dist/{browser-B7eYrBy7.js → browser-_2ndf80G.js} +5 -5
  9. package/dist/callback-BnUbjH7p.js +13 -0
  10. package/dist/{callback-Dlqwh9n7.js → callback-DI2r3NrV.js} +5 -5
  11. package/dist/{callbackCore-K99x3C75.js → callbackCore-Bbe7rpN2.js} +1 -1
  12. package/dist/{callbackCore-DcptaZwH.js → callbackCore-C0Cg9RbS.js} +1 -1
  13. package/dist/{channels-DQaZbare.js → channels-3gz9gHP7.js} +6 -6
  14. package/dist/{channels-yE1YUHCl.js → channels-CkaCyvEp.js} +2 -2
  15. package/dist/channels.js +5 -5
  16. package/dist/cli.js +11 -11
  17. package/dist/{cmdPs-D8gsRFVV.js → cmdPs-CVnlflxP.js} +7 -7
  18. package/dist/{configShared-DtHMBCl7.js → configShared-DYxlvrH0.js} +1 -1
  19. package/dist/{cwdConflictWarn-BpweoAiy.js → cwdConflictWarn-Buy3r_qc.js} +1 -1
  20. package/dist/{cwdPassthroughHint-BlRuOGo_.js → cwdPassthroughHint-BlWO-h5w.js} +1 -1
  21. package/dist/{e2e-CUMZC53I.js → e2e-OLfcNxiv.js} +1 -1
  22. package/dist/{expose-DyiZ5AU0.js → expose-Bfo-dZdy.js} +1 -1
  23. package/dist/{forkNested-B_4c-TQk.js → forkNested-jc2ER9xn.js} +1 -1
  24. package/dist/{hist-BoZPJ2ah.js → hist-DiOYRXuD.js} +1 -1
  25. package/dist/{html2canvas-pro.esm-cq4l_ZIw.js → html2canvas-pro.esm-D19oTQeV.js} +1 -1
  26. package/dist/identity-BmDYOuZE.js +3 -0
  27. package/dist/identity-CLaJ9-uE.js +109 -0
  28. package/dist/index.js +4 -3
  29. package/dist/{initMsg-D8cmdY76.js → initMsg-Bat5PUDf.js} +1 -1
  30. package/dist/{initMsg-BKqWDYFk.js → initMsg-CTAIqBWF.js} +8 -4
  31. package/dist/{needsInput-103zhZ90.js → needsInput-DhfIwxse.js} +1 -1
  32. package/dist/{nodeRuntime-QihGFQbI.js → nodeRuntime-CKc1BN5J.js} +1 -1
  33. package/dist/{notifyDaemon-TKtSYtEO.js → notifyDaemon-Cf4KHDOr.js} +7 -7
  34. package/dist/{notifyStore-CEDiVyis.js → notifyStore-0hlFPa6z.js} +1 -1
  35. package/dist/{notifyStore-Cbw2ZJ0X.js → notifyStore-Dus2D1XA.js} +1 -1
  36. package/dist/{openBrowser-Cs46NiH6.js → openBrowser-Dmg2Oi4q.js} +1 -1
  37. package/dist/{parentLink-CTgZZCgw.js → parentLink-BwX4jaGD.js} +1 -1
  38. package/dist/{parentLink-CQrn998L.js → parentLink-DS-XflaJ.js} +1 -1
  39. package/dist/{parentPingLoop-BEvJ2ElR.js → parentPingLoop-Bxd3dXWO.js} +6 -6
  40. package/dist/{peer-DIl5xtx8.js → peer-Dr4zvtcd.js} +3 -3
  41. package/dist/peer-u1AUAZeA.js +4 -0
  42. package/dist/{remotes-M8FbM92D.js → remotes-BiftUp09.js} +3 -3
  43. package/dist/{remotes-DdAfueOu.js → remotes-IzQHupeo.js} +3 -3
  44. package/dist/{rustBinary-B9FEbqTu.js → rustBinary-B2lNFJSe.js} +2 -2
  45. package/dist/{schedule-Bmh5uaDr.js → schedule-D1CNvuKT.js} +8 -7
  46. package/dist/{serve-BqRNIxeq.js → serve-BaHCO0bU.js} +49 -42
  47. package/dist/{setup-GYkQYxlf.js → setup-B8EL0SWL.js} +4 -4
  48. package/dist/{share-Db_zb6gH.js → share-DTPZgbfH.js} +2 -2
  49. package/dist/{share-NUhi-Wdd.js → share-RgJUhzqG.js} +2 -2
  50. package/dist/{spawnGate-CZlMs6uV.js → spawnGate-C6YKlnmb.js} +1 -1
  51. package/dist/{spawnGate-DN4wNXHq.js → spawnGate-CS3oFdJ9.js} +2 -2
  52. package/dist/{store-BW5NBouz.js → store-C7I2CAu9.js} +1 -1
  53. package/dist/{subcommands-DJpVXIb6.js → subcommands-03ghe-aZ.js} +192 -157
  54. package/dist/{subcommands-DLrJfG4D.js → subcommands-BS4lnTrb.js} +6 -6
  55. package/dist/{systemPathLink-BHv1I6wh.js → systemPathLink-DGVcx0fg.js} +1 -1
  56. package/dist/{terminal-CRLPUaKF.js → terminal-DrCP-200.js} +2 -2
  57. package/dist/terminal.js +1 -1
  58. package/dist/todoAutomation-BETGOik9.js +302 -0
  59. package/dist/{todoCli-BYGZ7_C5.js → todoCli-DUoxA1dh.js} +164 -37
  60. package/dist/todoIdentity-BffBxRHA.js +138 -0
  61. package/dist/{todoAutomation-BQRW614S.js → todoStore-Wk8U4zJ8.js} +63 -280
  62. package/dist/{tray-CzGs9-ch.js → tray-Bxq_sfvU.js} +1 -1
  63. package/dist/trayApp-BDMzlxRo.js +5 -0
  64. package/dist/{trayApp-DLMUu2sB.js → trayApp-DC4qjut7.js} +2 -2
  65. package/dist/{ts-ClwhahH9.js → ts-D0Pk-WJg.js} +14 -8
  66. package/dist/{versionChecker-ZQYirfMp.js → versionChecker-V6ceVsu9.js} +2 -2
  67. package/dist/{webrtcLink-DyCj380i.js → webrtcLink-B72AQ2eb.js} +2 -2
  68. package/dist/{webrtcRemote-Bl8eowma.js → webrtcRemote-D68dkqFp.js} +3 -3
  69. package/dist/{widget-DtZ1WXkf.js → widget-BM03OkC1.js} +3 -3
  70. package/dist/widget.js +1 -1
  71. package/dist/widgets.js +5 -5
  72. package/dist/{workspaceConfig-CBKlgA_q.js → workspaceConfig-EK7XNsgR.js} +1 -1
  73. package/dist/{ws-CiR2nOD3.js → ws-6l7Wb_0r.js} +7 -7
  74. package/dist/{ws-Ba372Tym.js → ws-pRFrqms9.js} +3 -3
  75. package/lab/ui/console-logic.js +64 -0
  76. package/lab/ui/index.html +58 -4
  77. package/package.json +1 -1
  78. package/ts/askCli.spec.ts +304 -0
  79. package/ts/askCli.ts +378 -0
  80. package/ts/identity.spec.ts +80 -1
  81. package/ts/identity.ts +16 -9
  82. package/ts/index.ts +6 -1
  83. package/ts/initMsg.spec.ts +22 -14
  84. package/ts/initMsg.ts +7 -2
  85. package/ts/ipcLock.spec.ts +107 -0
  86. package/ts/ipcLock.ts +130 -0
  87. package/ts/serve.ts +68 -17
  88. package/ts/subcommandMirror.spec.ts +76 -0
  89. package/ts/subcommands.spec.ts +115 -10
  90. package/ts/subcommands.ts +108 -36
  91. package/ts/todoAutomation.spec.ts +64 -0
  92. package/ts/todoAutomation.ts +25 -2
  93. package/ts/todoBlock.spec.ts +31 -1
  94. package/ts/todoBlock.ts +33 -1
  95. package/ts/todoCli.spec.ts +199 -4
  96. package/ts/todoCli.ts +266 -42
  97. package/ts/todoIdentity.spec.ts +85 -0
  98. package/ts/todoIdentity.ts +76 -0
  99. package/ts/todoLifecycle.spec.ts +13 -1
  100. package/ts/todoLifecycle.ts +35 -1
  101. package/ts/todoRoot.spec.ts +117 -0
  102. package/ts/todoRoot.ts +90 -0
  103. package/ts/todoStore.spec.ts +25 -0
  104. package/ts/todoStore.ts +36 -2
  105. package/dist/SUPPORTED_CLIS-DDs0xUvV.js +0 -11
  106. package/dist/callback-Bgehh-IW.js +0 -13
  107. package/dist/peer-Cvntlw1i.js +0 -4
  108. package/dist/trayApp-CoD89Y1_.js +0 -5
@@ -1,7 +1,9 @@
1
1
  import "./logger-CDIsZ-Pp.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-BQRW614S.js";
2
+ import { a as renderTree, c as describeBlock, i as renderDigest, n as buildTreeJSON, o as unblockedTasks, s as blockedOnAgent, t as reconcileTodos, u as removeControlCharacters } from "./todoAutomation-BETGOik9.js";
3
3
  import "./JsonlStore-CPGIR0Wy.js";
4
4
  import { i as readGlobalPids } from "./globalPidIndex-Dq4KY2IV.js";
5
+ import { a as LIFECYCLES, l as isKnownKind, r as openStore, t as CycleError } from "./todoStore-Wk8U4zJ8.js";
6
+ import { a as resolveTodoRoot, i as resolveSelf, n as SELF_OWNER, r as ownerLiveness, t as NO_OWNER } from "./todoIdentity-BffBxRHA.js";
5
7
  import yargs from "yargs";
6
8
 
7
9
  //#region ts/todoCli.ts
@@ -14,13 +16,18 @@ import yargs from "yargs";
14
16
  * `runTodoSubcommand`, so this file (and everything it pulls in) is only
15
17
  * loaded when `ay todo ...` is actually invoked.
16
18
  *
17
- * Store location: `--root <dir>` (default: current working directory) is
18
- * where `.agent-yes/todos.jsonl` lives, mirroring `PidStore`'s own
19
- * `<workingDir>/.agent-yes/...` convention. This module has no notion of any
20
- * particular consuming project's directory-resolution rules (e.g. "find the
21
- * monorepo root") — a project wanting that behavior supplies it by always
22
- * invoking with an explicit `--root`, or by calling `openStore()` as a
23
- * library from its own code instead of through this CLI.
19
+ * Store location: `--root <dir>` is where `.agent-yes/todos.jsonl` lives,
20
+ * mirroring `PidStore`'s own `<workingDir>/.agent-yes/...` convention. Left
21
+ * unset it resolves to the REPO's common root rather than the cwd, so agents
22
+ * in different worktrees share one list — see `todoRoot.ts` for why that
23
+ * matters and where the line is drawn.
24
+ *
25
+ * Cross-agent surface, on top of that shared store: `--owner me` resolves to
26
+ * the calling agent's registry id (`todoIdentity.ts`), `new` assigns to the
27
+ * caller by default, `claim` takes a task over while refusing to steal from a
28
+ * live agent, and `ls`/`get` annotate each owner with whether that agent is
29
+ * still alive. `reconcile` (already present) closes the loop by orphaning
30
+ * tasks whose owner agent exited.
24
31
  *
25
32
  * Argument parsing: a real yargs COMMAND TREE — one `yargs(argv)` instance
26
33
  * with a `CommandModule` per verb registered via `.command()`, `--root`/
@@ -72,13 +79,15 @@ function parseKind(raw) {
72
79
  if (!isKnownKind(raw)) fail(`unknown kind "${raw}" (one of: ${Object.keys(LIFECYCLES).join(", ")})`);
73
80
  return raw;
74
81
  }
75
- function renderRecord(t) {
82
+ function renderRecord(t, agents = []) {
83
+ const waitingOn = blockedOnAgent(t.block);
84
+ const waitStatus = waitingOn ? ownerLiveness(waitingOn, agents) : "unknown";
76
85
  const lines = [
77
86
  `${t._id} [${t.state}] ${t.summary}`,
78
87
  `kind: ${t.kind}${t.targetTier ? ` tier:${t.targetTier}` : ""}`,
79
88
  ...t.owner ? [`owner: ${t.owner}`] : [],
80
89
  ...t.acceptanceCriteria ? [`acceptanceCriteria: ${t.acceptanceCriteria}`] : [],
81
- ...t.block ? [`block: ${describeBlock(t.block)}`] : [],
90
+ ...t.block ? [`block: ${describeBlock(t.block)}${waitingOn && waitStatus !== "unknown" ? ` [${waitingOn} is ${waitStatus}]` : ""}`] : [],
82
91
  ...t.blockedBy.length ? [`blockedBy: ${t.blockedBy.join(", ")}`] : [],
83
92
  ...t.tags.length ? [`tags: ${t.tags.join(", ")}`] : [],
84
93
  ...t.satisfiedGates.length ? [`satisfiedGates: ${t.satisfiedGates.join(", ")}`] : [],
@@ -89,13 +98,39 @@ function renderRecord(t) {
89
98
  if (t.description) lines.push("", t.description);
90
99
  return lines.join("\n");
91
100
  }
92
- function renderList(tasks) {
101
+ /**
102
+ * `owner` as displayed: the stored string plus, when it names an agent the
103
+ * registry knows, that agent's current status — `agt-7(exited)`. An owner with
104
+ * no registry entry (a human, or an agent that never registered) prints bare,
105
+ * because "unknown" there means "not an agent", not "possibly dead".
106
+ *
107
+ * This is the whole point of the shared store being readable across agents:
108
+ * the interesting question about a task someone else owns is almost always
109
+ * "is that someone still running?", and answering it by cross-referencing
110
+ * `ay ls` by hand is exactly the step that gets skipped.
111
+ */
112
+ function ownerCell(t, agents) {
113
+ if (!t.owner) return "";
114
+ const status = ownerLiveness(t.owner, agents);
115
+ return status === "unknown" ? t.owner : `${t.owner}(${status})`;
116
+ }
117
+ /** Same treatment for the agent a task is WAITING ON — `ay ask`'s answerer. */
118
+ function waitingOnCell(t, agents) {
119
+ const who = blockedOnAgent(t.block);
120
+ if (!who) return "";
121
+ const status = ownerLiveness(who, agents);
122
+ return status === "unknown" ? who : `${who}(${status})`;
123
+ }
124
+ function renderList(tasks, agents = []) {
93
125
  if (tasks.length === 0) return "(no tasks match)";
94
- const rows = tasks.map((t) => [
126
+ const waiting = tasks.map((t) => waitingOnCell(t, agents));
127
+ const showWaiting = waiting.some(Boolean);
128
+ const rows = tasks.map((t, i) => [
95
129
  t._id,
96
130
  t.state,
97
131
  t.kind,
98
- t.owner ?? "",
132
+ ownerCell(t, agents),
133
+ ...showWaiting ? [waiting[i]] : [],
99
134
  t.summary
100
135
  ]);
101
136
  const header = [
@@ -103,6 +138,7 @@ function renderList(tasks) {
103
138
  "STATE",
104
139
  "KIND",
105
140
  "OWNER",
141
+ ...showWaiting ? ["WAITING-ON"] : [],
106
142
  "SUMMARY"
107
143
  ];
108
144
  const widths = header.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
@@ -112,6 +148,44 @@ function renderList(tasks) {
112
148
  function emit(opts, obj, human) {
113
149
  process.stdout.write((opts.format === "json" ? JSON.stringify(obj, null, 2) : human) + "\n");
114
150
  }
151
+ /**
152
+ * Open the store every verb works against. Resolution is deferred to call
153
+ * time (rather than baked into a yargs `default:`) because the default is
154
+ * DERIVED — it shells out to git — and a yargs default is evaluated eagerly at
155
+ * parse time, on every invocation including `--help`.
156
+ */
157
+ async function storeFor(opts) {
158
+ return openStore(await resolveTodoRoot(opts.root));
159
+ }
160
+ /**
161
+ * Turn an `--owner` value into what gets stored: `me` → this agent's
162
+ * `agent_id`, `none` → explicitly unowned, anything else verbatim.
163
+ *
164
+ * `me` is an ERROR outside an agent, never a silent fallback to a hostname or
165
+ * pid: those look like an agent assignment to a reader but match nothing in
166
+ * the agent index, so the task would sit permanently outside orphan recovery
167
+ * (see `todoIdentity.ts`).
168
+ */
169
+ /**
170
+ * The agent snapshot every cross-agent view is read against.
171
+ *
172
+ * `liveOnly: false` deliberately: the interesting answer for an owner is often
173
+ * "that agent EXITED", which a live-only read cannot express — it would drop
174
+ * the record and make a dead owner indistinguishable from a human one. Same
175
+ * reasoning (and same call) as `reconcile`, which needs exited records to
176
+ * orphan against.
177
+ */
178
+ async function liveAgents() {
179
+ return readGlobalPids({ liveOnly: false });
180
+ }
181
+ async function resolveOwnerArg(raw) {
182
+ if (raw === void 0) return void 0;
183
+ if (raw === NO_OWNER || raw === "") return void 0;
184
+ if (raw !== SELF_OWNER) return raw;
185
+ const self = await resolveSelf();
186
+ if (!self) fail(`--owner ${SELF_OWNER} needs an agent context: this process has no registered agent id (run it inside an \`ay\`-managed agent, or pass an explicit --owner).`);
187
+ return self.agentId;
188
+ }
115
189
  const newCmd = {
116
190
  command: "new <summary..>",
117
191
  describe: "create a new task",
@@ -129,7 +203,10 @@ const newCmd = {
129
203
  }).option("acceptance-criteria", {
130
204
  type: "string",
131
205
  describe: "definition of done for this task — what an independent validator should check before approving/verifying it"
132
- }).option("owner", { type: "string" }).option("tag", {
206
+ }).option("owner", {
207
+ type: "string",
208
+ describe: `"me" (this agent), "${NO_OWNER}" to leave unowned, or any handle. Default: this agent when run inside one, else unowned`
209
+ }).option("tag", {
133
210
  type: "string",
134
211
  array: true
135
212
  }).option("dep", {
@@ -138,16 +215,17 @@ const newCmd = {
138
215
  describe: "blocker task id(s)"
139
216
  }),
140
217
  handler: async (argv) => {
141
- const store = await openStore(argv.root);
218
+ const store = await storeFor(argv);
142
219
  const summary = argv.summary.map(String).join(" ");
143
220
  if (!summary) fail("usage: ay todo new <summary> --kind <kind> [--description ...] [--tier ...] [--acceptance-criteria ...] [--owner ...] [--tag t]... [--dep id]...");
221
+ const owner = argv.owner === void 0 ? (await resolveSelf())?.agentId : await resolveOwnerArg(argv.owner);
144
222
  const rec = await store.create({
145
223
  summary,
146
224
  kind: parseKind(argv.kind),
147
225
  description: argv.description,
148
226
  targetTier: argv.tier,
149
227
  acceptanceCriteria: argv["acceptance-criteria"],
150
- owner: argv.owner,
228
+ owner,
151
229
  tags: argv.tag ?? [],
152
230
  blockedBy: argv.dep ?? []
153
231
  });
@@ -157,19 +235,64 @@ const newCmd = {
157
235
  const lsCmd = {
158
236
  command: "ls",
159
237
  describe: "list tasks (filter by --kind, --state, --owner, --tag, --blocked)",
160
- builder: (y) => y.option("kind", { type: "string" }).option("state", { type: "string" }).option("owner", { type: "string" }).option("tag", { type: "string" }).option("blocked", {
238
+ builder: (y) => y.option("kind", { type: "string" }).option("state", { type: "string" }).option("owner", {
239
+ type: "string",
240
+ describe: `owner handle, or "${SELF_OWNER}" for this agent`
241
+ }).option("tag", { type: "string" }).option("blocked", {
161
242
  type: "boolean",
162
243
  default: false
163
244
  }),
164
245
  handler: async (argv) => {
165
- const tasks = (await openStore(argv.root)).list({
246
+ const tasks = (await storeFor(argv)).list({
166
247
  kind: argv.kind ? parseKind(argv.kind) : void 0,
167
248
  state: argv.state,
168
- owner: argv.owner,
249
+ owner: await resolveOwnerArg(argv.owner),
169
250
  tag: argv.tag,
170
251
  blocked: argv.blocked
171
252
  });
172
- emit(argv, tasks, renderList(tasks));
253
+ const agents = await liveAgents();
254
+ emit(argv, tasks.map((t) => ({
255
+ ...t,
256
+ ownerLiveness: ownerLiveness(t.owner, agents),
257
+ ...blockedOnAgent(t.block) ? { waitingOnLiveness: ownerLiveness(blockedOnAgent(t.block), agents) } : {}
258
+ })), renderList(tasks, agents));
259
+ }
260
+ };
261
+ /**
262
+ * `ay todo claim <id>` — take ownership of a task.
263
+ *
264
+ * Refuses when the current owner is an agent the registry still reports as
265
+ * `active`/`idle`, because in a shared store the overwhelmingly likely reading
266
+ * of "task X is owned by a running agent" is that the agent is working on it
267
+ * right now; silently reassigning would put two agents on one task, each
268
+ * believing it is theirs. A dead owner (`exited`) or a non-agent owner is
269
+ * taken over without ceremony — that is the recovery path this verb exists
270
+ * for. `--force` covers the rest ("it's wedged, I'm taking it").
271
+ */
272
+ const claimCmd = {
273
+ command: "claim <id>",
274
+ describe: "take ownership of a task (refuses to steal from a live agent)",
275
+ builder: (y) => y.positional("id", {
276
+ type: "string",
277
+ demandOption: true
278
+ }).option("owner", {
279
+ type: "string",
280
+ describe: `who to assign to (default: "${SELF_OWNER}", this agent)`
281
+ }).option("force", {
282
+ type: "boolean",
283
+ default: false,
284
+ describe: "claim even if the current owner agent is still running"
285
+ }),
286
+ handler: async (argv) => {
287
+ const store = await storeFor(argv);
288
+ const before = store.get(argv.id);
289
+ if (!before) fail(`no such task: ${argv.id}`);
290
+ const next = await resolveOwnerArg(argv.owner ?? SELF_OWNER);
291
+ if (!next) fail(`--owner ${NO_OWNER} is not a claim — use \`ay todo claim <id> --owner <who>\``);
292
+ const held = ownerLiveness(before.owner, await liveAgents());
293
+ if (before.owner && before.owner !== next && !argv.force && (held === "active" || held === "idle")) fail(`task ${argv.id} is owned by ${before.owner}, an agent that is still ${held}. Re-run with --force to take it anyway.`);
294
+ const rec = await store.setOwner(argv.id, next, before.owner ?? null);
295
+ emit(argv, rec, `claimed ${rec._id} → ${next}\n${renderRecord(rec)}`);
173
296
  }
174
297
  };
175
298
  const getCmd = {
@@ -180,9 +303,9 @@ const getCmd = {
180
303
  demandOption: true
181
304
  }),
182
305
  handler: async (argv) => {
183
- const rec = (await openStore(argv.root)).get(argv.id);
306
+ const rec = (await storeFor(argv)).get(argv.id);
184
307
  if (!rec) fail(`no such task: ${argv.id}`);
185
- emit(argv, rec, renderRecord(rec));
308
+ emit(argv, rec, renderRecord(rec, await liveAgents()));
186
309
  }
187
310
  };
188
311
  const transitionCmd = {
@@ -196,7 +319,7 @@ const transitionCmd = {
196
319
  demandOption: true
197
320
  }),
198
321
  handler: async (argv) => {
199
- const rec = await (await openStore(argv.root)).transition(argv.id, argv.toState);
322
+ const rec = await (await storeFor(argv)).transition(argv.id, argv.toState);
200
323
  emit(argv, rec, `transitioned ${rec._id} -> ${rec.state}\n${renderRecord(rec)}`);
201
324
  }
202
325
  };
@@ -214,7 +337,7 @@ const approveCmd = {
214
337
  demandOption: true
215
338
  }).option("note", { type: "string" }).option("link", { type: "string" }),
216
339
  handler: async (argv) => {
217
- const rec = await (await openStore(argv.root)).approve(argv.id, argv.gate, argv.validatorIdentity, {
340
+ const rec = await (await storeFor(argv)).approve(argv.id, argv.gate, argv.validatorIdentity, {
218
341
  note: argv.note,
219
342
  link: argv.link
220
343
  });
@@ -229,7 +352,7 @@ const verifyCmd = {
229
352
  demandOption: true
230
353
  }).positional("gate", { type: "string" }),
231
354
  handler: async (argv) => {
232
- const rec = await (await openStore(argv.root)).verify(argv.id, argv.gate || void 0);
355
+ const rec = await (await storeFor(argv)).verify(argv.id, argv.gate || void 0);
233
356
  emit(argv, rec, `verified ${rec._id} -> ${rec.state}\n${renderRecord(rec)}`);
234
357
  }
235
358
  };
@@ -311,7 +434,7 @@ const blockCmd = {
311
434
  };
312
435
  break;
313
436
  }
314
- const rec = await (await openStore(argv.root)).setBlock(argv.id, block);
437
+ const rec = await (await storeFor(argv)).setBlock(argv.id, block);
315
438
  emit(argv, rec, `blocked ${rec._id}: ${describeBlock(block)}\n${renderRecord(rec)}`);
316
439
  }
317
440
  };
@@ -323,7 +446,7 @@ const unblockCmd = {
323
446
  demandOption: true
324
447
  }),
325
448
  handler: async (argv) => {
326
- const rec = await (await openStore(argv.root)).setBlock(argv.id, null);
449
+ const rec = await (await storeFor(argv)).setBlock(argv.id, null);
327
450
  emit(argv, rec, `unblocked ${rec._id}\n${renderRecord(rec)}`);
328
451
  }
329
452
  };
@@ -339,7 +462,7 @@ const setCriteriaCmd = {
339
462
  demandOption: true
340
463
  }),
341
464
  handler: async (argv) => {
342
- const store = await openStore(argv.root);
465
+ const store = await storeFor(argv);
343
466
  const text = argv.text.map(String).join(" ");
344
467
  const rec = await store.setAcceptanceCriteria(argv.id, text);
345
468
  emit(argv, rec, `set acceptance criteria on ${rec._id}\n${renderRecord(rec)}`);
@@ -362,7 +485,7 @@ const depCmd = {
362
485
  describe: "task id it waits for"
363
486
  }),
364
487
  handler: async (argv) => {
365
- const store = await openStore(argv.root);
488
+ const store = await storeFor(argv);
366
489
  try {
367
490
  const rec = argv.verb === "add" ? await store.addDep(argv.id, argv.blockerId) : await store.rmDep(argv.id, argv.blockerId);
368
491
  emit(argv, rec, `${argv.verb === "add" ? "added" : "removed"} dep ${argv.blockerId} on ${rec._id}\n${renderRecord(rec)}`);
@@ -380,7 +503,7 @@ const treeCmd = {
380
503
  describe: "root task id (default: all roots)"
381
504
  }),
382
505
  handler: async (argv) => {
383
- const tasks = (await openStore(argv.root)).all();
506
+ const tasks = (await storeFor(argv)).all();
384
507
  if (argv.format === "json") emit(argv, buildTreeJSON(tasks, argv.id), "");
385
508
  else process.stdout.write(renderTree(tasks, argv.id) + "\n");
386
509
  }
@@ -389,7 +512,7 @@ const digestCmd = {
389
512
  command: "digest",
390
513
  describe: "per-tag board: state counts, blockers, unblocked tasks",
391
514
  handler: async (argv) => {
392
- const tasks = (await openStore(argv.root)).all();
515
+ const tasks = (await storeFor(argv)).all();
393
516
  if (argv.format === "json") emit(argv, {
394
517
  tasks,
395
518
  unblocked: unblockedTasks(tasks).map((t) => t._id)
@@ -401,7 +524,7 @@ const reconcileCmd = {
401
524
  command: "reconcile",
402
525
  describe: "apply automation: orphan dead-owned tasks, clear stale waiting-on-agent blocks, auto-verify, report unblocked tasks",
403
526
  handler: async (argv) => {
404
- const store = await openStore(argv.root);
527
+ const store = await storeFor(argv);
405
528
  const agents = await readGlobalPids({ liveOnly: false });
406
529
  const registeredGates = new Set(store.registeredGateNames());
407
530
  const actions = reconcileTodos(store.all(), agents, registeredGates);
@@ -421,6 +544,9 @@ const reconcileCmd = {
421
544
  applied.push(`auto-verified ${action.taskId} -> ${result.state}`);
422
545
  break;
423
546
  }
547
+ case "answerer-gone":
548
+ applied.push(`${action.taskId}: ${action.agentId} exited without answering` + (action.owner ? ` (asked by ${action.owner})` : ""));
549
+ break;
424
550
  case "notify-unblocked":
425
551
  applied.push(`${action.taskId} is now unblocked (owner: ${action.owner})`);
426
552
  break;
@@ -435,9 +561,9 @@ const reconcileCmd = {
435
561
  }
436
562
  };
437
563
  async function runTodoSubcommand(rest0) {
438
- await yargs(rest0).scriptName("ay todo").option("root", {
564
+ const y = yargs(rest0).scriptName("ay todo").option("root", {
439
565
  type: "string",
440
- default: process.cwd(),
566
+ defaultDescription: "the repo's common root (shared across worktrees), else cwd",
441
567
  describe: "project root holding .agent-yes/todos.jsonl",
442
568
  global: true
443
569
  }).option("format", {
@@ -448,12 +574,13 @@ async function runTodoSubcommand(rest0) {
448
574
  }).check((argv) => {
449
575
  if (argv.root === "") fail("--root must not be empty");
450
576
  return true;
451
- }).command(newCmd).command(lsCmd).command(getCmd).command(transitionCmd).command(approveCmd).command(verifyCmd).command(setCriteriaCmd).command(blockCmd).command(unblockCmd).command(depCmd).command(treeCmd).command(digestCmd).command(reconcileCmd).demandCommand(1, "unknown \"ay todo\" verb (expected: new/ls/get/transition/approve/verify/set-criteria/block/unblock/dep/tree/digest/reconcile)").strict().help().version(false).exitProcess(false).fail((msg, err) => {
577
+ }).command(newCmd).command(lsCmd).command(getCmd).command(transitionCmd).command(approveCmd).command(verifyCmd).command(setCriteriaCmd).command(blockCmd).command(unblockCmd).command(depCmd).command(claimCmd).command(treeCmd).command(digestCmd).command(reconcileCmd).strict().help().version(false).exitProcess(false).fail((msg, err) => {
452
578
  throw err ?? new Error(msg);
453
- }).parseAsync();
579
+ });
580
+ if ((await y.parseAsync())._.length === 0) y.showHelp((s) => process.stdout.write(`${s}\n`));
454
581
  return 0;
455
582
  }
456
583
 
457
584
  //#endregion
458
585
  export { runTodoSubcommand };
459
- //# sourceMappingURL=todoCli-BYGZ7_C5.js.map
586
+ //# sourceMappingURL=todoCli-DUoxA1dh.js.map
@@ -0,0 +1,138 @@
1
+ import { i as readGlobalPids } from "./globalPidIndex-Dq4KY2IV.js";
2
+ import { execFile } from "child_process";
3
+ import path from "path";
4
+ import { promisify } from "util";
5
+
6
+ //#region ts/todoRoot.ts
7
+ /**
8
+ * Where `ay todo`'s store lives when the caller did not say.
9
+ *
10
+ * The engine is meant to be shared by every agent working on a repo, but the
11
+ * store is a file at `<root>/.agent-yes/todos.jsonl`, so "which root" is what
12
+ * actually decides who can see whom. Defaulting to `process.cwd()` — the
13
+ * original behavior — quietly breaks that the moment agents run in git
14
+ * WORKTREES (this project's own normal mode: `.claude/worktrees/<name>/`, one
15
+ * agent each): every worktree is a different cwd, so every agent gets a
16
+ * PRIVATE todo list, while `reconcileTodos` goes on matching task owners
17
+ * against the GLOBAL cross-runtime agent index. That leaves the two halves of
18
+ * a cross-agent feature disagreeing about what "everyone" means, and it fails
19
+ * silently — an agent sees an empty list, not an error.
20
+ *
21
+ * So the default root is the repo's COMMON root: the main worktree, shared by
22
+ * every linked worktree of the same repo. That is the parent of `git rev-parse
23
+ * --git-common-dir`, NOT `--show-toplevel` — inside a linked worktree
24
+ * `--show-toplevel` returns that worktree's own path (exactly the private
25
+ * answer we are trying to get away from), while `--git-common-dir` is by
26
+ * definition the directory all worktrees of a repo share.
27
+ *
28
+ * Scope deliberately stops at the repo. Going fleet-global (one store for
29
+ * every repo on the machine, the way `ay ls` lists every agent) is a different
30
+ * product decision — unrelated projects' tasks in one list — and is not taken
31
+ * here; a caller who wants that passes an explicit `--root`.
32
+ */
33
+ const execFileAsync = promisify(execFile);
34
+ /**
35
+ * Minimal `git` runner: stdout on success, `null` if git is missing, this
36
+ * isn't a repo, or it hangs.
37
+ *
38
+ * `child_process` rather than `Bun.spawn` (which sibling modules like
39
+ * `subcommands.ts` use) so this module works under BOTH runtimes. Bun
40
+ * implements `child_process`, while `Bun` is undefined under Node — and the
41
+ * failure mode there is the quiet kind: the catch below would swallow the
42
+ * ReferenceError and every lookup would fall back to cwd, i.e. exactly the
43
+ * per-directory store this module exists to prevent, with no error to notice.
44
+ */
45
+ const spawnGit = async (args, cwd) => {
46
+ try {
47
+ const { stdout } = await execFileAsync("git", args, {
48
+ cwd,
49
+ timeout: 2e3
50
+ });
51
+ return stdout;
52
+ } catch {
53
+ return null;
54
+ }
55
+ };
56
+ /**
57
+ * Resolve the todo store root, most explicit source first:
58
+ *
59
+ * 1. `--root <dir>` — the caller said it; never second-guessed.
60
+ * 2. `$AGENT_YES_TODO_ROOT` — pins a root for a whole shell/agent session
61
+ * (also how tests point at a scratch dir with no git repo involved).
62
+ * 3. the repo's common root — shared across worktrees, see the file header.
63
+ * 4. `cwd` — not a git repo at all.
64
+ *
65
+ * Relative values in (1)/(2) resolve against `cwd`, so `--root .` keeps
66
+ * meaning "here" instead of depending on where the process happened to start.
67
+ */
68
+ async function resolveTodoRoot(explicit, cwd = process.cwd(), env = process.env, runGit = spawnGit) {
69
+ if (explicit) return path.resolve(cwd, explicit);
70
+ const pinned = env.AGENT_YES_TODO_ROOT;
71
+ if (pinned) return path.resolve(cwd, pinned);
72
+ const common = (await runGit(["rev-parse", "--git-common-dir"], cwd))?.trim();
73
+ if (!common) return path.resolve(cwd);
74
+ return path.dirname(path.resolve(cwd, common));
75
+ }
76
+
77
+ //#endregion
78
+ //#region ts/todoIdentity.ts
79
+ /**
80
+ * "Who am I" for `ay todo`, so a task's owner is a value the rest of the
81
+ * engine can actually reason about.
82
+ *
83
+ * `TodoRecord.owner` is deliberately opaque — a human handle OR an agent's
84
+ * identifier — but `todoAutomation.ts` gives one specific string special
85
+ * meaning: `deadOwnerAgent()` looks the owner up in the global agent index by
86
+ * `agent_id`, and orphans the task when that agent has exited. An owner that
87
+ * is anything else (a human name, a bare pid, a hostname) simply never matches
88
+ * a record, and is therefore treated as a human owner and never orphaned.
89
+ *
90
+ * That asymmetry is the reason this module refuses to guess. Writing "some
91
+ * plausible identifier" for an agent owner would not fail loudly; it would
92
+ * produce a task that LOOKS assigned to an agent and silently opts out of
93
+ * orphan recovery forever — the one automation that exists to notice its owner
94
+ * died. So `resolveSelfAgentId` returns the registry's own `agent_id` or
95
+ * nothing, and the CLI turns "nothing" into an error the caller can read.
96
+ *
97
+ * Resolution mirrors `resolveSender()` in `subcommands.ts`: the wrapper
98
+ * injects its own pid as `AGENT_YES_PID` into the agent's environment (the
99
+ * agent's own pid is not knowable until after spawn), so the env value maps
100
+ * back to a record via `wrapper_pid`, falling back to `pid` for a process that
101
+ * IS the wrapper.
102
+ */
103
+ /** The literal an owner argument uses to mean "the agent running this command". */
104
+ const SELF_OWNER = "me";
105
+ /** The literal that explicitly means "no owner" — distinct from omitting the flag, which defaults to self. */
106
+ const NO_OWNER = "none";
107
+ /**
108
+ * The calling agent's registry record, or `null` when this is a human shell
109
+ * (no `AGENT_YES_PID`) or the agent is not registered under a stable id.
110
+ *
111
+ * The `agent_id`-less case returns `null` rather than falling back to the pid:
112
+ * a pid is not what `reconcileTodos` compares against, so storing one would be
113
+ * indistinguishable from a human owner to every downstream consumer while
114
+ * looking, to a reader, like a working agent assignment.
115
+ */
116
+ async function resolveSelf(env = process.env, readPids = () => readGlobalPids()) {
117
+ const envPid = env.AGENT_YES_PID ? Number(env.AGENT_YES_PID) : null;
118
+ if (!envPid || Number.isNaN(envPid)) return null;
119
+ const recs = await readPids();
120
+ const rec = recs.find((r) => r.wrapper_pid === envPid) ?? recs.find((r) => r.pid === envPid);
121
+ if (!rec?.agent_id) return null;
122
+ return {
123
+ agentId: rec.agent_id,
124
+ pid: rec.pid,
125
+ cwd: rec.cwd,
126
+ cli: rec.cli,
127
+ title: rec.title
128
+ };
129
+ }
130
+ /** Look an owner up in an agent snapshot. Same `agent_id` matching `deadOwnerAgent()` uses, so the two never disagree about who is an agent. */
131
+ function ownerLiveness(owner, agents) {
132
+ if (!owner) return "unknown";
133
+ return agents.find((a) => a.agent_id === owner)?.status ?? "unknown";
134
+ }
135
+
136
+ //#endregion
137
+ export { resolveTodoRoot as a, resolveSelf as i, SELF_OWNER as n, ownerLiveness as r, NO_OWNER as t };
138
+ //# sourceMappingURL=todoIdentity-BffBxRHA.js.map