wezard 2.6.1 → 2.6.3
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/.claude-plugin/plugin.json +1 -1
- package/config.example.jsonc +3 -1
- package/dist/daemon/answer-transfer.js +56 -0
- package/dist/daemon/answer-transfer.js.map +1 -0
- package/dist/daemon/approval.js +1 -1
- package/dist/daemon/approval.js.map +1 -1
- package/dist/daemon/cfg-sync.js +1 -1
- package/dist/daemon/cfg-sync.js.map +1 -1
- package/dist/daemon/chat-name.js +4 -4
- package/dist/daemon/chat-name.js.map +1 -1
- package/dist/daemon/config-api.js +2 -2
- package/dist/daemon/config-api.js.map +1 -1
- package/dist/daemon/image-input.js +34 -0
- package/dist/daemon/image-input.js.map +1 -0
- package/dist/daemon/inbound.js +4 -11
- package/dist/daemon/inbound.js.map +1 -1
- package/dist/daemon/index.js +880 -280
- package/dist/daemon/index.js.map +1 -1
- package/dist/daemon/jobs.js +32 -13
- package/dist/daemon/jobs.js.map +1 -1
- package/dist/daemon/last-response.js +16 -6
- package/dist/daemon/last-response.js.map +1 -1
- package/dist/daemon/message-format.js +40 -0
- package/dist/daemon/message-format.js.map +1 -0
- package/dist/daemon/mirror-bridge.js +673 -295
- package/dist/daemon/mirror-bridge.js.map +1 -1
- package/dist/daemon/pane-input.js +48 -0
- package/dist/daemon/pane-input.js.map +1 -0
- package/dist/daemon/peers.js +15 -14
- package/dist/daemon/peers.js.map +1 -1
- package/dist/daemon/pending-items.js +8 -0
- package/dist/daemon/pending-items.js.map +1 -1
- package/dist/daemon/receipts.js +346 -120
- package/dist/daemon/receipts.js.map +1 -1
- package/dist/daemon/scheduled-delivery.js +52 -0
- package/dist/daemon/scheduled-delivery.js.map +1 -0
- package/dist/daemon/spawn-tmux.js +7 -2
- package/dist/daemon/spawn-tmux.js.map +1 -1
- package/dist/daemon/steward-model.js +10 -10
- package/dist/daemon/steward-model.js.map +1 -1
- package/dist/daemon/tasks.js +83 -27
- package/dist/daemon/tasks.js.map +1 -1
- package/dist/daemon/tmux-buffer.js +44 -9
- package/dist/daemon/tmux-buffer.js.map +1 -1
- package/dist/daemon/weixin-cmd.js +2 -4
- package/dist/daemon/weixin-cmd.js.map +1 -1
- package/dist/daemon/wizard.js +2 -1
- package/dist/daemon/wizard.js.map +1 -1
- package/dist/daemon/workflow-nodes.js +217 -0
- package/dist/daemon/workflow-nodes.js.map +1 -0
- package/dist/daemon/workflow-plan-http.js +83 -0
- package/dist/daemon/workflow-plan-http.js.map +1 -0
- package/dist/daemon/workflow-plans.js +274 -0
- package/dist/daemon/workflow-plans.js.map +1 -0
- package/dist/mcp/server.js +27 -1
- package/dist/mcp/server.js.map +1 -1
- package/dist/shared/config.js +12 -10
- package/dist/shared/config.js.map +1 -1
- package/dist/shared/json-map-store.js +46 -9
- package/dist/shared/json-map-store.js.map +1 -1
- package/dist/shared/keyed-scheduler.js +51 -0
- package/dist/shared/keyed-scheduler.js.map +1 -0
- package/dist/shared/message-footer.js +34 -0
- package/dist/shared/message-footer.js.map +1 -0
- package/dist/shared/model-tiers.js +1 -2
- package/dist/shared/model-tiers.js.map +1 -1
- package/dist/shared/reminder.js +3 -0
- package/dist/shared/reminder.js.map +1 -1
- package/dist/shared/session-label.js +19 -1
- package/dist/shared/session-label.js.map +1 -1
- package/dist/shared/workflow-plan.js +36 -0
- package/dist/shared/workflow-plan.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
// Opt-in execution history. Receipt completion and acceptance are separate facts.
|
|
2
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync, openSync, fsyncSync, closeSync } from "node:fs";
|
|
4
|
+
import { dirname, isAbsolute, resolve } from "node:path";
|
|
5
|
+
import { parseClosing } from "./peers.js";
|
|
6
|
+
import { expandHome } from "../shared/paths.js";
|
|
7
|
+
export const resultDigest = (body) => createHash("sha256").update(body).digest("hex");
|
|
8
|
+
const fileDigest = (path) => createHash("sha256").update(readFileSync(path)).digest("hex");
|
|
9
|
+
const versionsMatch = (files) => files.every((f) => {
|
|
10
|
+
try {
|
|
11
|
+
return isAbsolute(f.path) && /^[a-f0-9]{64}$/.test(f.sha256) && fileDigest(f.path) === f.sha256 && (!f.snapshot || fileDigest(f.snapshot) === f.sha256);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
export const createWorkflowNodes = (file) => {
|
|
18
|
+
const abs = resolve(expandHome(file));
|
|
19
|
+
// Corrupt/unwritable execution history must fail closed, unlike advisory stores.
|
|
20
|
+
let rows = existsSync(abs) ? JSON.parse(readFileSync(abs, "utf8")) : [];
|
|
21
|
+
if (!Array.isArray(rows) || rows.some((a) => ![a.id, a.node, a.owner, a.job, a.turn, a.to].every((s) => typeof s === "string" && s.length > 0)
|
|
22
|
+
|| !Number.isFinite(a.at)
|
|
23
|
+
|| (a.body !== undefined && typeof a.body !== "string")
|
|
24
|
+
|| (a.digest !== undefined && (typeof a.digest !== "string" || typeof a.body !== "string" || a.digest !== resultDigest(a.body)))
|
|
25
|
+
|| !["queued", "dispatching", "dispatch-unknown", "running", "needs-input", "errored", "terminal"].includes(a.execution)
|
|
26
|
+
|| !["pending", "accepted", "rejected"].includes(a.acceptance)
|
|
27
|
+
|| [a.stopped, a.exposed, a.canceled].some((v) => v !== undefined && typeof v !== "boolean")
|
|
28
|
+
|| (a.status !== undefined && !["done", "need", "error", "timeout", "silent", "dead", "canceled"].includes(a.status))
|
|
29
|
+
|| (a.acceptance === "accepted" && (a.execution !== "terminal" || a.status !== "done" || !a.body?.trim() || a.digest !== resultDigest(a.body)))
|
|
30
|
+
|| (a.files !== undefined && (!Array.isArray(a.files) || a.files.some((f) => typeof f.path !== "string" || typeof f.sha256 !== "string" || (f.snapshot !== undefined && (typeof f.snapshot !== "string" || !isAbsolute(f.snapshot))))))
|
|
31
|
+
|| (a.accountingVersion !== undefined && a.accountingVersion !== 1)
|
|
32
|
+
|| (a.dispatches !== undefined && (!Array.isArray(a.dispatches) || a.dispatches.some((d) => typeof d.key !== "string" || !d.key || (d.tell && (d.tell.from !== a.owner || d.tell.to !== a.to || d.tell.turn !== a.turn)) || typeof d.text !== "string" || !Number.isFinite(d.at) || !["task", "ask"].includes(d.kind) || ([d.free, d.continuation].some((v) => v !== undefined && typeof v !== "boolean"))) || new Set(a.dispatches.map((d) => d.key)).size !== a.dispatches.length))
|
|
33
|
+
|| (a.tell && (a.tell.from !== a.owner || a.tell.to !== a.to || a.tell.turn !== a.turn))))
|
|
34
|
+
throw new Error("invalid workflow node history");
|
|
35
|
+
if (new Set(rows.map((a) => a.id)).size !== rows.length || new Set(rows.map((a) => `${a.owner}\0${a.turn}`)).size !== rows.length)
|
|
36
|
+
throw new Error("duplicate workflow attempt identity");
|
|
37
|
+
const write = (next) => {
|
|
38
|
+
mkdirSync(dirname(abs), { recursive: true });
|
|
39
|
+
const tmp = `${abs}.${randomUUID()}.tmp`;
|
|
40
|
+
writeFileSync(tmp, JSON.stringify(next, null, 2), { mode: 0o600 });
|
|
41
|
+
const fd = openSync(tmp, "r");
|
|
42
|
+
try {
|
|
43
|
+
fsyncSync(fd);
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
closeSync(fd);
|
|
47
|
+
}
|
|
48
|
+
renameSync(tmp, abs);
|
|
49
|
+
const dir = openSync(dirname(abs), "r");
|
|
50
|
+
try {
|
|
51
|
+
fsyncSync(dir);
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
closeSync(dir);
|
|
55
|
+
}
|
|
56
|
+
rows = next;
|
|
57
|
+
};
|
|
58
|
+
const update = (id, change) => {
|
|
59
|
+
const old = rows.find((a) => a.id === id);
|
|
60
|
+
if (!old)
|
|
61
|
+
throw new Error("attempt not found");
|
|
62
|
+
const next = { ...old, ...change };
|
|
63
|
+
write(rows.map((a) => a.id === id ? next : a));
|
|
64
|
+
return structuredClone(next);
|
|
65
|
+
};
|
|
66
|
+
// A process death anywhere around injection leaves an uncertain attempt, never a retry.
|
|
67
|
+
if (rows.some((a) => a.execution === "dispatching"))
|
|
68
|
+
write(rows.map((a) => a.execution === "dispatching" ? { ...a, execution: "dispatch-unknown" } : a));
|
|
69
|
+
// First-slice records had optional file evidence and no snapshots. Keep their
|
|
70
|
+
// history, but require fresh validation rather than trusting an old accepted bit.
|
|
71
|
+
const legacy = rows.filter((a) => a.acceptance === "accepted" && (parseClosing(a.body ?? "").artifacts.some((f) => !(a.files ?? []).some((v) => v.path === f.path && v.snapshot))
|
|
72
|
+
|| (a.files ?? []).some((f) => !f.snapshot)));
|
|
73
|
+
if (legacy.length)
|
|
74
|
+
write(rows.map((a) => legacy.some((b) => b.id === a.id) ? { ...a, acceptance: "pending", reason: "legacy file acceptance needs complete version evidence and snapshots" } : a));
|
|
75
|
+
const byTurn = (owner, turn) => rows.find((a) => a.owner === owner && a.turn === turn);
|
|
76
|
+
const begin = (owner, job, node, turn, to, queued) => {
|
|
77
|
+
const same = byTurn(owner, turn);
|
|
78
|
+
if (same) {
|
|
79
|
+
if (same.node !== node || same.job !== job || same.to !== to)
|
|
80
|
+
throw new Error("attempt identity mismatch");
|
|
81
|
+
if (!["queued", "running", "needs-input", "errored"].includes(same.execution))
|
|
82
|
+
throw new Error("attempt requires reconciliation before dispatch");
|
|
83
|
+
return structuredClone(same);
|
|
84
|
+
}
|
|
85
|
+
const prev = rows.filter((a) => a.owner === owner && a.job === job && a.node === node).at(-1);
|
|
86
|
+
if (prev && (!prev.stopped || prev.acceptance === "accepted"))
|
|
87
|
+
throw new Error("previous attempt may still execute; reconcile stopped before retry");
|
|
88
|
+
const busy = rows.find((a) => a.owner === owner && a.to === to && !["terminal", "queued"].includes(a.execution));
|
|
89
|
+
if (busy && !queued)
|
|
90
|
+
throw new Error("target already has a tracked execution");
|
|
91
|
+
const a = { id: `a${randomUUID()}`, node, owner, job, turn, to, at: Date.now(), execution: "queued", acceptance: "pending", accountingVersion: 1 };
|
|
92
|
+
write([...rows, a]);
|
|
93
|
+
return structuredClone(a);
|
|
94
|
+
};
|
|
95
|
+
return {
|
|
96
|
+
all: () => structuredClone(rows),
|
|
97
|
+
list: (owner, job) => structuredClone(rows.filter((a) => a.owner === owner && (!job || a.job === job))),
|
|
98
|
+
byTurn: (owner, turn) => { const a = byTurn(owner, turn); return a && structuredClone(a); },
|
|
99
|
+
begin,
|
|
100
|
+
cancel: (to, owner, job, turn, only = () => true) => {
|
|
101
|
+
const hit = rows.filter((a) => (!to || a.to === to) && (!owner || a.owner === owner) && (!job || a.job === job) && (!turn || a.turn === turn) && a.execution !== "terminal" && !a.canceled && only({ from: a.owner, turn: a.turn }));
|
|
102
|
+
if (hit.length)
|
|
103
|
+
write(rows.map((a) => hit.some((b) => b.id === a.id) ? { ...a, canceled: true, acceptance: "rejected", execution: a.exposed || a.execution === "dispatch-unknown" || ["running", "needs-input", "errored"].includes(a.execution) ? "dispatch-unknown" : "terminal", status: "canceled", stopped: !a.exposed && ["queued", "dispatching"].includes(a.execution), reason: "canceled; physical exposure requires stopped reconciliation" } : a));
|
|
104
|
+
return hit.length;
|
|
105
|
+
},
|
|
106
|
+
recordLeg: (id, data, reuse = false) => {
|
|
107
|
+
const a = rows.find((a) => a.id === id);
|
|
108
|
+
if (!a || a.canceled)
|
|
109
|
+
throw new Error("attempt not active");
|
|
110
|
+
const old = a.dispatches?.at(-1);
|
|
111
|
+
if (reuse && old) {
|
|
112
|
+
if (a.tell)
|
|
113
|
+
update(id, { tell: { ...a.tell, dispatchKey: old.key } });
|
|
114
|
+
return structuredClone(old);
|
|
115
|
+
}
|
|
116
|
+
const key = `${id}:${randomUUID()}`;
|
|
117
|
+
const leg = { ...data, key, ...(data.tell ? { tell: { ...data.tell, dispatchKey: key } } : {}) };
|
|
118
|
+
update(id, { accountingVersion: 1, dispatches: [...(a.dispatches ?? []), leg], ...(leg.tell ? { tell: leg.tell } : {}) });
|
|
119
|
+
return leg;
|
|
120
|
+
},
|
|
121
|
+
boundary: (id) => {
|
|
122
|
+
const a = rows.find((a) => a.id === id);
|
|
123
|
+
if (!a || a.canceled || a.execution !== "dispatching")
|
|
124
|
+
return false;
|
|
125
|
+
if (!a.exposed)
|
|
126
|
+
update(id, { exposed: true });
|
|
127
|
+
return true;
|
|
128
|
+
},
|
|
129
|
+
intent: (id, tell) => update(id, { execution: "dispatching", tell, status: undefined, body: undefined, digest: undefined, acceptance: "pending" }),
|
|
130
|
+
dispatched: (id, ok) => {
|
|
131
|
+
const a = rows.find((a) => a.id === id);
|
|
132
|
+
if (!a)
|
|
133
|
+
throw new Error("attempt not found");
|
|
134
|
+
return a.execution === "dispatching" ? update(id, { execution: ok ? "running" : "dispatch-unknown" }) : structuredClone(a);
|
|
135
|
+
},
|
|
136
|
+
outcome: (owner, turn, status, body) => {
|
|
137
|
+
const a = byTurn(owner, turn);
|
|
138
|
+
if (!a || a.execution === "terminal" || a.execution === "dispatch-unknown" || (["queued", "dispatching"].includes(a.execution) && status !== "canceled"))
|
|
139
|
+
return;
|
|
140
|
+
if (status === "canceled") {
|
|
141
|
+
const uncertain = !!a.exposed || ["running", "needs-input", "errored"].includes(a.execution);
|
|
142
|
+
update(a.id, { status, body, digest: resultDigest(body), canceled: true, acceptance: "rejected", execution: uncertain ? "dispatch-unknown" : "terminal", stopped: !uncertain });
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (status === "done" && !body.trim()) {
|
|
146
|
+
update(a.id, { execution: "dispatch-unknown", acceptance: "pending", status: undefined, body: undefined, digest: undefined, reason: "receipt claimed without result body; reconcile from transcript" });
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
update(a.id, { status, body, digest: resultDigest(body), acceptance: "pending", execution: status === "need" ? "needs-input" : status === "error" ? "errored" : "terminal" });
|
|
150
|
+
},
|
|
151
|
+
prereq: (owner, turn) => {
|
|
152
|
+
const a = byTurn(owner, turn);
|
|
153
|
+
if (!a)
|
|
154
|
+
return undefined;
|
|
155
|
+
const seen = { to: a.to, status: a.status ?? "canceled", body: a.body ?? "" };
|
|
156
|
+
if (a.canceled || a.acceptance === "rejected" || (a.execution === "terminal" && a.status !== "done"))
|
|
157
|
+
return { state: "failed", seen };
|
|
158
|
+
if (a.acceptance === "accepted")
|
|
159
|
+
return versionsMatch(a.files ?? []) ? { state: "done", seen } : { state: "failed", seen: { ...seen, status: "canceled", body: "accepted artifact version changed" } };
|
|
160
|
+
return { state: "pending" };
|
|
161
|
+
},
|
|
162
|
+
verdict: (owner, id, as, digest, reason, files = []) => {
|
|
163
|
+
const a = rows.find((a) => a.id === id && a.owner === owner);
|
|
164
|
+
if (!a)
|
|
165
|
+
throw new Error("attempt not found for owner");
|
|
166
|
+
if (!reason.trim())
|
|
167
|
+
throw new Error("evidence/reason required");
|
|
168
|
+
if (as === "sent") {
|
|
169
|
+
if (a.canceled || !["dispatch-unknown", "running"].includes(a.execution) || a.status || !a.tell)
|
|
170
|
+
throw new Error("only unknown dispatch can reconcile sent");
|
|
171
|
+
return update(id, { execution: "running", reason });
|
|
172
|
+
}
|
|
173
|
+
if (as === "stopped") {
|
|
174
|
+
if (a.acceptance === "accepted")
|
|
175
|
+
throw new Error("accepted attempt cannot be retried");
|
|
176
|
+
return update(id, { execution: "terminal", status: a.status ?? "canceled", stopped: true, acceptance: "rejected", reason });
|
|
177
|
+
}
|
|
178
|
+
if (a.execution !== "terminal" || a.status !== "done" || !a.digest || digest !== a.digest)
|
|
179
|
+
throw new Error("verdict requires terminal done and matching result digest");
|
|
180
|
+
if (a.acceptance !== "pending")
|
|
181
|
+
throw new Error("attempt already validated");
|
|
182
|
+
const declared = parseClosing(a.body ?? "").artifacts.map((f) => f.path);
|
|
183
|
+
if (as === "accepted" && declared.some((p) => !files.some((f) => f.path === p)))
|
|
184
|
+
throw new Error("every declared ARTIFACT requires files+sha256 evidence");
|
|
185
|
+
if (as === "accepted" && !versionsMatch(files))
|
|
186
|
+
throw new Error("artifact missing or sha256 mismatch");
|
|
187
|
+
const versions = as !== "accepted" ? files : files.map((f) => {
|
|
188
|
+
const data = readFileSync(f.path);
|
|
189
|
+
if (createHash("sha256").update(data).digest("hex") !== f.sha256)
|
|
190
|
+
throw new Error("artifact changed while snapshotting");
|
|
191
|
+
const snapshot = `${abs}.artifacts/${f.sha256}`;
|
|
192
|
+
mkdirSync(dirname(snapshot), { recursive: true });
|
|
193
|
+
if (!existsSync(snapshot))
|
|
194
|
+
writeFileSync(snapshot, data, { mode: 0o444, flag: "wx" });
|
|
195
|
+
if (fileDigest(snapshot) !== f.sha256)
|
|
196
|
+
throw new Error("snapshot corruption");
|
|
197
|
+
const fd = openSync(snapshot, "r");
|
|
198
|
+
try {
|
|
199
|
+
fsyncSync(fd);
|
|
200
|
+
}
|
|
201
|
+
finally {
|
|
202
|
+
closeSync(fd);
|
|
203
|
+
}
|
|
204
|
+
const parent = openSync(dirname(snapshot), "r");
|
|
205
|
+
try {
|
|
206
|
+
fsyncSync(parent);
|
|
207
|
+
}
|
|
208
|
+
finally {
|
|
209
|
+
closeSync(parent);
|
|
210
|
+
}
|
|
211
|
+
return { path: f.path, sha256: f.sha256, snapshot };
|
|
212
|
+
});
|
|
213
|
+
return update(id, { acceptance: as, reason, files: versions });
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
};
|
|
217
|
+
//# sourceMappingURL=workflow-nodes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow-nodes.js","sourceRoot":"","sources":["../../daemon/workflow-nodes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { WorkflowPlanSchema } from "../shared/workflow-plan.js";
|
|
2
|
+
import { json } from "./http.js";
|
|
3
|
+
export const registerWorkflowPlanRoutes = (http, o) => {
|
|
4
|
+
const inputOf = (body) => {
|
|
5
|
+
if (!body || typeof body !== "object" || Array.isArray(body))
|
|
6
|
+
throw new Error("object required");
|
|
7
|
+
const { sessionId: _sid, tmuxPane: _pane, target: _target, ...input } = body;
|
|
8
|
+
return input;
|
|
9
|
+
};
|
|
10
|
+
http.register("POST /workflow/plans/create", async (req, res) => {
|
|
11
|
+
const { self, body } = await o.read(req);
|
|
12
|
+
if (!self) {
|
|
13
|
+
json(res, 400, { ok: false, reason: "cannot resolve caller" });
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
let input;
|
|
17
|
+
try {
|
|
18
|
+
input = WorkflowPlanSchema.parse(inputOf(body));
|
|
19
|
+
}
|
|
20
|
+
catch (e) {
|
|
21
|
+
json(res, 400, { ok: false, reason: e.message });
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const existing = o.store.ofJob(input.job);
|
|
25
|
+
if (existing?.owner === self) {
|
|
26
|
+
json(res, 409, { ok: false, reason: "one immutable segment per job; existing plan unchanged", existingRun: existing.id, hint: "workflow_plan_status({run:existingRun}) recovers a lost create response; this request does not restart or update it" });
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const job = o.job(input.job);
|
|
30
|
+
if (!job || job.owner !== self || job.status !== "open") {
|
|
31
|
+
json(res, 409, { ok: false, reason: "plan requires your open job" });
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const origin = o.origin(self), denied = o.direction(self, job.id, origin);
|
|
35
|
+
if (denied) {
|
|
36
|
+
json(res, 409, { ok: false, reason: denied, gated: "direction" });
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const run = o.store.create(self, input, origin, job, o.attempts(self, job.id), o.occupied(self, job.id));
|
|
41
|
+
o.runner.kick();
|
|
42
|
+
json(res, 200, { ok: true, run: o.runner.status(self, run.id), hint: "immutable segment; owner must explicitly accept every result via workflow_verdict. NEED/error/unknown require reconciliation, never automatic retry. Cancel blocks new work but does not kill executing panes." });
|
|
43
|
+
}
|
|
44
|
+
catch (e) {
|
|
45
|
+
json(res, 409, { ok: false, reason: e.message });
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
http.register("POST /workflow/plans/status", async (req, res) => {
|
|
49
|
+
const { self, body } = await o.read(req);
|
|
50
|
+
if (!self) {
|
|
51
|
+
json(res, 400, { ok: false, reason: "cannot resolve caller" });
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
const b = inputOf(body);
|
|
56
|
+
if (Object.keys(b).some((k) => k !== "run") || typeof b.run !== "string" || !b.run)
|
|
57
|
+
throw new Error("run required; unknown fields are not allowed");
|
|
58
|
+
const run = o.runner.status(self, b.run);
|
|
59
|
+
json(res, 200, { ok: true, run });
|
|
60
|
+
}
|
|
61
|
+
catch (e) {
|
|
62
|
+
json(res, 409, { ok: false, reason: e.message });
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
http.register("POST /workflow/plans/cancel", async (req, res) => {
|
|
66
|
+
const { self, body } = await o.read(req);
|
|
67
|
+
if (!self) {
|
|
68
|
+
json(res, 400, { ok: false, reason: "cannot resolve caller" });
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const b = inputOf(body);
|
|
73
|
+
if (Object.keys(b).some((k) => !["run", "reason"].includes(k)) || typeof b.run !== "string" || typeof b.reason !== "string" || !b.reason.trim())
|
|
74
|
+
throw new Error("run and nonempty reason required; unknown fields are not allowed");
|
|
75
|
+
o.runner.cancel(self, b.run, b.reason.trim());
|
|
76
|
+
json(res, 200, { ok: true, run: o.runner.status(self, b.run) });
|
|
77
|
+
}
|
|
78
|
+
catch (e) {
|
|
79
|
+
json(res, 409, { ok: false, reason: e.message });
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
//# sourceMappingURL=workflow-plan-http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow-plan-http.js","sourceRoot":"","sources":["../../daemon/workflow-plan-http.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { baseOfKey, keyOf, normalizeTag } from "../shared/session-label.js";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { realpathSync, statSync } from "node:fs";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { loadJsonMap } from "../shared/json-map-store.js";
|
|
6
|
+
import { WorkflowPlanSchema } from "../shared/workflow-plan.js";
|
|
7
|
+
const OriginSchema = z.object({
|
|
8
|
+
k: z.union([
|
|
9
|
+
z.object({ kind: z.literal("chat"), channel: z.string(), turn: z.string() }).strict(),
|
|
10
|
+
z.object({ kind: z.literal("peer"), from: z.string(), turn: z.string() }).strict(),
|
|
11
|
+
]).optional(),
|
|
12
|
+
asker: z.object({ who: z.string(), chat: z.string() }).strict().optional(),
|
|
13
|
+
chain: z.array(z.string()),
|
|
14
|
+
}).strict();
|
|
15
|
+
const PhaseSchema = z.enum(["waiting", "spawning", "spawn-unknown", "spawned", "dispatching", "observing", "blocked"]);
|
|
16
|
+
const NodeStateSchema = z.object({
|
|
17
|
+
id: z.string(), turn: z.string().min(1), name: z.string().min(1), phase: PhaseSchema,
|
|
18
|
+
to: z.string().optional(), reason: z.string().optional(), birth: z.record(z.unknown()).optional(),
|
|
19
|
+
}).strict();
|
|
20
|
+
const RunSchema = z.object({
|
|
21
|
+
id: z.string().min(1), owner: z.string().min(1), plan: WorkflowPlanSchema,
|
|
22
|
+
origin: OriginSchema, at: z.number().finite(),
|
|
23
|
+
state: z.enum(["active", "completed", "failed", "canceled"]), reason: z.string().optional(),
|
|
24
|
+
nodes: z.array(NodeStateSchema),
|
|
25
|
+
}).strict();
|
|
26
|
+
export const loadWorkflowPlans = (file, prereq) => {
|
|
27
|
+
const db = loadJsonMap(file, undefined, { strict: true });
|
|
28
|
+
let healthy = true;
|
|
29
|
+
const all = () => structuredClone(Object.values(db.all()));
|
|
30
|
+
all().forEach((r) => {
|
|
31
|
+
RunSchema.parse(r);
|
|
32
|
+
if (r.nodes.length !== r.plan.nodes.length || r.nodes.some((n, i) => n.id !== r.plan.nodes[i]?.id)
|
|
33
|
+
|| new Set(r.nodes.map((n) => n.turn)).size !== r.nodes.length
|
|
34
|
+
|| new Set(r.nodes.map((n) => n.name)).size !== r.nodes.length
|
|
35
|
+
|| r.nodes.some((n) => ["spawned", "dispatching", "observing"].includes(n.phase) && (!n.to || n.birth?.target !== n.to || n.birth?.name !== n.name)))
|
|
36
|
+
throw new Error("invalid workflow plan identities");
|
|
37
|
+
});
|
|
38
|
+
if (Object.entries(db.all()).some(([id, r]) => id !== r.id)
|
|
39
|
+
|| new Set(all().flatMap((r) => r.nodes.map((n) => n.turn))).size !== all().reduce((sum, r) => sum + r.nodes.length, 0))
|
|
40
|
+
throw new Error("duplicate workflow plan run/turn identity");
|
|
41
|
+
if (new Set(all().map((r) => r.plan.job)).size !== all().length)
|
|
42
|
+
throw new Error("duplicate workflow plan job");
|
|
43
|
+
const write = (r) => {
|
|
44
|
+
try {
|
|
45
|
+
db.set(r.id, r);
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
healthy = false;
|
|
49
|
+
throw e;
|
|
50
|
+
}
|
|
51
|
+
return structuredClone(r);
|
|
52
|
+
};
|
|
53
|
+
const get = (id) => { const r = db.get(id); return r && structuredClone(r); };
|
|
54
|
+
const change = (id, f) => {
|
|
55
|
+
const r = get(id);
|
|
56
|
+
if (!r)
|
|
57
|
+
throw new Error("workflow plan not found");
|
|
58
|
+
return write(f(r));
|
|
59
|
+
};
|
|
60
|
+
// A birth may exist even if its result was never committed. Do not create another.
|
|
61
|
+
all().filter((r) => r.nodes.some((n) => n.phase === "spawning")).forEach((r) => write({ ...r, nodes: r.nodes.map((n) => n.phase === "spawning" ? { ...n, phase: "spawn-unknown", reason: "spawn interrupted; reconcile executor by recorded name, then cancel this segment" } : n) }));
|
|
62
|
+
const ofJob = (job) => all().find((r) => r.plan.job === job);
|
|
63
|
+
// Reserve logical addresses before birth, and physical keys after reconciliation.
|
|
64
|
+
// Terminal runs retain their reservations: cancellation never frees a pane for
|
|
65
|
+
// untracked task injection or silently resurrects the segment.
|
|
66
|
+
const reservation = (target, name) => all().find((r) => r.nodes.some((n) => n.name === normalizeTag(name) || (!!target && (n.to === target || keyOf(baseOfKey(r.owner), n.name) === target))));
|
|
67
|
+
const owned = (owner, id) => {
|
|
68
|
+
const r = get(id);
|
|
69
|
+
if (!r || r.owner !== owner)
|
|
70
|
+
throw new Error("workflow plan not found for owner");
|
|
71
|
+
return r;
|
|
72
|
+
};
|
|
73
|
+
const permitted = (owner, job, p) => {
|
|
74
|
+
const r = ofJob(job);
|
|
75
|
+
return healthy && !!r && r.state === "active" && r.owner === owner && !!p && p.run === r.id
|
|
76
|
+
&& r.nodes.some((n) => n.id === p.node && n.turn === p.turn) && p.alive();
|
|
77
|
+
};
|
|
78
|
+
return {
|
|
79
|
+
all, get, owned, ofJob, reservation, allowExternal: (target, name) => healthy && !reservation(target, name), healthy: () => healthy,
|
|
80
|
+
create: (owner, input, origin, job, attempts, occupied) => {
|
|
81
|
+
const plan = WorkflowPlanSchema.parse(input);
|
|
82
|
+
if (job.id !== plan.job || job.owner !== owner || job.status !== "open")
|
|
83
|
+
throw new Error("plan requires your open job");
|
|
84
|
+
if (ofJob(plan.job) || job.members.length || (job.turns ?? 0) || attempts.length || occupied)
|
|
85
|
+
throw new Error("plan requires a dedicated empty job; one immutable segment per job");
|
|
86
|
+
if (job.maxTurns && job.maxTurns < plan.nodes.length)
|
|
87
|
+
throw new Error("job budget cannot cover this plan");
|
|
88
|
+
if (!statSync(plan.cwd).isDirectory())
|
|
89
|
+
throw new Error("cwd must be a directory");
|
|
90
|
+
const id = `w${randomUUID().replace(/-/g, "")}`;
|
|
91
|
+
return write({ id, owner, plan: { ...plan, cwd: realpathSync(plan.cwd) }, origin: OriginSchema.parse(origin), at: Date.now(), state: "active",
|
|
92
|
+
nodes: plan.nodes.map((n, i) => ({ id: n.id, phase: "waiting", turn: `t${randomUUID().replace(/-/g, "")}`, name: `wf-${id.slice(1, 13)}-${i + 1}` })) });
|
|
93
|
+
},
|
|
94
|
+
node: (id, node, patch) => change(id, (r) => {
|
|
95
|
+
const old = r.nodes.find((n) => n.id === node);
|
|
96
|
+
if (!old)
|
|
97
|
+
throw new Error("plan node not found");
|
|
98
|
+
const moves = { waiting: ["spawning"], spawning: ["spawned", "spawn-unknown"], "spawn-unknown": [], spawned: ["dispatching"], dispatching: ["observing", "blocked"], observing: [], blocked: [] };
|
|
99
|
+
if (patch.phase && patch.phase !== old.phase && !moves[old.phase].includes(patch.phase))
|
|
100
|
+
throw new Error("illegal plan node transition");
|
|
101
|
+
const next = NodeStateSchema.parse({ ...old, ...patch });
|
|
102
|
+
if (["spawned", "dispatching", "observing"].includes(next.phase) && !next.to)
|
|
103
|
+
throw new Error("plan node requires executor identity");
|
|
104
|
+
return { ...r, nodes: r.nodes.map((n) => n.id === node ? next : n) };
|
|
105
|
+
}),
|
|
106
|
+
end: (id, state, reason) => change(id, (r) => r.state !== "active" ? r : { ...r, state, reason }),
|
|
107
|
+
allowSpawn: (owner, job, p, name, target) => {
|
|
108
|
+
const reserved = reservation(target, name);
|
|
109
|
+
const r = reserved ?? ofJob(job);
|
|
110
|
+
const node = p && r?.nodes.find((n) => n.id === p.node);
|
|
111
|
+
return !r || (r.plan.job === job && permitted(owner, job, p) && node?.phase === "spawning"
|
|
112
|
+
&& (!name || normalizeTag(name) === node.name) && (!target || target === (node.to ?? keyOf(baseOfKey(r.owner), node.name))));
|
|
113
|
+
},
|
|
114
|
+
allowTell: (owner, job, node, target, re, p, name) => {
|
|
115
|
+
const r = reservation(target, name) ?? ofJob(job);
|
|
116
|
+
if (!r)
|
|
117
|
+
return true;
|
|
118
|
+
if (!healthy || r.state !== "active" || r.owner !== owner || r.plan.job !== job)
|
|
119
|
+
return false;
|
|
120
|
+
const n = r.nodes.find((n) => n.to === target && n.id === node);
|
|
121
|
+
const depsValid = (r.plan.nodes.find((x) => x.id === node)?.after ?? []).every((id) => prereq(owner, r.nodes.find((n) => n.id === id).turn)?.state === "done");
|
|
122
|
+
return !!n && depsValid && (p ? permitted(owner, job, p) && p.node === node && n.phase === "dispatching" : re === n.turn && n.phase === "observing");
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
};
|
|
126
|
+
export const createWorkflowPlanRunner = (store, d) => {
|
|
127
|
+
const locks = new Set();
|
|
128
|
+
const dirty = new Set();
|
|
129
|
+
let started = false;
|
|
130
|
+
const attemptOf = (r, n) => {
|
|
131
|
+
const a = d.attempt(r.owner, n.turn);
|
|
132
|
+
if (a && (a.node !== n.id || a.job !== r.plan.job || a.to !== n.to))
|
|
133
|
+
throw new Error("plan attempt identity mismatch");
|
|
134
|
+
return a;
|
|
135
|
+
};
|
|
136
|
+
const prereqOf = (r, n) => {
|
|
137
|
+
attemptOf(r, n);
|
|
138
|
+
return d.prereq(r.owner, n.turn);
|
|
139
|
+
};
|
|
140
|
+
const valid = (r, node) => (node.after ?? []).every((id) => {
|
|
141
|
+
const n = r.nodes.find((n) => n.id === id);
|
|
142
|
+
return prereqOf(r, n)?.state === "done";
|
|
143
|
+
});
|
|
144
|
+
const alive = (id, node) => {
|
|
145
|
+
const r = store.get(id);
|
|
146
|
+
return store.healthy() && started && !!r && r.state === "active" && d.job(r.plan.job)?.status === "open" && d.job(r.plan.job)?.owner === r.owner && valid(r, r.plan.nodes.find((n) => n.id === node));
|
|
147
|
+
};
|
|
148
|
+
const cancel = (owner, id, reason) => {
|
|
149
|
+
const old = store.owned(owner, id);
|
|
150
|
+
if (!["active", "canceled"].includes(old.state))
|
|
151
|
+
throw new Error("terminal workflow plan cannot be canceled or resumed");
|
|
152
|
+
const r = store.end(old.id, "canceled", reason);
|
|
153
|
+
d.cancel(r);
|
|
154
|
+
return r;
|
|
155
|
+
};
|
|
156
|
+
const advance = async (id) => {
|
|
157
|
+
if (!started || !store.healthy())
|
|
158
|
+
return;
|
|
159
|
+
if (locks.has(id)) {
|
|
160
|
+
dirty.add(id);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
locks.add(id);
|
|
164
|
+
try {
|
|
165
|
+
let r = store.get(id);
|
|
166
|
+
if (!r || r.state !== "active")
|
|
167
|
+
return;
|
|
168
|
+
const job = d.job(r.plan.job);
|
|
169
|
+
if (job?.status !== "open" || job.owner !== r.owner) {
|
|
170
|
+
cancel(r.owner, r.id, "job closed or unavailable");
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const failed = r.nodes.find((n) => prereqOf(r, n)?.state === "failed");
|
|
174
|
+
if (failed) {
|
|
175
|
+
store.end(id, "failed", `node ${failed.id} failed/rejected or artifact version changed`);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (r.nodes.every((n) => prereqOf(r, n)?.state === "done")) {
|
|
179
|
+
store.end(id, "completed", "all nodes explicitly accepted with valid versions");
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
// Reconcile a crash between tell completion and writing the plan pointer.
|
|
183
|
+
for (const n of r.nodes.filter((n) => n.phase === "dispatching")) {
|
|
184
|
+
const a = attemptOf(r, n);
|
|
185
|
+
r = store.node(id, n.id, { phase: a ? "observing" : "blocked", ...(a ? {} : { reason: "dispatch intent has no attempt; no automatic replay" }) });
|
|
186
|
+
}
|
|
187
|
+
const pending = r.nodes.filter((n) => prereqOf(r, n)?.state !== "done");
|
|
188
|
+
if (pending.some((n) => !["waiting", "spawned"].includes(n.phase)))
|
|
189
|
+
return;
|
|
190
|
+
const state = pending.find((n) => valid(r, r.plan.nodes.find((x) => x.id === n.id)));
|
|
191
|
+
if (!state)
|
|
192
|
+
return;
|
|
193
|
+
const node = r.plan.nodes.find((n) => n.id === state.id);
|
|
194
|
+
if (job.maxTurns && (job.turns ?? 0) >= job.maxTurns) {
|
|
195
|
+
store.end(id, "failed", "job dispatch budget exhausted before spawn");
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const p = { run: id, node: node.id, turn: state.turn, origin: r.origin, alive: () => alive(id, node.id) };
|
|
199
|
+
if (state.phase === "waiting") {
|
|
200
|
+
r = store.node(id, node.id, { phase: "spawning" });
|
|
201
|
+
const born = await d.spawn(r, node, r.nodes.find((n) => n.id === node.id), p);
|
|
202
|
+
if (born.status !== 200 || typeof born.body.target !== "string" || !born.body.target || born.body.dispatched !== false
|
|
203
|
+
|| born.body.inherited !== false || born.body.keepalive !== false || born.body.cwd !== r.plan.cwd || born.body.name !== state.name) {
|
|
204
|
+
store.node(id, node.id, { phase: "spawn-unknown", reason: String(born.body.reason ?? "executor creation unconfirmed"), birth: born.body });
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
// Even after cancellation, retain the executor identity for operator cleanup.
|
|
208
|
+
r = store.node(id, node.id, { phase: "spawned", to: born.body.target, birth: born.body });
|
|
209
|
+
if (!p.alive())
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (!p.alive())
|
|
213
|
+
return;
|
|
214
|
+
const current = store.get(id).nodes.find((n) => n.id === node.id);
|
|
215
|
+
r = store.node(id, node.id, { phase: "dispatching" });
|
|
216
|
+
const after = (node.after ?? []).map((dep) => r.nodes.find((n) => n.id === dep).turn);
|
|
217
|
+
const sent = await d.tell(r, node, current, after, p);
|
|
218
|
+
const a = attemptOf(r, current);
|
|
219
|
+
store.node(id, node.id, { phase: a ? "observing" : "blocked", ...(sent.status === 200 ? {} : { reason: String(sent.body.reason ?? "dispatch unconfirmed") }) });
|
|
220
|
+
// Completion/version failure can happen during transport before its response.
|
|
221
|
+
// Reconcile once after dropping the lock; never re-enter a dispatched node.
|
|
222
|
+
dirty.add(id);
|
|
223
|
+
}
|
|
224
|
+
catch (e) {
|
|
225
|
+
d.error(e);
|
|
226
|
+
const r = store.get(id);
|
|
227
|
+
if (store.healthy() && r?.state === "active") {
|
|
228
|
+
const n = r.nodes.find((n) => ["spawning", "dispatching"].includes(n.phase));
|
|
229
|
+
if (n) {
|
|
230
|
+
try {
|
|
231
|
+
store.node(id, n.id, { phase: n.phase === "spawning" ? "spawn-unknown" : d.attempt(r.owner, n.turn) ? "observing" : "blocked", reason: String(e) });
|
|
232
|
+
}
|
|
233
|
+
catch (failure) {
|
|
234
|
+
d.error(failure);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
finally {
|
|
240
|
+
locks.delete(id);
|
|
241
|
+
if (dirty.delete(id))
|
|
242
|
+
queueMicrotask(() => { void advance(id); });
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
const kick = () => { if (started)
|
|
246
|
+
store.all().filter((r) => r.state === "active").forEach((r) => { void advance(r.id); }); };
|
|
247
|
+
const status = (owner, id) => {
|
|
248
|
+
const r = store.owned(owner, id);
|
|
249
|
+
const nodes = r.nodes.map((n) => {
|
|
250
|
+
const a = attemptOf(r, n), prereq = prereqOf(r, n);
|
|
251
|
+
const state = r.state === "canceled" ? "canceled" : prereq?.state === "failed" ? "failed"
|
|
252
|
+
: prereq?.state === "done" ? "succeeded" : a ? a.execution === "terminal" && a.status === "done" ? "validating" : a.execution
|
|
253
|
+
: n.phase === "waiting" ? r.state === "failed" ? "skipped" : valid(r, r.plan.nodes.find((x) => x.id === n.id)) ? "ready" : "pending" : n.phase;
|
|
254
|
+
return { ...n, state, ...(a ? { attempt: a } : {}), ...(r.state === "canceled" && a ? { physicalStopConfirmed: !!a.stopped } : {}) };
|
|
255
|
+
});
|
|
256
|
+
const versionsValid = r.nodes.every((n) => prereqOf(r, n)?.state === "done");
|
|
257
|
+
return { ...r, state: r.state === "completed" && !versionsValid ? "invalidated" : r.state, recordedState: r.state, nodes, versionsValid, storageHealthy: store.healthy() };
|
|
258
|
+
};
|
|
259
|
+
return { kick, status, cancel, start: () => {
|
|
260
|
+
// Cancellation is committed before touching other stores. Finish a partial
|
|
261
|
+
// cancellation after restart without reopening the run or replaying tasks.
|
|
262
|
+
store.all().filter((r) => r.state === "canceled").forEach((r) => { try {
|
|
263
|
+
d.cancel(r);
|
|
264
|
+
}
|
|
265
|
+
catch (e) {
|
|
266
|
+
d.error(e);
|
|
267
|
+
} });
|
|
268
|
+
started = true;
|
|
269
|
+
kick();
|
|
270
|
+
}, stop: () => { started = false; },
|
|
271
|
+
cancelJob: (owner, job) => { const r = store.ofJob(job); if (r?.owner === owner && r.state === "active")
|
|
272
|
+
cancel(owner, r.id, "job closed"); } };
|
|
273
|
+
};
|
|
274
|
+
//# sourceMappingURL=workflow-plans.js.map
|