zcode-acp-server 0.59.0 → 0.61.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +17 -13
  2. package/dist/code-fingerprint.json +2 -2
  3. package/dist/config/settings.d.ts +7 -0
  4. package/dist/config/settings.d.ts.map +1 -1
  5. package/dist/config/settings.js +15 -0
  6. package/dist/config/settings.js.map +1 -1
  7. package/dist/config/user-config.d.ts +8 -1
  8. package/dist/config/user-config.d.ts.map +1 -1
  9. package/dist/config/user-config.js +2 -0
  10. package/dist/config/user-config.js.map +1 -1
  11. package/dist/goal-loop/driver.d.ts +18 -0
  12. package/dist/goal-loop/driver.d.ts.map +1 -1
  13. package/dist/goal-loop/driver.js +100 -1
  14. package/dist/goal-loop/driver.js.map +1 -1
  15. package/dist/i18n.d.ts +6 -0
  16. package/dist/i18n.d.ts.map +1 -1
  17. package/dist/i18n.js +8 -0
  18. package/dist/i18n.js.map +1 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +23 -1
  21. package/dist/index.js.map +1 -1
  22. package/dist/push/config.d.ts +1 -1
  23. package/dist/push/config.d.ts.map +1 -1
  24. package/dist/quota/resume.d.ts +66 -0
  25. package/dist/quota/resume.d.ts.map +1 -0
  26. package/dist/quota/resume.js +217 -0
  27. package/dist/quota/resume.js.map +1 -0
  28. package/dist/quota/thresholds.d.ts +5 -4
  29. package/dist/quota/thresholds.d.ts.map +1 -1
  30. package/dist/quota/thresholds.js +7 -6
  31. package/dist/quota/thresholds.js.map +1 -1
  32. package/dist/remote/hub-server.d.ts.map +1 -1
  33. package/dist/remote/hub-server.js +21 -0
  34. package/dist/remote/hub-server.js.map +1 -1
  35. package/dist/remote/workflow-overview.d.ts +84 -0
  36. package/dist/remote/workflow-overview.d.ts.map +1 -0
  37. package/dist/remote/workflow-overview.js +364 -0
  38. package/dist/remote/workflow-overview.js.map +1 -0
  39. package/dist/workflow/quota-resume.d.ts +135 -0
  40. package/dist/workflow/quota-resume.d.ts.map +1 -0
  41. package/dist/workflow/quota-resume.js +773 -0
  42. package/dist/workflow/quota-resume.js.map +1 -0
  43. package/docs/DEVELOPMENT.md +7 -0
  44. package/docs/REMOTE-CLIENTS.md +40 -5
  45. package/package.json +1 -1
@@ -0,0 +1,364 @@
1
+ /**
2
+ * Machine-level workflow overview for remote clients (the hub's
3
+ * `GET /api/workflow-overview`).
4
+ *
5
+ * The per-instance settings routes answer with each BRIDGE's own slice: a
6
+ * workspace with two bridges (editor + serve) yields two project groups for
7
+ * the same project, and a bridge only sees the runs of its own cwd — so a
8
+ * client polling "is anything flying?" against the one instance it happens to
9
+ * be attached to reads an empty list while another project's run is very much
10
+ * alive (observed 2026-10-01: the management page double-listed ai-hot-app
11
+ * and its live-run zone showed nothing). Every part of that story is
12
+ * machine-level data:
13
+ *
14
+ * - the dwf journal (run history) is ONE sqlite store shared by every backend
15
+ * process — any bridge's `workflows/runs?scope=global` read (no cwd filter)
16
+ * answers for the whole machine, each row carrying its own `cwd`;
17
+ * - the saved-workflow FILES are per workspace, so the project list needs one
18
+ * bridge per workspace — but every bridge of that workspace reads the same
19
+ * directory, so ONE representative suffices;
20
+ * - the global saved-workflow directory is machine-wide, so one global group
21
+ * is enough;
22
+ * - and a journal row can LIE about a living run: upstream's orphan
23
+ * reconciliation writes stopped(interrupted) onto runs that are still
24
+ * executing in another process (and the row never flips back mid-run), so
25
+ * rows carrying that signature are cross-checked against the owning
26
+ * process's live read before the overview trusts them (collectLiveOverrides).
27
+ *
28
+ * This module folds all of that server-side (the hub owns the instance map):
29
+ * dedupe instances to one group per workspace, race the pair reads with the
30
+ * freshest-started bridge preferred (an old long-lived bridge may predate the
31
+ * workflow routes — fall back to a sibling that answers), and answer with a
32
+ * ready-to-render list plus the machine-wide `activeRuns` (journal rows with
33
+ * status pending/running, annotated with the instance that currently lists
34
+ * their session so the client can address stop/resume correctly).
35
+ *
36
+ * Everything is best-effort per group: a failing workspace degrades to an
37
+ * `error` group (the list must not blank out because one bridge is down),
38
+ * and the whole call only rejects on a structural bug.
39
+ */
40
+ import { get as httpGet } from "node:http";
41
+ /** Per-read budget — these are loopback scans, not backend round-trips. */
42
+ const FETCH_TIMEOUT_MS = 8_000;
43
+ const MAX_BODY_BYTES = 2 * 1024 * 1024;
44
+ /** Journal window for the global read (upstream caps the limit at 50). */
45
+ const RUNS_LIMIT = 50;
46
+ /** Live-registry statuses — a run reporting either is flying right now. */
47
+ const LIVE_STATUSES = new Set(["running", "pending"]);
48
+ /** GET one bridge route, parsed as JSON. Rejects on transport/status/parse. */
49
+ function fetchBridgeJson(port, path) {
50
+ return new Promise((resolve, reject) => {
51
+ const req = httpGet({ host: "127.0.0.1", port, path }, (up) => {
52
+ if ((up.statusCode ?? 500) !== 200) {
53
+ up.resume();
54
+ reject(new Error(`bridge answered ${up.statusCode ?? "?"}`));
55
+ return;
56
+ }
57
+ const chunks = [];
58
+ let size = 0;
59
+ up.on("data", (c) => {
60
+ size += c.length;
61
+ if (size > MAX_BODY_BYTES) {
62
+ up.destroy();
63
+ reject(new Error("bridge answer too large"));
64
+ return;
65
+ }
66
+ chunks.push(c);
67
+ });
68
+ up.on("end", () => {
69
+ try {
70
+ const parsed = JSON.parse(Buffer.concat(chunks).toString("utf8"));
71
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
72
+ reject(new Error("bridge answer is not an object"));
73
+ return;
74
+ }
75
+ resolve(parsed);
76
+ }
77
+ catch {
78
+ reject(new Error("bridge answer is not JSON"));
79
+ }
80
+ });
81
+ up.on("error", reject);
82
+ });
83
+ req.setTimeout(FETCH_TIMEOUT_MS, () => req.destroy(new Error("bridge fetch timed out")));
84
+ req.on("error", reject);
85
+ });
86
+ }
87
+ /**
88
+ * One LIST+RUNS pair for a scope, read from one bridge in parallel: racing the
89
+ * runs read separately lets the journal answer land before the list it belongs
90
+ * to (the client-side bug this endpoint replaces did exactly that).
91
+ */
92
+ async function fetchPair(inst, scope) {
93
+ const q = `?scope=${scope}&limit=${RUNS_LIMIT}`;
94
+ const [list, runs] = await Promise.all([
95
+ fetchBridgeJson(inst.port, `/settings/workflows${q}`),
96
+ fetchBridgeJson(inst.port, `/settings/workflows/runs${q}`),
97
+ ]);
98
+ return {
99
+ list,
100
+ runs: Array.isArray(runs["runs"]) ? runs["runs"] : [],
101
+ };
102
+ }
103
+ /**
104
+ * Ask every candidate bridge for the pair and keep the FRESHEST-started
105
+ * success. Parallel on purpose: a dead/old bridge must not serialize the
106
+ * workspaces that can answer. Undefined = every candidate failed (the caller
107
+ * renders an error group / drops the global group).
108
+ */
109
+ async function pickPair(candidates, scope) {
110
+ const results = await Promise.all(candidates.map(async (inst) => {
111
+ try {
112
+ return { inst, pair: await fetchPair(inst, scope) };
113
+ }
114
+ catch {
115
+ return { inst };
116
+ }
117
+ }));
118
+ const winners = results.filter((r) => "pair" in r);
119
+ if (winners.length === 0)
120
+ return undefined;
121
+ winners.sort((a, b) => b.inst.startedAt - a.inst.startedAt);
122
+ return winners[0];
123
+ }
124
+ /** Per workflow NAME, the newest journal row (`updatedAt` desc) — the badge join. */
125
+ function lastRunByName(runs) {
126
+ const byName = {};
127
+ const sorted = [...runs].sort((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0));
128
+ for (const r of sorted) {
129
+ if (typeof r.name === "string" && r.name && !byName[r.name])
130
+ byName[r.name] = r;
131
+ }
132
+ return byName;
133
+ }
134
+ function workflowsOf(list) {
135
+ return Array.isArray(list["workflows"]) ? list["workflows"] : [];
136
+ }
137
+ function invalidOf(list) {
138
+ return Array.isArray(list["invalid"]) ? list["invalid"] : [];
139
+ }
140
+ /**
141
+ * Annotate each run with the instance whose live session list currently holds
142
+ * its joined ACP session — the address for stop/resume from a client attached
143
+ * to a DIFFERENT instance. A session shown "running" on one instance wins;
144
+ * otherwise the freshest-started holder. Rows no instance lists stay bare
145
+ * (the client falls back to its own launch memory / current instance).
146
+ */
147
+ function annotateOwners(rows, instances) {
148
+ for (const row of rows) {
149
+ // The live-truth owner (set by applyLiveTruth from the instance whose
150
+ // live registry reported the run) is process evidence, not a heuristic —
151
+ // never overwrite it. The heuristic only fills rows it did not correct.
152
+ if (row.ownerInstanceId)
153
+ continue;
154
+ const acpSid = row.acpSessionId;
155
+ if (typeof acpSid !== "string" || !acpSid)
156
+ continue;
157
+ let best;
158
+ let bestRunning = false;
159
+ for (const inst of instances) {
160
+ const mine = inst.sessions.find((s) => s.sessionId === acpSid);
161
+ if (!mine)
162
+ continue;
163
+ const running = mine.status === "running";
164
+ const better = best === undefined ||
165
+ (running && !bestRunning) ||
166
+ (running === bestRunning && inst.startedAt > best.startedAt);
167
+ if (better) {
168
+ best = inst;
169
+ bestRunning = running;
170
+ }
171
+ }
172
+ if (best)
173
+ row.ownerInstanceId = best.id;
174
+ }
175
+ }
176
+ /**
177
+ * Live overrides for journal rows upstream may have wrongly declared dead.
178
+ *
179
+ * Why this exists: upstream's orphan reconciliation (`reconcileOrphanRuns`,
180
+ * bootstrap.app — it runs at every app construction) assumes "no in-flight
181
+ * run in MY process registry ⇒ every non-terminal row of this session is a
182
+ * dead process's leftover" and writes stopped(interrupted) — even when the
183
+ * run is still executing in ANOTHER process. Observed 2026-10-02: a second
184
+ * process constructed the session at 11:44 and wrote the interrupted verdict
185
+ * onto a run that kept dispatching sub-agents until it completed at 14:21;
186
+ * the row never flips back mid-run, so the whole flight window rendered as
187
+ * already-stopped. The owning process's live read
188
+ * (`/settings/workflow-runs?sessionId=` → `v4/conversation/workflowRuns` →
189
+ * `listRunsForSession`) overlays its live registry onto the journal — upstream
190
+ * built it exactly so "this read would not show a run marked dead by a foreign
191
+ * write" — and that overlay is the only trustworthy status here.
192
+ *
193
+ * Trigger = the reconciliation's signature alone (`stopReason:
194
+ * "interrupted"`); no suspicious row → zero probes. Do NOT gate on the
195
+ * instance showing the session as "running": a workflow keeps flying while its
196
+ * parent session sits idle between turns (exactly how the observed run looked
197
+ * for its last two hours), so a running-gated trigger misses the reported
198
+ * case. When suspicious rows exist, probe the sessions the OWNER WORKSPACE's
199
+ * instances list (any status), with each instance's OWN spelling — probing
200
+ * what it reported to the hub keeps this a same-process read. The row's
201
+ * `acpSessionId` is deliberately NOT used as the probe key: alias spellings
202
+ * differ per bridge (one bridge's `sess_…` is another's backend id), and an id
203
+ * a bridge never listed may still resolve through its durable alias store and
204
+ * cold-resume the conversation — materializing a second runtime for a live
205
+ * conversation, which scrambles the shared event log. The probe result joins
206
+ * on runId (a run's stable identity), so one confirmation corrects the row in
207
+ * every group that carries it.
208
+ */
209
+ async function collectLiveOverrides(instances, rows) {
210
+ const out = new Map();
211
+ const suspicious = rows.filter((r) => r["stopReason"] === "interrupted" &&
212
+ typeof r.runId === "string" &&
213
+ r.runId !== "" &&
214
+ typeof r.cwd === "string" &&
215
+ r.cwd !== "");
216
+ if (suspicious.length === 0)
217
+ return out;
218
+ // The workspace is the ownership evidence: a run's cwd is where its session
219
+ // lives, and the hub already groups instances by that same workspace string.
220
+ const byWorkspace = new Map();
221
+ for (const inst of instances) {
222
+ const list = byWorkspace.get(inst.workspace) ?? [];
223
+ list.push(inst);
224
+ byWorkspace.set(inst.workspace, list);
225
+ }
226
+ for (const list of byWorkspace.values())
227
+ list.sort((a, b) => b.startedAt - a.startedAt);
228
+ /** Hard ceiling on live probes per build (never hit in practice). */
229
+ const MAX_LIVE_PROBES = 24;
230
+ const probes = new Map();
231
+ const probe = (inst, sessionId) => {
232
+ const key = `${inst.id}\u0000${sessionId}`;
233
+ let p = probes.get(key);
234
+ if (!p) {
235
+ if (probes.size >= MAX_LIVE_PROBES)
236
+ return Promise.resolve(undefined);
237
+ p = fetchBridgeJson(inst.port, `/settings/workflow-runs?sessionId=${encodeURIComponent(sessionId)}`).catch(() => undefined);
238
+ probes.set(key, p);
239
+ }
240
+ return p;
241
+ };
242
+ for (const row of suspicious) {
243
+ const runId = row.runId;
244
+ const candidates = byWorkspace.get(row.cwd) ?? [];
245
+ outer: for (const inst of candidates) {
246
+ // Running sessions first (most likely to hold a flying run), then the
247
+ // freshest driving copy — the hub's own dedupe ranks bridges that way.
248
+ const sessions = [...inst.sessions].sort((a, b) => (a.status === "running" ? 0 : 1) - (b.status === "running" ? 0 : 1) ||
249
+ (b.updatedAt ?? 0) - (a.updatedAt ?? 0));
250
+ for (const s of sessions) {
251
+ const body = await probe(inst, s.sessionId);
252
+ if (!body)
253
+ continue; // dead / old bridge — next session or instance
254
+ const runs = Array.isArray(body["runs"]) ? body["runs"] : [];
255
+ const match = runs.find((r) => r.runId === runId);
256
+ if (!match)
257
+ continue; // this process does not know the run
258
+ if (typeof match.status === "string" && LIVE_STATUSES.has(match.status)) {
259
+ out.set(runId, { status: match.status, instanceId: inst.id });
260
+ break outer;
261
+ }
262
+ // Found terminal here — a sibling bridge may still hold the live
263
+ // copy, so keep trying the rest of the workspace's sessions.
264
+ }
265
+ }
266
+ }
267
+ return out;
268
+ }
269
+ /**
270
+ * Apply the live truth to a set of journal rows, in place: a row the live
271
+ * registry reports flying loses the foreign "interrupted" verdict, and a row
272
+ * with no owner gets the instance that actually reported it live (the address
273
+ * for stop/detail from a client attached elsewhere).
274
+ */
275
+ function applyLiveTruth(rows, liveRuns) {
276
+ for (const row of rows) {
277
+ const runId = typeof row.runId === "string" ? row.runId : "";
278
+ if (!runId)
279
+ continue;
280
+ const live = liveRuns.get(runId);
281
+ if (!live)
282
+ continue;
283
+ row.status = live.status;
284
+ delete row["stopReason"];
285
+ if (!row.ownerInstanceId)
286
+ row.ownerInstanceId = live.instanceId;
287
+ }
288
+ }
289
+ /**
290
+ * Build the machine-level overview from the hub's instance snapshot.
291
+ * Never throws for bridge-side failures — only for structural bugs.
292
+ */
293
+ export async function buildWorkflowOverview(instances) {
294
+ // One representative per workspace, freshest started first (the prefer
295
+ // order pickPair consumes); every duplicate bridge is a retry candidate.
296
+ const byWorkspace = new Map();
297
+ for (const inst of instances) {
298
+ const list = byWorkspace.get(inst.workspace) ?? [];
299
+ list.push(inst);
300
+ byWorkspace.set(inst.workspace, list);
301
+ }
302
+ for (const list of byWorkspace.values())
303
+ list.sort((a, b) => b.startedAt - a.startedAt);
304
+ const groups = [];
305
+ // Global saved workflows + the machine-wide journal read ride ONE pair:
306
+ // upstream's global runs read has no cwd filter, so its rows cover every
307
+ // project (each row carrying `cwd`) — the active-run feed and the global
308
+ // group's badges come from the same answer.
309
+ const global = await pickPair(instances, "global");
310
+ // Cross-check the global rows' reconciliation-signature verdicts against
311
+ // the owner's live read (see collectLiveOverrides); the runId-keyed result
312
+ // feeds every group carrying the same row.
313
+ const liveOverrides = global
314
+ ? await collectLiveOverrides(instances, global.pair.runs)
315
+ : new Map();
316
+ let activeRuns = [];
317
+ if (global) {
318
+ applyLiveTruth(global.pair.runs, liveOverrides);
319
+ groups.push({
320
+ instanceId: global.inst.id,
321
+ scope: "global",
322
+ workspace: "",
323
+ workflows: workflowsOf(global.pair.list),
324
+ invalid: invalidOf(global.pair.list),
325
+ lastRuns: lastRunByName(global.pair.runs),
326
+ error: null,
327
+ });
328
+ activeRuns = global.pair.runs.filter((r) => typeof r.status === "string" && LIVE_STATUSES.has(r.status));
329
+ annotateOwners(activeRuns, instances);
330
+ }
331
+ // One project group per workspace, workspace-path order (stable across
332
+ // refreshes — completion-order pushes reordered the sections client-side).
333
+ const workspaces = [...byWorkspace.keys()].sort();
334
+ const projectGroups = await Promise.all(workspaces.map(async (workspace) => {
335
+ const candidates = byWorkspace.get(workspace);
336
+ const picked = await pickPair(candidates, "project");
337
+ if (!picked) {
338
+ // Every bridge of this workspace failed — a group the client renders
339
+ // as unavailable, still carrying the workspace label.
340
+ return {
341
+ instanceId: candidates[0].id,
342
+ scope: "project",
343
+ workspace,
344
+ workflows: [],
345
+ invalid: [],
346
+ lastRuns: {},
347
+ error: "no bridge of this workspace answered",
348
+ };
349
+ }
350
+ await applyLiveTruth(picked.pair.runs, liveOverrides);
351
+ return {
352
+ instanceId: picked.inst.id,
353
+ scope: "project",
354
+ workspace,
355
+ workflows: workflowsOf(picked.pair.list),
356
+ invalid: invalidOf(picked.pair.list),
357
+ lastRuns: lastRunByName(picked.pair.runs),
358
+ error: null,
359
+ };
360
+ }));
361
+ groups.push(...projectGroups);
362
+ return { ok: true, groups, activeRuns };
363
+ }
364
+ //# sourceMappingURL=workflow-overview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflow-overview.js","sourceRoot":"","sources":["../../src/remote/workflow-overview.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Quota auto-resume watchdog for dynamic-workflow runs.
3
+ *
4
+ * Why: when a workflow subagent hits a usage cap (GLM 1308 …), upstream stops
5
+ * the WHOLE run as `stopped(provider)` with a `ProviderStopDetails{kind:
6
+ * "quota"}` and its notification instructs the agent to call
7
+ * ResumeWorkflowRun AFTER the reset (workflow-notification-copy.ts:55-64).
8
+ * Nothing schedules that "after" — the run sits stopped until a human acts.
9
+ * The bridge owns the process that can wait, so it does: a small persisted
10
+ * registry of pending resumes, a periodic scan over the project's run journal,
11
+ * and a timer per entry.
12
+ *
13
+ * Detection is deliberately journal-first: the v4 session summary carries the
14
+ * failure message (with the provider's business code in brackets) but NOT the
15
+ * structured providerStop/resetAt, and the workflow progress poller gives up
16
+ * after 10 minutes — long runs would never be seen there. The scan reads
17
+ * `workflows/runs` (one session-less RPC), keeps rows that are
18
+ * `stopped(provider)`, and only then resolves the few parent sessions through
19
+ * `v4/conversation/workflowRuns` to read their failure messages.
20
+ *
21
+ * Firing is defensive at every step: quota card first (a known future reset
22
+ * defers without burning an attempt), then a live state read (a run that is
23
+ * running again / finished / no longer resumable drops the entry), then the
24
+ * same `resumeWorkflowRun` command the App's Resume button uses. An accepted
25
+ * resume is re-verified after {@link VERIFY_AFTER_MS}; only a stopped-again run
26
+ * with no known reset burns an attempt and backs off.
27
+ *
28
+ * Store: `~/.zcode/v2/acp-quota-resumes.json`, shared by every bridge process
29
+ * and written only via temp+rename with merge-at-write (the dismissals-store
30
+ * discipline — a torn write would lose every pending resume).
31
+ */
32
+ import type { QuotaResult } from "../quota/types.js";
33
+ import type { ZcodeAcpServer } from "../server.js";
34
+ /** Scan cadence for newly quota-stopped runs. */
35
+ export declare const QUOTA_RESUME_SCAN_INTERVAL_MS: number;
36
+ /** One pending auto-resume. `attempts` counts resume attempts that did not stick. */
37
+ export interface QuotaResumeEntry {
38
+ runId: string;
39
+ /** Parent backend session (the run's journal parentSessionId). */
40
+ zcodeSid: string;
41
+ /** Workspace the run belongs to — bridges re-arm only their own project's entries. */
42
+ cwd: string;
43
+ /** ACP alias when known at schedule time (re-resolved at fire time anyway). */
44
+ acpSid?: string;
45
+ name?: string;
46
+ /** Epoch ms of the next fire. */
47
+ resumeAt: number;
48
+ /** Failed-attempt counter (drives the backoff ladder). */
49
+ attempts: number;
50
+ /** True while the previous resume attempt is inside its verify window. */
51
+ awaitingVerify?: boolean;
52
+ /**
53
+ * Terminal give-up reason. The journal row stays `stopped(provider)+quota`
54
+ * forever, so without this marker the scan would re-schedule the same run
55
+ * every cycle — a retry/give-up loop. A gave-up entry is skipped by scans,
56
+ * re-arms, and the idle-exit predicate; it expires via the TTL, by which
57
+ * time the row is out of the backfill window anyway.
58
+ */
59
+ gaveUp?: string;
60
+ /**
61
+ * In-flight fire claim (cross-process single-flight). Written under the
62
+ * settings file lock by {@link claimFire}; every normal mutation (updateEntry
63
+ * / reschedule / markGaveUp) clears it. Without it, two bridges of the same
64
+ * workspace (editor + serve/TUI) could both resume one run — a money-level
65
+ * double execution.
66
+ */
67
+ firing?: {
68
+ pid: number;
69
+ at: number;
70
+ };
71
+ scheduledAt: number;
72
+ updatedAt: number;
73
+ }
74
+ export interface QuotaResumeInput {
75
+ runId: string;
76
+ zcodeSid: string;
77
+ cwd: string;
78
+ name?: string;
79
+ acpSid?: string;
80
+ /** Provider-reported reset (Retry-After). Absent → the first fire re-derives. */
81
+ resetAt?: number;
82
+ }
83
+ /**
84
+ * Register (or merge) a pending auto-resume. Idempotent per runId; an earlier
85
+ * resumeAt wins so a duplicate detection never delays an armed timer.
86
+ *
87
+ * The whole read-decide-write runs under the store file lock: the merge must
88
+ * see the CURRENT record (a sibling's in-flight fire claim included — which it
89
+ * must never stomp) rather than a snapshot read before the lock was taken.
90
+ */
91
+ export declare function scheduleQuotaResume(server: ZcodeAcpServer, input: QuotaResumeInput): Promise<void>;
92
+ /**
93
+ * One scan over the project's journal for quota-stopped runs. Best-effort and
94
+ * side-effect free when nothing matches; never spawns a backend (the scan is
95
+ * discovery — only a fire may act).
96
+ */
97
+ export declare function scanQuotaStoppedRuns(server: ZcodeAcpServer): Promise<void>;
98
+ export interface QuotaResumeDeps {
99
+ /** Injectable for tests; defaults to the real quota card. */
100
+ queryQuota?: () => Promise<QuotaResult>;
101
+ }
102
+ /**
103
+ * Fire one pending entry. Everything is re-checked here because the state may
104
+ * have moved since scheduling (user resumed/stopped/dismissed the run, quota
105
+ * already recovered, bridge slept past the reset).
106
+ *
107
+ * Cross-process single-flight: the side-effecting tail runs only under a
108
+ * locked fire claim (see {@link claimFire}); a sibling bridge that loses the
109
+ * claim re-arms its local timer and lets the winner's store write (reschedule
110
+ * / retire) become the truth.
111
+ */
112
+ export declare function fire(server: ZcodeAcpServer, runId: string, deps?: QuotaResumeDeps): Promise<void>;
113
+ /**
114
+ * Arm the watchdog for one bridge: re-arm its project's persisted entries and
115
+ * start the periodic scan. Idempotent per server. Scans never spawn a backend
116
+ * and never throw; a bridge that never sees a backend stays silent.
117
+ */
118
+ export declare function armQuotaResumeWatchdog(server: ZcodeAcpServer): void;
119
+ /** Is a resume armed for this run? (test/observability hook) */
120
+ export declare function quotaResumePending(runId: string): boolean;
121
+ /**
122
+ * Is any resume pending for a workspace? The headless serve bridge consults
123
+ * this in its idle decision: exiting would kill the backend the pending
124
+ * resume must act on, breaking "the bridge owns the wait" exactly when the
125
+ * app is asleep (the case the watchdog exists for). Retired (gave-up) entries
126
+ * do not hold a serve bridge alive.
127
+ */
128
+ export declare function hasQuotaResumePendingForCwd(cwd: string): boolean;
129
+ /** Read one entry (test hook). */
130
+ export declare function quotaResumeEntryForTest(runId: string): QuotaResumeEntry | undefined;
131
+ /** Patch an entry and drop its timer (test hook; patches may include `firing`). */
132
+ export declare function updateQuotaResumeEntryForTest(runId: string, patch: Partial<QuotaResumeEntry>): void;
133
+ /** Drop every timer and the store file (test hook). */
134
+ export declare function resetQuotaResumeForTest(): void;
135
+ //# sourceMappingURL=quota-resume.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quota-resume.d.ts","sourceRoot":"","sources":["../../src/workflow/quota-resume.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAyBH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAMnD,iDAAiD;AACjD,eAAO,MAAM,6BAA6B,QAAa,CAAC;AA8CxD,qFAAqF;AACrF,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,QAAQ,EAAE,MAAM,CAAC;IACjB,sFAAsF;IACtF,GAAG,EAAE,MAAM,CAAC;IACZ,+EAA+E;IAC/E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,0DAA0D;IAC1D,QAAQ,EAAE,MAAM,CAAC;IACjB,0EAA0E;IAC1E,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;OAMG;IACH,MAAM,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AA8QD,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,gBAAgB,GACtB,OAAO,CAAC,IAAI,CAAC,CAyDf;AA0CD;;;;GAIG;AACH,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CA6EhF;AAED,MAAM,WAAW,eAAe;IAC9B,6DAA6D;IAC7D,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC,WAAW,CAAC,CAAC;CACzC;AAED;;;;;;;;;GASG;AACH,wBAAsB,IAAI,CACxB,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,MAAM,EACb,IAAI,GAAE,eAAoB,GACzB,OAAO,CAAC,IAAI,CAAC,CAqIf;AAmED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CA2BnE;AAED,gEAAgE;AAChE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAIzD;AAED;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAKhE;AAED,kCAAkC;AAClC,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAEnF;AAED,mFAAmF;AACnF,wBAAgB,6BAA6B,CAC3C,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAC/B,IAAI,CAQN;AAED,uDAAuD;AACvD,wBAAgB,uBAAuB,IAAI,IAAI,CAQ9C"}