@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.
Files changed (294) hide show
  1. package/README.md +3 -2
  2. package/dist/agent-config/catalog.d.ts +4 -0
  3. package/dist/agent-config/catalog.js +85 -3
  4. package/dist/agent-config/catalog.js.map +1 -1
  5. package/dist/agent-config/model-settings/copilot.d.ts +15 -0
  6. package/dist/agent-config/model-settings/copilot.js +25 -0
  7. package/dist/agent-config/model-settings/copilot.js.map +1 -0
  8. package/dist/agent-config/model-settings/junie.d.ts +12 -0
  9. package/dist/agent-config/model-settings/junie.js +18 -0
  10. package/dist/agent-config/model-settings/junie.js.map +1 -0
  11. package/dist/agent-config/models.js +4 -0
  12. package/dist/agent-config/models.js.map +1 -1
  13. package/dist/automations/prompts.d.ts +2 -2
  14. package/dist/automations/prompts.js +1 -1
  15. package/dist/automations/types.d.ts +12 -0
  16. package/dist/config.d.ts +4 -0
  17. package/dist/config.js +2 -0
  18. package/dist/config.js.map +1 -1
  19. package/dist/contract/agent-config.d.ts +4 -0
  20. package/dist/contract/agent-profiles.d.ts +30 -0
  21. package/dist/contract/automations.d.ts +32 -0
  22. package/dist/contract/dashboard-insights.d.ts +186 -0
  23. package/dist/contract/dashboard.d.ts +88 -0
  24. package/dist/contract/dispatch.d.ts +6 -0
  25. package/dist/contract/health.d.ts +8 -0
  26. package/dist/contract/index.d.ts +2 -0
  27. package/dist/contract/index.js +249 -72
  28. package/dist/contract/runs.d.ts +144 -0
  29. package/dist/contract/self-update.d.ts +111 -2
  30. package/dist/contract/skills.d.ts +19 -0
  31. package/dist/contract/star-count.d.ts +18 -0
  32. package/dist/contract/workflows.d.ts +12 -0
  33. package/dist/contract/workspace.d.ts +42 -2
  34. package/dist/core/acp-client.d.ts +99 -0
  35. package/dist/core/acp-client.js +198 -0
  36. package/dist/core/acp-client.js.map +1 -0
  37. package/dist/core/acp-ui-mapper.d.ts +95 -0
  38. package/dist/core/acp-ui-mapper.js +444 -0
  39. package/dist/core/acp-ui-mapper.js.map +1 -0
  40. package/dist/core/agent-env.js +10 -0
  41. package/dist/core/agent-env.js.map +1 -1
  42. package/dist/core/agent-profiles.d.ts +10 -0
  43. package/dist/core/agent-profiles.js +16 -0
  44. package/dist/core/agent-profiles.js.map +1 -1
  45. package/dist/core/agent-runner.d.ts +5 -3
  46. package/dist/core/agent-runner.js +5 -3
  47. package/dist/core/agent-runner.js.map +1 -1
  48. package/dist/core/backend-detect.d.ts +3 -3
  49. package/dist/core/backend-detect.js +61 -2
  50. package/dist/core/backend-detect.js.map +1 -1
  51. package/dist/core/claude-cli-runner.d.ts +5 -2
  52. package/dist/core/claude-cli-runner.js +11 -6
  53. package/dist/core/claude-cli-runner.js.map +1 -1
  54. package/dist/core/claude-ui-mapper.d.ts +14 -0
  55. package/dist/core/claude-ui-mapper.js +102 -11
  56. package/dist/core/claude-ui-mapper.js.map +1 -1
  57. package/dist/core/codex-app-server-transport.js +8 -4
  58. package/dist/core/codex-app-server-transport.js.map +1 -1
  59. package/dist/core/copilot-acp-runner.d.ts +60 -0
  60. package/dist/core/copilot-acp-runner.js +528 -0
  61. package/dist/core/copilot-acp-runner.js.map +1 -0
  62. package/dist/core/copilot-ui-mapper.d.ts +47 -0
  63. package/dist/core/copilot-ui-mapper.js +111 -0
  64. package/dist/core/copilot-ui-mapper.js.map +1 -0
  65. package/dist/core/cursor-agent-runner.js +4 -4
  66. package/dist/core/cursor-agent-runner.js.map +1 -1
  67. package/dist/core/disclaim-spawn.d.ts +9 -0
  68. package/dist/core/disclaim-spawn.js +53 -0
  69. package/dist/core/disclaim-spawn.js.map +1 -0
  70. package/dist/core/junie-acp-transport.d.ts +58 -0
  71. package/dist/core/junie-acp-transport.js +179 -0
  72. package/dist/core/junie-acp-transport.js.map +1 -0
  73. package/dist/core/junie-auth-probe.d.ts +12 -0
  74. package/dist/core/junie-auth-probe.js +30 -0
  75. package/dist/core/junie-auth-probe.js.map +1 -0
  76. package/dist/core/junie-model-catalog.d.ts +10 -0
  77. package/dist/core/junie-model-catalog.js +104 -0
  78. package/dist/core/junie-model-catalog.js.map +1 -0
  79. package/dist/core/junie-runner.d.ts +45 -0
  80. package/dist/core/junie-runner.js +533 -0
  81. package/dist/core/junie-runner.js.map +1 -0
  82. package/dist/core/junie-ui-mapper.d.ts +132 -0
  83. package/dist/core/junie-ui-mapper.js +459 -0
  84. package/dist/core/junie-ui-mapper.js.map +1 -0
  85. package/dist/core/model-identity.js +17 -0
  86. package/dist/core/model-identity.js.map +1 -1
  87. package/dist/core/model-presets.js +10 -0
  88. package/dist/core/model-presets.js.map +1 -1
  89. package/dist/core/opencode-server-runner.js +4 -4
  90. package/dist/core/opencode-server-runner.js.map +1 -1
  91. package/dist/core/pi-runner.js +4 -4
  92. package/dist/core/pi-runner.js.map +1 -1
  93. package/dist/core/provider-auth.d.ts +16 -2
  94. package/dist/core/provider-auth.js +113 -7
  95. package/dist/core/provider-auth.js.map +1 -1
  96. package/dist/core/runner-factory.js +6 -0
  97. package/dist/core/runner-factory.js.map +1 -1
  98. package/dist/core/runner-model-catalog.js +12 -8
  99. package/dist/core/runner-model-catalog.js.map +1 -1
  100. package/dist/core/tool-display.js +6 -1
  101. package/dist/core/tool-display.js.map +1 -1
  102. package/dist/core/ui-events.d.ts +6 -2
  103. package/dist/git-diff-base.js +23 -2
  104. package/dist/git-diff-base.js.map +1 -1
  105. package/dist/git-worktree.d.ts +21 -2
  106. package/dist/git-worktree.js +68 -5
  107. package/dist/git-worktree.js.map +1 -1
  108. package/dist/index.js +14 -4
  109. package/dist/index.js.map +1 -1
  110. package/dist/paths.js +2 -0
  111. package/dist/paths.js.map +1 -1
  112. package/dist/runs/event-history.js +3 -0
  113. package/dist/runs/event-history.js.map +1 -1
  114. package/dist/runs/store.d.ts +42 -3
  115. package/dist/runs/store.js +112 -6
  116. package/dist/runs/store.js.map +1 -1
  117. package/dist/self-update/checkouts.d.ts +53 -0
  118. package/dist/self-update/checkouts.js +268 -0
  119. package/dist/self-update/checkouts.js.map +1 -0
  120. package/dist/self-update/cli.d.ts +5 -3
  121. package/dist/self-update/cli.js +75 -8
  122. package/dist/self-update/cli.js.map +1 -1
  123. package/dist/self-update/layout.d.ts +33 -1
  124. package/dist/self-update/layout.js +102 -3
  125. package/dist/self-update/layout.js.map +1 -1
  126. package/dist/self-update/pulls.d.ts +40 -0
  127. package/dist/self-update/pulls.js +127 -0
  128. package/dist/self-update/pulls.js.map +1 -0
  129. package/dist/self-update/service.d.ts +26 -3
  130. package/dist/self-update/service.js +85 -9
  131. package/dist/self-update/service.js.map +1 -1
  132. package/dist/server/dashboard.d.ts +107 -0
  133. package/dist/server/dashboard.js +2 -1
  134. package/dist/server/dashboard.js.map +1 -1
  135. package/dist/server/forge/github.js +4 -1
  136. package/dist/server/forge/github.js.map +1 -1
  137. package/dist/server/fs-browse.d.ts +3 -1
  138. package/dist/server/fs-browse.js +27 -5
  139. package/dist/server/fs-browse.js.map +1 -1
  140. package/dist/server/open-in-app.js +2 -0
  141. package/dist/server/open-in-app.js.map +1 -1
  142. package/dist/server/provider-action-gate.js +5 -1
  143. package/dist/server/provider-action-gate.js.map +1 -1
  144. package/dist/server/server.d.ts +880 -511
  145. package/dist/server/server.js +40 -5
  146. package/dist/server/server.js.map +1 -1
  147. package/dist/server/star-count.d.ts +67 -0
  148. package/dist/server/star-count.js +186 -0
  149. package/dist/server/star-count.js.map +1 -0
  150. package/dist/skills-banner.d.ts +19 -0
  151. package/dist/skills-banner.js +24 -0
  152. package/dist/skills-banner.js.map +1 -1
  153. package/dist/workflows/run.d.ts +0 -2
  154. package/dist/workflows/run.js +45 -29
  155. package/dist/workflows/run.js.map +1 -1
  156. package/dist/workflows/types.d.ts +6 -0
  157. package/dist/workspace/agent-accounts.d.ts +9 -1
  158. package/dist/workspace/agent-accounts.js +2 -0
  159. package/dist/workspace/agent-accounts.js.map +1 -1
  160. package/dist/workspace/agent-profiles.d.ts +2 -2
  161. package/dist/workspace/agent-profiles.js +25 -6
  162. package/dist/workspace/agent-profiles.js.map +1 -1
  163. package/dist/workspace/config.d.ts +9 -1
  164. package/dist/workspace/config.js +10 -3
  165. package/dist/workspace/config.js.map +1 -1
  166. package/dist/workspace/dashboard-insights.d.ts +42 -0
  167. package/dist/workspace/dashboard-insights.js +220 -0
  168. package/dist/workspace/dashboard-insights.js.map +1 -0
  169. package/dist/workspace/dashboard.d.ts +71 -1
  170. package/dist/workspace/dashboard.js +14 -1
  171. package/dist/workspace/dashboard.js.map +1 -1
  172. package/dist/workspace/semaphore.d.ts +3 -0
  173. package/dist/workspace/semaphore.js +8 -1
  174. package/dist/workspace/semaphore.js.map +1 -1
  175. package/package.json +3 -3
  176. package/scripts/mock-claude.mjs +34 -1
  177. package/scripts/mock-copilot-acp.mjs +190 -0
  178. package/scripts/mock-junie-acp.mjs +208 -0
  179. package/web/dist/assets/agent-task-pickers-BeQi-UTm.js +1 -0
  180. package/web/dist/assets/alert-dialog-CnRAuqwb.js +1 -0
  181. package/web/dist/assets/arrow-down-Da0kNRNH.js +1 -0
  182. package/web/dist/assets/arrow-left-BIZHhBIP.js +1 -0
  183. package/web/dist/assets/automation-format-CfPmkcsg.js +1 -0
  184. package/web/dist/assets/automations-route-AMJbFP53.js +1 -0
  185. package/web/dist/assets/centered-state-3r2Kaar_.js +1 -0
  186. package/web/dist/assets/chevron-left-DLAUPC66.js +1 -0
  187. package/web/dist/assets/chevron-right-KVZcXiPL.js +1 -0
  188. package/web/dist/assets/{circle-x-C9UoMP1V.js → circle-x-C0Jm5Ppb.js} +1 -1
  189. package/web/dist/assets/collapsible-DG_SIWqU.js +1 -0
  190. package/web/dist/assets/command-Dfln3D4A.js +1 -0
  191. package/web/dist/assets/{commit-list-BSfPsVsu.js → commit-list-_RA72yB_.js} +1 -1
  192. package/web/dist/assets/compare-variants-D6Ji6od4.js +1 -0
  193. package/web/dist/assets/dashboard-B3-tPimG.js +44 -0
  194. package/web/dist/assets/dialog-CiDREdUK.js +1 -0
  195. package/web/dist/assets/{diff-I0xowULh.js → diff-CxdyRumG.js} +2 -2
  196. package/web/dist/assets/diff-controls-DqJMMV4z.js +1 -0
  197. package/web/dist/assets/{diff-stat-yKn7joEr.js → diff-stat-Hupj4hxh.js} +1 -1
  198. package/web/dist/assets/{diff-view-DjvMeW1M.js → diff-view-BBYT5k6c.js} +1 -1
  199. package/web/dist/assets/dist-BqMwqJFX.js +1 -0
  200. package/web/dist/assets/dist-DpiX6WjA.js +1 -0
  201. package/web/dist/assets/{download-BcgZ2yk4.js → download-BR7EN9gW.js} +1 -1
  202. package/web/dist/assets/dropdown-menu-Ci4HFML0.js +1 -0
  203. package/web/dist/assets/editable-title-DDtUgRKZ.js +1 -0
  204. package/web/dist/assets/{ellipsis-vertical-g4O4NlLV.js → ellipsis-vertical-DBklom4U.js} +1 -1
  205. package/web/dist/assets/facet-filter-CfI4QvOd.js +1 -0
  206. package/web/dist/assets/{file-wXNeZwi7.js → file-CZb7cKNH.js} +1 -1
  207. package/web/dist/assets/{file-text-DV1gE2L6.js → file-text-Dm6CrEGW.js} +1 -1
  208. package/web/dist/assets/{folder-wjVId5un.js → folder-zQj-FYOo.js} +1 -1
  209. package/web/dist/assets/{git-pull-request-BBEDdTLK.js → git-pull-request-AhwzJc7s.js} +1 -1
  210. package/web/dist/assets/git-toolbar-j7gKkhGL.js +1 -0
  211. package/web/dist/assets/github-De-DFy8s.js +1 -0
  212. package/web/dist/assets/{image-preview-u1f-wzxC.js → image-preview-Dd3NVB7N.js} +1 -1
  213. package/web/dist/assets/index-HS1hr5Sz.js +12 -0
  214. package/web/dist/assets/index-YwgrLyBj.css +2 -0
  215. package/web/dist/assets/loader-circle-sk4vKJKp.js +1 -0
  216. package/web/dist/assets/{markdown-Bnm47qUl.js → markdown-DjNR-1p8.js} +2 -2
  217. package/web/dist/assets/new-task-form-vLs2G1_z.js +1 -0
  218. package/web/dist/assets/picker-pill-VW-M1ZiV.js +1 -0
  219. package/web/dist/assets/{pill-7dm3vWF7.js → pill-CPl6sysj.js} +1 -1
  220. package/web/dist/assets/popover-CNGONAql.js +1 -0
  221. package/web/dist/assets/project-router-dCZQu1wQ.js +1 -0
  222. package/web/dist/assets/{prompt-templates-BMZ5q5H_.js → prompt-templates-jNjVTAws.js} +1 -1
  223. package/web/dist/assets/{refresh-cw-C69euZ2H.js → refresh-cw-BfkEydKA.js} +1 -1
  224. package/web/dist/assets/repo-git-BZWZ6_pR.js +1 -0
  225. package/web/dist/assets/{run-diff-CoNVGn23.js → run-diff-u_roDsAJ.js} +2 -2
  226. package/web/dist/assets/run-header-Br1yJR0m.js +1 -0
  227. package/web/dist/assets/{search-x-BNLFOxI6.js → search-x-CT0NTqK1.js} +1 -1
  228. package/web/dist/assets/segmented-IDD4ucht.js +1 -0
  229. package/web/dist/assets/skills-Dsi5YdLG.js +1 -0
  230. package/web/dist/assets/{sparkles-ChorK-gZ.js → sparkles-CvOeAoyJ.js} +1 -1
  231. package/web/dist/assets/{square-terminal-C7KCPznz.js → square-terminal-eSs8YYZe.js} +1 -1
  232. package/web/dist/assets/{tab-link-E5O2Cesc.js → tab-link-CPF6ORha.js} +1 -1
  233. package/web/dist/assets/task-changes-DIT9Y1rh.js +1 -0
  234. package/web/dist/assets/task-commits-B4VvdiHH.js +1 -0
  235. package/web/dist/assets/task-files-BMm9C0DW.js +2 -0
  236. package/web/dist/assets/task-thread-RXabJAt0.js +9 -0
  237. package/web/dist/assets/{textarea-DLAfWPb0.js → textarea-DtBqPhIs.js} +1 -1
  238. package/web/dist/assets/thread-loading-Dnxb6Ar-.js +1 -0
  239. package/web/dist/assets/toaster-CfRVb8vH.js +43 -0
  240. package/web/dist/assets/token-metrics-DldVlr6m.js +1 -0
  241. package/web/dist/assets/tooltip-_O50N3pt.js +1 -0
  242. package/web/dist/assets/tracker-J0fdcH1G.js +4 -0
  243. package/web/dist/assets/{trash-2-_vwSMdPG.js → trash-2-CUH7GOVh.js} +1 -1
  244. package/web/dist/assets/{triangle-alert-fBRUkB46.js → triangle-alert-D5K_zyD3.js} +1 -1
  245. package/web/dist/assets/{upload-B4OIpdyq.js → upload-D7_n-9cl.js} +1 -1
  246. package/web/dist/assets/{use-now-sqAvRMna.js → use-now-3lElDuTi.js} +1 -1
  247. package/web/dist/assets/utils-BXvEm7Ve.js +64 -0
  248. package/web/dist/assets/workflows-D-dx9c_0.js +7 -0
  249. package/web/dist/assets/x-kLybIvX4.js +1 -0
  250. package/web/dist/assets/{zoomable-image-X81SApIg.js → zoomable-image-CnyQ9wyw.js} +1 -1
  251. package/web/dist/index.html +38 -36
  252. package/web/dist/assets/agent-task-pickers-DQ1qvgrD.js +0 -1
  253. package/web/dist/assets/alert-dialog-B_2uU4Bg.js +0 -1
  254. package/web/dist/assets/arrow-down-DieVZxSE.js +0 -1
  255. package/web/dist/assets/arrow-left-C8M0efjn.js +0 -1
  256. package/web/dist/assets/automation-format-CzinzpHK.js +0 -1
  257. package/web/dist/assets/automations-route-DYHUjV9R.js +0 -1
  258. package/web/dist/assets/centered-state-DJLgwvA4.js +0 -43
  259. package/web/dist/assets/chevron-left-BhXrOEE3.js +0 -1
  260. package/web/dist/assets/chevron-right-B3Ta8VKK.js +0 -1
  261. package/web/dist/assets/collapsible-BcKvItwg.js +0 -1
  262. package/web/dist/assets/command-BvGGv2_u.js +0 -1
  263. package/web/dist/assets/compare-variants-DBvMYMs3.js +0 -1
  264. package/web/dist/assets/dashboard-CGaFh4RV.js +0 -44
  265. package/web/dist/assets/dialog-B6NgKh0-.js +0 -1
  266. package/web/dist/assets/diff-controls-DskkZqmD.js +0 -1
  267. package/web/dist/assets/dist-BvXEdcbG.js +0 -1
  268. package/web/dist/assets/dist-CTksUkKh.js +0 -1
  269. package/web/dist/assets/dropdown-menu-B7HeKCfN.js +0 -1
  270. package/web/dist/assets/editable-title-BHbHmSIK.js +0 -1
  271. package/web/dist/assets/facet-filter-DZ3wfvN9.js +0 -1
  272. package/web/dist/assets/git-toolbar-EZuX4Cuz.js +0 -1
  273. package/web/dist/assets/github-zcLRxv77.js +0 -1
  274. package/web/dist/assets/index-BrZhA4_u.css +0 -2
  275. package/web/dist/assets/index-CSNFuBvj.js +0 -12
  276. package/web/dist/assets/loader-circle-Cwgo6onL.js +0 -1
  277. package/web/dist/assets/new-task-form-CQODCzBP.js +0 -1
  278. package/web/dist/assets/picker-pill-DNB9ETjS.js +0 -1
  279. package/web/dist/assets/popover-BRq6QnwI.js +0 -1
  280. package/web/dist/assets/project-router-I8Pbsrxy.js +0 -1
  281. package/web/dist/assets/repo-git-G436Abni.js +0 -1
  282. package/web/dist/assets/run-header-CxdaA86t.js +0 -1
  283. package/web/dist/assets/skills-C_kBDesq.js +0 -1
  284. package/web/dist/assets/task-changes-Bhw4Ysvg.js +0 -1
  285. package/web/dist/assets/task-commits-DBuGXuGm.js +0 -1
  286. package/web/dist/assets/task-files-CYDbeffM.js +0 -2
  287. package/web/dist/assets/task-thread-RuxHMJgN.js +0 -9
  288. package/web/dist/assets/thread-loading-BAuVAbcA.js +0 -1
  289. package/web/dist/assets/token-metrics-rVl4kXe_.js +0 -1
  290. package/web/dist/assets/tooltip-Bdl8mQG8.js +0 -1
  291. package/web/dist/assets/tracker-DJ8JW59-.js +0 -4
  292. package/web/dist/assets/utils-DoBmOlX3.js +0 -64
  293. package/web/dist/assets/workflows-C54D7mlm.js +0 -7
  294. 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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"}
@@ -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>;
@@ -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"}
@@ -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.
@@ -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, createWorktree, resolveBaseRef, worktreeDiff, worktreeShortstat } from '../git-worktree.js';
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
- const accountSwitched = opts.agentProfile !== undefined && opts.agentProfile !== sessionAccount;
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 || (sessionOpen ? this.tryAutonomousNudge(runId, state, stepId, ask, dispatchTurn) : false);
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, keep the idle timer); the autonomous
3292
- // nudge above still wins over either.
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
- let base = recorded ?? repo.branch;
3606
- const configured = recorded ? undefined : config.baseBranch;
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 || (waiting ? this.tryAutonomousNudge(runId, state, step.id, ask, dispatchTurn) : false);
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 and keeps the idle timer. The autonomous nudge
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(IDLE_TIMEOUT_MS / 60_000)}m of inactivity`,
4925
+ message: `session closed after ${Math.round(timeoutMs / 60_000)}m of inactivity`,
4910
4926
  });
4911
4927
  state.session.end();
4912
4928
  }
4913
- }, IDLE_TIMEOUT_MS);
4929
+ }, timeoutMs);
4914
4930
  state.idleTimer.unref?.();
4915
4931
  }
4916
4932
  clearIdleTimer(state) {