@qodeca/xezar 0.13.0 → 0.14.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.
- package/README.md +72 -39
- package/dist/agent-config/account-identity.d.ts +2 -8
- package/dist/agent-config/account-identity.js +54 -10
- package/dist/agent-config/account-identity.js.map +1 -1
- package/dist/agent-config/catalog.d.ts +49 -7
- package/dist/agent-config/catalog.js +107 -0
- package/dist/agent-config/catalog.js.map +1 -1
- package/dist/agent-config/model-settings/pi.d.ts +12 -6
- package/dist/agent-config/model-settings/pi.js +12 -6
- package/dist/agent-config/model-settings/pi.js.map +1 -1
- package/dist/agent-config/seed.js +10 -0
- package/dist/agent-config/seed.js.map +1 -1
- package/dist/contract/github.d.ts +15 -0
- package/dist/contract/index.d.ts +9 -0
- package/dist/contract/index.js +1527 -895
- package/dist/contract/mcp-api-reference.d.ts +174 -0
- package/dist/contract/mcp-audit.d.ts +103 -0
- package/dist/contract/mcp-discovery.d.ts +257 -0
- package/dist/contract/mcp-event-catalog.d.ts +121 -0
- package/dist/contract/mcp-event-catalog.test.d.ts +1 -0
- package/dist/contract/mcp-idempotency.d.ts +200 -0
- package/dist/contract/mcp-journal.d.ts +268 -0
- package/dist/contract/mcp-leader.d.ts +88 -0
- package/dist/contract/mcp-ownership.d.ts +78 -0
- package/dist/contract/mcp-versioning.d.ts +139 -0
- package/dist/contract/runs.d.ts +81 -0
- package/dist/core/agent-profiles.d.ts +15 -4
- package/dist/core/agent-profiles.js +22 -8
- package/dist/core/agent-profiles.js.map +1 -1
- package/dist/core/opencode-server-runner.js +71 -11
- package/dist/core/opencode-server-runner.js.map +1 -1
- package/dist/core/secret-redaction.d.ts +7 -1
- package/dist/core/secret-redaction.js +44 -10
- package/dist/core/secret-redaction.js.map +1 -1
- package/dist/index.js +138 -27
- package/dist/index.js.map +1 -1
- package/dist/machine-identity.d.ts +66 -0
- package/dist/machine-identity.js +96 -0
- package/dist/machine-identity.js.map +1 -0
- package/dist/mcp/adapters/claude-code.d.ts +41 -0
- package/dist/mcp/adapters/claude-code.js +40 -0
- package/dist/mcp/adapters/claude-code.js.map +1 -0
- package/dist/mcp/adapters/codex.d.ts +140 -0
- package/dist/mcp/adapters/codex.js +323 -0
- package/dist/mcp/adapters/codex.js.map +1 -0
- package/dist/mcp/adapters/opencode.d.ts +148 -0
- package/dist/mcp/adapters/opencode.js +528 -0
- package/dist/mcp/adapters/opencode.js.map +1 -0
- package/dist/mcp/adapters/pi-link.d.ts +102 -0
- package/dist/mcp/adapters/pi-link.js +235 -0
- package/dist/mcp/adapters/pi-link.js.map +1 -0
- package/dist/mcp/adapters/pi.d.ts +188 -0
- package/dist/mcp/adapters/pi.js +418 -0
- package/dist/mcp/adapters/pi.js.map +1 -0
- package/dist/mcp/api-reference.d.ts +26 -0
- package/dist/mcp/api-reference.js +133 -0
- package/dist/mcp/api-reference.js.map +1 -0
- package/dist/mcp/audit-trail.d.ts +162 -0
- package/dist/mcp/audit-trail.js +306 -0
- package/dist/mcp/audit-trail.js.map +1 -0
- package/dist/mcp/bridge.d.ts +81 -0
- package/dist/mcp/bridge.js +407 -0
- package/dist/mcp/bridge.js.map +1 -0
- package/dist/mcp/connection-file.d.ts +49 -0
- package/dist/mcp/connection-file.js +63 -0
- package/dist/mcp/connection-file.js.map +1 -0
- package/dist/mcp/echo-guard.d.ts +94 -0
- package/dist/mcp/echo-guard.js +160 -0
- package/dist/mcp/echo-guard.js.map +1 -0
- package/dist/mcp/event-catalog.d.ts +120 -0
- package/dist/mcp/event-catalog.js +306 -0
- package/dist/mcp/event-catalog.js.map +1 -0
- package/dist/mcp/event-controller.d.ts +230 -0
- package/dist/mcp/event-controller.js +596 -0
- package/dist/mcp/event-controller.js.map +1 -0
- package/dist/mcp/event-journal.d.ts +57 -0
- package/dist/mcp/event-journal.js +423 -0
- package/dist/mcp/event-journal.js.map +1 -0
- package/dist/mcp/index.d.ts +63 -0
- package/dist/mcp/index.js +386 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/ipc.d.ts +202 -0
- package/dist/mcp/ipc.js +206 -0
- package/dist/mcp/ipc.js.map +1 -0
- package/dist/mcp/leader-delivery.d.ts +119 -0
- package/dist/mcp/leader-delivery.js +450 -0
- package/dist/mcp/leader-delivery.js.map +1 -0
- package/dist/mcp/operation-receipts.d.ts +210 -0
- package/dist/mcp/operation-receipts.js +723 -0
- package/dist/mcp/operation-receipts.js.map +1 -0
- package/dist/mcp/project-catalogs.d.ts +23 -0
- package/dist/mcp/project-catalogs.js +25 -0
- package/dist/mcp/project-catalogs.js.map +1 -0
- package/dist/mcp/project-leaders.d.ts +26 -0
- package/dist/mcp/project-leaders.js +17 -0
- package/dist/mcp/project-leaders.js.map +1 -0
- package/dist/mcp/protocol.d.ts +50 -0
- package/dist/mcp/protocol.js +47 -0
- package/dist/mcp/protocol.js.map +1 -0
- package/dist/mcp/reconnect.d.ts +297 -0
- package/dist/mcp/reconnect.js +465 -0
- package/dist/mcp/reconnect.js.map +1 -0
- package/dist/mcp/resource-ownership.d.ts +228 -0
- package/dist/mcp/resource-ownership.js +390 -0
- package/dist/mcp/resource-ownership.js.map +1 -0
- package/dist/mcp/service-adapter.d.ts +16488 -0
- package/dist/mcp/service-adapter.js +148 -0
- package/dist/mcp/service-adapter.js.map +1 -0
- package/dist/mcp/service.d.ts +83 -0
- package/dist/mcp/service.js +233 -0
- package/dist/mcp/service.js.map +1 -0
- package/dist/mcp/session-binding.d.ts +105 -0
- package/dist/mcp/session-binding.js +163 -0
- package/dist/mcp/session-binding.js.map +1 -0
- package/dist/mcp/stale-write.d.ts +145 -0
- package/dist/mcp/stale-write.js +219 -0
- package/dist/mcp/stale-write.js.map +1 -0
- package/dist/mcp/tool.d.ts +48 -0
- package/dist/mcp/tool.js +24 -0
- package/dist/mcp/tool.js.map +1 -0
- package/dist/mcp/tools/discovery.d.ts +40 -0
- package/dist/mcp/tools/discovery.js +232 -0
- package/dist/mcp/tools/discovery.js.map +1 -0
- package/dist/mcp/tools/execution-control.d.ts +171 -0
- package/dist/mcp/tools/execution-control.js +513 -0
- package/dist/mcp/tools/execution-control.js.map +1 -0
- package/dist/mcp/tools/handoff-git.d.ts +26 -0
- package/dist/mcp/tools/handoff-git.js +544 -0
- package/dist/mcp/tools/handoff-git.js.map +1 -0
- package/dist/mcp/tools/index.d.ts +2 -0
- package/dist/mcp/tools/index.js +23 -0
- package/dist/mcp/tools/index.js.map +1 -0
- package/dist/mcp/tools/leader-events.d.ts +46 -0
- package/dist/mcp/tools/leader-events.js +126 -0
- package/dist/mcp/tools/leader-events.js.map +1 -0
- package/dist/mcp/tools/local-handoff.d.ts +102 -0
- package/dist/mcp/tools/local-handoff.js +306 -0
- package/dist/mcp/tools/local-handoff.js.map +1 -0
- package/dist/mcp/tools/project-config.d.ts +340 -0
- package/dist/mcp/tools/project-config.js +1075 -0
- package/dist/mcp/tools/project-config.js.map +1 -0
- package/dist/mcp/tools/results-evidence.d.ts +112 -0
- package/dist/mcp/tools/results-evidence.js +1045 -0
- package/dist/mcp/tools/results-evidence.js.map +1 -0
- package/dist/mcp/tools/task-create.d.ts +70 -0
- package/dist/mcp/tools/task-create.js +473 -0
- package/dist/mcp/tools/task-create.js.map +1 -0
- package/dist/mcp/tools/task-reads.d.ts +102 -0
- package/dist/mcp/tools/task-reads.js +689 -0
- package/dist/mcp/tools/task-reads.js.map +1 -0
- package/dist/mcp/tools/work-organisation.d.ts +2 -0
- package/dist/mcp/tools/work-organisation.js +502 -0
- package/dist/mcp/tools/work-organisation.js.map +1 -0
- package/dist/paths.d.ts +17 -7
- package/dist/paths.js +18 -8
- package/dist/paths.js.map +1 -1
- package/dist/runs/project-writer.js +76 -11
- package/dist/runs/project-writer.js.map +1 -1
- package/dist/runs/retention.d.ts +25 -2
- package/dist/runs/retention.js +63 -2
- package/dist/runs/retention.js.map +1 -1
- package/dist/server/forge/github.js +44 -0
- package/dist/server/forge/github.js.map +1 -1
- package/dist/server/forge/types.d.ts +16 -0
- package/dist/server/project-context.d.ts +81 -8
- package/dist/server/project-context.js +143 -35
- package/dist/server/project-context.js.map +1 -1
- package/dist/server/server.d.ts +1050 -53
- package/dist/server/server.js +326 -120
- package/dist/server/server.js.map +1 -1
- package/dist/server/validators.d.ts +26 -6
- package/dist/server/validators.js +17 -0
- package/dist/server/validators.js.map +1 -1
- package/dist/skills-remote.js +64 -6
- package/dist/skills-remote.js.map +1 -1
- package/dist/workflows/run.d.ts +277 -12
- package/dist/workflows/run.js +599 -79
- package/dist/workflows/run.js.map +1 -1
- package/dist/workspace/agent-profiles.d.ts +30 -3
- package/dist/workspace/agent-profiles.js +39 -4
- package/dist/workspace/agent-profiles.js.map +1 -1
- package/dist/workspace/config.js +25 -4
- package/dist/workspace/config.js.map +1 -1
- package/dist/workspace/project-owner.d.ts +210 -0
- package/dist/workspace/project-owner.js +518 -0
- package/dist/workspace/project-owner.js.map +1 -0
- package/package.json +5 -3
- package/scripts/pi-leader-extension.ts +420 -0
- package/scripts/sync-readme.mjs +83 -2
- package/web/dist/assets/{alert-dialog-BVsVcbFy.js → alert-dialog-D6rP6d39.js} +1 -1
- package/web/dist/assets/arrow-down-DMOfFIzW.js +1 -0
- package/web/dist/assets/arrow-left-C4Ec2BUg.js +1 -0
- package/web/dist/assets/{centered-state-Djgly6Ni.js → centered-state-B0ZqrPaa.js} +12 -12
- package/web/dist/assets/chevron-right-mHJ-crvn.js +1 -0
- package/web/dist/assets/{collapsible-DZ4D-DHG.js → collapsible-DchEHgUM.js} +1 -1
- package/web/dist/assets/{commit-list-B-z1JL9G.js → commit-list-BpMim1Rv.js} +1 -1
- package/web/dist/assets/{compare-variants-CdS8r8E2.js → compare-variants-D5ItQbAq.js} +1 -1
- package/web/dist/assets/{diff-BKew9rQX.js → diff-HqqZ_WDR.js} +2 -2
- package/web/dist/assets/{diff-stat-D0mc5V6Z.js → diff-stat-NsU-3ecr.js} +1 -1
- package/web/dist/assets/{diff-view-xpIcuLIC.js → diff-view-B7c11Xux.js} +1 -1
- package/web/dist/assets/{dropdown-menu-CEQziH7X.js → dropdown-menu-CtTpnyod.js} +1 -1
- package/web/dist/assets/{editable-title-BCX3T2BY.js → editable-title-BXtuGQry.js} +1 -1
- package/web/dist/assets/{ellipsis-vertical-ByVdDc4z.js → ellipsis-vertical-MHtGZSH6.js} +1 -1
- package/web/dist/assets/{file-xBSB5hXO.js → file-DKLw5SOb.js} +1 -1
- package/web/dist/assets/{folder-CldgbZ5A.js → folder-Drw6-ufH.js} +1 -1
- package/web/dist/assets/{git-toolbar-BYlnf4qf.js → git-toolbar-BQH8bORV.js} +1 -1
- package/web/dist/assets/github-ynnpKFCJ.js +1 -0
- package/web/dist/assets/{image-preview-Cv5mZr0n.js → image-preview-DLhMN_R9.js} +1 -1
- package/web/dist/assets/index-BEsrNdXt.js +29 -0
- package/web/dist/assets/index-Da8DOBau.css +2 -0
- package/web/dist/assets/{markdown-CgEEE2ND.js → markdown-q365eN6Y.js} +1 -1
- package/web/dist/assets/{new-task-form-1r2bgsdD.js → new-task-form-CW-hNIWQ.js} +1 -1
- package/web/dist/assets/{pill-q-x22_ej.js → pill-B_TFulXa.js} +1 -1
- package/web/dist/assets/{project-router-VDN4jQM8.js → project-router-CLPc-ImZ.js} +1 -1
- package/web/dist/assets/{prompt-templates-BmZtCe-L.js → prompt-templates-CLc4RxyO.js} +1 -1
- package/web/dist/assets/{repo-git-C2On8SlI.js → repo-git-Is7R6-Tj.js} +1 -1
- package/web/dist/assets/{run-diff-vguEMJzh.js → run-diff-DHMvDlVo.js} +2 -2
- package/web/dist/assets/run-header-DvZJbQ8I.js +1 -0
- package/web/dist/assets/{search-x-QmhaAbg5.js → search-x-Ce2MMetu.js} +1 -1
- package/web/dist/assets/{skill-empty-hint-BrzUwByZ.js → skill-empty-hint-C_xCSvzB.js} +1 -1
- package/web/dist/assets/skills-CivqD4si.js +1 -0
- package/web/dist/assets/{sparkles-CP8QDOgX.js → sparkles-BCqPoj5g.js} +1 -1
- package/web/dist/assets/{square-terminal-CGypshSD.js → square-terminal-ikj5g-oK.js} +1 -1
- package/web/dist/assets/{tab-link-D4lFzzI1.js → tab-link-VsEjFV1Y.js} +1 -1
- package/web/dist/assets/{task-changes-6aopqAmV.js → task-changes-CH-E81-_.js} +1 -1
- package/web/dist/assets/{task-commits-u-dwaCyw.js → task-commits-DqfkMFxp.js} +1 -1
- package/web/dist/assets/{task-files-IMOZnIUK.js → task-files-D_nkHZ_a.js} +2 -2
- package/web/dist/assets/task-thread-bxQompxJ.js +9 -0
- package/web/dist/assets/{textarea-CyBCyKIk.js → textarea-xajuW1L_.js} +1 -1
- package/web/dist/assets/{thread-loading-Cuq2jchC.js → thread-loading-C3JQZbIE.js} +1 -1
- package/web/dist/assets/{trash-2-B6Dp3ZNs.js → trash-2-hJrBhpUD.js} +1 -1
- package/web/dist/assets/{triangle-alert-xBya8FUp.js → triangle-alert-D1u82y1a.js} +1 -1
- package/web/dist/assets/{upload-D6MXEB9p.js → upload-Bn6FBZ59.js} +1 -1
- package/web/dist/assets/{use-desktop-fLDKEJoB.js → use-desktop-DOIMfHfY.js} +1 -1
- package/web/dist/assets/{use-submit-shortcut-JTpNzU-u.js → use-submit-shortcut-Dj4DHTdE.js} +1 -1
- package/web/dist/assets/utils-YwBpOoRN.js +64 -0
- package/web/dist/assets/{workflows-BY3KTkFw.js → workflows-CTaAKz2m.js} +3 -3
- package/web/dist/assets/{zoomable-image-GTYwLtkS.js → zoomable-image-C1-6P3tg.js} +1 -1
- package/web/dist/index.html +23 -23
- package/web/dist/assets/arrow-down-CYgAlZtW.js +0 -1
- package/web/dist/assets/arrow-left-BOqE0a3k.js +0 -1
- package/web/dist/assets/chevron-right-Cc9BWnbN.js +0 -1
- package/web/dist/assets/github-CqGK62Lw.js +0 -1
- package/web/dist/assets/index-3nxVmdn5.css +0 -2
- package/web/dist/assets/index-qX7n11FX.js +0 -9
- package/web/dist/assets/refresh-cw-DcNCj2nu.js +0 -1
- package/web/dist/assets/run-header-DM7MiKj9.js +0 -1
- package/web/dist/assets/skills-Bj-uG_Hz.js +0 -1
- package/web/dist/assets/task-thread-CbMPmrWq.js +0 -9
- package/web/dist/assets/utils--z9hRtl3.js +0 -64
package/dist/workflows/run.d.ts
CHANGED
|
@@ -14,6 +14,22 @@ import { type WorkflowDef } from './types.ts';
|
|
|
14
14
|
* `DEFAULT_IDLE_TIMEOUT_MINUTES`.
|
|
15
15
|
*/
|
|
16
16
|
export declare const IDLE_TIMEOUT_MS: number;
|
|
17
|
+
/**
|
|
18
|
+
* Why a NON-FINAL agent step may not be marked `done` (#317), or null when it may.
|
|
19
|
+
*
|
|
20
|
+
* Such a step runs one turn and its session closes by itself, so it has no way to wait for an
|
|
21
|
+
* answer — and until #317 nothing decided it was done except "the session ended without an
|
|
22
|
+
* error". Run b86c6066's implement step ended on an `XEZ:ASK` design question, was marked done,
|
|
23
|
+
* and readiness, gates and the seal all ran on a branch with no work in it.
|
|
24
|
+
*
|
|
25
|
+
* The decision now belongs to the agent contract every step is given (`HANDOFF_INSTRUCTIONS`):
|
|
26
|
+
* `XEZ:DONE` means "my goal is achieved", and a turn that ends without it is "waiting on the
|
|
27
|
+
* user". A turn that ends on anything else — a question, the monitoring marker, or plain prose —
|
|
28
|
+
* stops the workflow here instead of carrying on without an answer. Deliberately fail-closed on
|
|
29
|
+
* plain prose: that is the case no agent-side marker can report, and the one the BLOCKED file
|
|
30
|
+
* could not catch either. `turnText` is null when the session ended without finishing a turn.
|
|
31
|
+
*/
|
|
32
|
+
export declare function unfinishedStepReason(turnText: string | null): string | null;
|
|
17
33
|
/**
|
|
18
34
|
* Preserve boundaries between complete assistant text blocks while a turn is
|
|
19
35
|
* accumulated for marker parsing. The runners join these same v1 blocks with
|
|
@@ -62,6 +78,17 @@ export declare const MAX_AUTO_RESUMES = 12;
|
|
|
62
78
|
* note instead of fired, so the only tasks a sweep can revive are ones someone is still waiting on.
|
|
63
79
|
*/
|
|
64
80
|
export declare const AUTO_RESUME_MISSED_WINDOW_MS: number;
|
|
81
|
+
/**
|
|
82
|
+
* How long a resumed turn must stay live before it counts as proof that the limit lifted (#285).
|
|
83
|
+
*
|
|
84
|
+
* The in-flight hold exists for the window where a resume is TESTING the account: a limit that
|
|
85
|
+
* has not lifted refuses the turn at its first API call, and the measured doomed run lives about
|
|
86
|
+
* 200 ms. A turn still running minutes later got past that call, so holding every other task on
|
|
87
|
+
* the account until it COMPLETES — which a long turn may not do for hours — is the hold outliving
|
|
88
|
+
* its purpose. Not the first streamed event: a refused Claude turn still streams its session init
|
|
89
|
+
* and a synthetic assistant frame before the error, so "it said something" proves nothing.
|
|
90
|
+
*/
|
|
91
|
+
export declare const AUTO_RESUME_PROOF_MS: number;
|
|
65
92
|
/**
|
|
66
93
|
* How often the queue checks that it is not wedged.
|
|
67
94
|
*
|
|
@@ -244,6 +271,12 @@ export declare class RunManager {
|
|
|
244
271
|
* from the record rather than losing the wait. Runs here are `failed` and therefore NOT in
|
|
245
272
|
* `active`, which is why the timer cannot live on an `ActiveRun` like the monitoring one. */
|
|
246
273
|
private readonly autoResumeTimers;
|
|
274
|
+
/** Wake-ups for the instant a live resume's proof window closes (#285). The hold lifts by
|
|
275
|
+
* derivation, and a derived release is not an event, so these are only the pump that notices;
|
|
276
|
+
* the hold itself stays on the records. */
|
|
277
|
+
private readonly resumeProofTimers;
|
|
278
|
+
/** `AUTO_RESUME_PROOF_MS`, overridable only so a test need not wait two minutes. */
|
|
279
|
+
private readonly resumeProofMs;
|
|
247
280
|
private pumping;
|
|
248
281
|
/** A pump that arrived while one was in flight — replayed by `pump()`'s own
|
|
249
282
|
* loop so a slot freed mid-sweep is never a lost wakeup. */
|
|
@@ -265,15 +298,77 @@ export declare class RunManager {
|
|
|
265
298
|
private readonly offUsage;
|
|
266
299
|
/** The stalled-queue watchdog (see `rescueStalledQueue`). */
|
|
267
300
|
private readonly queueWatchdog;
|
|
268
|
-
/**
|
|
269
|
-
*
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
|
|
301
|
+
/**
|
|
302
|
+
* Every background WRITE this manager started on its own initiative and has not settled yet.
|
|
303
|
+
* Two producers, one set: the queue-watchdog rescue sweeps (#125) and the terminal-transition
|
|
304
|
+
* worktree retention sweeps (#200). Neither is cancellable and both cross an await before they
|
|
305
|
+
* touch disk — a rescue appends NDJSON after `reviveWorkflow`, a retention sweep spawns
|
|
306
|
+
* `git worktree remove` after `resolveWorktreeRetention` — so `clearInterval` and the
|
|
307
|
+
* `disposed` flag between them still leave work running.
|
|
308
|
+
*
|
|
309
|
+
* dispose() settles this set, which is what makes its promise mean "provably finished writing"
|
|
310
|
+
* before the caller removes the data root. Retention was the half that was NOT tracked, and
|
|
311
|
+
* the harm differs by caller — be precise about which, because only one of them deletes
|
|
312
|
+
* anything. In a TEST teardown a disposed manager could still be spawning git inside a
|
|
313
|
+
* directory the case was already `rmSync`-ing, which is the reported ENOTEMPTY. In a LIVE
|
|
314
|
+
* server nothing removes the project directory at all: the sweep instead stamps
|
|
315
|
+
* `worktreeReclaimedAt` onto a record, scheduling a debounced `runs.json` write from a store
|
|
316
|
+
* whose lifecycle has ended — so a re-add inside that window can see the stale in-memory index
|
|
317
|
+
* overwrite the fresh one. Every producer therefore enrols through a wrapper
|
|
318
|
+
* (`rescueStalledQueue`, `enforceRetention`), so nothing can start a background write teardown
|
|
319
|
+
* cannot see.
|
|
320
|
+
*
|
|
321
|
+
* The live-server half needed the OTHER end fixed too, and the promise is worthless without it:
|
|
322
|
+
* `ProjectContexts.dispose` used to drop this promise on the floor (`teardown()` was
|
|
323
|
+
* synchronous), so the guarantee held only for callers that awaited — which was tests. It now
|
|
324
|
+
* awaits the manager before `store.flush()` + `removeAllListeners()`, and `DELETE /projects/:id`
|
|
325
|
+
* awaits that in turn.
|
|
326
|
+
*/
|
|
327
|
+
private readonly writesInFlight;
|
|
328
|
+
/**
|
|
329
|
+
* The run BODIES — `execute()` / `runContinuation()` — that have started and not settled yet,
|
|
330
|
+
* plus the turn-end bookkeeping (`recordTurnEnd`) that a body fires and does not await.
|
|
331
|
+
*
|
|
332
|
+
* Deliberately NOT `writesInFlight`. dispose() is documented as "the manager makes no further
|
|
333
|
+
* moves ON ITS OWN" and is explicitly not a run-stopper, so awaiting a live agent session in
|
|
334
|
+
* its promise would silently change what every existing caller gets. `quiesce()` is the opt-in
|
|
335
|
+
* that cancels first and then awaits this set; dispose()'s semantics are untouched.
|
|
336
|
+
*/
|
|
337
|
+
private readonly runsInFlight;
|
|
338
|
+
/**
|
|
339
|
+
* Cancellations that arrived while a run was still MATERIALIZING — dequeued into `starting`,
|
|
340
|
+
* or floated by the direct Continue path — and had nowhere to land.
|
|
341
|
+
*
|
|
342
|
+
* `cancel()` stops an active run by writing `state.cancelled` and calling `state.interrupt()`,
|
|
343
|
+
* and neither exists until the body has built its `ActiveRun`. Before this set, `cancel()` for
|
|
344
|
+
* such a run simply returned false and delivered NOTHING, so the run went on to spawn an agent
|
|
345
|
+
* turn that no one had asked for and that `quiesce()` then waited out — up to the runner's
|
|
346
|
+
* 30-minute default (#200). The body consumes the request through `adoptActive`, at the first
|
|
347
|
+
* instant a cancellation has somewhere to go.
|
|
348
|
+
*
|
|
349
|
+
* `dropActive` clears the entry too, because a body can leave without ever reaching
|
|
350
|
+
* `adoptActive` (the continuation whose isolation is gone), and a stale entry would cancel the
|
|
351
|
+
* NEXT run to reuse that id — which, for a Continue, is the same id again.
|
|
352
|
+
*/
|
|
353
|
+
private readonly cancelRequested;
|
|
273
354
|
/** Set by dispose(): this manager makes no further moves. Re-checked at every await boundary a
|
|
274
355
|
* rescue crosses, because a sweep that was already past its first check when dispose() landed
|
|
275
356
|
* would otherwise write an event — and re-populate the queue dispose() had just emptied. */
|
|
276
357
|
private disposed;
|
|
358
|
+
/**
|
|
359
|
+
* Set by `quiesce()` for the length of its drain: the scheduler starts nothing new.
|
|
360
|
+
*
|
|
361
|
+
* `disposed` cannot cover this window, because `quiesce()` disposes LAST — the whole point is
|
|
362
|
+
* to stop the runs while `cancel()` can still see them. During the drain the manager is
|
|
363
|
+
* otherwise fully live, and every settling run pumps the entire workspace on its way out
|
|
364
|
+
* (`dropActive` → `releaseSlot` → `semaphore.release()`, which awaits `pump()` on every
|
|
365
|
+
* registered participant, this one included). A pump reconciles auto-resumes from the RECORDS,
|
|
366
|
+
* and a record whose `autoResumeAt` has already passed arms at zero delay — so the drain could
|
|
367
|
+
* fire a resume, spawn a fresh agent turn into the very repo root the caller is about to
|
|
368
|
+
* delete, and then dutifully wait for it. Bounded by `MAX_AUTO_RESUMES`, and the exact opposite
|
|
369
|
+
* of what the method promises.
|
|
370
|
+
*/
|
|
371
|
+
private quiescing;
|
|
277
372
|
/** Set by the watchdog for exactly one sweep: ignore the usage-limit hold and make progress. */
|
|
278
373
|
private forceNextPump;
|
|
279
374
|
/** Runs the watchdog started despite the hold. The spawn-time gate (`requeueWhileHeld`) would
|
|
@@ -288,6 +383,7 @@ export declare class RunManager {
|
|
|
288
383
|
private readonly offSemaphore;
|
|
289
384
|
constructor(store: RunStore, repoRoot: string, options?: {
|
|
290
385
|
semaphore?: WorkspaceSemaphore;
|
|
386
|
+
resumeProofMs?: number;
|
|
291
387
|
});
|
|
292
388
|
/**
|
|
293
389
|
* Release everything this manager owns without touching run records
|
|
@@ -300,17 +396,110 @@ export declare class RunManager {
|
|
|
300
396
|
* dispose only guarantees the manager makes no further moves on its own.
|
|
301
397
|
*
|
|
302
398
|
* Every side effect below is SYNCHRONOUS, so a caller that ignores the return value behaves
|
|
303
|
-
* exactly as it did before #125. The returned promise settles the
|
|
304
|
-
* synchronously: a queue-watchdog rescue
|
|
305
|
-
*
|
|
399
|
+
* exactly as it did before #125. The returned promise settles the things dispose cannot do
|
|
400
|
+
* synchronously: the background writes already in flight — a queue-watchdog rescue (#125) and
|
|
401
|
+
* a worktree retention sweep (#200), both tracked in `writesInFlight`. `clearInterval` stops
|
|
402
|
+
* the next tick, never the running one — the same discipline `AGENTS.md` records for the e2e
|
|
306
403
|
* fixture servers, where `kill()` only delivers the signal and the helper awaits the exit.
|
|
307
404
|
* Await it whenever the data root is about to be removed; a test that deletes its temp
|
|
308
405
|
* directory without awaiting is the ENOENT in #125.
|
|
309
406
|
*
|
|
407
|
+
* It does NOT wait for running runs, and that is the point of `quiesce()`: dispose clears
|
|
408
|
+
* `active`/`starting`/`queue` without stopping anything, so after it returns a leaked run is
|
|
409
|
+
* invisible to `cancel()` and can no longer be stopped at all. Reach for `quiesce()` whenever
|
|
410
|
+
* the runs are yours to end; reach for `dispose()` when they are not.
|
|
411
|
+
*
|
|
412
|
+
* ## The states a disposed manager leaves behind, and how they end
|
|
413
|
+
*
|
|
414
|
+
* - A run that was `active` keeps running to its own terminal status and writes it to the
|
|
415
|
+
* record; only the manager's registries forgot it. `cancel()` answers false for it from here
|
|
416
|
+
* on, which is the leak `quiesce()` exists to avoid.
|
|
417
|
+
* - A run that was `queued` — or one `startRun()` accepts AFTER dispose, which still writes a
|
|
418
|
+
* `queued` record and still floats a `pump()` — never starts in this process: `pump()` bails
|
|
419
|
+
* on `disposed`, so nothing dequeues it. Its only exit is the next process: `recover()`
|
|
420
|
+
* re-queues every `queued` record at boot, and `rescueStalledQueue` re-adopts one the engine
|
|
421
|
+
* is holding no work item for. That is deliberate — a disposed manager's project has been
|
|
422
|
+
* removed from the registry, and starting its work would be the surprise — but it does mean
|
|
423
|
+
* the record outlives the process as `queued` rather than `cancelled`.
|
|
424
|
+
*
|
|
310
425
|
* It never rejects: a sweep that fails still belongs to whoever started it (the watchdog floats
|
|
311
426
|
* it exactly as before), and teardown must not become a second place that error surfaces.
|
|
312
427
|
*/
|
|
313
428
|
dispose(): Promise<void>;
|
|
429
|
+
/**
|
|
430
|
+
* Stop every run this manager owns, then dispose — "the data root is about to disappear".
|
|
431
|
+
*
|
|
432
|
+
* dispose() alone cannot deliver that: it is not a run-stopper by design, and because it
|
|
433
|
+
* clears `active`/`starting`/`queue` first, a run left behind is afterwards invisible to
|
|
434
|
+
* `cancel()` (which looks only in `queue` then `active`) and can never be stopped. The order
|
|
435
|
+
* here is the whole fix — cancel, drain, dispose — and it is the order four test files were
|
|
436
|
+
* already hand-rolling three different ways.
|
|
437
|
+
*
|
|
438
|
+
* ## Why the cancel is INSIDE the loop, and why termination needs `quiescing`
|
|
439
|
+
*
|
|
440
|
+
* Cancelling once up front is not enough, because the population is not fixed while the drain
|
|
441
|
+
* runs. `starting` promotes to `active` (the `ActiveRun` a `cancel()` needs did not exist a
|
|
442
|
+
* moment ago), a queued continuation crosses `rematerializeReclaimedWorktree` before it
|
|
443
|
+
* registers anywhere at all, and every settling body pumps the whole workspace on its way out.
|
|
444
|
+
* So each pass re-asks `activeRunIds()` and re-issues `cancel()`; `cancelRequested` carries a
|
|
445
|
+
* cancellation into the one window where neither registry can hold it.
|
|
446
|
+
*
|
|
447
|
+
* Termination then rests on `quiescing`, not on "the queue is empty". The manager has three
|
|
448
|
+
* self-starting entry points — `pump()`, `fireAutoResume()` and the queue watchdog's
|
|
449
|
+
* `rescueStalledQueue()` — and all three bail while it is set, so no pass can enrol a run the
|
|
450
|
+
* previous pass did not cancel. Ask "who fires this?" of any new one: the watchdog is an
|
|
451
|
+
* unref'd 60 s interval, so it is invisible in a short drain and lands squarely in a long one
|
|
452
|
+
* (an agent session that will not close is bounded only by the runner's 30-minute default),
|
|
453
|
+
* where it would re-adopt queued records — NDJSON, `pendingJobs`, a `queue` push — inside the
|
|
454
|
+
* window this method exists to make quiet.
|
|
455
|
+
*
|
|
456
|
+
* What a pass CAN still enrol is bounded bookkeeping: a body fires one `recordTurnEnd` per turn
|
|
457
|
+
* it finishes, and a cancelled run finishes no further turns (`autoContinueTurn` refuses a
|
|
458
|
+
* cancelled state). Each iteration therefore awaits a strictly older generation of work than the
|
|
459
|
+
* last, and the generations are finite.
|
|
460
|
+
*
|
|
461
|
+
* An external caller CAN still enrol during the drain — `startRun()` and `continueRun()` are
|
|
462
|
+
* not gated by `quiescing`, because refusing a user's request is the HTTP layer's decision and
|
|
463
|
+
* not a teardown helper's. The loop absorbs one: the next pass finds it, cancels it and waits
|
|
464
|
+
* for it, which is exactly what the "an agent turn enrolled after the drain took its snapshot"
|
|
465
|
+
* case pins. What is NOT bounded is an unbroken STREAM of them — a caller that keeps starting
|
|
466
|
+
* work into a project it is tearing down gets what it asked for.
|
|
467
|
+
*
|
|
468
|
+
* ## Each pass delivers its cancel ONCE — the bodies are what make that enough
|
|
469
|
+
*
|
|
470
|
+
* Re-issuing `cancel()` per pass reaches runs whose REGISTRY membership changed; it does not
|
|
471
|
+
* reach a run that was already in `active` when the pass ran, because the next re-issue waits
|
|
472
|
+
* on the same `Promise.allSettled` the stuck body is holding open. So the guarantee has to live
|
|
473
|
+
* in the body: every point at which a run body can park must consume a cancellation that
|
|
474
|
+
* arrived while it was elsewhere. Those points are `adoptActive` (the `starting` window),
|
|
475
|
+
* `acquireRepoRoot` (the lease wait, which races its own abort), the step loop's
|
|
476
|
+
* `if (state.cancelled) break`, `publishSession` (the gap between the loop's check and a live
|
|
477
|
+
* `state.interrupt`, which is what #199 hung on), and `state.interrupt()` itself once a session
|
|
478
|
+
* is up. Adding a new `await` inside a run body means asking which of those covers it.
|
|
479
|
+
*
|
|
480
|
+
* ## If a tracked promise never settles
|
|
481
|
+
*
|
|
482
|
+
* `quiesce()` has no deadline of its own and stays pending — the caller's timeout is the only
|
|
483
|
+
* bound. That is deliberate. Cancellation is delivered, an agent step's own wall clock still
|
|
484
|
+
* applies, and a session that refuses to close is a real writer: returning early would hand the
|
|
485
|
+
* caller a promise that says "nothing is writing any more" while something is, which is the
|
|
486
|
+
* exact lie this method exists to remove. An invented deadline would only move the ENOTEMPTY a
|
|
487
|
+
* few hundred milliseconds later — and, in the #199 hang, would have shipped a teardown that
|
|
488
|
+
* deleted a repository out from under a live agent CLI instead of failing loudly at 90 s.
|
|
489
|
+
*
|
|
490
|
+
* Never rejects, the same contract as dispose(): teardown must not become a second place a run's
|
|
491
|
+
* error surfaces.
|
|
492
|
+
*/
|
|
493
|
+
quiesce(): Promise<void>;
|
|
494
|
+
/**
|
|
495
|
+
* Enrol a run body in `runsInFlight` so `quiesce()` can wait for it.
|
|
496
|
+
*
|
|
497
|
+
* The body arrives with its own `.catch(…)` already attached — the failure policy stays at the
|
|
498
|
+
* call site, where it can name the run — and the extra `.catch` here only keeps the tracked
|
|
499
|
+
* copy from ever rejecting, so a handler that itself throws cannot turn teardown into an
|
|
500
|
+
* unhandled rejection.
|
|
501
|
+
*/
|
|
502
|
+
private trackRun;
|
|
314
503
|
/**
|
|
315
504
|
* Pause any active run whose whole process tree exceeds the WORKSPACE
|
|
316
505
|
* `resources.memoryLimitMb`, freeing its slot so the queue advances
|
|
@@ -503,7 +692,7 @@ export declare class RunManager {
|
|
|
503
692
|
*
|
|
504
693
|
* Public so a test can drive the wedge directly instead of waiting out the interval.
|
|
505
694
|
*
|
|
506
|
-
* The sweep itself is `sweepStalledQueue`; this wrapper only publishes it to `
|
|
695
|
+
* The sweep itself is `sweepStalledQueue`; this wrapper only publishes it to `writesInFlight`
|
|
507
696
|
* so dispose() can settle it (#125). Every entry point goes through here — the interval and the
|
|
508
697
|
* tests alike — so nothing can start a sweep that teardown cannot see. The schedule, the
|
|
509
698
|
* conditions and the effects are untouched.
|
|
@@ -532,13 +721,48 @@ export declare class RunManager {
|
|
|
532
721
|
* which is the route's 404.
|
|
533
722
|
*/
|
|
534
723
|
cancelAutoResume(runId: string): boolean;
|
|
724
|
+
/**
|
|
725
|
+
* Pump the whole workspace once a live resume's proof window has closed (#285). The hold is
|
|
726
|
+
* workspace-wide — one account can be queued in several projects — so this is `release()`,
|
|
727
|
+
* not this manager's own pump. It carries no state: whether the hold has lifted is decided by
|
|
728
|
+
* `accountHolds()` from the record when the pump reads it, so a turn that failed or finished
|
|
729
|
+
* first just makes this a no-op pump. The slack keeps a timer that fires a millisecond early
|
|
730
|
+
* from reading a window that has not quite closed yet.
|
|
731
|
+
*/
|
|
732
|
+
private armResumeProofPump;
|
|
535
733
|
/** Retire a pending resume — timer, deadline and counter. The counter goes too because every
|
|
536
734
|
* caller is a fresh epoch: a human Continue, or a resume that re-stamps its own count. */
|
|
537
735
|
private clearAutoResume;
|
|
538
|
-
/**
|
|
539
|
-
*
|
|
540
|
-
*
|
|
736
|
+
/**
|
|
737
|
+
* Reclaim finished worktrees beyond the keep-limit (#483) — directory only, `xez/<id8>` branch
|
|
738
|
+
* kept. Best-effort; a failure never affects run lifecycle. `review`/live runs are excluded by
|
|
739
|
+
* the selector.
|
|
740
|
+
*
|
|
741
|
+
* The sweep itself is `reclaimRetiredWorktrees`; this wrapper only publishes it to
|
|
742
|
+
* `writesInFlight` so dispose() can settle it, the same shape `rescueStalledQueue` already
|
|
743
|
+
* uses. Callers still float it — retention must never delay or throw into a terminal
|
|
744
|
+
* transition — but a floated promise nobody holds is one teardown cannot wait for, and this
|
|
745
|
+
* one spawns `git worktree remove` and `git worktree prune` inside `repoRoot` (#200).
|
|
746
|
+
*/
|
|
541
747
|
private enforceRetention;
|
|
748
|
+
/**
|
|
749
|
+
* The retention sweep proper. `disposed` is re-read after `resolveWorktreeRetention` — the one
|
|
750
|
+
* await before any git spawn — for the reason the flag exists: a sweep already past its first
|
|
751
|
+
* check when dispose() landed would otherwise start removing directories under a root its
|
|
752
|
+
* caller is deleting. `shouldStop` carries the same question INTO the loop, because a sweep
|
|
753
|
+
* removes one directory per iteration and a single check before the first one stops nothing on
|
|
754
|
+
* a five-worktree pass that straddles a dispose.
|
|
755
|
+
*
|
|
756
|
+
* Bailing there reclaims nothing, and nothing is lost by it: retention is count-based and
|
|
757
|
+
* re-enforced from scratch by the boot sweep (`index.ts`) and by the Settings → Worktrees
|
|
758
|
+
* reclaim route, so a skipped pass costs one over-limit directory until the next boot. The
|
|
759
|
+
* alternative — finishing the pass — spends two git spawns inside a directory whose owner has
|
|
760
|
+
* just said it is done with it, plus a `worktreeReclaimedAt` stamp through `store.updateRun`
|
|
761
|
+
* that schedules a debounced `runs.json` write from a store nobody owns any more. That is the
|
|
762
|
+
* whole of #200, and the same hazard `armRepoHandle` is guarded against in
|
|
763
|
+
* `server/project-context.ts`.
|
|
764
|
+
*/
|
|
765
|
+
private reclaimRetiredWorktrees;
|
|
542
766
|
/** Last live-refresh namer inputs per run — unchanged inputs skip the call. */
|
|
543
767
|
private lastNamerKey;
|
|
544
768
|
/**
|
|
@@ -561,7 +785,48 @@ export declare class RunManager {
|
|
|
561
785
|
*/
|
|
562
786
|
private acquireRepoRoot;
|
|
563
787
|
cancel(runId: string): boolean;
|
|
788
|
+
/**
|
|
789
|
+
* Publish a run's freshly built `ActiveRun` and adopt any cancellation that arrived while it
|
|
790
|
+
* was being built. Returns true when the caller must abandon the run.
|
|
791
|
+
*
|
|
792
|
+
* One helper for both bodies, because the two `active.set` sites are the two halves of the same
|
|
793
|
+
* moment and had already drifted once (`state.autonomous`, #141). Ordering inside is
|
|
794
|
+
* load-bearing: the flag is consumed BEFORE `active.set`, so a `cancel()` landing after this
|
|
795
|
+
* returns finds a state to write to and takes the ordinary path.
|
|
796
|
+
*/
|
|
797
|
+
private adoptActive;
|
|
798
|
+
/**
|
|
799
|
+
* Publish a freshly started agent session on the run's `ActiveRun`, and deliver any
|
|
800
|
+
* cancellation that arrived while the session was being BUILT.
|
|
801
|
+
*
|
|
802
|
+
* The twin of `adoptActive`, one phase later and for the same reason. `cancel()` stops a live
|
|
803
|
+
* turn by calling `state.interrupt()`, and between the step loop's own `state.cancelled` check
|
|
804
|
+
* and this moment that function is still the `() => undefined` placeholder: everything in
|
|
805
|
+
* between — `configuredModelProvider`, `agentEnvForStep`, a team skill's `materializeSkillDir`
|
|
806
|
+
* — is `await`ed, and a cancel landing in any of those gaps set the flag and delivered nothing.
|
|
807
|
+
*
|
|
808
|
+
* What that cost is a run that never ends, not a turn that runs one step too long (#199/#200).
|
|
809
|
+
* The session spawns anyway; an interactive step passes `timeoutMs: 0`, so the runner has no
|
|
810
|
+
* wall clock at all; and the turn-end handler computes `sessionOpen` as
|
|
811
|
+
* `!state.cancelled && session.open`, so a cancelled run neither parks at `waiting` nor closes
|
|
812
|
+
* the session on `XEZ:DONE`. The body then sits in `await session.result` for as long as the
|
|
813
|
+
* agent CLI keeps stdin open — which for the bundled mock is forever. `quiesce()` issues its
|
|
814
|
+
* cancel once per drain generation and only re-issues it after `Promise.allSettled` resolves,
|
|
815
|
+
* so a single missed delivery is a permanent hang: measured as the 90 s teardown timeout on a
|
|
816
|
+
* 2-core CI runner, where the widened gap makes the window easy to land in.
|
|
817
|
+
*
|
|
818
|
+
* ONE helper for both construction sites — `runAgentStep` and `runContinuation` — because they
|
|
819
|
+
* are the same moment written twice and had already drifted (only one of them re-stamped
|
|
820
|
+
* `currentStepId`). A third session site must come through here too, or it re-opens the hole.
|
|
821
|
+
*/
|
|
822
|
+
private publishSession;
|
|
564
823
|
isActive(runId: string): boolean;
|
|
824
|
+
/** The id set behind `isActive` — `active ∪ starting ∪ queue`. `quiesce()` needs the whole
|
|
825
|
+
* population rather than one membership test, and a second spelling of that union is exactly
|
|
826
|
+
* how the two drift apart: `run-quiesce.test.ts` pins the observable half by putting one run
|
|
827
|
+
* in `queue` and one in `active`, asserting `isActive` for both before `quiesce()` and for
|
|
828
|
+
* neither after. A registry this set forgets would leave a live writer behind. */
|
|
829
|
+
private activeRunIds;
|
|
565
830
|
/**
|
|
566
831
|
* Fold a queued run's persisted prompt — `run.task` plus everything stacked
|
|
567
832
|
* onto it (#472) — into the job input that is about to execute.
|