@open-mercato/cezar 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 +3 -2
- package/dist/agent-config/catalog.d.ts +4 -0
- package/dist/agent-config/catalog.js +85 -3
- package/dist/agent-config/catalog.js.map +1 -1
- package/dist/agent-config/model-settings/copilot.d.ts +15 -0
- package/dist/agent-config/model-settings/copilot.js +25 -0
- package/dist/agent-config/model-settings/copilot.js.map +1 -0
- package/dist/agent-config/model-settings/junie.d.ts +12 -0
- package/dist/agent-config/model-settings/junie.js +18 -0
- package/dist/agent-config/model-settings/junie.js.map +1 -0
- package/dist/agent-config/models.js +4 -0
- package/dist/agent-config/models.js.map +1 -1
- package/dist/automations/prompts.d.ts +2 -2
- package/dist/automations/prompts.js +1 -1
- package/dist/automations/types.d.ts +12 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -1
- package/dist/contract/agent-config.d.ts +4 -0
- package/dist/contract/agent-profiles.d.ts +30 -0
- package/dist/contract/automations.d.ts +32 -0
- package/dist/contract/dashboard-insights.d.ts +186 -0
- package/dist/contract/dashboard.d.ts +88 -0
- package/dist/contract/dispatch.d.ts +6 -0
- package/dist/contract/health.d.ts +8 -0
- package/dist/contract/index.d.ts +2 -0
- package/dist/contract/index.js +249 -72
- package/dist/contract/runs.d.ts +144 -0
- package/dist/contract/self-update.d.ts +111 -2
- package/dist/contract/skills.d.ts +19 -0
- package/dist/contract/star-count.d.ts +18 -0
- package/dist/contract/workflows.d.ts +12 -0
- package/dist/contract/workspace.d.ts +42 -2
- package/dist/core/acp-client.d.ts +99 -0
- package/dist/core/acp-client.js +198 -0
- package/dist/core/acp-client.js.map +1 -0
- package/dist/core/acp-ui-mapper.d.ts +95 -0
- package/dist/core/acp-ui-mapper.js +444 -0
- package/dist/core/acp-ui-mapper.js.map +1 -0
- package/dist/core/agent-env.js +10 -0
- package/dist/core/agent-env.js.map +1 -1
- package/dist/core/agent-profiles.d.ts +10 -0
- package/dist/core/agent-profiles.js +16 -0
- package/dist/core/agent-profiles.js.map +1 -1
- package/dist/core/agent-runner.d.ts +5 -3
- package/dist/core/agent-runner.js +5 -3
- package/dist/core/agent-runner.js.map +1 -1
- package/dist/core/backend-detect.d.ts +3 -3
- package/dist/core/backend-detect.js +61 -2
- package/dist/core/backend-detect.js.map +1 -1
- package/dist/core/claude-cli-runner.d.ts +5 -2
- package/dist/core/claude-cli-runner.js +11 -6
- package/dist/core/claude-cli-runner.js.map +1 -1
- package/dist/core/claude-ui-mapper.d.ts +14 -0
- package/dist/core/claude-ui-mapper.js +102 -11
- package/dist/core/claude-ui-mapper.js.map +1 -1
- package/dist/core/codex-app-server-transport.js +8 -4
- package/dist/core/codex-app-server-transport.js.map +1 -1
- package/dist/core/copilot-acp-runner.d.ts +60 -0
- package/dist/core/copilot-acp-runner.js +528 -0
- package/dist/core/copilot-acp-runner.js.map +1 -0
- package/dist/core/copilot-ui-mapper.d.ts +47 -0
- package/dist/core/copilot-ui-mapper.js +111 -0
- package/dist/core/copilot-ui-mapper.js.map +1 -0
- package/dist/core/cursor-agent-runner.js +4 -4
- package/dist/core/cursor-agent-runner.js.map +1 -1
- package/dist/core/disclaim-spawn.d.ts +9 -0
- package/dist/core/disclaim-spawn.js +53 -0
- package/dist/core/disclaim-spawn.js.map +1 -0
- package/dist/core/junie-acp-transport.d.ts +58 -0
- package/dist/core/junie-acp-transport.js +179 -0
- package/dist/core/junie-acp-transport.js.map +1 -0
- package/dist/core/junie-auth-probe.d.ts +12 -0
- package/dist/core/junie-auth-probe.js +30 -0
- package/dist/core/junie-auth-probe.js.map +1 -0
- package/dist/core/junie-model-catalog.d.ts +10 -0
- package/dist/core/junie-model-catalog.js +104 -0
- package/dist/core/junie-model-catalog.js.map +1 -0
- package/dist/core/junie-runner.d.ts +45 -0
- package/dist/core/junie-runner.js +533 -0
- package/dist/core/junie-runner.js.map +1 -0
- package/dist/core/junie-ui-mapper.d.ts +132 -0
- package/dist/core/junie-ui-mapper.js +459 -0
- package/dist/core/junie-ui-mapper.js.map +1 -0
- package/dist/core/model-identity.js +17 -0
- package/dist/core/model-identity.js.map +1 -1
- package/dist/core/model-presets.js +10 -0
- package/dist/core/model-presets.js.map +1 -1
- package/dist/core/opencode-server-runner.js +4 -4
- package/dist/core/opencode-server-runner.js.map +1 -1
- package/dist/core/pi-runner.js +4 -4
- package/dist/core/pi-runner.js.map +1 -1
- package/dist/core/provider-auth.d.ts +16 -2
- package/dist/core/provider-auth.js +113 -7
- package/dist/core/provider-auth.js.map +1 -1
- package/dist/core/runner-factory.js +6 -0
- package/dist/core/runner-factory.js.map +1 -1
- package/dist/core/runner-model-catalog.js +12 -8
- package/dist/core/runner-model-catalog.js.map +1 -1
- package/dist/core/tool-display.js +6 -1
- package/dist/core/tool-display.js.map +1 -1
- package/dist/core/ui-events.d.ts +6 -2
- package/dist/git-diff-base.js +23 -2
- package/dist/git-diff-base.js.map +1 -1
- package/dist/git-worktree.d.ts +21 -2
- package/dist/git-worktree.js +68 -5
- package/dist/git-worktree.js.map +1 -1
- package/dist/index.js +14 -4
- package/dist/index.js.map +1 -1
- package/dist/paths.js +2 -0
- package/dist/paths.js.map +1 -1
- package/dist/runs/event-history.js +3 -0
- package/dist/runs/event-history.js.map +1 -1
- package/dist/runs/store.d.ts +42 -3
- package/dist/runs/store.js +112 -6
- package/dist/runs/store.js.map +1 -1
- package/dist/self-update/checkouts.d.ts +53 -0
- package/dist/self-update/checkouts.js +268 -0
- package/dist/self-update/checkouts.js.map +1 -0
- package/dist/self-update/cli.d.ts +5 -3
- package/dist/self-update/cli.js +75 -8
- package/dist/self-update/cli.js.map +1 -1
- package/dist/self-update/layout.d.ts +33 -1
- package/dist/self-update/layout.js +102 -3
- package/dist/self-update/layout.js.map +1 -1
- package/dist/self-update/pulls.d.ts +40 -0
- package/dist/self-update/pulls.js +127 -0
- package/dist/self-update/pulls.js.map +1 -0
- package/dist/self-update/service.d.ts +26 -3
- package/dist/self-update/service.js +85 -9
- package/dist/self-update/service.js.map +1 -1
- package/dist/server/dashboard.d.ts +107 -0
- package/dist/server/dashboard.js +2 -1
- package/dist/server/dashboard.js.map +1 -1
- package/dist/server/forge/github.js +4 -1
- package/dist/server/forge/github.js.map +1 -1
- package/dist/server/fs-browse.d.ts +3 -1
- package/dist/server/fs-browse.js +27 -5
- package/dist/server/fs-browse.js.map +1 -1
- package/dist/server/open-in-app.js +2 -0
- package/dist/server/open-in-app.js.map +1 -1
- package/dist/server/provider-action-gate.js +5 -1
- package/dist/server/provider-action-gate.js.map +1 -1
- package/dist/server/server.d.ts +880 -511
- package/dist/server/server.js +40 -5
- package/dist/server/server.js.map +1 -1
- package/dist/server/star-count.d.ts +67 -0
- package/dist/server/star-count.js +186 -0
- package/dist/server/star-count.js.map +1 -0
- package/dist/skills-banner.d.ts +19 -0
- package/dist/skills-banner.js +24 -0
- package/dist/skills-banner.js.map +1 -1
- package/dist/workflows/run.d.ts +0 -2
- package/dist/workflows/run.js +45 -29
- package/dist/workflows/run.js.map +1 -1
- package/dist/workflows/types.d.ts +6 -0
- package/dist/workspace/agent-accounts.d.ts +9 -1
- package/dist/workspace/agent-accounts.js +2 -0
- package/dist/workspace/agent-accounts.js.map +1 -1
- package/dist/workspace/agent-profiles.d.ts +2 -2
- package/dist/workspace/agent-profiles.js +25 -6
- package/dist/workspace/agent-profiles.js.map +1 -1
- package/dist/workspace/config.d.ts +9 -1
- package/dist/workspace/config.js +10 -3
- package/dist/workspace/config.js.map +1 -1
- package/dist/workspace/dashboard-insights.d.ts +42 -0
- package/dist/workspace/dashboard-insights.js +220 -0
- package/dist/workspace/dashboard-insights.js.map +1 -0
- package/dist/workspace/dashboard.d.ts +71 -1
- package/dist/workspace/dashboard.js +14 -1
- package/dist/workspace/dashboard.js.map +1 -1
- package/dist/workspace/semaphore.d.ts +3 -0
- package/dist/workspace/semaphore.js +8 -1
- package/dist/workspace/semaphore.js.map +1 -1
- package/package.json +3 -3
- package/scripts/mock-claude.mjs +34 -1
- package/scripts/mock-copilot-acp.mjs +190 -0
- package/scripts/mock-junie-acp.mjs +208 -0
- package/web/dist/assets/agent-task-pickers-BeQi-UTm.js +1 -0
- package/web/dist/assets/alert-dialog-CnRAuqwb.js +1 -0
- package/web/dist/assets/arrow-down-Da0kNRNH.js +1 -0
- package/web/dist/assets/arrow-left-BIZHhBIP.js +1 -0
- package/web/dist/assets/automation-format-CfPmkcsg.js +1 -0
- package/web/dist/assets/automations-route-AMJbFP53.js +1 -0
- package/web/dist/assets/centered-state-3r2Kaar_.js +1 -0
- package/web/dist/assets/chevron-left-DLAUPC66.js +1 -0
- package/web/dist/assets/chevron-right-KVZcXiPL.js +1 -0
- package/web/dist/assets/{circle-x-C9UoMP1V.js → circle-x-C0Jm5Ppb.js} +1 -1
- package/web/dist/assets/collapsible-DG_SIWqU.js +1 -0
- package/web/dist/assets/command-Dfln3D4A.js +1 -0
- package/web/dist/assets/{commit-list-BSfPsVsu.js → commit-list-_RA72yB_.js} +1 -1
- package/web/dist/assets/compare-variants-D6Ji6od4.js +1 -0
- package/web/dist/assets/dashboard-B3-tPimG.js +44 -0
- package/web/dist/assets/dialog-CiDREdUK.js +1 -0
- package/web/dist/assets/{diff-I0xowULh.js → diff-CxdyRumG.js} +2 -2
- package/web/dist/assets/diff-controls-DqJMMV4z.js +1 -0
- package/web/dist/assets/{diff-stat-yKn7joEr.js → diff-stat-Hupj4hxh.js} +1 -1
- package/web/dist/assets/{diff-view-DjvMeW1M.js → diff-view-BBYT5k6c.js} +1 -1
- package/web/dist/assets/dist-BqMwqJFX.js +1 -0
- package/web/dist/assets/dist-DpiX6WjA.js +1 -0
- package/web/dist/assets/{download-BcgZ2yk4.js → download-BR7EN9gW.js} +1 -1
- package/web/dist/assets/dropdown-menu-Ci4HFML0.js +1 -0
- package/web/dist/assets/editable-title-DDtUgRKZ.js +1 -0
- package/web/dist/assets/{ellipsis-vertical-g4O4NlLV.js → ellipsis-vertical-DBklom4U.js} +1 -1
- package/web/dist/assets/facet-filter-CfI4QvOd.js +1 -0
- package/web/dist/assets/{file-wXNeZwi7.js → file-CZb7cKNH.js} +1 -1
- package/web/dist/assets/{file-text-DV1gE2L6.js → file-text-Dm6CrEGW.js} +1 -1
- package/web/dist/assets/{folder-wjVId5un.js → folder-zQj-FYOo.js} +1 -1
- package/web/dist/assets/{git-pull-request-BBEDdTLK.js → git-pull-request-AhwzJc7s.js} +1 -1
- package/web/dist/assets/git-toolbar-j7gKkhGL.js +1 -0
- package/web/dist/assets/github-De-DFy8s.js +1 -0
- package/web/dist/assets/{image-preview-u1f-wzxC.js → image-preview-Dd3NVB7N.js} +1 -1
- package/web/dist/assets/index-HS1hr5Sz.js +12 -0
- package/web/dist/assets/index-YwgrLyBj.css +2 -0
- package/web/dist/assets/loader-circle-sk4vKJKp.js +1 -0
- package/web/dist/assets/{markdown-Bnm47qUl.js → markdown-DjNR-1p8.js} +2 -2
- package/web/dist/assets/new-task-form-vLs2G1_z.js +1 -0
- package/web/dist/assets/picker-pill-VW-M1ZiV.js +1 -0
- package/web/dist/assets/{pill-7dm3vWF7.js → pill-CPl6sysj.js} +1 -1
- package/web/dist/assets/popover-CNGONAql.js +1 -0
- package/web/dist/assets/project-router-dCZQu1wQ.js +1 -0
- package/web/dist/assets/{prompt-templates-BMZ5q5H_.js → prompt-templates-jNjVTAws.js} +1 -1
- package/web/dist/assets/{refresh-cw-C69euZ2H.js → refresh-cw-BfkEydKA.js} +1 -1
- package/web/dist/assets/repo-git-BZWZ6_pR.js +1 -0
- package/web/dist/assets/{run-diff-CoNVGn23.js → run-diff-u_roDsAJ.js} +2 -2
- package/web/dist/assets/run-header-Br1yJR0m.js +1 -0
- package/web/dist/assets/{search-x-BNLFOxI6.js → search-x-CT0NTqK1.js} +1 -1
- package/web/dist/assets/segmented-IDD4ucht.js +1 -0
- package/web/dist/assets/skills-Dsi5YdLG.js +1 -0
- package/web/dist/assets/{sparkles-ChorK-gZ.js → sparkles-CvOeAoyJ.js} +1 -1
- package/web/dist/assets/{square-terminal-C7KCPznz.js → square-terminal-eSs8YYZe.js} +1 -1
- package/web/dist/assets/{tab-link-E5O2Cesc.js → tab-link-CPF6ORha.js} +1 -1
- package/web/dist/assets/task-changes-DIT9Y1rh.js +1 -0
- package/web/dist/assets/task-commits-B4VvdiHH.js +1 -0
- package/web/dist/assets/task-files-BMm9C0DW.js +2 -0
- package/web/dist/assets/task-thread-RXabJAt0.js +9 -0
- package/web/dist/assets/{textarea-DLAfWPb0.js → textarea-DtBqPhIs.js} +1 -1
- package/web/dist/assets/thread-loading-Dnxb6Ar-.js +1 -0
- package/web/dist/assets/toaster-CfRVb8vH.js +43 -0
- package/web/dist/assets/token-metrics-DldVlr6m.js +1 -0
- package/web/dist/assets/tooltip-_O50N3pt.js +1 -0
- package/web/dist/assets/tracker-J0fdcH1G.js +4 -0
- package/web/dist/assets/{trash-2-_vwSMdPG.js → trash-2-CUH7GOVh.js} +1 -1
- package/web/dist/assets/{triangle-alert-fBRUkB46.js → triangle-alert-D5K_zyD3.js} +1 -1
- package/web/dist/assets/{upload-B4OIpdyq.js → upload-D7_n-9cl.js} +1 -1
- package/web/dist/assets/{use-now-sqAvRMna.js → use-now-3lElDuTi.js} +1 -1
- package/web/dist/assets/utils-BXvEm7Ve.js +64 -0
- package/web/dist/assets/workflows-D-dx9c_0.js +7 -0
- package/web/dist/assets/x-kLybIvX4.js +1 -0
- package/web/dist/assets/{zoomable-image-X81SApIg.js → zoomable-image-CnyQ9wyw.js} +1 -1
- package/web/dist/index.html +38 -36
- package/web/dist/assets/agent-task-pickers-DQ1qvgrD.js +0 -1
- package/web/dist/assets/alert-dialog-B_2uU4Bg.js +0 -1
- package/web/dist/assets/arrow-down-DieVZxSE.js +0 -1
- package/web/dist/assets/arrow-left-C8M0efjn.js +0 -1
- package/web/dist/assets/automation-format-CzinzpHK.js +0 -1
- package/web/dist/assets/automations-route-DYHUjV9R.js +0 -1
- package/web/dist/assets/centered-state-DJLgwvA4.js +0 -43
- package/web/dist/assets/chevron-left-BhXrOEE3.js +0 -1
- package/web/dist/assets/chevron-right-B3Ta8VKK.js +0 -1
- package/web/dist/assets/collapsible-BcKvItwg.js +0 -1
- package/web/dist/assets/command-BvGGv2_u.js +0 -1
- package/web/dist/assets/compare-variants-DBvMYMs3.js +0 -1
- package/web/dist/assets/dashboard-CGaFh4RV.js +0 -44
- package/web/dist/assets/dialog-B6NgKh0-.js +0 -1
- package/web/dist/assets/diff-controls-DskkZqmD.js +0 -1
- package/web/dist/assets/dist-BvXEdcbG.js +0 -1
- package/web/dist/assets/dist-CTksUkKh.js +0 -1
- package/web/dist/assets/dropdown-menu-B7HeKCfN.js +0 -1
- package/web/dist/assets/editable-title-BHbHmSIK.js +0 -1
- package/web/dist/assets/facet-filter-DZ3wfvN9.js +0 -1
- package/web/dist/assets/git-toolbar-EZuX4Cuz.js +0 -1
- package/web/dist/assets/github-zcLRxv77.js +0 -1
- package/web/dist/assets/index-BrZhA4_u.css +0 -2
- package/web/dist/assets/index-CSNFuBvj.js +0 -12
- package/web/dist/assets/loader-circle-Cwgo6onL.js +0 -1
- package/web/dist/assets/new-task-form-CQODCzBP.js +0 -1
- package/web/dist/assets/picker-pill-DNB9ETjS.js +0 -1
- package/web/dist/assets/popover-BRq6QnwI.js +0 -1
- package/web/dist/assets/project-router-I8Pbsrxy.js +0 -1
- package/web/dist/assets/repo-git-G436Abni.js +0 -1
- package/web/dist/assets/run-header-CxdaA86t.js +0 -1
- package/web/dist/assets/skills-C_kBDesq.js +0 -1
- package/web/dist/assets/task-changes-Bhw4Ysvg.js +0 -1
- package/web/dist/assets/task-commits-DBuGXuGm.js +0 -1
- package/web/dist/assets/task-files-CYDbeffM.js +0 -2
- package/web/dist/assets/task-thread-RuxHMJgN.js +0 -9
- package/web/dist/assets/thread-loading-BAuVAbcA.js +0 -1
- package/web/dist/assets/token-metrics-rVl4kXe_.js +0 -1
- package/web/dist/assets/tooltip-Bdl8mQG8.js +0 -1
- package/web/dist/assets/tracker-DJ8JW59-.js +0 -4
- package/web/dist/assets/utils-DoBmOlX3.js +0 -64
- package/web/dist/assets/workflows-C54D7mlm.js +0 -7
- package/web/dist/assets/x-HXr9KZBr.js +0 -1
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export declare const CEZAR_REPO_URL = "https://github.com/open-mercato/cezar";
|
|
2
|
+
/** Six hours. A star count moves slowly and nothing here depends on it being exact. */
|
|
3
|
+
export declare const STAR_CACHE_TTL_MS: number;
|
|
4
|
+
/**
|
|
5
|
+
* Fifteen minutes, and the reason a FAILED read is remembered too.
|
|
6
|
+
*
|
|
7
|
+
* `fetchedAtMs` only advances on success, so without this the failure path has no cache at all:
|
|
8
|
+
* an offline or proxied machine pays the full `TIMEOUT_MS` on every cockpit load forever, and a
|
|
9
|
+
* rate-limited IP (60/h unauthenticated) keeps spending requests into the very `403` that is
|
|
10
|
+
* telling it to stop. `inFlight` collapses a concurrent burst, never a sequential one.
|
|
11
|
+
*
|
|
12
|
+
* Shorter than the success TTL on purpose — offline is usually temporary and the cost of being
|
|
13
|
+
* wrong here is one quiet chip, not a stale number.
|
|
14
|
+
*/
|
|
15
|
+
export declare const STAR_FAILURE_BACKOFF_MS: number;
|
|
16
|
+
export interface StarCount {
|
|
17
|
+
/** `false` whenever there is no number to show — offline, rate-limited, or promos silenced.
|
|
18
|
+
* The cockpit renders no chip at all in that case; it never renders an empty one. */
|
|
19
|
+
available: boolean;
|
|
20
|
+
/** Only present when `available`. */
|
|
21
|
+
count?: number;
|
|
22
|
+
/** Where the ⭐ button points. Always sent, so the link never depends on the count. */
|
|
23
|
+
url: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function starCachePath(home?: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* The promo's single off switch (AGENTS.md § Zero config: a network widening needs one, and a
|
|
28
|
+
* second spelling of an existing switch is a knob rather than a feature). `CEZ_NO_BANNER=1`
|
|
29
|
+
* already means "no `open-mercato` promo in my terminal"; it now means no star ask anywhere —
|
|
30
|
+
* no terminal line, no sidebar chip, no toast, and no request to github.com.
|
|
31
|
+
*/
|
|
32
|
+
export declare function starAskSilenced(env?: NodeJS.ProcessEnv): boolean;
|
|
33
|
+
/** One request, zod-validated, silent on every failure. `null` means "nothing known". */
|
|
34
|
+
export declare function fetchStarCount(fetchImpl?: typeof fetch): Promise<number | null>;
|
|
35
|
+
export interface StarCountReaderOptions {
|
|
36
|
+
fetchImpl?: typeof fetch;
|
|
37
|
+
/** Injectable so tests never touch a real home. */
|
|
38
|
+
cachePath?: string;
|
|
39
|
+
env?: NodeJS.ProcessEnv;
|
|
40
|
+
now?: () => number;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Memoized reader behind `GET /api/v1/star-count`. Mirrors `self-update/registry.ts`'s
|
|
44
|
+
* `RegistryCache` on purpose — same shape, same guarantees — with a disk tier added because this
|
|
45
|
+
* value survives a restart usefully and a version document does not.
|
|
46
|
+
*/
|
|
47
|
+
export declare class StarCountReader {
|
|
48
|
+
private count;
|
|
49
|
+
private fetchedAtMs;
|
|
50
|
+
/** `null` — not `0` — means "never failed": a `now()` near the epoch (every test clock) would
|
|
51
|
+
* otherwise read as a failure one tick ago and suppress the very first request. */
|
|
52
|
+
private failedAtMs;
|
|
53
|
+
private inFlight;
|
|
54
|
+
private diskRead;
|
|
55
|
+
private readonly fetchImpl;
|
|
56
|
+
private readonly cachePath;
|
|
57
|
+
private readonly env;
|
|
58
|
+
private readonly now;
|
|
59
|
+
constructor(options?: StarCountReaderOptions);
|
|
60
|
+
read(): Promise<StarCount>;
|
|
61
|
+
/** Once per process: fold a previous run's answer in before deciding to spend a request. */
|
|
62
|
+
private seedFromDisk;
|
|
63
|
+
/** Whether the last attempt failed recently enough that another one is not worth its timeout. */
|
|
64
|
+
private backingOff;
|
|
65
|
+
/** Always goes to GitHub, and never twice at once — a burst of cockpit tabs is one request. */
|
|
66
|
+
private refresh;
|
|
67
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
/**
|
|
6
|
+
* cezar's own GitHub star count, for the cockpit's ⭐ ask (sidebar chip + first-run toast).
|
|
7
|
+
*
|
|
8
|
+
* The count is read HERE and not in the browser, deliberately. The cockpit has never made a
|
|
9
|
+
* third-party request and should not start: one cezar process asking once per TTL is strictly
|
|
10
|
+
* less exposure than every open tab asking, it keeps working in remote mode where the browser
|
|
11
|
+
* may not reach the internet at all, and it lets the whole promo sit behind one off switch the
|
|
12
|
+
* server already owns.
|
|
13
|
+
*
|
|
14
|
+
* Failure is not exceptional here, it is the common case — offline, rate-limited (60/h per IP
|
|
15
|
+
* unauthenticated), behind a proxy, `CEZ_NO_BANNER=1`. Every one of them answers
|
|
16
|
+
* `{ available: false }` the way `server/github.ts` does, never an error and never a throw: a
|
|
17
|
+
* promo that can break a cockpit is worse than a promo nobody sees.
|
|
18
|
+
*
|
|
19
|
+
* Two caches, both cheap: in memory for the life of the process, and a single small JSON file
|
|
20
|
+
* under `~/.cache/cez/` so a restart (the desktop shell restarts often) paints the count
|
|
21
|
+
* immediately and does not spend another request. A stale disk entry is still served while the
|
|
22
|
+
* refresh is in flight — the number is decoration, and a slightly old one is better than a
|
|
23
|
+
* flicker.
|
|
24
|
+
*/
|
|
25
|
+
const REPO = 'open-mercato/cezar';
|
|
26
|
+
export const CEZAR_REPO_URL = `https://github.com/${REPO}`;
|
|
27
|
+
const API_URL = `https://api.github.com/repos/${REPO}`;
|
|
28
|
+
const TIMEOUT_MS = 5_000;
|
|
29
|
+
/** Six hours. A star count moves slowly and nothing here depends on it being exact. */
|
|
30
|
+
export const STAR_CACHE_TTL_MS = 6 * 60 * 60_000;
|
|
31
|
+
/**
|
|
32
|
+
* Fifteen minutes, and the reason a FAILED read is remembered too.
|
|
33
|
+
*
|
|
34
|
+
* `fetchedAtMs` only advances on success, so without this the failure path has no cache at all:
|
|
35
|
+
* an offline or proxied machine pays the full `TIMEOUT_MS` on every cockpit load forever, and a
|
|
36
|
+
* rate-limited IP (60/h unauthenticated) keeps spending requests into the very `403` that is
|
|
37
|
+
* telling it to stop. `inFlight` collapses a concurrent burst, never a sequential one.
|
|
38
|
+
*
|
|
39
|
+
* Shorter than the success TTL on purpose — offline is usually temporary and the cost of being
|
|
40
|
+
* wrong here is one quiet chip, not a stale number.
|
|
41
|
+
*/
|
|
42
|
+
export const STAR_FAILURE_BACKOFF_MS = 15 * 60_000;
|
|
43
|
+
/** Frozen: `read()` hands this same object to every caller, and a shared mutable answer is a
|
|
44
|
+
* bug waiting for its first mutator. */
|
|
45
|
+
const UNAVAILABLE = Object.freeze({ available: false, url: CEZAR_REPO_URL });
|
|
46
|
+
/** The one field we want, validated at the boundary like every other `--json`/API read. */
|
|
47
|
+
const repoSchema = z
|
|
48
|
+
.object({ stargazers_count: z.number().int().nonnegative() })
|
|
49
|
+
.passthrough();
|
|
50
|
+
const diskEntrySchema = z.object({
|
|
51
|
+
count: z.number().int().nonnegative(),
|
|
52
|
+
fetchedAtMs: z.number().int().nonnegative(),
|
|
53
|
+
});
|
|
54
|
+
export function starCachePath(home = homedir()) {
|
|
55
|
+
return join(home, '.cache', 'cez', 'star-count.json');
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The promo's single off switch (AGENTS.md § Zero config: a network widening needs one, and a
|
|
59
|
+
* second spelling of an existing switch is a knob rather than a feature). `CEZ_NO_BANNER=1`
|
|
60
|
+
* already means "no `open-mercato` promo in my terminal"; it now means no star ask anywhere —
|
|
61
|
+
* no terminal line, no sidebar chip, no toast, and no request to github.com.
|
|
62
|
+
*/
|
|
63
|
+
export function starAskSilenced(env = process.env) {
|
|
64
|
+
if (env.CEZ_NO_BANNER === '1')
|
|
65
|
+
return true;
|
|
66
|
+
// `npm test` is the fast unit gate: "no server, no browser", and it must stay that way
|
|
67
|
+
// (AGENTS.md § Validation). Every test today injects its own reader or its own `fetchImpl`, so
|
|
68
|
+
// nothing reaches github.com — but that is an accident of which routes the suite happens to
|
|
69
|
+
// request, and `createApp` hands a DEFAULT reader to every case that builds an app. The first
|
|
70
|
+
// test to call `GET /api/v1/star-count` without meaning to would make the unit gate networked
|
|
71
|
+
// and non-deterministic, and it would do so silently.
|
|
72
|
+
//
|
|
73
|
+
// Same shape and same reasoning as `assertCezarHomeWriteIsSandboxed` (src/paths.ts): under
|
|
74
|
+
// vitest, reaching the real outside world is a leaked test rather than intent. A case that
|
|
75
|
+
// genuinely wants the fetch path passes its own `fetchImpl` to `fetchStarCount`, which this
|
|
76
|
+
// does not gate — only the shared reader's default is closed off.
|
|
77
|
+
if (env.VITEST)
|
|
78
|
+
return true;
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
/** One request, zod-validated, silent on every failure. `null` means "nothing known". */
|
|
82
|
+
export async function fetchStarCount(fetchImpl = fetch) {
|
|
83
|
+
try {
|
|
84
|
+
const res = await fetchImpl(API_URL, {
|
|
85
|
+
signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
86
|
+
headers: { accept: 'application/vnd.github+json' },
|
|
87
|
+
});
|
|
88
|
+
if (!res.ok)
|
|
89
|
+
return null;
|
|
90
|
+
const parsed = repoSchema.safeParse(await res.json());
|
|
91
|
+
return parsed.success ? parsed.data.stargazers_count : null;
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async function readDisk(path) {
|
|
98
|
+
try {
|
|
99
|
+
const parsed = diskEntrySchema.safeParse(JSON.parse(await readFile(path, 'utf8')));
|
|
100
|
+
return parsed.success ? parsed.data : null;
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async function writeDisk(path, entry) {
|
|
107
|
+
try {
|
|
108
|
+
await mkdir(dirname(path), { recursive: true });
|
|
109
|
+
await writeFile(path, JSON.stringify(entry), 'utf8');
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
// A read-only home is not a reason to lose the count — the memory cache still holds it.
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Memoized reader behind `GET /api/v1/star-count`. Mirrors `self-update/registry.ts`'s
|
|
117
|
+
* `RegistryCache` on purpose — same shape, same guarantees — with a disk tier added because this
|
|
118
|
+
* value survives a restart usefully and a version document does not.
|
|
119
|
+
*/
|
|
120
|
+
export class StarCountReader {
|
|
121
|
+
count = null;
|
|
122
|
+
fetchedAtMs = 0;
|
|
123
|
+
/** `null` — not `0` — means "never failed": a `now()` near the epoch (every test clock) would
|
|
124
|
+
* otherwise read as a failure one tick ago and suppress the very first request. */
|
|
125
|
+
failedAtMs = null;
|
|
126
|
+
inFlight = null;
|
|
127
|
+
diskRead = false;
|
|
128
|
+
fetchImpl;
|
|
129
|
+
cachePath;
|
|
130
|
+
env;
|
|
131
|
+
now;
|
|
132
|
+
constructor(options = {}) {
|
|
133
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
134
|
+
this.cachePath = options.cachePath ?? starCachePath();
|
|
135
|
+
this.env = options.env ?? process.env;
|
|
136
|
+
this.now = options.now ?? Date.now;
|
|
137
|
+
}
|
|
138
|
+
async read() {
|
|
139
|
+
if (starAskSilenced(this.env))
|
|
140
|
+
return UNAVAILABLE;
|
|
141
|
+
await this.seedFromDisk();
|
|
142
|
+
if (this.count !== null && this.now() - this.fetchedAtMs < STAR_CACHE_TTL_MS) {
|
|
143
|
+
return { available: true, count: this.count, url: CEZAR_REPO_URL };
|
|
144
|
+
}
|
|
145
|
+
const fresh = this.backingOff() ? null : await this.refresh();
|
|
146
|
+
// A failed refresh falls back to whatever is still held: a stale number beats no number,
|
|
147
|
+
// and "no number" here would make the chip disappear on the first flaky minute.
|
|
148
|
+
const count = fresh ?? this.count;
|
|
149
|
+
return count === null ? UNAVAILABLE : { available: true, count, url: CEZAR_REPO_URL };
|
|
150
|
+
}
|
|
151
|
+
/** Once per process: fold a previous run's answer in before deciding to spend a request. */
|
|
152
|
+
async seedFromDisk() {
|
|
153
|
+
if (this.diskRead)
|
|
154
|
+
return;
|
|
155
|
+
this.diskRead = true;
|
|
156
|
+
const entry = await readDisk(this.cachePath);
|
|
157
|
+
if (!entry)
|
|
158
|
+
return;
|
|
159
|
+
this.count = entry.count;
|
|
160
|
+
this.fetchedAtMs = entry.fetchedAtMs;
|
|
161
|
+
}
|
|
162
|
+
/** Whether the last attempt failed recently enough that another one is not worth its timeout. */
|
|
163
|
+
backingOff() {
|
|
164
|
+
return this.failedAtMs !== null && this.now() - this.failedAtMs < STAR_FAILURE_BACKOFF_MS;
|
|
165
|
+
}
|
|
166
|
+
/** Always goes to GitHub, and never twice at once — a burst of cockpit tabs is one request. */
|
|
167
|
+
async refresh() {
|
|
168
|
+
if (this.inFlight)
|
|
169
|
+
return this.inFlight;
|
|
170
|
+
this.inFlight = fetchStarCount(this.fetchImpl).then(async (count) => {
|
|
171
|
+
if (count === null) {
|
|
172
|
+
this.failedAtMs = this.now();
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
this.count = count;
|
|
176
|
+
this.fetchedAtMs = this.now();
|
|
177
|
+
this.failedAtMs = null;
|
|
178
|
+
await writeDisk(this.cachePath, { count, fetchedAtMs: this.fetchedAtMs });
|
|
179
|
+
}
|
|
180
|
+
this.inFlight = null;
|
|
181
|
+
return count;
|
|
182
|
+
});
|
|
183
|
+
return this.inFlight;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=star-count.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"star-count.js","sourceRoot":"","sources":["../../src/server/star-count.ts"],"names":[],"mappings":"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"}
|
package/dist/skills-banner.d.ts
CHANGED
|
@@ -20,3 +20,22 @@ export declare const SKILLS_BANNER_LINES: readonly string[];
|
|
|
20
20
|
export declare function shouldShowSkillsBanner(repoRoot: string): Promise<boolean>;
|
|
21
21
|
/** The `serve` startup banner. `log` is injectable so the wiring is testable. */
|
|
22
22
|
export declare function printSkillsBanner(repoRoot: string, log?: (line?: string) => void): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* The star ask's terminal surface: ONE line, under the skills promo, in the same already-chatty
|
|
25
|
+
* startup block and behind the same two off switches. A request and nothing else — no reward, no
|
|
26
|
+
* count to beat, nothing about cezar works differently for someone who never clicks it.
|
|
27
|
+
*
|
|
28
|
+
* One line is the whole design. The other two surfaces (the sidebar ⭐ chip and the one-time
|
|
29
|
+
* first-run toast) are where this ask has room to say more; a terminal that prints on every
|
|
30
|
+
* `serve` is exactly where a promo turns into a nag, so it gets a sentence and no repeat.
|
|
31
|
+
*/
|
|
32
|
+
export declare const STAR_BANNER_LINE = " \u2B50 Find cezar useful? A star helps others find it: https://github.com/open-mercato/cezar";
|
|
33
|
+
/**
|
|
34
|
+
* The star line, printed after the skills promo on `serve`.
|
|
35
|
+
*
|
|
36
|
+
* It shares `shouldShowSkillsBanner` deliberately rather than growing a switch of its own: the
|
|
37
|
+
* two lines are one promo block from where the user sits, and someone who silenced that block —
|
|
38
|
+
* with `CEZ_NO_BANNER=1` or by dismissing it back when the cockpit could — silenced this too.
|
|
39
|
+
* A second spelling of an existing off switch is a knob, not a feature (AGENTS.md § Zero config).
|
|
40
|
+
*/
|
|
41
|
+
export declare function printStarBanner(repoRoot: string, log?: (line?: string) => void): Promise<void>;
|
package/dist/skills-banner.js
CHANGED
|
@@ -38,4 +38,28 @@ export async function printSkillsBanner(repoRoot, log = console.log) {
|
|
|
38
38
|
log(line);
|
|
39
39
|
log();
|
|
40
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* The star ask's terminal surface: ONE line, under the skills promo, in the same already-chatty
|
|
43
|
+
* startup block and behind the same two off switches. A request and nothing else — no reward, no
|
|
44
|
+
* count to beat, nothing about cezar works differently for someone who never clicks it.
|
|
45
|
+
*
|
|
46
|
+
* One line is the whole design. The other two surfaces (the sidebar ⭐ chip and the one-time
|
|
47
|
+
* first-run toast) are where this ask has room to say more; a terminal that prints on every
|
|
48
|
+
* `serve` is exactly where a promo turns into a nag, so it gets a sentence and no repeat.
|
|
49
|
+
*/
|
|
50
|
+
export const STAR_BANNER_LINE = ' ⭐ Find cezar useful? A star helps others find it: https://github.com/open-mercato/cezar';
|
|
51
|
+
/**
|
|
52
|
+
* The star line, printed after the skills promo on `serve`.
|
|
53
|
+
*
|
|
54
|
+
* It shares `shouldShowSkillsBanner` deliberately rather than growing a switch of its own: the
|
|
55
|
+
* two lines are one promo block from where the user sits, and someone who silenced that block —
|
|
56
|
+
* with `CEZ_NO_BANNER=1` or by dismissing it back when the cockpit could — silenced this too.
|
|
57
|
+
* A second spelling of an existing off switch is a knob, not a feature (AGENTS.md § Zero config).
|
|
58
|
+
*/
|
|
59
|
+
export async function printStarBanner(repoRoot, log = console.log) {
|
|
60
|
+
if (!(await shouldShowSkillsBanner(repoRoot)))
|
|
61
|
+
return;
|
|
62
|
+
log(STAR_BANNER_LINE);
|
|
63
|
+
log();
|
|
64
|
+
}
|
|
41
65
|
//# sourceMappingURL=skills-banner.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skills-banner.js","sourceRoot":"","sources":["../src/skills-banner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAsB;IACpD,0DAA0D;IAC1D,0EAA0E;IAC1E,iEAAiE;IACjE,uFAAuF;IACvF,8EAA8E;CAC/E,CAAC;AAEF,oDAAoD;AACpD,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,QAAgB;IAC3D,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa,KAAK,GAAG;QAAE,OAAO,KAAK,CAAC;IACpD,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,CAAC;IAC5C,OAAO,OAAO,CAAC,qBAAqB,KAAK,IAAI,CAAC;AAChD,CAAC;AAED,iFAAiF;AACjF,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,QAAgB,EAChB,GAAG,GAA4B,OAAO,CAAC,GAAG;IAE1C,IAAI,CAAC,CAAC,MAAM,sBAAsB,CAAC,QAAQ,CAAC,CAAC;QAAE,OAAO;IACtD,KAAK,MAAM,IAAI,IAAI,mBAAmB;QAAE,GAAG,CAAC,IAAI,CAAC,CAAC;IAClD,GAAG,EAAE,CAAC;AACR,CAAC"}
|
|
1
|
+
{"version":3,"file":"skills-banner.js","sourceRoot":"","sources":["../src/skills-banner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAsB;IACpD,0DAA0D;IAC1D,0EAA0E;IAC1E,iEAAiE;IACjE,uFAAuF;IACvF,8EAA8E;CAC/E,CAAC;AAEF,oDAAoD;AACpD,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,QAAgB;IAC3D,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa,KAAK,GAAG;QAAE,OAAO,KAAK,CAAC;IACpD,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,CAAC;IAC5C,OAAO,OAAO,CAAC,qBAAqB,KAAK,IAAI,CAAC;AAChD,CAAC;AAED,iFAAiF;AACjF,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,QAAgB,EAChB,GAAG,GAA4B,OAAO,CAAC,GAAG;IAE1C,IAAI,CAAC,CAAC,MAAM,sBAAsB,CAAC,QAAQ,CAAC,CAAC;QAAE,OAAO;IACtD,KAAK,MAAM,IAAI,IAAI,mBAAmB;QAAE,GAAG,CAAC,IAAI,CAAC,CAAC;IAClD,GAAG,EAAE,CAAC;AACR,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAC3B,2FAA2F,CAAC;AAE9F;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,QAAgB,EAChB,GAAG,GAA4B,OAAO,CAAC,GAAG;IAE1C,IAAI,CAAC,CAAC,MAAM,sBAAsB,CAAC,QAAQ,CAAC,CAAC;QAAE,OAAO;IACtD,GAAG,CAAC,gBAAgB,CAAC,CAAC;IACtB,GAAG,EAAE,CAAC;AACR,CAAC"}
|
package/dist/workflows/run.d.ts
CHANGED
|
@@ -7,8 +7,6 @@ import type { QueuedMessage, RunRecord, RunStore } from '../runs/store.ts';
|
|
|
7
7
|
import type { DispatchInput, DispatchIntent, DispatchReport, RunDispatch } from '../contract/index.js';
|
|
8
8
|
import { WorkspaceSemaphore, type AccountHolds } from '../workspace/semaphore.ts';
|
|
9
9
|
import { type WorkflowDef } from './types.ts';
|
|
10
|
-
/** An interactive session that hears nothing from the user closes itself. */
|
|
11
|
-
export declare const IDLE_TIMEOUT_MS: number;
|
|
12
10
|
/**
|
|
13
11
|
* The text a turn-end marker is matched against: the accumulated turn text with
|
|
14
12
|
* trailing whitespace and trailing task-reference lines removed.
|
package/dist/workflows/run.js
CHANGED
|
@@ -24,7 +24,7 @@ import { materializeSkillDir } from '../skills-remote.js';
|
|
|
24
24
|
import { seedAgentConfigLocalLayer } from '../agent-config/seed.js';
|
|
25
25
|
import { readAgentModelProvider } from '../agent-config/models.js';
|
|
26
26
|
import { loadConfig, resolveWorktreeRetention } from '../config.js';
|
|
27
|
-
import { autosaveCommit,
|
|
27
|
+
import { autosaveCommit, chooseForkBase, createWorktree, worktreeDiff, worktreeShortstat } from '../git-worktree.js';
|
|
28
28
|
import { getHeadCommit, getRepoInfo } from '../server/git.js';
|
|
29
29
|
import { loadWorkflows } from './load.js';
|
|
30
30
|
import { resolveCapabilities } from '../server/capabilities.js';
|
|
@@ -47,8 +47,6 @@ const CHECK_OUTPUT_CAP = 20_000;
|
|
|
47
47
|
async function configuredModelProvider(backend, repoRoot) {
|
|
48
48
|
return readAgentModelProvider(backend, repoRoot).catch(() => undefined);
|
|
49
49
|
}
|
|
50
|
-
/** An interactive session that hears nothing from the user closes itself. */
|
|
51
|
-
export const IDLE_TIMEOUT_MS = 15 * 60_000;
|
|
52
50
|
/** Maximum time a live provider gets to honor cancellation before its slot is reaped. */
|
|
53
51
|
const CANCEL_GRACE_MS = 1_000;
|
|
54
52
|
/**
|
|
@@ -1197,9 +1195,13 @@ export class RunManager {
|
|
|
1197
1195
|
if (queuedContinuation && sessionStep?.sessionId) {
|
|
1198
1196
|
const backend = run.runner ?? 'claude';
|
|
1199
1197
|
const sessionBackend = sessionStep.backend ?? backend;
|
|
1198
|
+
// Same rule as `continueRun`: a session created under another account than the run's
|
|
1199
|
+
// chosen one cannot be resumed without silently switching the login back.
|
|
1200
|
+
const sessionAccount = sessionStep.profileId ?? DEFAULT_AGENT_ACCOUNT_ID;
|
|
1201
|
+
const accountMatches = run.agentProfile === undefined || run.agentProfile === sessionAccount;
|
|
1200
1202
|
this.pendingContinuations.set(run.id, {
|
|
1201
1203
|
stepId: queuedContinuation.id,
|
|
1202
|
-
sessionId: sessionBackend === backend ? sessionStep.sessionId : undefined,
|
|
1204
|
+
sessionId: sessionBackend === backend && accountMatches ? sessionStep.sessionId : undefined,
|
|
1203
1205
|
backend,
|
|
1204
1206
|
prompt: RESTART_CONTINUATION_PROMPT,
|
|
1205
1207
|
images: [],
|
|
@@ -2972,7 +2974,13 @@ export class RunManager {
|
|
|
2972
2974
|
// open a fresh conversation while the thread claimed it had resumed. A step that recorded no
|
|
2973
2975
|
// account predates the feature and therefore ran under the discovered one.
|
|
2974
2976
|
const sessionAccount = sessionStep.profileId ?? DEFAULT_AGENT_ACCOUNT_ID;
|
|
2975
|
-
|
|
2977
|
+
// The account this turn must run on: the composer's pick, else the one the run already
|
|
2978
|
+
// chose. The run's own choice counts too — a record whose newest session predates an
|
|
2979
|
+
// account switch (a fresh continuation that recorded no session id) must not be resumed
|
|
2980
|
+
// under the account the user switched away from.
|
|
2981
|
+
const targetAccount = opts.agentProfile
|
|
2982
|
+
?? (targetRunner === (run.runner ?? 'claude') ? run.agentProfile : undefined);
|
|
2983
|
+
const accountSwitched = targetAccount !== undefined && targetAccount !== sessionAccount;
|
|
2976
2984
|
const resume = sessionBackend === targetRunner && !accountSwitched;
|
|
2977
2985
|
// Follow-up runner/model/account override (#401, spec 2026-07-29-agent-profiles): the composer
|
|
2978
2986
|
// lets the user pick which backend, model and login handle this continuation — the same flat
|
|
@@ -3080,6 +3088,13 @@ export class RunManager {
|
|
|
3080
3088
|
const portableContext = record && sessionId === undefined
|
|
3081
3089
|
? freshContinuationContext(record, this.store.readEvents(runId))
|
|
3082
3090
|
: undefined;
|
|
3091
|
+
// A fresh session is pinned and recorded up front, exactly like a workflow step's
|
|
3092
|
+
// (`runAgentStep`): Claude emits no `session` event of its own, so an unpinned fresh
|
|
3093
|
+
// continuation left its step without a session id, and the NEXT Continue resumed the last
|
|
3094
|
+
// step that had one — an older session under whatever account created it, silently undoing
|
|
3095
|
+
// the account switch this continuation was opened for. Runners that mint their own id still
|
|
3096
|
+
// overwrite it through the `session` event.
|
|
3097
|
+
const spawnSessionId = sessionId ?? randomUUID();
|
|
3083
3098
|
// The env is a live ceiling: a run created while the inbox was on must not keep writing
|
|
3084
3099
|
// follow-ups after it is switched off.
|
|
3085
3100
|
const generateFollowups = followupsEnabled() && record?.generateFollowups !== false;
|
|
@@ -3157,7 +3172,7 @@ export class RunManager {
|
|
|
3157
3172
|
status: 'running',
|
|
3158
3173
|
iterations: 1,
|
|
3159
3174
|
startedAt: new Date().toISOString(),
|
|
3160
|
-
sessionId,
|
|
3175
|
+
sessionId: spawnSessionId,
|
|
3161
3176
|
backend,
|
|
3162
3177
|
});
|
|
3163
3178
|
this.store.appendEvent(runId, { type: 'step-start', stepId, name: 'Continue', kind: 'agent', iteration: 1 });
|
|
@@ -3275,7 +3290,8 @@ export class RunManager {
|
|
|
3275
3290
|
// with `runAgentStep`'s twin turn-end so the two cannot drift — including the shape:
|
|
3276
3291
|
// hoisted out of the branch below because the heartbeat at the end of this handler
|
|
3277
3292
|
// needs to know whether the turn parked.
|
|
3278
|
-
const nudged = dispatchTurn.rePrompted ||
|
|
3293
|
+
const nudged = dispatchTurn.rePrompted ||
|
|
3294
|
+
(!monitoring && (sessionOpen ? this.tryAutonomousNudge(runId, state, stepId, ask, dispatchTurn) : false));
|
|
3279
3295
|
// Compaction alone never means the user owns the next action (#955). Tried LAST, so
|
|
3280
3296
|
// every marker, the dispatch rules and the autonomous nudge keep their precedence —
|
|
3281
3297
|
// the twin of `runAgentStep`'s call, through the one helper both sites share.
|
|
@@ -3288,8 +3304,8 @@ export class RunManager {
|
|
|
3288
3304
|
// `CEZ:ASK` → park `waiting` (attention) AND surface the structured
|
|
3289
3305
|
// question as an ask card (#473). `CEZ:MONITORING` → non-attention
|
|
3290
3306
|
// `running`/`activity:'monitoring'` (#490). Both share the waiting
|
|
3291
|
-
// lifecycle (free the slot,
|
|
3292
|
-
// nudge
|
|
3307
|
+
// lifecycle (free the slot), while only plain waiting keeps the idle timer. Monitoring is
|
|
3308
|
+
// checked before the autonomous nudge, so it remains non-attention.
|
|
3293
3309
|
if (ask)
|
|
3294
3310
|
this.recordAsk(runId, sink, ask);
|
|
3295
3311
|
if (monitoring) {
|
|
@@ -3353,6 +3369,9 @@ export class RunManager {
|
|
|
3353
3369
|
status: 'failed',
|
|
3354
3370
|
error: message,
|
|
3355
3371
|
finishedAt: failedAt,
|
|
3372
|
+
// The fresh session pinned up front was never created: leaving its id on the step
|
|
3373
|
+
// would make the next Continue `--resume` a conversation that does not exist.
|
|
3374
|
+
...(sessionId === undefined ? { sessionId: undefined } : {}),
|
|
3356
3375
|
});
|
|
3357
3376
|
this.store.updateRun(runId, {
|
|
3358
3377
|
status: 'failed',
|
|
@@ -3463,7 +3482,7 @@ export class RunManager {
|
|
|
3463
3482
|
additionalDirectories: agentDirectories(join(this.dataDir, 'runs'), this.grantableAttachmentLibrary(), continueProfile.env),
|
|
3464
3483
|
env: continueProfile.env,
|
|
3465
3484
|
model: continueModel,
|
|
3466
|
-
sessionId,
|
|
3485
|
+
sessionId: spawnSessionId,
|
|
3467
3486
|
resume: sessionId !== undefined,
|
|
3468
3487
|
timeoutMs: 0,
|
|
3469
3488
|
}, onEvent, { onUiEvent: (event) => this.handleRunnerUiEvent(runId, state, sink, event) });
|
|
@@ -3595,27 +3614,19 @@ export class RunManager {
|
|
|
3595
3614
|
});
|
|
3596
3615
|
// Fork from the configured base branch (config.json `baseBranch`, e.g.
|
|
3597
3616
|
// `develop`) — also the target of the eventual draft PR. Unresolvable
|
|
3598
|
-
// (typo, not fetched) → note + the currently checked-out branch.
|
|
3617
|
+
// (typo, not fetched) → note + the currently checked-out branch. Either
|
|
3618
|
+
// way the base goes through `resolveBaseRef` (`chooseForkBase`), which
|
|
3619
|
+
// fetches origin first so a new task forks from the newest tip, never a
|
|
3620
|
+
// stale local ref — while a checked-out branch that diverged from origin
|
|
3621
|
+
// keeps the user's local work.
|
|
3599
3622
|
//
|
|
3600
3623
|
// A task that already recorded a fork point keeps it: its worktree is
|
|
3601
3624
|
// reused as-is, and re-resolving against a since-changed config would
|
|
3602
3625
|
// silently re-anchor the `merge-base` every diff/shortstat is measured
|
|
3603
3626
|
// from, shifting "what did this task change" under an existing task.
|
|
3604
3627
|
const recorded = this.store.getRun(runId)?.baseBranch;
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
if (configured) {
|
|
3608
|
-
const resolved = await resolveBaseRef(this.repoRoot, configured);
|
|
3609
|
-
if (resolved) {
|
|
3610
|
-
base = resolved;
|
|
3611
|
-
}
|
|
3612
|
-
else {
|
|
3613
|
-
emit({
|
|
3614
|
-
type: 'note',
|
|
3615
|
-
message: `configured base branch "${configured}" not found (locally or on origin) — using "${repo.branch}"`,
|
|
3616
|
-
});
|
|
3617
|
-
}
|
|
3618
|
-
}
|
|
3628
|
+
const base = recorded ??
|
|
3629
|
+
(await chooseForkBase(this.repoRoot, repo.branch, config.baseBranch, (message) => emit({ type: 'note', message })));
|
|
3619
3630
|
try {
|
|
3620
3631
|
const wt = await createWorktree(this.repoRoot, runId, base);
|
|
3621
3632
|
state.cwd = wt.path;
|
|
@@ -4090,7 +4101,8 @@ export class RunManager {
|
|
|
4090
4101
|
// that repeats the SAME question after being nudged sets `lastOverriddenAsk` and parks on
|
|
4091
4102
|
// the second ask. For every non-autonomous run `tryAutonomousNudge` returns at its first
|
|
4092
4103
|
// line, so the park below behaves exactly as #917 designed it.
|
|
4093
|
-
const autoContinued = dispatchTurn.rePrompted ||
|
|
4104
|
+
const autoContinued = dispatchTurn.rePrompted ||
|
|
4105
|
+
(!monitoring && (waiting ? this.tryAutonomousNudge(runId, state, step.id, ask, dispatchTurn) : false));
|
|
4094
4106
|
// The compaction continuation (#955), through the same helper `runContinuation` calls.
|
|
4095
4107
|
// Deliberately NOT gated on `waiting`: that flag is about who the turn hands control
|
|
4096
4108
|
// to, and an ordinary intermediate step never hands control to anyone — it is closed
|
|
@@ -4108,7 +4120,7 @@ export class RunManager {
|
|
|
4108
4120
|
// still working on its own downstream work with `CEZ:MONITORING`, which
|
|
4109
4121
|
// parks as `running`/`activity:'monitoring'`, a non-attention state,
|
|
4110
4122
|
// instead of raising "needs you" (#490). Lifecycle is identical: the
|
|
4111
|
-
// run frees its slot
|
|
4123
|
+
// run frees its slot; only plain waiting keeps the idle timer. The autonomous nudge
|
|
4112
4124
|
// above still wins over either.
|
|
4113
4125
|
if (ask)
|
|
4114
4126
|
this.recordAsk(runId, sink, ask);
|
|
@@ -4902,15 +4914,19 @@ export class RunManager {
|
|
|
4902
4914
|
}
|
|
4903
4915
|
armIdleTimer(runId, state) {
|
|
4904
4916
|
this.clearIdleTimer(state);
|
|
4917
|
+
const timeoutMinutes = this.semaphore.idleTimeoutMinutes();
|
|
4918
|
+
if (timeoutMinutes === null || timeoutMinutes === 0)
|
|
4919
|
+
return;
|
|
4920
|
+
const timeoutMs = timeoutMinutes * 60_000;
|
|
4905
4921
|
state.idleTimer = setTimeout(() => {
|
|
4906
4922
|
if (state.session?.open && !state.cancelled) {
|
|
4907
4923
|
this.store.appendEvent(runId, {
|
|
4908
4924
|
type: 'lifecycle',
|
|
4909
|
-
message: `session closed after ${Math.round(
|
|
4925
|
+
message: `session closed after ${Math.round(timeoutMs / 60_000)}m of inactivity`,
|
|
4910
4926
|
});
|
|
4911
4927
|
state.session.end();
|
|
4912
4928
|
}
|
|
4913
|
-
},
|
|
4929
|
+
}, timeoutMs);
|
|
4914
4930
|
state.idleTimer.unref?.();
|
|
4915
4931
|
}
|
|
4916
4932
|
clearIdleTimer(state) {
|