@open-mercato/cezar 0.9.3 → 0.10.0-pr892.1298
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 +165 -36
- package/dist/agent-config/account-identity.d.ts +44 -0
- package/dist/agent-config/account-identity.js +128 -0
- package/dist/agent-config/account-identity.js.map +1 -0
- package/dist/agent-config/catalog.d.ts +8 -0
- package/dist/agent-config/catalog.js +18 -0
- package/dist/agent-config/catalog.js.map +1 -1
- package/dist/agent-config/model-settings/claude.d.ts +2 -0
- package/dist/agent-config/model-settings/claude.js +11 -0
- package/dist/agent-config/model-settings/claude.js.map +1 -0
- package/dist/agent-config/model-settings/codex.d.ts +2 -0
- package/dist/agent-config/model-settings/codex.js +16 -0
- package/dist/agent-config/model-settings/codex.js.map +1 -0
- package/dist/agent-config/model-settings/opencode.d.ts +2 -0
- package/dist/agent-config/model-settings/opencode.js +8 -0
- package/dist/agent-config/model-settings/opencode.js.map +1 -0
- package/dist/agent-config/model-settings/pi.d.ts +12 -0
- package/dist/agent-config/model-settings/pi.js +18 -0
- package/dist/agent-config/model-settings/pi.js.map +1 -0
- package/dist/agent-config/model-settings/shared.d.ts +9 -0
- package/dist/agent-config/model-settings/shared.js +93 -0
- package/dist/agent-config/model-settings/shared.js.map +1 -0
- package/dist/agent-config/model-settings/types.d.ts +9 -0
- package/dist/agent-config/model-settings/types.js +2 -0
- package/dist/agent-config/model-settings/types.js.map +1 -0
- package/dist/agent-config/models.d.ts +9 -0
- package/dist/agent-config/models.js +32 -0
- package/dist/agent-config/models.js.map +1 -0
- package/dist/agent-config/service.js +4 -4
- package/dist/agent-config/service.js.map +1 -1
- package/dist/automations/types.d.ts +4 -0
- package/dist/automations/types.js +2 -1
- package/dist/automations/types.js.map +1 -1
- package/dist/config.d.ts +10 -1
- package/dist/config.js +48 -5
- package/dist/config.js.map +1 -1
- package/dist/contract/agent-config.d.ts +2 -0
- package/dist/contract/agent-profiles.d.ts +347 -0
- package/dist/contract/automations.d.ts +24 -0
- package/dist/contract/events.d.ts +59 -0
- package/dist/contract/github.d.ts +88 -0
- package/dist/contract/health.d.ts +5 -1
- package/dist/contract/index.d.ts +1 -0
- package/dist/contract/index.js +981 -544
- package/dist/contract/projects.d.ts +33 -5
- package/dist/contract/runs.d.ts +247 -0
- package/dist/contract/skills.d.ts +7 -0
- package/dist/contract/workflows.d.ts +6 -0
- package/dist/contract/workspace.d.ts +137 -2
- package/dist/core/agent-env.js +41 -16
- package/dist/core/agent-env.js.map +1 -1
- package/dist/core/agent-model-policy.d.ts +10 -0
- package/dist/core/agent-model-policy.js +36 -0
- package/dist/core/agent-model-policy.js.map +1 -0
- package/dist/core/agent-profiles.d.ts +62 -0
- package/dist/core/agent-profiles.js +90 -0
- package/dist/core/agent-profiles.js.map +1 -0
- package/dist/core/agent-runner.d.ts +37 -6
- package/dist/core/agent-runner.js +35 -3
- package/dist/core/agent-runner.js.map +1 -1
- package/dist/core/backend-detect.d.ts +5 -5
- package/dist/core/backend-detect.js +44 -7
- package/dist/core/backend-detect.js.map +1 -1
- package/dist/core/claude-cli-runner.js +10 -5
- package/dist/core/claude-cli-runner.js.map +1 -1
- package/dist/core/codex-app-server-runner.js +13 -3
- package/dist/core/codex-app-server-runner.js.map +1 -1
- package/dist/core/codex-app-server-transport.js +7 -2
- package/dist/core/codex-app-server-transport.js.map +1 -1
- package/dist/core/model-identity.d.ts +23 -11
- package/dist/core/model-identity.js +41 -24
- package/dist/core/model-identity.js.map +1 -1
- package/dist/core/model-presets.d.ts +8 -2
- package/dist/core/model-presets.js +51 -8
- package/dist/core/model-presets.js.map +1 -1
- package/dist/core/opencode-model-catalog.d.ts +32 -0
- package/dist/core/opencode-model-catalog.js +153 -0
- package/dist/core/opencode-model-catalog.js.map +1 -0
- package/dist/core/opencode-server-runner.d.ts +2 -0
- package/dist/core/opencode-server-runner.js +42 -11
- package/dist/core/opencode-server-runner.js.map +1 -1
- package/dist/core/pi-runner.d.ts +24 -0
- package/dist/core/pi-runner.js +349 -0
- package/dist/core/pi-runner.js.map +1 -0
- package/dist/core/pi-ui-mapper.d.ts +34 -0
- package/dist/core/pi-ui-mapper.js +270 -0
- package/dist/core/pi-ui-mapper.js.map +1 -0
- package/dist/core/provider-auth.d.ts +95 -3
- package/dist/core/provider-auth.js +234 -15
- package/dist/core/provider-auth.js.map +1 -1
- package/dist/core/provider-availability.d.ts +1 -1
- package/dist/core/provider-availability.js +2 -1
- package/dist/core/provider-availability.js.map +1 -1
- package/dist/core/runner-factory.js +3 -0
- package/dist/core/runner-factory.js.map +1 -1
- package/dist/core/shell-env.d.ts +28 -0
- package/dist/core/shell-env.js +56 -0
- package/dist/core/shell-env.js.map +1 -0
- package/dist/core/ui-events.d.ts +1 -1
- package/dist/core/usage-limit.d.ts +42 -0
- package/dist/core/usage-limit.js +209 -0
- package/dist/core/usage-limit.js.map +1 -0
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/paths.d.ts +53 -5
- package/dist/paths.js +63 -9
- package/dist/paths.js.map +1 -1
- package/dist/planner.js +6 -0
- package/dist/planner.js.map +1 -1
- package/dist/release/snapshot.d.ts +13 -2
- package/dist/release/snapshot.js +25 -1
- package/dist/release/snapshot.js.map +1 -1
- package/dist/runs/agent-tmpdir.d.ts +41 -0
- package/dist/runs/agent-tmpdir.js +183 -0
- package/dist/runs/agent-tmpdir.js.map +1 -0
- package/dist/runs/auto-name.js +6 -1
- package/dist/runs/auto-name.js.map +1 -1
- package/dist/runs/event-history.d.ts +50 -0
- package/dist/runs/event-history.js +613 -0
- package/dist/runs/event-history.js.map +1 -0
- package/dist/runs/run-index.d.ts +19 -0
- package/dist/runs/run-index.js +43 -0
- package/dist/runs/run-index.js.map +1 -0
- package/dist/runs/store.d.ts +78 -19
- package/dist/runs/store.js +175 -43
- package/dist/runs/store.js.map +1 -1
- package/dist/server/forge/github.d.ts +169 -0
- package/dist/server/forge/github.js +511 -0
- package/dist/server/forge/github.js.map +1 -1
- package/dist/server/forge/index.d.ts +9 -0
- package/dist/server/forge/index.js +14 -0
- package/dist/server/forge/index.js.map +1 -1
- package/dist/server/github.d.ts +2 -2
- package/dist/server/github.js +1 -1
- package/dist/server/github.js.map +1 -1
- package/dist/server/open-in-app.d.ts +2 -1
- package/dist/server/open-in-app.js +3 -1
- package/dist/server/open-in-app.js.map +1 -1
- package/dist/server/open-in-terminal.d.ts +46 -1
- package/dist/server/open-in-terminal.js +72 -13
- package/dist/server/open-in-terminal.js.map +1 -1
- package/dist/server/provider-action-gate.js +3 -1
- package/dist/server/provider-action-gate.js.map +1 -1
- package/dist/server/server.d.ts +2199 -428
- package/dist/server/server.js +1171 -139
- package/dist/server/server.js.map +1 -1
- package/dist/server-install/engine.js +5 -18
- package/dist/server-install/engine.js.map +1 -1
- package/dist/server-install/steps.js +6 -2
- package/dist/server-install/steps.js.map +1 -1
- package/dist/skills.js +4 -0
- package/dist/skills.js.map +1 -1
- package/dist/workflows/run.d.ts +202 -2
- package/dist/workflows/run.js +782 -115
- package/dist/workflows/run.js.map +1 -1
- package/dist/workflows/types.d.ts +3 -0
- package/dist/workflows/types.js +11 -2
- package/dist/workflows/types.js.map +1 -1
- package/dist/workspace/agent-accounts.d.ts +153 -0
- package/dist/workspace/agent-accounts.js +304 -0
- package/dist/workspace/agent-accounts.js.map +1 -0
- package/dist/workspace/agent-profiles.d.ts +78 -0
- package/dist/workspace/agent-profiles.js +115 -0
- package/dist/workspace/agent-profiles.js.map +1 -0
- package/dist/workspace/config.d.ts +19 -1
- package/dist/workspace/config.js +56 -2
- package/dist/workspace/config.js.map +1 -1
- package/dist/workspace/projects-cli.js +54 -4
- package/dist/workspace/projects-cli.js.map +1 -1
- package/dist/workspace/projects.d.ts +19 -1
- package/dist/workspace/projects.js +34 -1
- package/dist/workspace/projects.js.map +1 -1
- package/dist/workspace/semaphore.d.ts +39 -0
- package/dist/workspace/semaphore.js +31 -0
- package/dist/workspace/semaphore.js.map +1 -1
- package/package.json +3 -3
- package/scripts/mock-claude.mjs +20 -0
- package/scripts/mock-pi-rpc.mjs +83 -0
- package/web/dist/assets/{alert-dialog-V9cigyAO.js → alert-dialog-Byp42_IG.js} +1 -1
- package/web/dist/assets/arrow-down-CGDbDYKD.js +1 -0
- package/web/dist/assets/arrow-left-ESCjr1Hq.js +1 -0
- package/web/dist/assets/centered-state-LfAW-igm.js +43 -0
- package/web/dist/assets/chevron-right-Du-P1IUd.js +1 -0
- package/web/dist/assets/{collapsible-BlF-7CeC.js → collapsible-CBiiqDJM.js} +1 -1
- package/web/dist/assets/{commit-list-D2mKElUx.js → commit-list-6PPve7PE.js} +1 -1
- package/web/dist/assets/{compare-variants-CFBY16Rc.js → compare-variants-BDnp6mXJ.js} +1 -1
- package/web/dist/assets/{diff-DE6u5dTe.js → diff-_jK0OWpF.js} +2 -2
- package/web/dist/assets/{diff-stat-B1j6L7C6.js → diff-stat-CIrTu4Ym.js} +1 -1
- package/web/dist/assets/{diff-view-Gb2kk3Le.js → diff-view-COA_5IyY.js} +1 -1
- package/web/dist/assets/dropdown-menu-BofYpFFw.js +1 -0
- package/web/dist/assets/editable-title-_J0zFDl-.js +1 -0
- package/web/dist/assets/{ellipsis-vertical-BbxtXb6s.js → ellipsis-vertical-BeMR-l62.js} +1 -1
- package/web/dist/assets/{file-j5O4NryU.js → file-DY996Pee.js} +1 -1
- package/web/dist/assets/{folder-w9Lwj9x9.js → folder-NO3Ec-TY.js} +1 -1
- package/web/dist/assets/git-toolbar-BqJeUWcD.js +1 -0
- package/web/dist/assets/github-C96HPV9E.js +1 -0
- package/web/dist/assets/{image-preview-CcFdkZXW.js → image-preview-D9cCDfKQ.js} +1 -1
- package/web/dist/assets/index-Bx-grqa9.css +2 -0
- package/web/dist/assets/index-DoVvGDfW.js +7 -0
- package/web/dist/assets/{markdown-q9lm7kQ2.js → markdown-DDzk11zj.js} +1 -1
- package/web/dist/assets/new-task-form-BNLLgC6P.js +1 -0
- package/web/dist/assets/pill-CVM1EmV4.js +1 -0
- package/web/dist/assets/{project-router-DudOmkHf.js → project-router-LRCtP48W.js} +1 -1
- package/web/dist/assets/prompt-templates-DFOvYgtW.js +15 -0
- package/web/dist/assets/{refresh-cw-_C2EGJK5.js → refresh-cw-D1ys6QY2.js} +1 -1
- package/web/dist/assets/repo-git-CzUnsgWY.js +1 -0
- package/web/dist/assets/{run-diff-BLoKEnTx.js → run-diff-BlnqwfSS.js} +2 -2
- package/web/dist/assets/run-header-Bn7cN0Z1.js +1 -0
- package/web/dist/assets/{search-x-BgxaJgro.js → search-x-D6FSpxId.js} +1 -1
- package/web/dist/assets/{skill-empty-hint-D5MOmycv.js → skill-empty-hint-iwpCcqgI.js} +1 -1
- package/web/dist/assets/skills-CezbOaol.js +1 -0
- package/web/dist/assets/{sparkles-ANSka5bH.js → sparkles-MrQrgCI9.js} +1 -1
- package/web/dist/assets/{square-terminal-qojq20cH.js → square-terminal-TTH64lO7.js} +1 -1
- package/web/dist/assets/tab-link-FYksz037.js +1 -0
- package/web/dist/assets/task-changes-ClWlnzBL.js +1 -0
- package/web/dist/assets/{task-commits-yXmDrCoE.js → task-commits-Crbrl86b.js} +1 -1
- package/web/dist/assets/{task-files-BDRmCCPN.js → task-files-uSCWyD0-.js} +2 -2
- package/web/dist/assets/task-thread-DPmamBA1.js +9 -0
- package/web/dist/assets/{textarea-DVO7pg8Y.js → textarea-B9Ahj4E4.js} +1 -1
- package/web/dist/assets/thread-loading-BH_ypQoy.js +1 -0
- package/web/dist/assets/{trash-2-DKcnmg6e.js → trash-2-D8S3V3fu.js} +1 -1
- package/web/dist/assets/{triangle-alert-CPUEVSZ3.js → triangle-alert-bB7p9n5E.js} +1 -1
- package/web/dist/assets/{upload-D5Jrq7QK.js → upload-Bd5oRLPV.js} +1 -1
- package/web/dist/assets/{use-desktop-CXae6uyk.js → use-desktop-BNvbRBeZ.js} +1 -1
- package/web/dist/assets/{use-submit-shortcut-RKJLKdsW.js → use-submit-shortcut-B-xhu4Bf.js} +1 -1
- package/web/dist/assets/utils-BhpbVMwN.js +64 -0
- package/web/dist/assets/{workflows-CGyTdyds.js → workflows-RmfbM9Vy.js} +3 -3
- package/web/dist/assets/{zoomable-image-hcqwUIlQ.js → zoomable-image-BCW6ZBV0.js} +1 -1
- package/web/dist/index.html +24 -23
- package/web/dist/assets/arrow-down-DflHdNlN.js +0 -1
- package/web/dist/assets/arrow-left-IIL4uR-C.js +0 -1
- package/web/dist/assets/centered-state-BkAJVLJE.js +0 -43
- package/web/dist/assets/chevron-right-CxAoD1eD.js +0 -1
- package/web/dist/assets/circle-check-CdwcIbkx.js +0 -1
- package/web/dist/assets/circle-x-Cnc43xJ2.js +0 -1
- package/web/dist/assets/dropdown-menu-Ns-rY6sm.js +0 -1
- package/web/dist/assets/editable-title-BGuiDxFT.js +0 -1
- package/web/dist/assets/git-pull-request-DlSop3Ch.js +0 -1
- package/web/dist/assets/git-toolbar-BdJ8XSxI.js +0 -1
- package/web/dist/assets/github-CCLn2OAU.js +0 -1
- package/web/dist/assets/index-BmHHLbwc.js +0 -6
- package/web/dist/assets/index-u8OAnq0r.css +0 -2
- package/web/dist/assets/new-task-form-BuYxAWN7.js +0 -1
- package/web/dist/assets/pill-t97MvPW_.js +0 -1
- package/web/dist/assets/prompt-templates-C5iO3Lda.js +0 -15
- package/web/dist/assets/repo-git-DqQzj33d.js +0 -1
- package/web/dist/assets/run-header-Dho4eurS.js +0 -1
- package/web/dist/assets/skills-B5g064CC.js +0 -1
- package/web/dist/assets/tab-link-CIcwFJtC.js +0 -1
- package/web/dist/assets/task-changes-yRfsEsnh.js +0 -1
- package/web/dist/assets/task-thread-C9AnIz4Z.js +0 -9
- package/web/dist/assets/thread-loading-DJDl701Z.js +0 -1
- package/web/dist/assets/utils-Wtkd5E6j.js +0 -64
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type RunRecord } from './store.ts';
|
|
2
|
+
/**
|
|
3
|
+
* The READ-ONLY half of `runs.json`, for the workspace-level run index (`GET
|
|
4
|
+
* /workspace/runs-index`) — the one place that must read a project's runs WITHOUT owning it.
|
|
5
|
+
*
|
|
6
|
+
* `RunStore.open` cannot be used here and the reason is the whole point of this module. Opening a
|
|
7
|
+
* store `mkdir`s `<dataDir>/runs/`, and the caller that opens one goes on to build a
|
|
8
|
+
* `ProjectContext` — which prunes orphan worktrees and calls `manager.recover()`, resuming
|
|
9
|
+
* interrupted runs. Building the workspace index must never do any of that: answering "which
|
|
10
|
+
* tasks exist" would restart agents across every registered project, and typing into a search box
|
|
11
|
+
* would spend tokens. Cold projects are precisely the ones the index exists to reach, so
|
|
12
|
+
* `contexts.peek()` (which returns nothing for them) is not an answer either.
|
|
13
|
+
*
|
|
14
|
+
* What this does share with the store is the schema and `reconcileLoadedRun`, so a `running` row
|
|
15
|
+
* left behind by a crashed process reads as interrupted here exactly as it would once the project
|
|
16
|
+
* were opened for real. A second, subtly different parse of that field would show a task as
|
|
17
|
+
* running in the palette and failed the moment you clicked it.
|
|
18
|
+
*/
|
|
19
|
+
export declare function readRunIndexFromDisk(dataDir: string): RunRecord[];
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { reconcileLoadedRun, runRecordSchema } from './store.js';
|
|
5
|
+
/**
|
|
6
|
+
* The READ-ONLY half of `runs.json`, for the workspace-level run index (`GET
|
|
7
|
+
* /workspace/runs-index`) — the one place that must read a project's runs WITHOUT owning it.
|
|
8
|
+
*
|
|
9
|
+
* `RunStore.open` cannot be used here and the reason is the whole point of this module. Opening a
|
|
10
|
+
* store `mkdir`s `<dataDir>/runs/`, and the caller that opens one goes on to build a
|
|
11
|
+
* `ProjectContext` — which prunes orphan worktrees and calls `manager.recover()`, resuming
|
|
12
|
+
* interrupted runs. Building the workspace index must never do any of that: answering "which
|
|
13
|
+
* tasks exist" would restart agents across every registered project, and typing into a search box
|
|
14
|
+
* would spend tokens. Cold projects are precisely the ones the index exists to reach, so
|
|
15
|
+
* `contexts.peek()` (which returns nothing for them) is not an answer either.
|
|
16
|
+
*
|
|
17
|
+
* What this does share with the store is the schema and `reconcileLoadedRun`, so a `running` row
|
|
18
|
+
* left behind by a crashed process reads as interrupted here exactly as it would once the project
|
|
19
|
+
* were opened for real. A second, subtly different parse of that field would show a task as
|
|
20
|
+
* running in the palette and failed the moment you clicked it.
|
|
21
|
+
*/
|
|
22
|
+
export function readRunIndexFromDisk(dataDir) {
|
|
23
|
+
const indexPath = join(dataDir, 'runs.json');
|
|
24
|
+
if (!existsSync(indexPath))
|
|
25
|
+
return [];
|
|
26
|
+
try {
|
|
27
|
+
const raw = JSON.parse(readFileSync(indexPath, 'utf8'));
|
|
28
|
+
const parsed = z.array(runRecordSchema).safeParse(raw);
|
|
29
|
+
if (!parsed.success)
|
|
30
|
+
return [];
|
|
31
|
+
// `reconcileLoadedRun` mutates, which is safe here in a way it is not in the store: these
|
|
32
|
+
// records were just parsed into fresh objects that nothing else holds a reference to.
|
|
33
|
+
// Never `keepLive` — this reader has no RunManager, so there is nothing to recover into.
|
|
34
|
+
return parsed.data.map((run) => reconcileLoadedRun(run));
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// Corrupt or unreadable index. A project that cannot be read contributes nothing to the
|
|
38
|
+
// index rather than failing the whole workspace's search — the same degrade-quietly rule
|
|
39
|
+
// the rest of the registry follows.
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=run-index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-index.js","sourceRoot":"","sources":["../../src/runs/run-index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAkB,MAAM,YAAY,CAAC;AAEjF;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAe;IAClD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IAC7C,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;QAAE,OAAO,EAAE,CAAC;IACtC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC;QACxD,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QACvD,IAAI,CAAC,MAAM,CAAC,OAAO;YAAE,OAAO,EAAE,CAAC;QAC/B,0FAA0F;QAC1F,sFAAsF;QACtF,yFAAyF;QACzF,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3D,CAAC;IAAC,MAAM,CAAC;QACP,wFAAwF;QACxF,yFAAyF;QACzF,oCAAoC;QACpC,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC"}
|
package/dist/runs/store.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
+
import type { RunnerId } from '../core/agent-runner.ts';
|
|
3
4
|
export type RunStatus = 'queued' | 'running' | 'waiting' | 'review' | 'done' | 'failed' | 'cancelled';
|
|
4
5
|
/**
|
|
5
6
|
* A sub-state of `running` (spec 2026-07-18-subagent-monitoring-status, #490):
|
|
@@ -40,11 +41,14 @@ declare const stepStateSchema: z.ZodObject<{
|
|
|
40
41
|
finishedAt: z.ZodOptional<z.ZodString>;
|
|
41
42
|
error: z.ZodOptional<z.ZodString>;
|
|
42
43
|
sessionId: z.ZodOptional<z.ZodString>;
|
|
43
|
-
backend: z.ZodOptional<z.ZodEnum<{
|
|
44
|
+
backend: z.ZodOptional<z.ZodPipe<z.ZodEnum<{
|
|
44
45
|
claude: "claude";
|
|
46
|
+
"claude-cli": "claude-cli";
|
|
45
47
|
codex: "codex";
|
|
46
48
|
opencode: "opencode";
|
|
47
|
-
|
|
49
|
+
pi: "pi";
|
|
50
|
+
}>, z.ZodTransform<"claude" | "codex" | "opencode" | "pi", "claude" | "claude-cli" | "codex" | "opencode" | "pi">>>;
|
|
51
|
+
profileId: z.ZodOptional<z.ZodString>;
|
|
48
52
|
costUsd: z.ZodOptional<z.ZodNumber>;
|
|
49
53
|
}, z.core.$strip>;
|
|
50
54
|
/** One prompt message stacked onto a run while it waits for a free agent slot
|
|
@@ -57,7 +61,9 @@ declare const queuedMessageSchema: z.ZodObject<{
|
|
|
57
61
|
images: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
58
62
|
createdAt: z.ZodString;
|
|
59
63
|
}, z.core.$strip>;
|
|
60
|
-
|
|
64
|
+
/** Exported for `./run-index.ts`, the read-only reader of the same file. Nothing else should
|
|
65
|
+
* parse `runs.json` — see `reconcileLoadedRun` for why a second parser is a correctness risk. */
|
|
66
|
+
export declare const runRecordSchema: z.ZodObject<{
|
|
61
67
|
id: z.ZodString;
|
|
62
68
|
title: z.ZodString;
|
|
63
69
|
titleSummary: z.ZodOptional<z.ZodString>;
|
|
@@ -78,11 +84,14 @@ declare const runRecordSchema: z.ZodObject<{
|
|
|
78
84
|
taskImages: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
79
85
|
model: z.ZodOptional<z.ZodString>;
|
|
80
86
|
modelIdentity: z.ZodOptional<z.ZodString>;
|
|
81
|
-
runner: z.ZodOptional<z.ZodEnum<{
|
|
87
|
+
runner: z.ZodOptional<z.ZodPipe<z.ZodEnum<{
|
|
82
88
|
claude: "claude";
|
|
89
|
+
"claude-cli": "claude-cli";
|
|
83
90
|
codex: "codex";
|
|
84
91
|
opencode: "opencode";
|
|
85
|
-
|
|
92
|
+
pi: "pi";
|
|
93
|
+
}>, z.ZodTransform<"claude" | "codex" | "opencode" | "pi", "claude" | "claude-cli" | "codex" | "opencode" | "pi">>>;
|
|
94
|
+
agentProfile: z.ZodOptional<z.ZodString>;
|
|
86
95
|
systemPrompt: z.ZodOptional<z.ZodString>;
|
|
87
96
|
generateFollowups: z.ZodOptional<z.ZodBoolean>;
|
|
88
97
|
autonomous: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -107,6 +116,8 @@ declare const runRecordSchema: z.ZodObject<{
|
|
|
107
116
|
}>>;
|
|
108
117
|
monitoringWakeAt: z.ZodCatch<z.ZodOptional<z.ZodString>>;
|
|
109
118
|
monitoringWakeCapReached: z.ZodOptional<z.ZodBoolean>;
|
|
119
|
+
autoResumeAt: z.ZodCatch<z.ZodOptional<z.ZodString>>;
|
|
120
|
+
autoResumeAttempts: z.ZodCatch<z.ZodOptional<z.ZodNumber>>;
|
|
110
121
|
createdAt: z.ZodString;
|
|
111
122
|
startedAt: z.ZodOptional<z.ZodString>;
|
|
112
123
|
finishedAt: z.ZodOptional<z.ZodString>;
|
|
@@ -175,11 +186,14 @@ declare const runRecordSchema: z.ZodObject<{
|
|
|
175
186
|
finishedAt: z.ZodOptional<z.ZodString>;
|
|
176
187
|
error: z.ZodOptional<z.ZodString>;
|
|
177
188
|
sessionId: z.ZodOptional<z.ZodString>;
|
|
178
|
-
backend: z.ZodOptional<z.ZodEnum<{
|
|
189
|
+
backend: z.ZodOptional<z.ZodPipe<z.ZodEnum<{
|
|
179
190
|
claude: "claude";
|
|
191
|
+
"claude-cli": "claude-cli";
|
|
180
192
|
codex: "codex";
|
|
181
193
|
opencode: "opencode";
|
|
182
|
-
|
|
194
|
+
pi: "pi";
|
|
195
|
+
}>, z.ZodTransform<"claude" | "codex" | "opencode" | "pi", "claude" | "claude-cli" | "codex" | "opencode" | "pi">>>;
|
|
196
|
+
profileId: z.ZodOptional<z.ZodString>;
|
|
183
197
|
costUsd: z.ZodOptional<z.ZodNumber>;
|
|
184
198
|
}, z.core.$strip>>;
|
|
185
199
|
workflowDef: z.ZodCatch<z.ZodOptional<z.ZodObject<{
|
|
@@ -195,6 +209,7 @@ declare const runRecordSchema: z.ZodObject<{
|
|
|
195
209
|
claude: "claude";
|
|
196
210
|
codex: "codex";
|
|
197
211
|
opencode: "opencode";
|
|
212
|
+
pi: "pi";
|
|
198
213
|
}>>;
|
|
199
214
|
allowedTools: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
200
215
|
bashAllowlist: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
@@ -222,6 +237,22 @@ export interface RunEvent {
|
|
|
222
237
|
type: string;
|
|
223
238
|
[key: string]: unknown;
|
|
224
239
|
}
|
|
240
|
+
/**
|
|
241
|
+
* Reconcile one record just read off disk with the fact that whichever process wrote it is gone.
|
|
242
|
+
*
|
|
243
|
+
* Mutates and returns `run`. Extracted from `RunStore.open` so the read-only index reader
|
|
244
|
+
* (`./run-index.ts`) answers the SAME question about a `running` row on disk. Two parsers that
|
|
245
|
+
* disagree here is a visible bug, not an internal one: the cockpit would show a task as running
|
|
246
|
+
* in the ⌘K index and failed the moment you opened it.
|
|
247
|
+
*
|
|
248
|
+
* `keepLive` (#367): leave `queued`/`running`/`waiting` untouched so the caller can recover them
|
|
249
|
+
* (RunManager.recover re-queues queued runs, resumes interrupted ones). Without it — one-shot CLI
|
|
250
|
+
* paths that never recover, and the index reader, which has no manager at all — live-looking runs
|
|
251
|
+
* are marked failed so no ghost stays behind.
|
|
252
|
+
*/
|
|
253
|
+
export declare function reconcileLoadedRun(run: RunRecord, opts?: {
|
|
254
|
+
keepLive?: boolean;
|
|
255
|
+
}): RunRecord;
|
|
225
256
|
/**
|
|
226
257
|
* File-backed run store: `runs.json` index (atomic tmp+rename writes, the
|
|
227
258
|
* pattern from @cezar/core's IssueStore) plus one append-only NDJSON event
|
|
@@ -233,12 +264,7 @@ export declare class RunStore extends EventEmitter {
|
|
|
233
264
|
private runs;
|
|
234
265
|
private saveTimer;
|
|
235
266
|
private constructor();
|
|
236
|
-
/**
|
|
237
|
-
* `keepLive` (#367): leave `queued`/`running`/`waiting` statuses untouched
|
|
238
|
-
* so the caller can recover them (RunManager.recover re-queues queued runs,
|
|
239
|
-
* resumes interrupted ones). Without it — one-shot CLI paths that never
|
|
240
|
-
* recover — live-looking runs are marked failed so no ghost stays behind.
|
|
241
|
-
*/
|
|
267
|
+
/** See `reconcileLoadedRun` for what `keepLive` (#367) decides about live-looking rows. */
|
|
242
268
|
static open(dataDir: string, opts?: {
|
|
243
269
|
keepLive?: boolean;
|
|
244
270
|
}): RunStore;
|
|
@@ -249,7 +275,9 @@ export declare class RunStore extends EventEmitter {
|
|
|
249
275
|
workflow: string;
|
|
250
276
|
task: string;
|
|
251
277
|
model?: string;
|
|
252
|
-
runner?:
|
|
278
|
+
runner?: RunnerId;
|
|
279
|
+
/** Composer's per-task agent account (spec 2026-07-29-agent-profiles). */
|
|
280
|
+
agentProfile?: string;
|
|
253
281
|
generateFollowups?: boolean;
|
|
254
282
|
autonomous?: boolean;
|
|
255
283
|
worktree?: false;
|
|
@@ -292,13 +320,44 @@ export declare class RunStore extends EventEmitter {
|
|
|
292
320
|
* updated record rides the existing `run` SSE with no new event. Idempotent by
|
|
293
321
|
* design: opening an already-read thread just re-stamps a later `seenAt`. */
|
|
294
322
|
setRead(id: string): RunRecord | undefined;
|
|
323
|
+
/** Mark one run as UNread (#775): drop the read receipt so the run rejoins the unread
|
|
324
|
+
* list. The inverse of `setRead` and, like it, `touch`es so the updated record rides the
|
|
325
|
+
* existing `run` SSE.
|
|
326
|
+
*
|
|
327
|
+
* Deleting the field rather than adding a "manually unread" flag is the whole point:
|
|
328
|
+
* absent `seenAt` is ALREADY what every reader treats as unread (`isUnread` in the
|
|
329
|
+
* cockpit's read-state.ts, and `markAllRead`'s clause-for-clause copy of it below), so
|
|
330
|
+
* clearing needs no new state and writes a shape any older cezar already parses.
|
|
331
|
+
*
|
|
332
|
+
* Deliberately unconditional: clearing a receipt is always a legal write, so this
|
|
333
|
+
* succeeds for an already-unread run (idempotent) and for statuses that can never wear
|
|
334
|
+
* the marker. WHETHER the action means anything for a given run is UI policy, and lives
|
|
335
|
+
* in the cockpit's `runActionFlags` — the same split the rest of the store keeps. */
|
|
336
|
+
setUnread(id: string): RunRecord | undefined;
|
|
295
337
|
/** Bulk mark-read: stamp every currently-unread finished run; returns the count.
|
|
296
338
|
* "Unread" here is the same rule the cockpit paints (`isUnread` in read-state.ts),
|
|
297
|
-
* clause for clause:
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
*
|
|
301
|
-
*
|
|
339
|
+
* clause for clause:
|
|
340
|
+
* - a `done` or `failed` run that finished and has not been seen since;
|
|
341
|
+
* - cancelled runs are never unread — you stopped them yourself;
|
|
342
|
+
* - archived ones never are either, since archiving is a stronger "done with this"
|
|
343
|
+
* than reading;
|
|
344
|
+
* - and a `failed` run with a pending `autoResumeAt` is not a done item AT ALL
|
|
345
|
+
* (`isScheduledResume`, spec 2026-08-03-auto-resume-after-usage-limit): it has an
|
|
346
|
+
* appointment to pick the work back up, so there is no outcome to have missed.
|
|
347
|
+
*
|
|
348
|
+
* Keeping the two rules identical is what makes the returned count the number the
|
|
349
|
+
* cockpit's unread badge was showing. The `autoResumeAt` clause is the one that drifted
|
|
350
|
+
* (#803): `isUnread` gained it with auto-resume and this sweep did not, so a task waiting
|
|
351
|
+
* out a usage limit was uncounted by the badge but stamped read by the sweep — and this
|
|
352
|
+
* comment asserted an invariant the code no longer held.
|
|
353
|
+
*
|
|
354
|
+
* This rule lives in two languages of the same repo, which is why it has now drifted
|
|
355
|
+
* once. The cockpit cannot import it (`packages/web` does not depend on the service, and
|
|
356
|
+
* should not), so a single definition would have to move to `packages/contract` — the one
|
|
357
|
+
* package both sides already import. Worth doing; deliberately not done here, because
|
|
358
|
+
* widening the contract package's remit from "shapes" to "behavior" is a design change
|
|
359
|
+
* that deserves its own review rather than riding along in a bug fix. Until then: EDIT
|
|
360
|
+
* BOTH, and the case-table tests on either side are what catch you if you don't. */
|
|
302
361
|
markAllRead(): number;
|
|
303
362
|
appendEvent(runId: string, event: {
|
|
304
363
|
type: string;
|
package/dist/runs/store.js
CHANGED
|
@@ -6,7 +6,31 @@ import { z } from 'zod';
|
|
|
6
6
|
import { collectSecretValues, redactDeep, redactSecrets } from '../core/secret-redaction.js';
|
|
7
7
|
// Type-only module (zod + nothing else), so this cannot cycle back into the store.
|
|
8
8
|
import { workflowDefSchema } from '../workflows/types.js';
|
|
9
|
+
import { RUNNER_IDS } from '../core/agent-runner.js';
|
|
9
10
|
const usageCounterSchema = z.number().finite().nonnegative();
|
|
11
|
+
/**
|
|
12
|
+
* A runner id as it may appear in a PERSISTED record, normalized to the three
|
|
13
|
+
* ids the rest of cezar speaks (#547).
|
|
14
|
+
*
|
|
15
|
+
* `claude-cli` is the legacy spelling of `claude` — still a member of
|
|
16
|
+
* `AgentBackend` and still accepted by `createRunner`, and named by
|
|
17
|
+
* `BACKWARD_COMPATIBILITY.md` §3 as an id `runs.json` keeps parseable. The enum
|
|
18
|
+
* here did not accept it, so that promise was false: the loader `safeParse`s the
|
|
19
|
+
* WHOLE array, so one record carrying it would have dropped every run in the
|
|
20
|
+
* file — the exact failure mode §3 exists to warn about.
|
|
21
|
+
*
|
|
22
|
+
* Parse-and-fold rather than widen: the legacy id is accepted on the way in and
|
|
23
|
+
* collapsed to `claude`, so no consumer, wire type or contract schema ever sees
|
|
24
|
+
* a fourth runner. The narrowing is one-way and permanent (the index is
|
|
25
|
+
* re-serialized from the parsed records), which is what "old run records
|
|
26
|
+
* normalise identically to `claude`" in `core/model-identity.ts` has always
|
|
27
|
+
* claimed. Use ONLY for read-back of stored state — request bodies, settings and
|
|
28
|
+
* workflow step defs stay the three selectable ids (`RunnerId`), because nothing
|
|
29
|
+
* should be able to ASK for the legacy spelling.
|
|
30
|
+
*/
|
|
31
|
+
const storedRunnerSchema = z
|
|
32
|
+
.enum([...RUNNER_IDS, 'claude-cli'])
|
|
33
|
+
.transform((id) => (id === 'claude-cli' ? 'claude' : id));
|
|
10
34
|
const stepStateSchema = z.object({
|
|
11
35
|
id: z.string(),
|
|
12
36
|
name: z.string(),
|
|
@@ -26,8 +50,15 @@ const stepStateSchema = z.object({
|
|
|
26
50
|
error: z.string().optional(),
|
|
27
51
|
/** Latest backend-owned session id, used for same-backend Continue. */
|
|
28
52
|
sessionId: z.string().optional(),
|
|
29
|
-
/** Backend that owns `sessionId`. Optional so pre-affinity runs.json files still parse
|
|
30
|
-
|
|
53
|
+
/** Backend that owns `sessionId`. Optional so pre-affinity runs.json files still parse;
|
|
54
|
+
* `storedRunnerSchema` so a legacy `claude-cli` folds to `claude` instead of failing (#547). */
|
|
55
|
+
backend: storedRunnerSchema.optional(),
|
|
56
|
+
/** Agent profile (account) this step actually spawned under — `default`, or a stored profile
|
|
57
|
+
* id (spec 2026-07-29-agent-profiles). Recorded rather than re-derived because a session id
|
|
58
|
+
* only means something inside the config dir that created it: `sessionId` and `profileId` are
|
|
59
|
+
* a PAIR. Without it, changing the project's account would silently make Continue resume
|
|
60
|
+
* against the wrong account's session store. Absent = the discovered default. */
|
|
61
|
+
profileId: z.string().optional(),
|
|
31
62
|
/** Dollar cost reported by the claude CLI for this step's turns. */
|
|
32
63
|
costUsd: z.number().optional(),
|
|
33
64
|
});
|
|
@@ -42,7 +73,9 @@ const queuedMessageSchema = z.object({
|
|
|
42
73
|
images: z.array(z.string()).optional(),
|
|
43
74
|
createdAt: z.string(),
|
|
44
75
|
});
|
|
45
|
-
|
|
76
|
+
/** Exported for `./run-index.ts`, the read-only reader of the same file. Nothing else should
|
|
77
|
+
* parse `runs.json` — see `reconcileLoadedRun` for why a second parser is a correctness risk. */
|
|
78
|
+
export const runRecordSchema = z.object({
|
|
46
79
|
id: z.string(),
|
|
47
80
|
title: z.string(),
|
|
48
81
|
/** Display title (#389): the auto-derived summary of the first agent turn,
|
|
@@ -80,10 +113,20 @@ const runRecordSchema = z.object({
|
|
|
80
113
|
* (e.g. `anthropic/claude-opus-4-8`) the run actually used, resolved from the
|
|
81
114
|
* free-text `model` against the chosen runner. Additive and optional: pre-#405
|
|
82
115
|
* records carry only `model`, and it stays the human/hand-edit surface; this
|
|
83
|
-
* is the parseable identity cost attribution and reproducible replay key off.
|
|
116
|
+
* is the parseable identity cost attribution and reproducible replay key off.
|
|
117
|
+
*
|
|
118
|
+
* Read in production by the session header's agent badge (#546), which shows it
|
|
119
|
+
* whenever it says something `model` does not — so this is no longer a
|
|
120
|
+
* write-only field whose next reader has to guess whether it is load-bearing. */
|
|
84
121
|
modelIdentity: z.string().optional(),
|
|
85
|
-
/** Agent backend this run used — drives "open in CLI" resume command.
|
|
86
|
-
|
|
122
|
+
/** Agent backend this run used — drives "open in CLI" resume command. `storedRunnerSchema`
|
|
123
|
+
* so a legacy `claude-cli` record folds to `claude` instead of failing the whole index (#547). */
|
|
124
|
+
runner: storedRunnerSchema.optional(),
|
|
125
|
+
/** Per-task agent-account override from the composer (spec 2026-07-29-agent-profiles), applying
|
|
126
|
+
* to steps that run on `runner`. Steps on a DIFFERENT backend still resolve from the project's
|
|
127
|
+
* own selection — an override for Claude says nothing about which Codex account a mixed
|
|
128
|
+
* workflow's codex step should use. Absent = follow the project. */
|
|
129
|
+
agentProfile: z.string().optional(),
|
|
87
130
|
/** Echo of the extra system prompt this run actually used (R2): the
|
|
88
131
|
* `POST /api/runs` override, or the `config.json` default it fell back to.
|
|
89
132
|
* Deliberately NOT the full composed prompt — skill bodies and the handoff
|
|
@@ -118,6 +161,17 @@ const runRecordSchema = z.object({
|
|
|
118
161
|
monitoringWakeAt: z.string().datetime().optional().catch(undefined),
|
|
119
162
|
/** True only for the live epoch that exhausted all automatic monitoring checks. */
|
|
120
163
|
monitoringWakeCapReached: z.boolean().optional(),
|
|
164
|
+
/**
|
|
165
|
+
* Exact deadline at which a run stopped by a provider USAGE LIMIT resumes itself
|
|
166
|
+
* (spec 2026-08-03-auto-resume-after-usage-limit) — the reset instant the provider named plus a
|
|
167
|
+
* short grace. Present only while such a resume is pending: the run is `failed`, the timer is
|
|
168
|
+
* armed, and the cockpit says so. Deliberately survives a restart (`RunStore.open` keeps it) —
|
|
169
|
+
* it is what lets `recover()` re-arm a wait that may be hours long.
|
|
170
|
+
*/
|
|
171
|
+
autoResumeAt: z.string().datetime().optional().catch(undefined),
|
|
172
|
+
/** Consecutive automatic resumes since the last human turn — the safety cap's counter.
|
|
173
|
+
* Persisted so a restart cannot reset a loop back to zero. */
|
|
174
|
+
autoResumeAttempts: z.number().int().min(0).optional().catch(undefined),
|
|
121
175
|
createdAt: z.string(),
|
|
122
176
|
startedAt: z.string().optional(),
|
|
123
177
|
finishedAt: z.string().optional(),
|
|
@@ -191,7 +245,8 @@ const runRecordSchema = z.object({
|
|
|
191
245
|
/** Read receipt (#unread-done-items): the ISO time the cockpit last opened this
|
|
192
246
|
* run's thread. A finished run reads as "unread" until it has been seen since it
|
|
193
247
|
* finished — see `isUnread()` in the cockpit's `lib/read-state.ts`. Absent on old
|
|
194
|
-
* runs
|
|
248
|
+
* runs, on every run not yet opened, and on one `setUnread` put back to unread
|
|
249
|
+
* (#775) — the unread rule treats all three alike. */
|
|
195
250
|
seenAt: z.string().optional(),
|
|
196
251
|
currentStepId: z.string().optional(),
|
|
197
252
|
error: z.string().optional(),
|
|
@@ -235,6 +290,19 @@ const MAX_PR_CANDIDATES = 8;
|
|
|
235
290
|
* invisible to the janitor, #407). Reasoning items are skipped: thinking text
|
|
236
291
|
* speculates about PRs the task never touches.
|
|
237
292
|
*/
|
|
293
|
+
/**
|
|
294
|
+
* Archiving IS resigning from a task, so an archived run can never carry a pending usage-limit
|
|
295
|
+
* resume (spec 2026-08-03-auto-resume-after-usage-limit). The rule lives HERE rather than in the
|
|
296
|
+
* archive route because the bulk "Archive finished" sweep never goes through that route, and a
|
|
297
|
+
* user who archives fifty finished tasks has resigned from all fifty.
|
|
298
|
+
*
|
|
299
|
+
* The engine needs no telling: its timer re-reads the record before it fires and no sweep re-arms
|
|
300
|
+
* an archived run, so a cleared field is the whole cancellation.
|
|
301
|
+
*/
|
|
302
|
+
function clearPendingAutoResume(run) {
|
|
303
|
+
run.autoResumeAt = undefined;
|
|
304
|
+
run.autoResumeAttempts = undefined;
|
|
305
|
+
}
|
|
238
306
|
function eventTextFragments(event) {
|
|
239
307
|
const fragments = [];
|
|
240
308
|
for (const key of ['text', 'result', 'message']) {
|
|
@@ -326,6 +394,49 @@ function createdPrUrl(haystack) {
|
|
|
326
394
|
}
|
|
327
395
|
return before;
|
|
328
396
|
}
|
|
397
|
+
/**
|
|
398
|
+
* Reconcile one record just read off disk with the fact that whichever process wrote it is gone.
|
|
399
|
+
*
|
|
400
|
+
* Mutates and returns `run`. Extracted from `RunStore.open` so the read-only index reader
|
|
401
|
+
* (`./run-index.ts`) answers the SAME question about a `running` row on disk. Two parsers that
|
|
402
|
+
* disagree here is a visible bug, not an internal one: the cockpit would show a task as running
|
|
403
|
+
* in the ⌘K index and failed the moment you opened it.
|
|
404
|
+
*
|
|
405
|
+
* `keepLive` (#367): leave `queued`/`running`/`waiting` untouched so the caller can recover them
|
|
406
|
+
* (RunManager.recover re-queues queued runs, resumes interrupted ones). Without it — one-shot CLI
|
|
407
|
+
* paths that never recover, and the index reader, which has no manager at all — live-looking runs
|
|
408
|
+
* are marked failed so no ghost stays behind.
|
|
409
|
+
*/
|
|
410
|
+
export function reconcileLoadedRun(run, opts) {
|
|
411
|
+
// A run that was live when the previous process exited can never finish —
|
|
412
|
+
// surface that instead of a forever-"running" ghost. `review` survives
|
|
413
|
+
// restarts on purpose: the gate is pure data (worktree + branch + record)
|
|
414
|
+
// with no live process, so the diff panel, Send back (resume) and Draft PR
|
|
415
|
+
// all still work.
|
|
416
|
+
if (!opts?.keepLive &&
|
|
417
|
+
(run.status === 'running' || run.status === 'queued' || run.status === 'waiting')) {
|
|
418
|
+
run.status = 'failed';
|
|
419
|
+
run.error = 'interrupted — cezar process exited during the run';
|
|
420
|
+
run.finishedAt = run.finishedAt ?? new Date().toISOString();
|
|
421
|
+
for (const step of run.steps) {
|
|
422
|
+
if (step.status === 'running' || step.status === 'waiting')
|
|
423
|
+
step.status = 'failed';
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
if (!['running', 'waiting', 'queued'].includes(run.status)) {
|
|
427
|
+
run.activity = undefined;
|
|
428
|
+
run.monitoringWakeAt = undefined;
|
|
429
|
+
}
|
|
430
|
+
// A pending usage-limit resume survives the restart on purpose (the wait can be
|
|
431
|
+
// hours) — `RunManager.recover()` re-arms it from this field. It can only mean
|
|
432
|
+
// anything on a `failed` run, so anywhere else it is stale bookkeeping.
|
|
433
|
+
if (run.status !== 'failed')
|
|
434
|
+
run.autoResumeAt = undefined;
|
|
435
|
+
// The wake counter is intentionally process-local, so a restarted process
|
|
436
|
+
// starts a fresh epoch instead of displaying a stale cap.
|
|
437
|
+
run.monitoringWakeCapReached = undefined;
|
|
438
|
+
return run;
|
|
439
|
+
}
|
|
329
440
|
/**
|
|
330
441
|
* File-backed run store: `runs.json` index (atomic tmp+rename writes, the
|
|
331
442
|
* pattern from @cezar/core's IssueStore) plus one append-only NDJSON event
|
|
@@ -341,12 +452,7 @@ export class RunStore extends EventEmitter {
|
|
|
341
452
|
this.dataDir = dataDir;
|
|
342
453
|
this.setMaxListeners(100);
|
|
343
454
|
}
|
|
344
|
-
/**
|
|
345
|
-
* `keepLive` (#367): leave `queued`/`running`/`waiting` statuses untouched
|
|
346
|
-
* so the caller can recover them (RunManager.recover re-queues queued runs,
|
|
347
|
-
* resumes interrupted ones). Without it — one-shot CLI paths that never
|
|
348
|
-
* recover — live-looking runs are marked failed so no ghost stays behind.
|
|
349
|
-
*/
|
|
455
|
+
/** See `reconcileLoadedRun` for what `keepLive` (#367) decides about live-looking rows. */
|
|
350
456
|
static open(dataDir, opts) {
|
|
351
457
|
mkdirSync(join(dataDir, 'runs'), { recursive: true });
|
|
352
458
|
const store = new RunStore(dataDir);
|
|
@@ -357,31 +463,7 @@ export class RunStore extends EventEmitter {
|
|
|
357
463
|
const parsed = z.array(runRecordSchema).safeParse(raw);
|
|
358
464
|
if (parsed.success) {
|
|
359
465
|
for (const run of parsed.data) {
|
|
360
|
-
|
|
361
|
-
// finish — surface that instead of a forever-"running" ghost.
|
|
362
|
-
// `review` survives restarts on purpose: the gate is pure data
|
|
363
|
-
// (worktree + branch + record) with no live process, so the diff
|
|
364
|
-
// panel, Send back (resume) and Draft PR all still work.
|
|
365
|
-
if (!opts?.keepLive &&
|
|
366
|
-
(run.status === 'running' ||
|
|
367
|
-
run.status === 'queued' ||
|
|
368
|
-
run.status === 'waiting')) {
|
|
369
|
-
run.status = 'failed';
|
|
370
|
-
run.error = 'interrupted — cezar process exited during the run';
|
|
371
|
-
run.finishedAt = run.finishedAt ?? new Date().toISOString();
|
|
372
|
-
for (const step of run.steps) {
|
|
373
|
-
if (step.status === 'running' || step.status === 'waiting')
|
|
374
|
-
step.status = 'failed';
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
if (!['running', 'waiting', 'queued'].includes(run.status)) {
|
|
378
|
-
run.activity = undefined;
|
|
379
|
-
run.monitoringWakeAt = undefined;
|
|
380
|
-
}
|
|
381
|
-
// The wake counter is intentionally process-local, so a restarted
|
|
382
|
-
// process starts a fresh epoch instead of displaying a stale cap.
|
|
383
|
-
run.monitoringWakeCapReached = undefined;
|
|
384
|
-
store.runs.set(run.id, run);
|
|
466
|
+
store.runs.set(run.id, reconcileLoadedRun(run, opts));
|
|
385
467
|
}
|
|
386
468
|
}
|
|
387
469
|
}
|
|
@@ -411,6 +493,7 @@ export class RunStore extends EventEmitter {
|
|
|
411
493
|
task: input.task,
|
|
412
494
|
model: input.model,
|
|
413
495
|
runner: input.runner,
|
|
496
|
+
agentProfile: input.agentProfile,
|
|
414
497
|
generateFollowups: input.generateFollowups,
|
|
415
498
|
autonomous: input.autonomous,
|
|
416
499
|
worktree: input.worktree,
|
|
@@ -450,6 +533,13 @@ export class RunStore extends EventEmitter {
|
|
|
450
533
|
normalized.monitoringWakeAt = undefined;
|
|
451
534
|
normalized.monitoringWakeCapReached = undefined;
|
|
452
535
|
}
|
|
536
|
+
// …and the mirror image for the usage-limit resume (spec
|
|
537
|
+
// 2026-08-03-auto-resume-after-usage-limit): it is a promise made ABOUT a failed run, so a
|
|
538
|
+
// run coming back to life — the resume itself, a user Continue, a re-queue — retires it.
|
|
539
|
+
// The manager's timer re-checks the record before it fires, so a cleared field is enough.
|
|
540
|
+
if (normalized.status && ['running', 'waiting', 'queued'].includes(normalized.status)) {
|
|
541
|
+
normalized.autoResumeAt = undefined;
|
|
542
|
+
}
|
|
453
543
|
Object.assign(run, this.redactPatch(normalized));
|
|
454
544
|
this.touch(run);
|
|
455
545
|
return run;
|
|
@@ -536,6 +626,8 @@ export class RunStore extends EventEmitter {
|
|
|
536
626
|
return undefined;
|
|
537
627
|
run.archived = archived;
|
|
538
628
|
run.archivedAt = archived ? new Date().toISOString() : undefined;
|
|
629
|
+
if (archived)
|
|
630
|
+
clearPendingAutoResume(run);
|
|
539
631
|
this.touch(run);
|
|
540
632
|
return run;
|
|
541
633
|
}
|
|
@@ -546,6 +638,7 @@ export class RunStore extends EventEmitter {
|
|
|
546
638
|
if (!run.archived && ['done', 'failed', 'cancelled'].includes(run.status)) {
|
|
547
639
|
run.archived = true;
|
|
548
640
|
run.archivedAt = new Date().toISOString();
|
|
641
|
+
clearPendingAutoResume(run);
|
|
549
642
|
this.touch(run);
|
|
550
643
|
count++;
|
|
551
644
|
}
|
|
@@ -564,19 +657,58 @@ export class RunStore extends EventEmitter {
|
|
|
564
657
|
this.touch(run);
|
|
565
658
|
return run;
|
|
566
659
|
}
|
|
660
|
+
/** Mark one run as UNread (#775): drop the read receipt so the run rejoins the unread
|
|
661
|
+
* list. The inverse of `setRead` and, like it, `touch`es so the updated record rides the
|
|
662
|
+
* existing `run` SSE.
|
|
663
|
+
*
|
|
664
|
+
* Deleting the field rather than adding a "manually unread" flag is the whole point:
|
|
665
|
+
* absent `seenAt` is ALREADY what every reader treats as unread (`isUnread` in the
|
|
666
|
+
* cockpit's read-state.ts, and `markAllRead`'s clause-for-clause copy of it below), so
|
|
667
|
+
* clearing needs no new state and writes a shape any older cezar already parses.
|
|
668
|
+
*
|
|
669
|
+
* Deliberately unconditional: clearing a receipt is always a legal write, so this
|
|
670
|
+
* succeeds for an already-unread run (idempotent) and for statuses that can never wear
|
|
671
|
+
* the marker. WHETHER the action means anything for a given run is UI policy, and lives
|
|
672
|
+
* in the cockpit's `runActionFlags` — the same split the rest of the store keeps. */
|
|
673
|
+
setUnread(id) {
|
|
674
|
+
const run = this.runs.get(id);
|
|
675
|
+
if (!run)
|
|
676
|
+
return undefined;
|
|
677
|
+
delete run.seenAt;
|
|
678
|
+
this.touch(run);
|
|
679
|
+
return run;
|
|
680
|
+
}
|
|
567
681
|
/** Bulk mark-read: stamp every currently-unread finished run; returns the count.
|
|
568
682
|
* "Unread" here is the same rule the cockpit paints (`isUnread` in read-state.ts),
|
|
569
|
-
* clause for clause:
|
|
570
|
-
*
|
|
571
|
-
*
|
|
572
|
-
*
|
|
573
|
-
*
|
|
683
|
+
* clause for clause:
|
|
684
|
+
* - a `done` or `failed` run that finished and has not been seen since;
|
|
685
|
+
* - cancelled runs are never unread — you stopped them yourself;
|
|
686
|
+
* - archived ones never are either, since archiving is a stronger "done with this"
|
|
687
|
+
* than reading;
|
|
688
|
+
* - and a `failed` run with a pending `autoResumeAt` is not a done item AT ALL
|
|
689
|
+
* (`isScheduledResume`, spec 2026-08-03-auto-resume-after-usage-limit): it has an
|
|
690
|
+
* appointment to pick the work back up, so there is no outcome to have missed.
|
|
691
|
+
*
|
|
692
|
+
* Keeping the two rules identical is what makes the returned count the number the
|
|
693
|
+
* cockpit's unread badge was showing. The `autoResumeAt` clause is the one that drifted
|
|
694
|
+
* (#803): `isUnread` gained it with auto-resume and this sweep did not, so a task waiting
|
|
695
|
+
* out a usage limit was uncounted by the badge but stamped read by the sweep — and this
|
|
696
|
+
* comment asserted an invariant the code no longer held.
|
|
697
|
+
*
|
|
698
|
+
* This rule lives in two languages of the same repo, which is why it has now drifted
|
|
699
|
+
* once. The cockpit cannot import it (`packages/web` does not depend on the service, and
|
|
700
|
+
* should not), so a single definition would have to move to `packages/contract` — the one
|
|
701
|
+
* package both sides already import. Worth doing; deliberately not done here, because
|
|
702
|
+
* widening the contract package's remit from "shapes" to "behavior" is a design change
|
|
703
|
+
* that deserves its own review rather than riding along in a bug fix. Until then: EDIT
|
|
704
|
+
* BOTH, and the case-table tests on either side are what catch you if you don't. */
|
|
574
705
|
markAllRead() {
|
|
575
706
|
const now = new Date().toISOString();
|
|
576
707
|
let count = 0;
|
|
577
708
|
for (const run of this.runs.values()) {
|
|
578
709
|
const unread = !run.archived &&
|
|
579
710
|
(run.status === 'done' || run.status === 'failed') &&
|
|
711
|
+
!(run.status === 'failed' && run.autoResumeAt !== undefined) &&
|
|
580
712
|
run.finishedAt !== undefined &&
|
|
581
713
|
(run.seenAt === undefined || run.seenAt < run.finishedAt);
|
|
582
714
|
if (!unread)
|