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.
- package/README.md +73 -0
- package/dist/{SUPPORTED_CLIS-CpWKDCtL.js → SUPPORTED_CLIS-Bne04-66.js} +2 -2
- package/dist/SUPPORTED_CLIS-CdyjhpZP.js +12 -0
- package/dist/{agent-yes.config-DzNini0E.js → agent-yes.config-2xKNJM5Y.js} +2 -2
- package/dist/{agentShare-D0reU6LW.js → agentShare-BI126Ej9.js} +8 -8
- package/dist/askCli-Bbxt9Umz.js +272 -0
- package/dist/{browser-DSduVZeZ.js → browser-CorYp8Yu.js} +1 -1
- package/dist/{browser-B7eYrBy7.js → browser-_2ndf80G.js} +5 -5
- package/dist/callback-BnUbjH7p.js +13 -0
- package/dist/{callback-Dlqwh9n7.js → callback-DI2r3NrV.js} +5 -5
- package/dist/{callbackCore-K99x3C75.js → callbackCore-Bbe7rpN2.js} +1 -1
- package/dist/{callbackCore-DcptaZwH.js → callbackCore-C0Cg9RbS.js} +1 -1
- package/dist/{channels-DQaZbare.js → channels-3gz9gHP7.js} +6 -6
- package/dist/{channels-yE1YUHCl.js → channels-CkaCyvEp.js} +2 -2
- package/dist/channels.js +5 -5
- package/dist/cli.js +11 -11
- package/dist/{cmdPs-D8gsRFVV.js → cmdPs-CVnlflxP.js} +7 -7
- package/dist/{configShared-DtHMBCl7.js → configShared-DYxlvrH0.js} +1 -1
- package/dist/{cwdConflictWarn-BpweoAiy.js → cwdConflictWarn-Buy3r_qc.js} +1 -1
- package/dist/{cwdPassthroughHint-BlRuOGo_.js → cwdPassthroughHint-BlWO-h5w.js} +1 -1
- package/dist/{e2e-CUMZC53I.js → e2e-OLfcNxiv.js} +1 -1
- package/dist/{expose-DyiZ5AU0.js → expose-Bfo-dZdy.js} +1 -1
- package/dist/{forkNested-B_4c-TQk.js → forkNested-jc2ER9xn.js} +1 -1
- package/dist/{hist-BoZPJ2ah.js → hist-DiOYRXuD.js} +1 -1
- package/dist/{html2canvas-pro.esm-cq4l_ZIw.js → html2canvas-pro.esm-D19oTQeV.js} +1 -1
- package/dist/identity-BmDYOuZE.js +3 -0
- package/dist/identity-CLaJ9-uE.js +109 -0
- package/dist/index.js +4 -3
- package/dist/{initMsg-D8cmdY76.js → initMsg-Bat5PUDf.js} +1 -1
- package/dist/{initMsg-BKqWDYFk.js → initMsg-CTAIqBWF.js} +8 -4
- package/dist/{needsInput-103zhZ90.js → needsInput-DhfIwxse.js} +1 -1
- package/dist/{nodeRuntime-QihGFQbI.js → nodeRuntime-CKc1BN5J.js} +1 -1
- package/dist/{notifyDaemon-TKtSYtEO.js → notifyDaemon-Cf4KHDOr.js} +7 -7
- package/dist/{notifyStore-CEDiVyis.js → notifyStore-0hlFPa6z.js} +1 -1
- package/dist/{notifyStore-Cbw2ZJ0X.js → notifyStore-Dus2D1XA.js} +1 -1
- package/dist/{openBrowser-Cs46NiH6.js → openBrowser-Dmg2Oi4q.js} +1 -1
- package/dist/{parentLink-CTgZZCgw.js → parentLink-BwX4jaGD.js} +1 -1
- package/dist/{parentLink-CQrn998L.js → parentLink-DS-XflaJ.js} +1 -1
- package/dist/{parentPingLoop-BEvJ2ElR.js → parentPingLoop-Bxd3dXWO.js} +6 -6
- package/dist/{peer-DIl5xtx8.js → peer-Dr4zvtcd.js} +3 -3
- package/dist/peer-u1AUAZeA.js +4 -0
- package/dist/{remotes-M8FbM92D.js → remotes-BiftUp09.js} +3 -3
- package/dist/{remotes-DdAfueOu.js → remotes-IzQHupeo.js} +3 -3
- package/dist/{rustBinary-B9FEbqTu.js → rustBinary-B2lNFJSe.js} +2 -2
- package/dist/{schedule-Bmh5uaDr.js → schedule-D1CNvuKT.js} +8 -7
- package/dist/{serve-BqRNIxeq.js → serve-BaHCO0bU.js} +49 -42
- package/dist/{setup-GYkQYxlf.js → setup-B8EL0SWL.js} +4 -4
- package/dist/{share-Db_zb6gH.js → share-DTPZgbfH.js} +2 -2
- package/dist/{share-NUhi-Wdd.js → share-RgJUhzqG.js} +2 -2
- package/dist/{spawnGate-CZlMs6uV.js → spawnGate-C6YKlnmb.js} +1 -1
- package/dist/{spawnGate-DN4wNXHq.js → spawnGate-CS3oFdJ9.js} +2 -2
- package/dist/{store-BW5NBouz.js → store-C7I2CAu9.js} +1 -1
- package/dist/{subcommands-DJpVXIb6.js → subcommands-03ghe-aZ.js} +192 -157
- package/dist/{subcommands-DLrJfG4D.js → subcommands-BS4lnTrb.js} +6 -6
- package/dist/{systemPathLink-BHv1I6wh.js → systemPathLink-DGVcx0fg.js} +1 -1
- package/dist/{terminal-CRLPUaKF.js → terminal-DrCP-200.js} +2 -2
- package/dist/terminal.js +1 -1
- package/dist/todoAutomation-BETGOik9.js +302 -0
- package/dist/{todoCli-BYGZ7_C5.js → todoCli-DUoxA1dh.js} +164 -37
- package/dist/todoIdentity-BffBxRHA.js +138 -0
- package/dist/{todoAutomation-BQRW614S.js → todoStore-Wk8U4zJ8.js} +63 -280
- package/dist/{tray-CzGs9-ch.js → tray-Bxq_sfvU.js} +1 -1
- package/dist/trayApp-BDMzlxRo.js +5 -0
- package/dist/{trayApp-DLMUu2sB.js → trayApp-DC4qjut7.js} +2 -2
- package/dist/{ts-ClwhahH9.js → ts-D0Pk-WJg.js} +14 -8
- package/dist/{versionChecker-ZQYirfMp.js → versionChecker-V6ceVsu9.js} +2 -2
- package/dist/{webrtcLink-DyCj380i.js → webrtcLink-B72AQ2eb.js} +2 -2
- package/dist/{webrtcRemote-Bl8eowma.js → webrtcRemote-D68dkqFp.js} +3 -3
- package/dist/{widget-DtZ1WXkf.js → widget-BM03OkC1.js} +3 -3
- package/dist/widget.js +1 -1
- package/dist/widgets.js +5 -5
- package/dist/{workspaceConfig-CBKlgA_q.js → workspaceConfig-EK7XNsgR.js} +1 -1
- package/dist/{ws-CiR2nOD3.js → ws-6l7Wb_0r.js} +7 -7
- package/dist/{ws-Ba372Tym.js → ws-pRFrqms9.js} +3 -3
- package/lab/ui/console-logic.js +64 -0
- package/lab/ui/index.html +58 -4
- package/package.json +1 -1
- package/ts/askCli.spec.ts +304 -0
- package/ts/askCli.ts +378 -0
- package/ts/identity.spec.ts +80 -1
- package/ts/identity.ts +16 -9
- package/ts/index.ts +6 -1
- package/ts/initMsg.spec.ts +22 -14
- package/ts/initMsg.ts +7 -2
- package/ts/ipcLock.spec.ts +107 -0
- package/ts/ipcLock.ts +130 -0
- package/ts/serve.ts +68 -17
- package/ts/subcommandMirror.spec.ts +76 -0
- package/ts/subcommands.spec.ts +115 -10
- package/ts/subcommands.ts +108 -36
- package/ts/todoAutomation.spec.ts +64 -0
- package/ts/todoAutomation.ts +25 -2
- package/ts/todoBlock.spec.ts +31 -1
- package/ts/todoBlock.ts +33 -1
- package/ts/todoCli.spec.ts +199 -4
- package/ts/todoCli.ts +266 -42
- package/ts/todoIdentity.spec.ts +85 -0
- package/ts/todoIdentity.ts +76 -0
- package/ts/todoLifecycle.spec.ts +13 -1
- package/ts/todoLifecycle.ts +35 -1
- package/ts/todoRoot.spec.ts +117 -0
- package/ts/todoRoot.ts +90 -0
- package/ts/todoStore.spec.ts +25 -0
- package/ts/todoStore.ts +36 -2
- package/dist/SUPPORTED_CLIS-DDs0xUvV.js +0 -11
- package/dist/callback-Bgehh-IW.js +0 -13
- package/dist/peer-Cvntlw1i.js +0 -4
- package/dist/trayApp-CoD89Y1_.js +0 -5
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import "./logger-CDIsZ-Pp.js";
|
|
2
|
-
import {
|
|
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>`
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
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
|
-
|
|
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
|
|
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
|
|
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", {
|
|
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
|
|
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
|
|
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", {
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
564
|
+
const y = yargs(rest0).scriptName("ay todo").option("root", {
|
|
439
565
|
type: "string",
|
|
440
|
-
|
|
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).
|
|
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
|
-
})
|
|
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-
|
|
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
|