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
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { i as DONE_STATE, o as ORPHANED_STATE, p as transitionsOf } from "./todoStore-Wk8U4zJ8.js";
|
|
2
|
+
|
|
3
|
+
//#region ts/removeControlCharacters.ts
|
|
4
|
+
const ESC = String.fromCharCode(27);
|
|
5
|
+
const C1 = String.fromCharCode(155);
|
|
6
|
+
const BACKSLASH = String.fromCharCode(92);
|
|
7
|
+
const ST_PATTERN_SOURCE = ESC + BACKSLASH + BACKSLASH;
|
|
8
|
+
const OSC_PATTERN = new RegExp(ESC + "][^\x07\x1B]*(?:\x07|" + ST_PATTERN_SOURCE + ")", "g");
|
|
9
|
+
const CSI_PATTERN = new RegExp("[" + ESC + C1 + "][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]", "g");
|
|
10
|
+
function removeControlCharacters(str) {
|
|
11
|
+
return str.replace(OSC_PATTERN, "").replace(CSI_PATTERN, "");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region ts/todoBlock.ts
|
|
16
|
+
/**
|
|
17
|
+
* How a block of this type should be watched. Kept as one pure function so
|
|
18
|
+
* every caller (CLI rendering, automation, `/ask` aggregation) agrees on the
|
|
19
|
+
* same classification instead of re-deriving it ad hoc.
|
|
20
|
+
*/
|
|
21
|
+
function monitorHint(block) {
|
|
22
|
+
switch (block.type) {
|
|
23
|
+
case "blocked-by-task":
|
|
24
|
+
case "blocked-by-human": return "none";
|
|
25
|
+
case "waiting-on-agent":
|
|
26
|
+
case "waiting-on-answer": return "notify-agent";
|
|
27
|
+
case "blocked-by-external": return "poll-external";
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** One-line human-readable summary, used by CLI/digest rendering. */
|
|
31
|
+
function describeBlock(block) {
|
|
32
|
+
switch (block.type) {
|
|
33
|
+
case "blocked-by-task": return `blocked by task ${block.taskId}`;
|
|
34
|
+
case "blocked-by-human": return `waiting on ${block.who}${block.question ? `: ${block.question}` : ""}${block.actionLink ? ` (action: ${block.actionLink})` : ""}`;
|
|
35
|
+
case "blocked-by-external": return `waiting on external signal: ${block.signal}`;
|
|
36
|
+
case "waiting-on-agent": return `waiting on agent ${block.agentId}`;
|
|
37
|
+
case "waiting-on-answer": return `waiting for an answer from ${block.agentId}${block.question ? `: ${block.question}` : ""}`;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The agent this block is waiting on, if any — the one identifier a caller
|
|
42
|
+
* needs to ask "is that party still alive?". Centralised so every surface
|
|
43
|
+
* (list rendering, reconcile, JSON output) agrees on which block shapes name
|
|
44
|
+
* an agent, instead of each one re-listing the cases and quietly missing the
|
|
45
|
+
* next shape that gets added.
|
|
46
|
+
*/
|
|
47
|
+
function blockedOnAgent(block) {
|
|
48
|
+
if (!block) return null;
|
|
49
|
+
return block.type === "waiting-on-agent" || block.type === "waiting-on-answer" ? block.agentId : null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region ts/todoDigest.ts
|
|
54
|
+
/**
|
|
55
|
+
* Read-side views over the `ay todo` store: dependency-tree rendering and a
|
|
56
|
+
* per-tag digest board. Pure functions over an already-loaded task list — no
|
|
57
|
+
* I/O — so they are trivially unit-testable and reusable by both the CLI
|
|
58
|
+
* (`todoCli.ts`) and any future web view.
|
|
59
|
+
*
|
|
60
|
+
* `unblockedTasks` is ported near-verbatim from the equivalent, already
|
|
61
|
+
* mutation-tested algorithm in a private sibling CLI's `deps.ts` (a closed-source
|
|
62
|
+
* downstream consumer of an earlier, less general version of this idea) —
|
|
63
|
+
* generalized here to the new `TodoRecord` shape and the `done` state name
|
|
64
|
+
* from `todoLifecycle.ts` instead of a hardcoded status string.
|
|
65
|
+
*/
|
|
66
|
+
/**
|
|
67
|
+
* The full set of task ids `t` is waiting on: the structural `blockedBy`
|
|
68
|
+
* array PLUS, if `t.block` is a `blocked-by-task` shape, that block's own
|
|
69
|
+
* `taskId`. Without folding the latter in, a task blocked via
|
|
70
|
+
* `ay todo block --type blocked-by-task --task X` would never be detected
|
|
71
|
+
* as resolved once X reaches `done` — contradicting `todoBlock.ts`'s own
|
|
72
|
+
* documented promise that this block type "clears itself... pure data, no
|
|
73
|
+
* monitor needed" (codex-review Important: the two mechanisms didn't
|
|
74
|
+
* actually talk to each other, making `blocked-by-task` blocks inert).
|
|
75
|
+
*/
|
|
76
|
+
function declaredTaskDeps(t) {
|
|
77
|
+
return t.block?.type === "blocked-by-task" ? [...t.blockedBy, t.block.taskId] : t.blockedBy;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* A task is "unblocked" when it is not itself finished, it declares task
|
|
81
|
+
* dependencies (via `blockedBy` and/or a `blocked-by-task` block), and
|
|
82
|
+
* every one of them has reached `done`.
|
|
83
|
+
* Surfaced for a caller to act on — never auto-applied here (deciding to
|
|
84
|
+
* resume a task is left to automation, a later milestone, or a human).
|
|
85
|
+
*/
|
|
86
|
+
function unblockedTasks(tasks) {
|
|
87
|
+
const byId = new Map(tasks.map((t) => [t._id, t]));
|
|
88
|
+
return tasks.filter((t) => {
|
|
89
|
+
if (t.state === DONE_STATE) return false;
|
|
90
|
+
const deps = declaredTaskDeps(t);
|
|
91
|
+
if (deps.length === 0) return false;
|
|
92
|
+
return deps.every((d) => byId.get(d)?.state === DONE_STATE);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/** Blockers of `t` that have not reached `done` yet (a missing id is reported as-is, still "open"). */
|
|
96
|
+
function openBlockers(t, byId) {
|
|
97
|
+
return declaredTaskDeps(t).filter((d) => byId.get(d)?.state !== DONE_STATE);
|
|
98
|
+
}
|
|
99
|
+
function nodeLine(t) {
|
|
100
|
+
return [`${t._id} [${t.state}] ${t.summary}`, ...t.owner ? [`owner:${t.owner}`] : []].join(" ");
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Dependency tree, rendered from roots (tasks nothing depends on, that
|
|
104
|
+
* themselves declare at least one dependency) down their `blockedBy` edges:
|
|
105
|
+
* a parent's children are what it is waiting for.
|
|
106
|
+
*/
|
|
107
|
+
function renderTree(tasks, rootId) {
|
|
108
|
+
const byId = new Map(tasks.map((t) => [t._id, t]));
|
|
109
|
+
const dependedOn = new Set(tasks.flatMap((t) => t.blockedBy));
|
|
110
|
+
const roots = rootId ? [byId.get(rootId) ?? null].filter((t) => t !== null) : tasks.filter((t) => !dependedOn.has(t._id) && t.blockedBy.length > 0);
|
|
111
|
+
if (rootId && roots.length === 0) throw new Error(`no such task: ${rootId}`);
|
|
112
|
+
if (roots.length === 0) return "(no dependency links)";
|
|
113
|
+
const lines = [];
|
|
114
|
+
const walk = (t, prefix, isLast, depth, seen) => {
|
|
115
|
+
lines.push(depth === 0 ? nodeLine(t) : `${prefix}${isLast ? "└─" : "├─"} ${nodeLine(t)}`);
|
|
116
|
+
if (seen.has(t._id)) return;
|
|
117
|
+
seen.add(t._id);
|
|
118
|
+
const deps = t.blockedBy.map((d) => byId.get(d)).filter((x) => x !== void 0);
|
|
119
|
+
deps.forEach((d, i) => {
|
|
120
|
+
walk(d, depth === 0 ? "" : `${prefix}${isLast ? " " : "│ "}`, i === deps.length - 1, depth + 1, seen);
|
|
121
|
+
});
|
|
122
|
+
};
|
|
123
|
+
roots.forEach((r) => walk(r, "", true, 0, /* @__PURE__ */ new Set()));
|
|
124
|
+
return lines.join("\n");
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* JSON-shaped equivalent of `renderTree` — same roots-and-edges logic, built
|
|
128
|
+
* as data instead of formatted lines, so a machine caller (`--format json`)
|
|
129
|
+
* gets the actual tree structure rather than a string it would have to
|
|
130
|
+
* re-parse (codex-review Important: the CLI previously ignored --format for
|
|
131
|
+
* this command entirely, always writing the human text with exit code 0).
|
|
132
|
+
*/
|
|
133
|
+
function buildTreeJSON(tasks, rootId) {
|
|
134
|
+
const byId = new Map(tasks.map((t) => [t._id, t]));
|
|
135
|
+
const dependedOn = new Set(tasks.flatMap((t) => t.blockedBy));
|
|
136
|
+
const roots = rootId ? [byId.get(rootId) ?? null].filter((t) => t !== null) : tasks.filter((t) => !dependedOn.has(t._id) && t.blockedBy.length > 0);
|
|
137
|
+
if (rootId && roots.length === 0) throw new Error(`no such task: ${rootId}`);
|
|
138
|
+
const build = (t, ancestors) => {
|
|
139
|
+
const node = {
|
|
140
|
+
id: t._id,
|
|
141
|
+
state: t.state,
|
|
142
|
+
summary: t.summary,
|
|
143
|
+
...t.owner ? { owner: t.owner } : {},
|
|
144
|
+
children: []
|
|
145
|
+
};
|
|
146
|
+
if (ancestors.has(t._id)) return node;
|
|
147
|
+
const nextAncestors = new Set(ancestors).add(t._id);
|
|
148
|
+
node.children = t.blockedBy.map((d) => byId.get(d)).filter((x) => x !== void 0).map((d) => build(d, nextAncestors));
|
|
149
|
+
return node;
|
|
150
|
+
};
|
|
151
|
+
return roots.map((r) => build(r, /* @__PURE__ */ new Set()));
|
|
152
|
+
}
|
|
153
|
+
/** Per-tag board: state counts per tag, plus an unblocked-tasks callout. */
|
|
154
|
+
function renderDigest(tasks) {
|
|
155
|
+
if (tasks.length === 0) return "(no tasks)";
|
|
156
|
+
const byId = new Map(tasks.map((t) => [t._id, t]));
|
|
157
|
+
const unblocked = new Set(unblockedTasks(tasks).map((t) => t._id));
|
|
158
|
+
const streams = /* @__PURE__ */ new Map();
|
|
159
|
+
for (const t of tasks) for (const tag of t.tags.length ? t.tags : ["(untagged)"]) {
|
|
160
|
+
if (!streams.has(tag)) streams.set(tag, []);
|
|
161
|
+
streams.get(tag).push(t);
|
|
162
|
+
}
|
|
163
|
+
const lines = [];
|
|
164
|
+
for (const [stream, items] of [...streams.entries()].sort(([a], [b]) => a.localeCompare(b))) {
|
|
165
|
+
const allStates = [...new Set(items.map((t) => t.state))].sort();
|
|
166
|
+
const counts = allStates.map((s) => `${s}:${items.filter((t) => t.state === s).length}`).join(" ");
|
|
167
|
+
lines.push(`## ${stream} (${counts})`);
|
|
168
|
+
for (const s of allStates) for (const t of items.filter((x) => x.state === s)) {
|
|
169
|
+
const open = openBlockers(t, byId);
|
|
170
|
+
const extra = [
|
|
171
|
+
...t.owner ? [`owner:${t.owner}`] : [],
|
|
172
|
+
...t.block ? [`block:${t.block.type}`] : [],
|
|
173
|
+
...open.length ? [`blockedBy:${open.join(",")}`] : [],
|
|
174
|
+
...unblocked.has(t._id) ? ["UNBLOCKED"] : []
|
|
175
|
+
];
|
|
176
|
+
lines.push(` [${t.state}] ${t._id} ${t.summary}${extra.length ? ` [${extra.join(" ")}]` : ""}`);
|
|
177
|
+
}
|
|
178
|
+
lines.push("");
|
|
179
|
+
}
|
|
180
|
+
const ub = unblockedTasks(tasks);
|
|
181
|
+
if (ub.length) {
|
|
182
|
+
lines.push("## unblocked (all blockers reached done — resume these)");
|
|
183
|
+
for (const t of ub) lines.push(` ${t._id} ${t.summary}${t.owner ? ` (owner:${t.owner})` : ""}`);
|
|
184
|
+
lines.push("");
|
|
185
|
+
}
|
|
186
|
+
return lines.join("\n").trimEnd();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
//#endregion
|
|
190
|
+
//#region ts/todoAutomation.ts
|
|
191
|
+
/**
|
|
192
|
+
* Reactive layer for the `ay todo` engine (Milestone 2): makes the engine
|
|
193
|
+
* self-driving for agent-facing signals `agent-yes` already tracks, instead
|
|
194
|
+
* of requiring every state change to be typed by hand.
|
|
195
|
+
*
|
|
196
|
+
* `reconcileTodos` is pure — decision logic only, no I/O — exactly the same
|
|
197
|
+
* "decide" / "do" split a private sibling CLI's existing `watchdog.ts`
|
|
198
|
+
* (`decideActions`) already uses successfully: the hard-to-get-right parts
|
|
199
|
+
* (edge cases) live in a function that takes plain data in and returns plain
|
|
200
|
+
* data out, so they are trivially unit-testable without a real store, real
|
|
201
|
+
* processes, or real time. Applying the returned actions to a real
|
|
202
|
+
* `TodoStore` (I/O) is a separate, thin step (see `todoCli.ts`'s `reconcile`
|
|
203
|
+
* verb) deliberately kept out of this file — including revalidating each
|
|
204
|
+
* action against a FRESH record immediately before writing, since the
|
|
205
|
+
* snapshot this function decided from can be stale by the time the caller
|
|
206
|
+
* applies it (codex-review Important, see the action shapes below).
|
|
207
|
+
*/
|
|
208
|
+
/**
|
|
209
|
+
* `TodoRecord.owner` is opaque — a human name/handle OR a tracked agent's
|
|
210
|
+
* stable id, this module cannot tell which just by looking at the string.
|
|
211
|
+
* The only safe signal is whether the identifier appears in the live-agent
|
|
212
|
+
* registry AT ALL: a KNOWN agent id whose latest record says `exited` is
|
|
213
|
+
* confirmed dead and orphan-eligible; an identifier with NO record at all is
|
|
214
|
+
* assumed to be a human owner (who was never going to appear there) and is
|
|
215
|
+
* never orphaned on that basis alone. Getting this backwards would orphan
|
|
216
|
+
* every human-owned task on every tick, since a human obviously never shows
|
|
217
|
+
* up in the agent process table.
|
|
218
|
+
*/
|
|
219
|
+
function deadOwnerAgent(ownerId, agents) {
|
|
220
|
+
const rec = agents.find((a) => a.agent_id === ownerId);
|
|
221
|
+
return rec !== void 0 && rec.status === "exited";
|
|
222
|
+
}
|
|
223
|
+
/** Up to `limit` OTHER agent ids currently idle — candidates to hand an orphaned task to. */
|
|
224
|
+
function idleCandidates(excludeAgentId, agents, limit = 3) {
|
|
225
|
+
return agents.filter((a) => !!a.agent_id).filter((a) => a.agent_id !== excludeAgentId && a.status === "idle").map((a) => a.agent_id).slice(0, limit);
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Decide what should happen given the current tasks and the current live-agent
|
|
229
|
+
* snapshot. Four rules, each traceable to a specific requirement (see
|
|
230
|
+
* `docs/2026-07-23-todo-lifecycle-execplan.md`'s Milestone 2 section):
|
|
231
|
+
*
|
|
232
|
+
* - owner's agent process is gone and the task isn't finished → `orphan`.
|
|
233
|
+
* - a `waiting-on-agent` block whose agent went idle or exited successfully
|
|
234
|
+
* (exit code 0) → `clear-waiting-on-agent`.
|
|
235
|
+
* - a task currently in `unblockedTasks()` with an owner → `notify-unblocked`,
|
|
236
|
+
* reported on EVERY call (no dedup/state file): there is no real delivery
|
|
237
|
+
* channel wired yet (see `todoCli.ts`'s `reconcile` verb doc comment), so
|
|
238
|
+
* persisting "already notified" would silently retire the one signal an
|
|
239
|
+
* owner would ever get with nobody having actually received it
|
|
240
|
+
* (codex-review Important) — the same no-dedup convention `renderDigest`'s
|
|
241
|
+
* own "unblocked" section already uses.
|
|
242
|
+
* - a task in a state whose outgoing edge's gate is registered → `auto-verify`
|
|
243
|
+
* (the actual gate CALL is real I/O and happens in the caller via `store.verify()`;
|
|
244
|
+
* this only decides which tasks are eligible to try).
|
|
245
|
+
*
|
|
246
|
+
* An orphaned task is skipped for the OTHER rules this same tick (its owner is
|
|
247
|
+
* gone, so it cannot also be "waiting on" that same dead agent in a meaningful
|
|
248
|
+
* way, and it is no longer eligible for auto-verify either).
|
|
249
|
+
*/
|
|
250
|
+
function reconcileTodos(tasks, agents, registeredGates) {
|
|
251
|
+
const actions = [];
|
|
252
|
+
const orphanedThisTick = /* @__PURE__ */ new Set();
|
|
253
|
+
for (const t of tasks) {
|
|
254
|
+
if (t.state === DONE_STATE || t.state === ORPHANED_STATE) continue;
|
|
255
|
+
if (t.owner && deadOwnerAgent(t.owner, agents)) {
|
|
256
|
+
actions.push({
|
|
257
|
+
type: "orphan",
|
|
258
|
+
taskId: t._id,
|
|
259
|
+
from: t.state,
|
|
260
|
+
expectedOwner: t.owner,
|
|
261
|
+
candidates: idleCandidates(t.owner, agents)
|
|
262
|
+
});
|
|
263
|
+
orphanedThisTick.add(t._id);
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
if (t.block?.type === "waiting-on-answer") {
|
|
267
|
+
const block = t.block;
|
|
268
|
+
if (agents.find((a) => a.agent_id === block.agentId)?.status === "exited") actions.push({
|
|
269
|
+
type: "answerer-gone",
|
|
270
|
+
taskId: t._id,
|
|
271
|
+
agentId: block.agentId,
|
|
272
|
+
...t.owner ? { owner: t.owner } : {}
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
if (t.block?.type === "waiting-on-agent") {
|
|
276
|
+
const agentId = t.block.agentId;
|
|
277
|
+
const agent = agents.find((a) => a.agent_id === agentId);
|
|
278
|
+
if (agent && (agent.status === "idle" || agent.status === "exited" && agent.exit_code === 0)) actions.push({
|
|
279
|
+
type: "clear-waiting-on-agent",
|
|
280
|
+
taskId: t._id,
|
|
281
|
+
expectedAgentId: agentId
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
if (transitionsOf(t.kind).some((edge) => edge.from === t.state && edge.gate && registeredGates.has(edge.gate))) actions.push({
|
|
285
|
+
type: "auto-verify",
|
|
286
|
+
taskId: t._id
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
for (const t of unblockedTasks(tasks)) {
|
|
290
|
+
if (orphanedThisTick.has(t._id) || !t.owner) continue;
|
|
291
|
+
actions.push({
|
|
292
|
+
type: "notify-unblocked",
|
|
293
|
+
taskId: t._id,
|
|
294
|
+
owner: t.owner
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
return actions;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
//#endregion
|
|
301
|
+
export { renderTree as a, describeBlock as c, renderDigest as i, monitorHint as l, buildTreeJSON as n, unblockedTasks as o, openBlockers as r, blockedOnAgent as s, reconcileTodos as t, removeControlCharacters as u };
|
|
302
|
+
//# sourceMappingURL=todoAutomation-BETGOik9.js.map
|