@overdeck/desktop 0.45.16 → 0.45.19

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 (365) hide show
  1. package/dist-electron/main.js +149 -158
  2. package/dist-electron/main.js.map +1 -1
  3. package/dist-electron/preload.js +17 -2
  4. package/dist-electron/preload.js.map +1 -1
  5. package/package.json +5 -4
  6. package/server/acceptance-criteria-DQqRIuQ-.js +62 -0
  7. package/server/{advancing-selfheal-DJYAvGv-.js → advancing-selfheal-BYidNkmp.js} +6 -6
  8. package/server/{agent-gc-D0wDIeV1.js → agent-gc-ClNQLl_V.js} +3 -3
  9. package/server/agent-state-BMrpX7kU.js +3 -0
  10. package/server/{agent-state-service-DFslS5go.js → agent-state-service-BpAKmodj.js} +5 -5
  11. package/server/{agent-state-service-CZrayD_G.js → agent-state-service-CC9NMIWn.js} +1 -1
  12. package/server/{briefing-freshness-DESTCWo-.js → agents--ljN-wVY.js} +5 -131
  13. package/server/agents-DAUyr_Z1.js +3 -0
  14. package/server/{archive-planning-CoyIbgGK.js → archive-planning-2w8WQaeJ.js} +1 -1
  15. package/server/{archive-planning-CbghE9-o.js → archive-planning-BW3te5Vw.js} +5 -10
  16. package/server/{artifact-index-jobs-CcSrUoSE.js → artifact-index-jobs-DFO3eceF.js} +2 -2
  17. package/server/{backlog-auto-trigger-BfgDsII0.js → backlog-auto-trigger-DECBMxLq.js} +2 -2
  18. package/server/{behavior-BTpYbAv4.js → behavior-CWqNMqE5.js} +2 -2
  19. package/server/briefing-freshness-B1delh4l.js +131 -0
  20. package/server/{checkpoint-manager-DWSXooZV.js → checkpoint-manager-CjLY6A4r.js} +1 -1
  21. package/server/{checkpoint-manager-BwFdegJF.js → checkpoint-manager-D6_71Rpb.js} +3 -3
  22. package/server/checkpoint-worker.js +1 -1
  23. package/server/{checkpoints-BtUonbs3.js → checkpoints-BYrLxVle.js} +2 -2
  24. package/server/{checkpoints-fjadfOKF.js → checkpoints-xSuv4v4I.js} +1 -1
  25. package/server/{claude-hooks-provision-DiK62u0k.js → claude-hooks-provision-DL6yfJB_.js} +1 -1
  26. package/server/{claude-hooks-provision-DAtxutOs.js → claude-hooks-provision-DvfVZbbT.js} +2 -2
  27. package/server/{claude-permissions-CThLgf8d.js → claude-permissions-CcncrTNF.js} +2 -2
  28. package/server/{claude-settings-overlay-BF9MPIwx.js → claude-settings-overlay-1IfYetv2.js} +2 -2
  29. package/server/{claude-settings-overlay-Dx_Uqcmv.js → claude-settings-overlay-Ca3LXjAC.js} +1 -1
  30. package/server/cli-BxZHwgRK.js +3 -0
  31. package/server/{cli-ByKIIMfT.js → cli-DGZWYXio.js} +9 -9
  32. package/server/{cliproxy-B4c1BUvs.js → cliproxy-Dw42Xy3t.js} +2 -2
  33. package/server/{cliproxy-C_GuT5DU.js → cliproxy-DziJLbfo.js} +1 -1
  34. package/server/{close-issue-BOuLKr6e.js → close-issue-DEGjxifn.js} +5 -5
  35. package/server/{closed-issue-reaper-DCAYzNXW.js → closed-issue-reaper-BsJ3JqQy.js} +9 -9
  36. package/server/{closed-issue-reaper-CksfuiFD.js → closed-issue-reaper-CNr7X4MB.js} +1 -1
  37. package/server/codex-C8OlW-dN.js +3 -0
  38. package/server/{codex-BbqIRZjd.js → codex-D5RyWzNL.js} +43 -18
  39. package/server/{codex-parser-mFYZh8vR.js → codex-parser-BRxvUkk8.js} +2 -2
  40. package/server/{config-Dj6N8EP9.js → config-CQ2u44iP.js} +1 -1
  41. package/server/{config-DVLdmnMJ.js → config-Cntj_tFu.js} +2 -2
  42. package/server/{config-yaml-cNOZcEww.js → config-yaml-BQs6GbD_.js} +75 -74
  43. package/server/{conflict-gate-CuXUv-bP.js → conflict-gate-BE6JdfkO.js} +1 -1
  44. package/server/{conflict-gate-BMKm_5DV.js → conflict-gate-DdfwKgy0.js} +3 -3
  45. package/server/{context-layers-c0JqYHcS.js → context-layers-DFucrVVG.js} +17 -3
  46. package/server/{context-layers-CeAwSYNt.js → context-layers-DO0Eyq2E.js} +2 -2
  47. package/server/{control-settings-D4EFsRBd.js → control-settings-bxOCLsuE.js} +4 -4
  48. package/server/{conversation-compaction-BCggyih4.js → conversation-compaction-Dq6MfZdL.js} +8 -8
  49. package/server/{conversation-compaction-kHVfVRg3.js → conversation-compaction-lSlUb-cU.js} +1 -1
  50. package/server/{conversation-service-BTzObqGi.js → conversation-service-BVt4MQ1_.js} +1 -1
  51. package/server/{conversation-service-B8LFQQm1.js → conversation-service-TZsQjkjT.js} +6 -6
  52. package/server/{conversations-B-lcHv-5.js → conversations-CxOWHNzv.js} +4 -4
  53. package/server/{conversations-BM7zlXjr.js → conversations-DaF6elGK.js} +1 -1
  54. package/server/{cost-BcpRQ-7o.js → cost-Dlh0IEiv.js} +3 -3
  55. package/server/{cost-D9kPIY_5.js → cost-oinpUqvE.js} +2 -2
  56. package/server/{cost-sync-DF6odxK9.js → cost-sync-CVIAsrhB.js} +3 -3
  57. package/server/{dag-DKI3sOmX.js → dag-ClFr7Bah.js} +7 -243
  58. package/server/{dangerous-git-ops-Bn18PEcN.js → dangerous-git-ops-q5OPtXMd.js} +1 -1
  59. package/server/dashboard-db-worker.js +6 -6
  60. package/server/{deacon-DONuopff.js → deacon-B8ygB3e8.js} +3 -3
  61. package/server/{deacon-DdARMjZq.js → deacon-D2-xTesT.js} +233 -162
  62. package/server/{deacon-nudge-log-B-naf89q.js → deacon-nudge-log-ilDfxrhK.js} +1 -1
  63. package/server/deacon-swarm-1vPLuj_l.js +2 -0
  64. package/server/{deacon-swarm-BNGkmYR_.js → deacon-swarm-yWKQhxiX.js} +298 -72
  65. package/server/deacon.js +14 -7
  66. package/server/{devcontainer-renderer-C-34Sxyz.js → devcontainer-renderer-xPhZHCrx.js} +3 -3
  67. package/server/{dist-BMXvKiZP.js → dist-BwlEiLCU.js} +1 -1
  68. package/server/dist-Bx4KkgYL.js +3 -0
  69. package/server/{dist-src-BgGUblXg.js → dist-src-BE0Denfs.js} +1 -1
  70. package/server/{docker-apZ9pBYw.js → docker-DPEglAwA.js} +2 -2
  71. package/server/{ensure-devcontainer-Bqi1EF1P.js → ensure-devcontainer-SzSvEdKq.js} +4 -4
  72. package/server/{ensure-devcontainer-Dq3KoOIl.js → ensure-devcontainer-wU3ym1b5.js} +1 -1
  73. package/server/{esm-shims-BEjvINX3.js → esm-shims-Cso7IGBC.js} +1 -1
  74. package/server/{event-store-CqDgfbAs.js → event-store-BZoXexYS.js} +1 -1
  75. package/server/{event-store-D89fMfXS.js → event-store-D4s6eQ6o.js} +3 -3
  76. package/server/{events-rIGncmP1.js → events-Bn-wdUBF.js} +1 -1
  77. package/server/{events-S_C0pvl6.js → events-D1hMLl2a.js} +3 -3
  78. package/server/{factory-DV18m0rc.js → factory-ltWnu0V_.js} +20 -17
  79. package/server/{feature-registry-population-CqZSnqQi.js → feature-registry-population-xj-J1Wm7.js} +4 -4
  80. package/server/{feature-registry-storage-BeXmNBwK.js → feature-registry-storage-CQZukiLm.js} +2 -2
  81. package/server/{features-u5XeMaOK.js → features-Dyqi6N8f.js} +2 -2
  82. package/server/{feedback-target-DDG1PWl3.js → feedback-target-Ba0zhSYI.js} +1 -1
  83. package/server/{feedback-target-CwyWf0Dg.js → feedback-target-Chd5dm-Q.js} +9 -7
  84. package/server/{feedback-writer-BsJiw5aT.js → feedback-writer-BtaoJGl2.js} +1 -1
  85. package/server/{feedback-writer-D20HSXre.js → feedback-writer-BujzReaQ.js} +4 -4
  86. package/server/{flywheel-Dczmgchd.js → flywheel-FTInS73X.js} +15 -28
  87. package/server/{flywheel-RCnHAgc4.js → flywheel-bTImiVrk.js} +1 -1
  88. package/server/{flywheel-merge-order-CtCYsFJP.js → flywheel-merge-order-DuenRJLZ.js} +6 -6
  89. package/server/{flywheel-merge-order-DEtEd3aK.js → flywheel-merge-order-O5Zu8iPw.js} +1 -1
  90. package/server/{flywheel-9Y1X7IM-.js → flywheel-rmYbW-lh.js} +13 -13
  91. package/server/{forge-DFDThFil.js → forge-OWm99lie.js} +22 -12
  92. package/server/forge-x5lszTd0.js +2 -0
  93. package/server/{from-NSJypkI8.js → from-B2tOYhYV.js} +1 -1
  94. package/server/{fts-db-C5A_TTOM.js → fts-db-BRfS5ukG.js} +2 -2
  95. package/server/{fts-operations-DvCSimfU.js → fts-operations-L5sNLoHl.js} +2 -2
  96. package/server/{git-utils-C0neHvQx.js → git-utils-BIEzv2Zp.js} +1 -1
  97. package/server/{git-utils-Dr0L7mFk.js → git-utils-CP-bg-aM.js} +2 -2
  98. package/server/{github-app-u9-SX7Gc.js → github-app-CRUIvHuM.js} +8 -3
  99. package/server/{github-app-Bwt9lK4A.js → github-app-Dtn4ib6M.js} +1 -1
  100. package/server/{github-client-Btyy3uan.js → github-client-CWEFlCqL.js} +3 -3
  101. package/server/{github-client-Do3k-N1W.js → github-client-DSVv6wX8.js} +1 -1
  102. package/server/{github-pr-lookup-DOF1U2WW.js → github-pr-lookup-D6CS9vtG.js} +2 -2
  103. package/server/{harness-resolve-C6g2R0Cd.js → harness-resolve-ScD6rV47.js} +3 -3
  104. package/server/{health-events-Bos3sk5g.js → health-events-DbcJmYmd.js} +1 -1
  105. package/server/{health-events-B4u-2a9m.js → health-events-Dm1Vcvqe.js} +2 -2
  106. package/server/{hooks-uEr8WTx_.js → hooks-B6gaI6Kg.js} +1 -1
  107. package/server/{hooks-BCoOjL_R.js → hooks-VXuCnzCv.js} +3 -3
  108. package/server/hume-B4seN95Y.js +3 -0
  109. package/server/{hume-BqxYKbUf.js → hume-BRhNKyXQ.js} +2 -2
  110. package/server/{idle-stack-reaper-H0W6XtHh.js → idle-stack-reaper-Gv32ZCrQ.js} +1 -1
  111. package/server/{idle-stack-reaper-DbZ8ukMd.js → idle-stack-reaper-po700Lfg.js} +2 -2
  112. package/server/{infra-Ba9mS6e6.js → infra-DYwIXlK5.js} +9 -4
  113. package/server/{injection-C1DKXeIZ.js → injection-CTfFnpsG.js} +1 -1
  114. package/server/{injection-yrMBxzVq.js → injection-DPsUoCJp.js} +7 -7
  115. package/server/inspect-agent-B25IP-0O.js +2 -0
  116. package/server/{inspect-agent-B-mnb4uh.js → inspect-agent-CLkwfLRx.js} +35 -67
  117. package/server/{internal-token-BtqSbKTx.js → internal-token--50JhWP7.js} +2 -2
  118. package/server/{internal-token-BPYEaXFh.js → internal-token-BOgxXMGy.js} +1 -1
  119. package/server/{io-VEGSAqsd.js → io-Cv9xQTks.js} +36 -43
  120. package/server/{issue-closed-BxcePXL-.js → issue-closed-CyqtoFUj.js} +41 -6
  121. package/server/{issue-lifecycle-CdMUauyb.js → issue-lifecycle-Cm7ydBGK.js} +1 -1
  122. package/server/{issue-lifecycle--8MRxrG1.js → issue-lifecycle-DR6n40bh.js} +22 -37
  123. package/server/{issue-service-singleton-MZsDhAob.js → issue-service-singleton--LQ5c2zT.js} +1 -1
  124. package/server/{issue-service-singleton-CkTUI-eu.js → issue-service-singleton-DKKPneH4.js} +29 -29
  125. package/server/{issues-CBAsuoAO.js → issues-Cr-BLFLO.js} +2 -2
  126. package/server/{jsonl-parser-BUW5_sSK.js → jsonl-parser-Bp_xCkX4.js} +3 -3
  127. package/server/{label-cleanup-htK6fBRU.js → label-cleanup-BbLy2FZe.js} +3 -3
  128. package/server/{state-home-CEvD_v5F.js → launcher-generator-C-FU4PNT.js} +7 -127
  129. package/server/{layers-jjL16At6.js → layers-C-6lBDUE.js} +2 -2
  130. package/server/lifecycle-Bmw8yU_A.js +12 -0
  131. package/server/{lifecycle-io-DXv8Utct.js → lifecycle-io-B150XprC.js} +1 -1
  132. package/server/{lifecycle-io-IehcRt2v.js → lifecycle-io-Dcu-_ktz.js} +4 -4
  133. package/server/{linear-client-D9cSQfvD.js → linear-client-BHjs0luk.js} +3 -3
  134. package/server/{linear-client-By7hjwuN.js → linear-client-s65LNmlG.js} +1 -1
  135. package/server/memory-fts-worker.js +1 -1
  136. package/server/{merge-BLcEZNXz.js → merge-CM8swd2V.js} +1 -1
  137. package/server/{merge-BN8FGJ2q.js → merge-CS0b-E3u.js} +3 -3
  138. package/server/{merge-agent-B3u2szOL.js → merge-agent-Bu1hw9cz.js} +64 -51
  139. package/server/{merge-agent-ejC7Wos8.js → merge-agent-D6LRIW63.js} +1 -1
  140. package/server/{merge-batch-DyqlgYGp.js → merge-batch-Bv6Z5K2H.js} +1 -1
  141. package/server/{merge-blockers-cq-aS2KG.js → merge-blockers-Dt43hJiW.js} +2 -2
  142. package/server/merge-ops-DwYi3U_x.js +2 -0
  143. package/server/{merge-rebase-Dl7yIxtV.js → merge-rebase-B3vQcTq1.js} +2 -2
  144. package/server/{merge-set-Dy0D0r3i.js → merge-set-BMEGEKQg.js} +1 -1
  145. package/server/{merge-set-CZ9Nbay0.js → merge-set-Pe3spe8k.js} +3 -3
  146. package/server/{merge-sync-CG9kDCdT.js → merge-sync-CpxFRIQ7.js} +1 -1
  147. package/server/{merge-sync-D7jWLgCC.js → merge-sync-STpg1hk3.js} +2 -2
  148. package/server/{merge-train-CH33XJOH.js → merge-train-DLw2hDk0.js} +3 -3
  149. package/server/{merge-train-deps-JoVdGnOg.js → merge-train-deps-BlCGLqwy.js} +4 -4
  150. package/server/{merge-verification-D4wTChdX.js → merge-verification-C4WMP40D.js} +4 -4
  151. package/server/{multipart-parser-Csn8U-K9.js → multipart-parser-9DhuAkPd.js} +2 -2
  152. package/server/node_modules/.package-lock.json +11 -11
  153. package/server/node_modules/@effect/platform-node/node_modules/@effect/platform-node-shared/package.json +3 -3
  154. package/server/node_modules/toml/README.md +54 -0
  155. package/server/node_modules/toml/index.d.ts +42 -1
  156. package/server/node_modules/toml/index.js +3 -3
  157. package/server/node_modules/toml/lib/compiler.js +35 -3
  158. package/server/node_modules/toml/lib/parser.js +224 -143
  159. package/server/node_modules/toml/package.json +3 -1
  160. package/server/node_modules/ws/lib/receiver.js +15 -32
  161. package/server/node_modules/ws/lib/websocket-server.js +4 -4
  162. package/server/node_modules/ws/lib/websocket.js +4 -4
  163. package/server/node_modules/ws/package.json +5 -1
  164. package/server/{orphan-proposed-reconciler-BIcNu4tl.js → orphan-proposed-reconciler-eetUXmmt.js} +1 -1
  165. package/server/{orphan-proposed-reconciler-DI_IS1p_.js → orphan-proposed-reconciler-k9eydM1E.js} +14 -24
  166. package/server/package-lock.json +12 -12
  167. package/server/package.json +1 -1
  168. package/server/{pan-dir-CQIfKG8c.js → pan-dir-D3vABXMs.js} +4 -4
  169. package/server/{paths-Blj6A9AJ.js → paths-DIoslfsu.js} +2 -2
  170. package/server/{pipeline-notifier-BH4PAi-w.js → pipeline-notifier-Bgidcelx.js} +1 -1
  171. package/server/{pipeline-notifier-D_WYtEwl.js → pipeline-notifier-CuBONomU.js} +2 -2
  172. package/server/{project-pipelines-jMTitpeO.js → project-pipelines-B9b_JVTS.js} +2 -2
  173. package/server/projects-DxMUi2eI.js +3 -0
  174. package/server/{prompts-B3njaImU.js → prompts-CY1xnHS-.js} +2 -2
  175. package/server/{providers-DnPxkHoG.js → providers-B7BNcK_j.js} +1 -1
  176. package/server/{providers-BbEyFhPX.js → providers-CAlK2If5.js} +7 -7
  177. package/server/public/assets/{chunk-BO2N2NFS-3qh9TUJi.js → chunk-BO2N2NFS-DzKZyoKY.js} +4 -4
  178. package/server/public/assets/{diagram-OG6HWLK6-D8UVTPj7.js → diagram-OG6HWLK6-Cx8mWUb8.js} +1 -1
  179. package/server/public/assets/{dist-BvLLn1YC.js → dist-BqccLhaw.js} +3 -3
  180. package/server/public/assets/{dist-CyubL7Wj.js → dist-rC3nAs_O.js} +1 -1
  181. package/server/public/assets/{ganttDiagram-6RSMTGT7-BI4E1LbQ.js → ganttDiagram-6RSMTGT7-DLYPLf5T.js} +1 -1
  182. package/server/public/assets/{highlighted-body-OFNGDK62-D_bapWkF.js → highlighted-body-OFNGDK62-D4DqixbV.js} +1 -1
  183. package/server/public/assets/index-CPcysUHk.js +796 -0
  184. package/server/public/assets/index-H5M0yhM4.css +1 -0
  185. package/server/public/assets/{linear-CEdobObr.js → linear-yqcXkDfj.js} +1 -1
  186. package/server/public/assets/mermaid-GHXKKRXX-CWdZ_heB.js +1 -0
  187. package/server/public/assets/{pieDiagram-4H26LBE5-DT79L01f.js → pieDiagram-4H26LBE5-DhraIOaV.js} +1 -1
  188. package/server/public/assets/{prod-CR_K2DFV.js → prod-DKUunUdN.js} +4 -4
  189. package/server/public/assets/{quadrantDiagram-W4KKPZXB-BEkKXXrL.js → quadrantDiagram-W4KKPZXB-DPiRcToa.js} +1 -1
  190. package/server/public/assets/{sankeyDiagram-5OEKKPKP-BxSCqn-j.js → sankeyDiagram-5OEKKPKP-3An5HNkZ.js} +1 -1
  191. package/server/public/assets/{xychartDiagram-2RQKCTM6-DicwJGG_.js → xychartDiagram-2RQKCTM6-lbEOjfqQ.js} +1 -1
  192. package/server/public/index.html +2 -2
  193. package/server/{pull-requests-43LPDfM8.js → pull-requests-Cs4VLxiY.js} +1 -1
  194. package/server/{pull-requests-DyUkQl4W.js → pull-requests-CxfrQiWX.js} +3 -3
  195. package/server/{agents-B9GwfCyp.js → queries-srgusoi8.js} +710 -325
  196. package/server/{rally-BtWjybOa.js → rally-AKkjuGdi.js} +3 -3
  197. package/server/rally-BOza5j3_.js +3 -0
  198. package/server/{rally-api-B-osULNL.js → rally-api-BGxATnyr.js} +2 -2
  199. package/server/{rally-api-B-YA_oXx.js → rally-api-Dbqw1YW9.js} +1 -1
  200. package/server/{rally-client-CzFymP7P.js → rally-client-B1U5ewpO.js} +1 -1
  201. package/server/{rally-client-sErNQBO0.js → rally-client-D8h73ei_.js} +5 -5
  202. package/server/{reap-terminal-sessions-DnDQ7pf9.js → reap-terminal-sessions-BR39qflC.js} +1 -1
  203. package/server/{reap-terminal-sessions-2s-fvilz.js → reap-terminal-sessions-CFkuquQj.js} +1 -1
  204. package/server/{rebuild-stack-CoU-HqZj.js → rebuild-stack-BYdtwS6f.js} +5 -5
  205. package/server/{rebuild-stack-nGD_nPZo.js → rebuild-stack-Dsm9WjKv.js} +1 -1
  206. package/server/{reconstruct-cache-Wj8GrYB0.js → reconstruct-cache-D7oM1lNv.js} +6 -6
  207. package/server/record-BeXdn9Ue.js +3 -0
  208. package/server/record-update-Cb_pPM-F.js +168 -0
  209. package/server/record-update-KFiISAmu.js +3 -0
  210. package/server/recovery-trip-DwbsONZf.js +44 -0
  211. package/server/{release-engine-CBLQXDlH.js → release-engine-CAai5W0V.js} +5 -5
  212. package/server/{release-set-BNiqi7hy.js → release-set-vNEAfyDH.js} +3 -3
  213. package/server/{remote-DW_TWQnr.js → remote-8HlOfuF1.js} +3 -3
  214. package/server/{remote-DcO9An_f.js → remote-RQMKOL_R.js} +3 -3
  215. package/server/{remote-agents-C4cyxHQS.js → remote-agents-C4a4DdTj.js} +6 -33
  216. package/server/{remote-agents-Cv-U-_V8.js → remote-agents-_Z1SAaSK.js} +1 -1
  217. package/server/{remote-completion-PhXGGFQy.js → remote-completion-CH3igKY8.js} +13 -13
  218. package/server/resume-Cr34JAXO.js +3 -0
  219. package/server/{review-agent-Dn-yn7Zj.js → review-agent-CQ7oRh_G.js} +31 -23
  220. package/server/{review-agent-u5q3ip7H.js → review-agent-CUVj54v0.js} +1 -1
  221. package/server/{review-artifacts-D4T04aYI.js → review-artifacts-CC3yGv7o.js} +5 -19
  222. package/server/{review-context-Rc0VDLG_.js → review-context-BuxaAgAd.js} +8 -8
  223. package/server/{review-monitor-BC--miCO.js → review-monitor-uogOppaW.js} +2 -2
  224. package/server/{review-status-CG0iDuWa.js → review-status-BoJdyvUQ.js} +1 -1
  225. package/server/{review-status-Dc1l4dVz.js → review-status-By86XA6Z.js} +38 -62
  226. package/server/{review-status-enrichment-C1a1OG_1.js → review-status-enrichment-CRQAO27G.js} +3 -3
  227. package/server/{review-status-sync-CfSixhhX.js → review-status-sync-BI4WhUkF.js} +1 -1
  228. package/server/{review-status-sync-CAr-hfnJ.js → review-status-sync-DUQmrLkK.js} +2 -2
  229. package/server/{review-verdict-feedback-CVDbIDBe.js → review-verdict-feedback-BNcGkBRD.js} +8 -8
  230. package/server/{review-verdict-feedback-D2Q960wX.js → review-verdict-feedback-CWz9_9Kp.js} +1 -1
  231. package/server/{rollup-CakjztPl.js → rollup-B-K6PCbm.js} +1 -1
  232. package/server/{rollup-DctoIu-A.js → rollup-CqiFgtBp.js} +8 -8
  233. package/server/{scanner-BTM1jc_u.js → scanner-BZmDUjyD.js} +7 -7
  234. package/server/{sequence-io-pnoIDGv4.js → sequence-io-BuUx1ZAY.js} +4 -4
  235. package/server/{sequencer-agent-9Soi-WL-.js → sequencer-agent-2uWOA_BC.js} +6 -17
  236. package/server/server.js +4700 -4096
  237. package/server/{service-DfTVoqJg.js → service-COB19Uhm.js} +116 -90
  238. package/server/{session-fork-C4jAFHj8.js → session-fork-D18TscUZ.js} +2 -2
  239. package/server/{session-history-DanhhiCw.js → session-history-DbWnzfo-.js} +4 -4
  240. package/server/settings-C3w2std9.js +2 -0
  241. package/server/{settings-Cuc12oP_.js → settings-CFEdfovT.js} +2 -2
  242. package/server/{settings-DfT3f86D.js → settings-DNPAf2X_.js} +3 -3
  243. package/server/{shadow-engineering-bKuKTCn-.js → shadow-engineering-D8xTtraU.js} +3 -3
  244. package/server/{shadow-state-DJz6_pb_.js → shadow-state-CTgub0ME.js} +2 -2
  245. package/server/{shadow-state-CVHpRWlb.js → shadow-state-fRDYhcyx.js} +1 -1
  246. package/server/shadow-utils-DqMLnRgQ.js +39 -0
  247. package/server/{ship-log-CwqPFfqx.js → ship-log-BRQQi2lU.js} +1 -1
  248. package/server/{smart-compaction-B4KlpZGN.js → smart-compaction-CL6yaFpc.js} +1 -1
  249. package/server/{smart-compaction-CiWfGfRZ.js → smart-compaction-CsJDKM-N.js} +7 -7
  250. package/server/{spawn-planning-session-D5N3RuLG.js → spawn-planning-session-C_wIQ-Zg.js} +1 -1
  251. package/server/{spawn-planning-session-CU-DwyDl.js → spawn-planning-session-D0-NlNK1.js} +16 -16
  252. package/server/{specialist-completion-CJcv-NXH.js → specialist-completion-pJtESo7m.js} +1 -1
  253. package/server/{specialist-context-B2HlbNxS.js → specialist-context-DXj5Itg8.js} +5 -5
  254. package/server/{specialist-handoff-logger-CQyF1ygu.js → specialist-handoff-logger-BFH-IB0n.js} +2 -2
  255. package/server/{specialist-logs-CdcUpfjj.js → specialist-logs-B-rldhNg.js} +3 -3
  256. package/server/{specialists-CFgw_0Q6.js → specialists-C6-b3YZb.js} +161 -448
  257. package/server/{specialists-D7AcFi1u.js → specialists-CI_eqHAz.js} +1 -1
  258. package/server/{specialists-CwzFdapR.js → specialists-Dp8ABxVh.js} +18 -18
  259. package/server/{specialists-k22_Btaq.js → specialists-Hq3EojmD.js} +1 -1
  260. package/server/specs-DPGNVh51.js +3 -0
  261. package/server/{src-B4TTzNSr.js → src-By3D85Ry.js} +6 -1
  262. package/server/{src-BvpMg4EB.js → src-CqyFg52C.js} +5 -5
  263. package/server/{src-D4YUZrwR.js → src-DhxM-eCb.js} +6 -6
  264. package/server/{stack-health-DVN7zRnL.js → stack-health-Bxt-oGE6.js} +1 -1
  265. package/server/{stack-health-CSgcYcr8.js → stack-health-FtNYSTCS.js} +2 -2
  266. package/server/stale-task-claims-DnCHCATD.js +69 -0
  267. package/server/state-home-BzzKVh_C.js +213 -0
  268. package/server/{state-mapping-C13UaCL7.js → state-mapping-Ci0pkac4.js} +1 -1
  269. package/server/state-migrate-BS4IybIS.js +362 -0
  270. package/server/{stuck-remediation-nTIvhGOR.js → stuck-remediation-CTNT4LUC.js} +1 -1
  271. package/server/{stuck-remediation-CxsyxwFO.js → stuck-remediation-DbwhGg3g.js} +36 -41
  272. package/server/{summary-fork-CnRMPCYd.js → summary-fork-CloCB8dV.js} +2 -2
  273. package/server/{summary-fork-gKSoFAnK.js → summary-fork-DaCs0tCq.js} +8 -8
  274. package/server/supervisor/{paths-3R6b989k.js → paths-C1yU0jq4.js} +5 -3
  275. package/server/supervisor/pty-supervisor.js +2 -2
  276. package/server/supervisor/{pty-token-CScQ32Rj.js → pty-token-CSryb8Mb.js} +2 -2
  277. package/server/{supervisor-CZMs8s0M.js → supervisor-CRED6PD6.js} +2 -2
  278. package/server/supervisor-DHwuClN_.js +2 -0
  279. package/server/{swarm-readiness-h6-pwY8w.js → swarm-readiness-CEOrP9rE.js} +3 -3
  280. package/server/{system-prerequisites-DZZTi2G6.js → system-prerequisites-Bz6KdkFJ.js} +1 -13
  281. package/server/{teardown-workspace-B4D-oCVg.js → teardown-workspace-Ckn7HGhp.js} +14 -71
  282. package/server/{test-agent-queue-CnuYku4B.js → test-agent-queue-C8lE8r2j.js} +5 -5
  283. package/server/{tldr-daemon-CgZQ3jmZ.js → tldr-daemon-BE5rqR90.js} +1 -1
  284. package/server/{tldr-daemon-CuA8hz21.js → tldr-daemon-CGwDr28O.js} +2 -2
  285. package/server/{tmux-C3g9VFcg.js → tmux-C3X2XmV0.js} +92 -4
  286. package/server/tmux-DNaD06Gc.js +3 -0
  287. package/server/{token-g_sJ19g3.js → token-Dsa-i36o.js} +2 -2
  288. package/server/{token-util-DiwnnbJo.js → token-util-BWNpH96m.js} +1 -1
  289. package/server/token-util-DRt0j2nQ.js +3 -0
  290. package/server/{tracker-config-dQxC15vH.js → tracker-config-5Cabf1MU.js} +2 -2
  291. package/server/{tracker-utils--z8FLSWn.js → tracker-utils-Cl-Eaqrv.js} +2 -2
  292. package/server/{tracker-utils-DkKyRT0H.js → tracker-utils-FexeJhyf.js} +1 -1
  293. package/server/{tunnel-CN2px87h.js → tunnel-D4px7sKc.js} +1 -1
  294. package/server/{tunnel-BpWZs0Jy.js → tunnel-KId6Z59z.js} +3 -3
  295. package/server/typed-errors-CH4roFIj.js +3 -0
  296. package/server/{typed-errors-sLpGfegz.js → typed-errors-n_Tnb4Fh.js} +3 -4
  297. package/server/{types-BLS_Zbmd.js → types-DOjWNv3Z.js} +1 -1
  298. package/server/{types-tlXf80x3.js → types-DXBMm4mX.js} +1 -1
  299. package/server/{uat-train-BRo6DM7p.js → uat-train-B16skN7L.js} +24 -25
  300. package/server/{uat-train-utGVmqdm.js → uat-train-BBMrnecu.js} +1 -1
  301. package/server/validation-Zc4kAmqa.js +3 -0
  302. package/server/{validation-CsPVsoA0.js → validation-xAD7y5vW.js} +70 -23
  303. package/server/verification-artifact-DK1xb1zG.js +56 -0
  304. package/server/verification-artifact-JD_edpZ9.js +2 -0
  305. package/server/{verification-runner-CMT5WRMS.js → verification-runner-32y8NRfL.js} +338 -58
  306. package/server/verification-runner-DAat1p1u.js +2 -0
  307. package/server/{vtt-parser-C_XPlifj.js → vtt-parser-QnRfcjiU.js} +1 -1
  308. package/server/{work-agent-prompt-ZTAb2Hyf.js → work-agent-prompt-S3Fe1kpV.js} +12 -88
  309. package/server/{workflows-CXRwhw_o.js → workflows-BNvk5gbE.js} +17 -24
  310. package/server/workflows-Cw2-a_hn.js +2 -0
  311. package/server/{workspace-CNfuj1-5.js → workspace-BhUnCpcq.js} +3 -3
  312. package/server/{workspace-9UEsKuiP.js → workspace-C-gP6JAz.js} +1 -1
  313. package/server/{workspace-config-ChWOECy0.js → workspace-config-C77NKnsV.js} +1 -1
  314. package/server/{workspace-manager-D8AByFIT.js → workspace-manager-BKiMmb0c.js} +23 -48
  315. package/server/{workspace-manager-BO-6k3sP.js → workspace-manager-DD6PDqV8.js} +1 -1
  316. package/server/{workspace-metadata-CvC268MN.js → workspace-metadata-Cnq0snrf.js} +2 -2
  317. package/server/{workspace-metadata-DZiJtnuF.js → workspace-metadata-D5gNR61G.js} +1 -1
  318. package/server/{workspaces-D-uIoqbg.js → workspaces-DiAwBsb9.js} +182 -152
  319. package/sync-sources/hooks/codex-notify-hook +64 -8
  320. package/sync-sources/hooks/record-cost-event.js +1110 -37
  321. package/sync-sources/hooks/record-cost-event.js.map +1 -1
  322. package/sync-sources/hooks/statusline.sh +2 -2
  323. package/sync-sources/hooks/work-agent-stop-hook +1 -2
  324. package/server/acceptance-criteria-BZ_nQS1f.js +0 -33
  325. package/server/agent-state-lGvgPP4m.js +0 -3
  326. package/server/agents-CEcaxSkR.js +0 -3
  327. package/server/auto-commit-IW-eik_b.js +0 -3
  328. package/server/bd-process-lock-XAXAet9O.js +0 -390
  329. package/server/beads-B1u04n3o.js +0 -2
  330. package/server/beads-BeSMEjGT.js +0 -649
  331. package/server/beads-query-Dl4FhT8J.js +0 -95
  332. package/server/beads-restore-BUItPOjI.js +0 -23
  333. package/server/cli-D9cUTbBr.js +0 -3
  334. package/server/codex-BYPz6ZdF.js +0 -3
  335. package/server/compact-beads-CyJtoXJi.js +0 -94
  336. package/server/compact-beads-N00E4EVa.js +0 -3
  337. package/server/deacon-swarm-CvdFekIO.js +0 -2
  338. package/server/dist-BNwrgsUO.js +0 -3
  339. package/server/export-BJHSVwKP.js +0 -87
  340. package/server/forge-8y6aEPTB.js +0 -2
  341. package/server/hume-CerGi2RH.js +0 -3
  342. package/server/inspect-agent-CWkeAB7J.js +0 -2
  343. package/server/lifecycle-Ck2UR27D.js +0 -14
  344. package/server/merge-ops-CFkQtXEj.js +0 -2
  345. package/server/projects-CrBE6IhN.js +0 -3
  346. package/server/public/assets/index-BAZxZ-8I.js +0 -796
  347. package/server/public/assets/index-FBG3JBfq.css +0 -1
  348. package/server/public/assets/mermaid-GHXKKRXX-BYP8MI37.js +0 -1
  349. package/server/rally-oggyp4cs.js +0 -3
  350. package/server/record-lEYK__LD.js +0 -3
  351. package/server/recovery-trip-BT7nb9OV.js +0 -98
  352. package/server/resolver-C7dLn8OK.js +0 -158
  353. package/server/resume-BYZBIab-.js +0 -3
  354. package/server/settings-Cy7SCFO9.js +0 -2
  355. package/server/specs-RzcDgozr.js +0 -3
  356. package/server/supervisor-CfX192ma.js +0 -2
  357. package/server/tmux-By8E_Yv_.js +0 -3
  358. package/server/token-util-C9u4An3K.js +0 -3
  359. package/server/tracker-config-BzfVj41Z.js +0 -3
  360. package/server/typed-errors-CrN1AByg.js +0 -3
  361. package/server/validation-Dq_wwcw3.js +0 -3
  362. package/server/verification-runner-D0Uh-9jc.js +0 -2
  363. package/server/workflows-BLwxUMP7.js +0 -2
  364. package/server/writer-BUweuRPx.js +0 -3
  365. package/server/writer-CrP2xuBn.js +0 -251
@@ -1 +1 @@
1
- {"version":3,"file":"main.js","names":["Path","app","FS","nativeImage","Menu","app","Tray","Crypto","FS","ChildProcess","BrowserWindow","autoUpdater","Menu","app","shell","Notification","dialog","Path","FS","Path","app","FS","BrowserWindow","shell","protocol"],"sources":["../src/settings.ts","../src/tray.ts","../src/server.ts","../src/updater.ts","../src/menu.ts","../src/notifications.ts","../src/autostart.ts","../src/protocol.ts","../src/main.ts"],"sourcesContent":["/**\n * Desktop-specific settings, persisted to userData/desktop-settings.json.\n *\n * Covers: tray appearance, per-event notification toggles, auto-start config.\n * Loaded at app startup; updated via IPC from renderer.\n */\n\nimport * as FS from \"node:fs\";\nimport * as Path from \"node:path\";\n\nimport { app } from \"electron\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface DesktopSettings {\n tray: {\n showBadge: boolean;\n tooltipDetail: \"minimal\" | \"full\";\n };\n notifications: {\n inputNeeded: boolean;\n stuckAgents: boolean;\n mergeFailures: boolean;\n workComplete: boolean;\n planningDone: boolean;\n mergeReady: boolean;\n };\n autoStart: {\n enabled: boolean;\n nagCount: number;\n nagDismissed: boolean;\n };\n}\n\nexport type NotificationEventType = keyof DesktopSettings[\"notifications\"];\n\n// ─── Defaults ─────────────────────────────────────────────────────────────────\n\nconst DEFAULTS: DesktopSettings = {\n tray: {\n showBadge: true,\n tooltipDetail: \"full\",\n },\n notifications: {\n inputNeeded: true,\n stuckAgents: true,\n mergeFailures: true,\n workComplete: true,\n planningDone: false,\n mergeReady: true,\n },\n autoStart: {\n enabled: false,\n nagCount: 0,\n nagDismissed: false,\n },\n};\n\n// ─── State ────────────────────────────────────────────────────────────────────\n\nlet settings: DesktopSettings = deepClone(DEFAULTS);\n\nfunction deepClone<T>(obj: T): T {\n return JSON.parse(JSON.stringify(obj)) as T;\n}\n\nfunction settingsPath(): string {\n return Path.join(app.getPath(\"userData\"), \"desktop-settings.json\");\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\nexport function loadDesktopSettings(): void {\n try {\n const raw = FS.readFileSync(settingsPath(), \"utf8\");\n const parsed = JSON.parse(raw) as Partial<DesktopSettings>;\n settings = {\n tray: { ...DEFAULTS.tray, ...parsed.tray },\n notifications: { ...DEFAULTS.notifications, ...parsed.notifications },\n autoStart: { ...DEFAULTS.autoStart, ...parsed.autoStart },\n };\n } catch {\n settings = deepClone(DEFAULTS);\n }\n}\n\nexport function saveDesktopSettings(): void {\n try {\n FS.writeFileSync(settingsPath(), JSON.stringify(settings, null, 2));\n } catch {\n console.error(\"[desktop] failed to save desktop settings\");\n }\n}\n\nexport function getDesktopSettings(): DesktopSettings {\n return settings;\n}\n\n/**\n * Update a single setting by dotted key (e.g. \"notifications.inputNeeded\").\n * Returns true if the key was found and updated.\n */\nexport function updateDesktopSetting(key: string, value: unknown): boolean {\n const [section, field] = key.split(\".\");\n if (!section || !field) return false;\n const s = settings as unknown as Record<string, Record<string, unknown>>;\n if (!s[section] || !(field in s[section])) return false;\n s[section][field] = value;\n saveDesktopSettings();\n return true;\n}\n","/**\n * System tray for the Overdeck desktop app.\n *\n * Creates a Tray with:\n * - Color-coded icon: green (idle), yellow (working), red (attention needed)\n * - Agent count badge overlay (macOS dock badge)\n * - Rich tooltip: agent count, attention items, last activity\n * - Context menu: Show Dashboard, Start/Stop Cloister, Emergency Stop, Settings, Quit\n *\n * Polls /api/health every 5s for live status.\n */\n\nimport * as Path from \"node:path\";\n\nimport { app, Menu, nativeImage, Tray } from \"electron\";\nimport type { MenuItemConstructorOptions } from \"electron\";\n\nimport { callServerApi, serverUrl, showOrCreateWindow, dispatchMenuAction, isQuitting } from \"./main.js\";\nimport { resolveResourcePath } from \"./main.js\";\nimport { getDesktopSettings } from \"./settings.js\";\n\nexport type AgentStatus = \"idle\" | \"working\" | \"attention\";\n\nlet tray: Tray | null = null;\nlet pollTimer: ReturnType<typeof setInterval> | null = null;\n\n// ─── Icon ─────────────────────────────────────────────────────────────────────\n\nconst STATUS_COLORS: Record<AgentStatus, string> = {\n idle: \"#22c55e\",\n working: \"#f59e0b\",\n attention: \"#ef4444\",\n};\n\nfunction createTrayIcon(status: AgentStatus): Electron.NativeImage {\n const color = STATUS_COLORS[status];\n // 16×16 circle SVG rendered to NativeImage buffer\n const svg = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\">\n <circle cx=\"8\" cy=\"8\" r=\"7\" fill=\"${color}\" stroke=\"#00000033\" stroke-width=\"0.5\"/>\n <ellipse cx=\"8\" cy=\"8\" rx=\"5.5\" ry=\"3.8\" stroke=\"#ffffff44\" stroke-width=\"0.5\" fill=\"none\"/>\n <circle cx=\"8\" cy=\"8\" r=\"2\" fill=\"#ffffff99\"/>\n <circle cx=\"8\" cy=\"8\" r=\"1\" fill=\"#00000066\"/>\n </svg>`;\n\n return nativeImage.createFromBuffer(Buffer.from(svg));\n}\n\n// ─── Tooltip ──────────────────────────────────────────────────────────────────\n\nfunction buildTooltip(agentCount: number, attentionCount: number, lastActivity: string | null): string {\n const settings = getDesktopSettings();\n if (settings.tray.tooltipDetail === \"minimal\") {\n return `Overdeck — ${agentCount} agent${agentCount !== 1 ? \"s\" : \"\"}`;\n }\n\n const lines = [\"Overdeck\"];\n lines.push(`${agentCount} agent${agentCount !== 1 ? \"s\" : \"\"} running`);\n if (attentionCount > 0) {\n lines.push(`⚠ ${attentionCount} need${attentionCount !== 1 ? \"\" : \"s\"} attention`);\n }\n if (lastActivity) {\n lines.push(`Last: ${lastActivity}`);\n }\n return lines.join(\"\\n\");\n}\n\n// ─── Context menu ─────────────────────────────────────────────────────────────\n\nfunction buildContextMenu(): Electron.Menu {\n const template: MenuItemConstructorOptions[] = [\n {\n label: \"Show Dashboard\",\n click: () => showOrCreateWindow(),\n },\n { type: \"separator\" },\n {\n label: \"Start Cloister\",\n click: () => callServerApi(\"/api/cloister/start\", \"POST\"),\n },\n {\n label: \"Stop Cloister\",\n click: () => callServerApi(\"/api/cloister/stop\", \"POST\"),\n },\n {\n label: \"Emergency Stop All\",\n click: () => callServerApi(\"/api/agents/emergency-stop\", \"POST\"),\n },\n { type: \"separator\" },\n {\n label: \"Settings\",\n click: () => {\n showOrCreateWindow();\n dispatchMenuAction(\"open-settings\");\n },\n },\n { type: \"separator\" },\n {\n label: \"Quit\",\n click: () => {\n // isQuitting is set by app.on(\"before-quit\")\n app.quit();\n },\n },\n ];\n return Menu.buildFromTemplate(template);\n}\n\n// ─── Polling ──────────────────────────────────────────────────────────────────\n\ninterface HealthResponse {\n agentCount?: number;\n attentionCount?: number;\n lastActivity?: string;\n status?: string;\n}\n\nasync function refreshTrayStatus(): Promise<void> {\n if (!tray || isQuitting) return;\n const url = serverUrl;\n if (!url) return;\n\n try {\n const resp = await fetch(`${url}/api/health`, { signal: AbortSignal.timeout(3_000) });\n if (!resp.ok) return;\n\n const data = (await resp.json()) as HealthResponse;\n const agentCount = data.agentCount ?? 0;\n const attentionCount = data.attentionCount ?? 0;\n const lastActivity = data.lastActivity ?? null;\n\n const status: AgentStatus =\n attentionCount > 0 ? \"attention\" : agentCount > 0 ? \"working\" : \"idle\";\n\n tray.setImage(createTrayIcon(status));\n tray.setToolTip(buildTooltip(agentCount, attentionCount, lastActivity));\n tray.setContextMenu(buildContextMenu());\n\n // macOS dock badge\n const settings = getDesktopSettings();\n if (settings.tray.showBadge && process.platform === \"darwin\" && app.dock) {\n app.dock.setBadge(agentCount > 0 ? String(agentCount) : \"\");\n }\n } catch {\n // Server not ready yet — silently skip\n }\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\nexport function createTray(): void {\n if (tray) return;\n\n const icon = createTrayIcon(\"idle\");\n tray = new Tray(icon);\n tray.setToolTip(\"Overdeck\");\n tray.setContextMenu(buildContextMenu());\n\n // Single-click opens dashboard on all platforms\n tray.on(\"click\", () => showOrCreateWindow());\n\n // Start polling\n pollTimer = setInterval(() => void refreshTrayStatus(), 5_000);\n void refreshTrayStatus();\n}\n\nexport function destroyTray(): void {\n if (pollTimer) {\n clearInterval(pollTimer);\n pollTimer = null;\n }\n tray?.destroy();\n tray = null;\n}\n","/**\n * Embedded dashboard server management.\n *\n * Spawns dist/dashboard/server.js (or the packaged equivalent) as a child\n * process, passing config via environment variables. Supports exponential-\n * backoff restart on crash. Graceful shutdown on app quit.\n *\n * Bootstrap config passed via env vars:\n * OVERDECK_PORT — TCP port for HTTP + WS\n * OVERDECK_AUTH_TOKEN — random hex token (future: auth middleware)\n * OVERDECK_MODE — \"desktop\" (enables desktop-specific behaviours)\n * OVERDECK_NO_BROWSER — \"1\" (suppresses auto browser open)\n */\n\nimport * as ChildProcess from \"node:child_process\";\nimport * as Crypto from \"node:crypto\";\nimport * as FS from \"node:fs\";\n\nimport { resolveServerEntry, resolveServerStaticDir } from \"./main.js\";\n\n// ─── Constants ────────────────────────────────────────────────────────────────\n\nconst BASE_PORT = 7825;\nconst MAX_RESTART_DELAY_MS = 30_000;\nconst MAX_RESTART_ATTEMPTS = 8;\nconst SIGTERM_GRACE_MS = 3_000;\n\n// ─── State ────────────────────────────────────────────────────────────────────\n\nlet serverProcess: ChildProcess.ChildProcess | null = null;\nlet restartAttempt = 0;\nlet restartTimer: ReturnType<typeof setTimeout> | null = null;\nlet waitInterval: ReturnType<typeof setInterval> | null = null;\nlet quitting = false;\n\n// Callbacks so main.ts can react to server-ready / URL changes\nlet onReadyCallback: ((port: number, wsUrl: string) => void) | null = null;\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction randomHex(bytes: number): string {\n return Crypto.randomBytes(bytes).toString(\"hex\");\n}\n\nfunction resolvePort(): number {\n // Each restart attempt increments the port to avoid EADDRINUSE during quick restarts\n return BASE_PORT + (restartAttempt % 10);\n}\n\n// ─── Start / Stop ─────────────────────────────────────────────────────────────\n\nexport function startServer(onReady: (port: number, wsUrl: string) => void): void {\n // Reset quit flag so manual restarts (after stopServer) can re-spawn.\n // The app-quit path sets isQuitting in main.ts before stopServer, so this\n // only un-latches the flag when the caller intends a real restart.\n quitting = false;\n // A deliberate (re)start gets a fresh crash budget.\n restartAttempt = 0;\n onReadyCallback = onReady;\n spawnServer();\n}\n\nfunction spawnServer(): void {\n if (quitting) return;\n\n const entry = resolveServerEntry();\n if (!FS.existsSync(entry)) {\n console.error(`[desktop/server] Server entry not found: ${entry}`);\n console.error(\"[desktop/server] Run 'npm run build' to build the dashboard server first.\");\n return;\n }\n\n const port = resolvePort();\n const authToken = randomHex(32);\n\n const child = ChildProcess.spawn(\n process.execPath, // Node.js binary (same version Electron bundles)\n [entry],\n {\n env: {\n ...process.env,\n // The server's config reads API_PORT || PORT (src/dashboard/server/config.ts)\n API_PORT: String(port),\n OVERDECK_PORT: String(port),\n // Packaged layout puts the frontend at resources/server/public, which\n // the server cannot derive from its own bundle path (it assumes a\n // repo-root dist/dashboard/public layout).\n OVERDECK_FRONTEND_DIR: resolveServerStaticDir() ?? undefined,\n OVERDECK_AUTH_TOKEN: authToken,\n OVERDECK_MODE: \"desktop\",\n OVERDECK_NO_BROWSER: \"1\",\n // Terminal settings for Claude Code / tmux rendering\n TERM: process.env.TERM || \"xterm-256color\",\n COLORTERM: process.env.COLORTERM || \"truecolor\",\n LANG: process.env.LANG || \"en_US.UTF-8\",\n // process.execPath is the Electron binary; without this flag a\n // packaged app ignores [entry] and boots the full desktop app again,\n // fork-bombing the machine (PAN-2559).\n ELECTRON_RUN_AS_NODE: \"1\",\n } as NodeJS.ProcessEnv,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n },\n );\n\n serverProcess = child;\n\n child.stdout?.on(\"data\", (chunk: Buffer) => {\n process.stdout.write(`[server] ${chunk}`);\n });\n child.stderr?.on(\"data\", (chunk: Buffer) => {\n process.stderr.write(`[server] ${chunk}`);\n });\n\n child.on(\"spawn\", () => {\n console.log(`[desktop/server] spawned pid=${child.pid} port=${port}`);\n // Wait for server to be listening before signalling ready\n waitForServer(`http://127.0.0.1:${port}`, (healthy) => {\n console.log(`[desktop/server] ready on port ${port} (healthy=${String(healthy)})`);\n if (healthy) {\n // A confirmed-healthy boot resets the crash budget so the cap only\n // counts consecutive failures.\n restartAttempt = 0;\n }\n onReadyCallback?.(port, `ws://127.0.0.1:${port}`);\n });\n });\n\n child.on(\"exit\", (code, signal) => {\n serverProcess = null;\n console.warn(`[desktop/server] exited code=${String(code)} signal=${String(signal)}`);\n if (!quitting) {\n scheduleRestart();\n }\n });\n\n child.on(\"error\", (err) => {\n console.error(\"[desktop/server] spawn error:\", err);\n serverProcess = null;\n if (!quitting) scheduleRestart();\n });\n}\n\nfunction waitForServer(url: string, callback: (healthy: boolean) => void, maxMs = 30_000): void {\n // Only one poller may be live: a crash-restart spawns a new child on a new\n // port, and a stale poller's late callback would clobber serverUrl with the\n // dead port (and, pre-PAN-2570, open another window).\n if (waitInterval) clearInterval(waitInterval);\n const start = Date.now();\n const interval = setInterval(() => {\n if (Date.now() - start > maxMs) {\n clearInterval(interval);\n if (waitInterval === interval) waitInterval = null;\n callback(false); // call anyway — server might still come up\n return;\n }\n fetch(url + \"/api/health\", { signal: AbortSignal.timeout(1_000) })\n .then((r) => {\n if (r.ok) {\n clearInterval(interval);\n if (waitInterval === interval) waitInterval = null;\n callback(true);\n }\n })\n .catch(() => {\n /* not ready yet */\n });\n }, 500);\n waitInterval = interval;\n}\n\nfunction scheduleRestart(): void {\n if (quitting) return;\n restartAttempt++;\n if (restartAttempt > MAX_RESTART_ATTEMPTS) {\n console.error(\n `[desktop/server] server failed ${MAX_RESTART_ATTEMPTS} consecutive times — giving up. ` +\n \"Use the app menu's dashboard restart to retry.\",\n );\n return;\n }\n const delay = Math.min(1_000 * Math.pow(2, restartAttempt - 1), MAX_RESTART_DELAY_MS);\n console.log(`[desktop/server] restarting in ${delay}ms (attempt ${restartAttempt})`);\n restartTimer = setTimeout(() => {\n restartTimer = null;\n spawnServer();\n }, delay);\n}\n\n// ─── Graceful Shutdown ────────────────────────────────────────────────────────\n\nexport function stopServer(): void {\n quitting = true;\n\n if (restartTimer) {\n clearTimeout(restartTimer);\n restartTimer = null;\n }\n\n if (waitInterval) {\n clearInterval(waitInterval);\n waitInterval = null;\n }\n\n const child = serverProcess;\n if (!child) return;\n\n serverProcess = null;\n child.kill(\"SIGTERM\");\n\n // Force-kill if it doesn't exit within grace period\n const forceKill = setTimeout(() => {\n try {\n child.kill(\"SIGKILL\");\n } catch {\n /* already dead */\n }\n }, SIGTERM_GRACE_MS);\n forceKill.unref();\n}\n","/**\n * Auto-updater service using electron-updater.\n *\n * Handles automatic background checks for updates, downloads, and installation.\n * Uses GitHub Releases as the update server.\n */\n\nimport { autoUpdater, UpdateInfo } from \"electron-updater\";\nimport { BrowserWindow } from \"electron\";\n\n// Auto-updater configuration\nconst FOUR_HOURS_MS = 4 * 60 * 60 * 1000;\n\nlet checkIntervalId: ReturnType<typeof setInterval> | null = null;\nlet initialized = false;\n\n// Update state for IPC bridge\nexport interface UpdateStatus {\n checking: boolean;\n available: boolean;\n downloaded: boolean;\n version: string | null;\n error: string | null;\n}\n\nexport let currentStatus: UpdateStatus = {\n checking: false,\n available: false,\n downloaded: false,\n version: null,\n error: null,\n};\n\n// Callbacks for update events\ntype UpdateStatusCallback = (status: UpdateStatus) => void;\nlet statusCallbacks: UpdateStatusCallback[] = [];\n\n/**\n * Register a callback to receive update status changes.\n */\nexport function onUpdateStatusChange(callback: UpdateStatusCallback): void {\n statusCallbacks.push(callback);\n}\n\n/**\n * Notify all listeners of status change\n */\nfunction notifyStatusChange(): void {\n for (const cb of statusCallbacks) {\n cb(currentStatus);\n }\n}\n\n/**\n * Send update event to all browser windows\n */\nfunction broadcastToRenderers(channel: string, ...args: unknown[]): void {\n for (const win of BrowserWindow.getAllWindows()) {\n if (!win.isDestroyed()) {\n win.webContents.send(channel, ...args);\n }\n }\n}\n\n/**\n * Initialize the auto-updater service.\n * Sets up event handlers and starts periodic update checks.\n */\nexport function initializeAutoUpdater(channel: string = \"latest\"): void {\n if (initialized) {\n console.log(\"[updater] Already initialized, skipping...\");\n return;\n }\n initialized = true;\n\n // Configure to use GitHub Releases\n autoUpdater.setFeedURL({\n provider: \"github\",\n owner: \"eltmon\",\n repo: \"overdeck\",\n });\n\n // Respect release channel so canary users don't get stable updates and vice versa\n autoUpdater.channel = channel;\n\n // Don't auto-download - we want to notify user first\n autoUpdater.autoDownload = false;\n autoUpdater.autoInstallOnAppQuit = true;\n\n // Set up event handlers\n autoUpdater.on(\"checking-for-update\", () => {\n currentStatus = { ...currentStatus, checking: true, error: null };\n notifyStatusChange();\n broadcastToRenderers(\"update-status\", currentStatus);\n console.log(\"[updater] Checking for update...\");\n });\n\n autoUpdater.on(\"update-available\", (info: UpdateInfo) => {\n currentStatus = {\n checking: false,\n available: true,\n downloaded: false,\n version: info.version,\n error: null,\n };\n notifyStatusChange();\n broadcastToRenderers(\"update-status\", currentStatus);\n console.log(`[updater] Update available: ${info.version}`);\n });\n\n autoUpdater.on(\"update-not-available\", (info: UpdateInfo) => {\n currentStatus = {\n checking: false,\n available: false,\n downloaded: false,\n version: info.version,\n error: null,\n };\n notifyStatusChange();\n broadcastToRenderers(\"update-status\", currentStatus);\n console.log(`[updater] Update not available. Current version: ${info.version}`);\n });\n\n autoUpdater.on(\"download-progress\", (progressObj) => {\n const percent = progressObj.percent.toFixed(1);\n currentStatus = { ...currentStatus, checking: false };\n notifyStatusChange();\n broadcastToRenderers(\"update-download-progress\", {\n percent,\n transferred: progressObj.transferred,\n total: progressObj.total,\n });\n console.log(`[updater] Download progress: ${percent}%`);\n });\n\n autoUpdater.on(\"update-downloaded\", (info: UpdateInfo) => {\n currentStatus = {\n checking: false,\n available: true,\n downloaded: true,\n version: info.version,\n error: null,\n };\n notifyStatusChange();\n broadcastToRenderers(\"update-status\", currentStatus);\n broadcastToRenderers(\"update-downloaded\", { version: info.version });\n console.log(`[updater] Update downloaded: ${info.version}`);\n });\n\n autoUpdater.on(\"error\", (err: Error) => {\n currentStatus = {\n checking: false,\n available: false,\n downloaded: false,\n version: null,\n error: err.message,\n };\n notifyStatusChange();\n broadcastToRenderers(\"update-status\", currentStatus);\n console.error(\"[updater] Error:\", err.message);\n });\n\n // Check for updates on app startup (after a short delay to let the app initialize)\n setTimeout(() => {\n checkForUpdates();\n }, 3000);\n\n // Start periodic update checks\n startPeriodicChecks();\n}\n\n/**\n * Start periodic update checks every 4 hours.\n */\nexport function startPeriodicChecks(): void {\n if (checkIntervalId !== null) return; // Already running\n\n checkIntervalId = setInterval(() => {\n checkForUpdates();\n }, FOUR_HOURS_MS);\n\n console.log(\"[updater] Started periodic update checks (every 4 hours)\");\n}\n\n/**\n * Stop periodic update checks.\n */\nexport function stopPeriodicChecks(): void {\n if (checkIntervalId !== null) {\n clearInterval(checkIntervalId);\n checkIntervalId = null;\n console.log(\"[updater] Stopped periodic update checks\");\n }\n}\n\n/**\n * Check for updates manually.\n * Returns a promise that resolves when the check completes.\n */\nexport async function checkForUpdates(): Promise<void> {\n if (currentStatus.checking) {\n console.log(\"[updater] Already checking for update, skipping...\");\n return;\n }\n\n try {\n console.log(\"[updater] Starting update check...\");\n await autoUpdater.checkForUpdates();\n } catch (err) {\n console.error(\"[updater] Check for updates failed:\", err);\n }\n}\n\n/**\n * Download the available update.\n */\nexport async function downloadUpdate(): Promise<void> {\n if (!currentStatus.available) {\n console.log(\"[updater] No update available to download\");\n return;\n }\n\n try {\n console.log(\"[updater] Starting update download...\");\n await autoUpdater.downloadUpdate();\n } catch (err) {\n console.error(\"[updater] Download update failed:\", err);\n }\n}\n\n/**\n * Quit and install the downloaded update.\n */\nexport function quitAndInstall(): void {\n if (!currentStatus.downloaded) {\n console.log(\"[updater] No update downloaded to install\");\n return;\n }\n\n console.log(\"[updater] Quitting and installing update...\");\n autoUpdater.quitAndInstall();\n}\n\n/**\n * Get current update status.\n */\nexport function getUpdateStatus(): UpdateStatus {\n return currentStatus;\n}\n","/**\n * Application menu bar for the Overdeck desktop app.\n *\n * Standard Electron menus: File, Edit, View, Window, Help\n * Plus a Overdeck menu with all orchestration actions.\n *\n * macOS: app name menu with About, Settings, Services etc.\n * Linux/Windows: Settings in File menu.\n */\n\nimport { app, Menu, shell } from \"electron\";\nimport type { MenuItemConstructorOptions } from \"electron\";\n\nimport { callServerApi, showOrCreateWindow, dispatchMenuAction, serverUrl } from \"./main.js\";\nimport { checkForUpdates, quitAndInstall, onUpdateStatusChange } from \"./updater.js\";\n\n// ─── Workspace submenu (refreshed on open) ────────────────────────────────────\n\ninterface WorkspaceSummary {\n issueId: string;\n title?: string;\n}\n\nasync function fetchActiveWorkspaces(): Promise<WorkspaceSummary[]> {\n if (!serverUrl) return [];\n try {\n const resp = await fetch(`${serverUrl}/api/workspaces`, { signal: AbortSignal.timeout(2_000) });\n if (!resp.ok) return [];\n const data = (await resp.json()) as { workspaces?: WorkspaceSummary[] };\n return data.workspaces?.slice(0, 10) ?? [];\n } catch {\n return [];\n }\n}\n\n// ─── Update menu state ─────────────────────────────────────────────────────────\n\nlet updateDownloaded = false;\n\nfunction rebuildMenu(): void {\n const menu = Menu.buildFromTemplate(buildMenuTemplate());\n Menu.setApplicationMenu(menu);\n\n // Re-attach workspace submenu refresh listener\n const overdeckMenu = menu.items.find((item) => item.label === \"Overdeck\");\n if (overdeckMenu?.submenu) {\n overdeckMenu.submenu.on(\"menu-will-show\", () => {\n void fetchActiveWorkspaces().then((workspaces) => {\n const wsItem = overdeckMenu.submenu?.items.find(\n (i) => i.id === \"open-workspace-submenu\",\n );\n if (wsItem) {\n wsItem.label = workspaces.length\n ? `Open Workspace (${workspaces.length})`\n : \"Open Workspace\";\n }\n });\n });\n }\n}\n\nfunction buildWorkspaceSubmenu(workspaces: WorkspaceSummary[]): MenuItemConstructorOptions[] {\n if (workspaces.length === 0) {\n return [{ label: \"No active workspaces\", enabled: false }];\n }\n return workspaces.map((ws) => ({\n label: ws.title ? `${ws.issueId} — ${ws.title}` : ws.issueId,\n click: () => {\n showOrCreateWindow();\n dispatchMenuAction(`open-workspace:${ws.issueId}`);\n },\n }));\n}\n\n// ─── Menu template ────────────────────────────────────────────────────────────\n\nfunction buildMenuTemplate(): MenuItemConstructorOptions[] {\n const template: MenuItemConstructorOptions[] = [];\n\n // macOS: app menu (first menu = app name on macOS)\n if (process.platform === \"darwin\") {\n template.push({\n label: app.name,\n submenu: [\n { role: \"about\" },\n { type: \"separator\" },\n {\n label: \"Settings...\",\n accelerator: \"CmdOrCtrl+,\",\n click: () => {\n showOrCreateWindow();\n dispatchMenuAction(\"open-settings\");\n },\n },\n { type: \"separator\" },\n { role: \"services\" },\n { type: \"separator\" },\n { role: \"hide\" },\n { role: \"hideOthers\" },\n { role: \"unhide\" },\n { type: \"separator\" },\n { role: \"quit\" },\n ],\n });\n }\n\n // File menu\n template.push({\n label: \"File\",\n submenu: [\n ...(process.platform !== \"darwin\"\n ? [\n {\n label: \"Settings...\",\n accelerator: \"CmdOrCtrl+,\",\n click: () => {\n showOrCreateWindow();\n dispatchMenuAction(\"open-settings\");\n },\n },\n { type: \"separator\" as const },\n ]\n : []),\n { role: process.platform === \"darwin\" ? (\"close\" as const) : (\"quit\" as const) },\n ],\n });\n\n // Edit\n template.push({ role: \"editMenu\" as const });\n\n // View\n template.push({\n label: \"View\",\n submenu: [\n { role: \"reload\" as const },\n { role: \"forceReload\" as const },\n { role: \"toggleDevTools\" as const },\n { type: \"separator\" as const },\n { role: \"resetZoom\" as const },\n { role: \"zoomIn\" as const, accelerator: \"CmdOrCtrl+=\" },\n { role: \"zoomOut\" as const },\n { type: \"separator\" as const },\n { role: \"togglefullscreen\" as const },\n ],\n });\n\n // Window\n template.push({ role: \"windowMenu\" as const });\n\n // Overdeck\n template.push({\n label: \"Overdeck\",\n submenu: [\n {\n label: \"Start Cloister\",\n click: () => callServerApi(\"/api/cloister/start\", \"POST\"),\n },\n {\n label: \"Stop Cloister\",\n click: () => callServerApi(\"/api/cloister/stop\", \"POST\"),\n },\n {\n label: \"Emergency Stop All Agents\",\n click: () => callServerApi(\"/api/agents/emergency-stop\", \"POST\"),\n },\n { type: \"separator\" },\n {\n label: \"Open Workspace\",\n id: \"open-workspace-submenu\",\n submenu: [{ label: \"Loading...\", enabled: false }],\n },\n { type: \"separator\" },\n {\n label: \"Settings...\",\n accelerator: process.platform !== \"darwin\" ? \"CmdOrCtrl+,\" : undefined,\n click: () => {\n showOrCreateWindow();\n dispatchMenuAction(\"open-settings\");\n },\n },\n ],\n });\n\n // Help\n template.push({\n role: \"help\" as const,\n submenu: [\n {\n label: \"Check for Updates...\",\n click: () => {\n void checkForUpdates();\n },\n },\n { type: \"separator\" },\n {\n label: \"Flywheel Documentation\",\n click: () =>\n void shell.openExternal(\n \"https://github.com/eltmon/overdeck/blob/main/docs/FLYWHEEL.md\",\n ),\n },\n {\n label: \"Overdeck on GitHub\",\n click: () =>\n void shell.openExternal(\"https://github.com/eltmon/overdeck\"),\n },\n {\n label: \"Report an Issue\",\n click: () =>\n void shell.openExternal(\"https://github.com/eltmon/overdeck/issues\"),\n },\n ],\n });\n\n // Add \"Install Update and Restart\" item if update is downloaded\n if (updateDownloaded) {\n const helpMenu = template[template.length - 1];\n if (helpMenu && helpMenu.submenu && Array.isArray(helpMenu.submenu)) {\n helpMenu.submenu.push(\n { type: \"separator\" },\n {\n label: \"Install Update and Restart\",\n click: () => {\n quitAndInstall();\n },\n },\n );\n }\n }\n\n return template;\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\nexport function configureApplicationMenu(): void {\n const menu = Menu.buildFromTemplate(buildMenuTemplate());\n Menu.setApplicationMenu(menu);\n\n // Refresh workspace submenu when the Overdeck menu is about to open\n const overdeckMenu = menu.items.find((item) => item.label === \"Overdeck\");\n if (overdeckMenu?.submenu) {\n overdeckMenu.submenu.on(\"menu-will-show\", () => {\n void fetchActiveWorkspaces().then((workspaces) => {\n const wsItem = overdeckMenu.submenu?.items.find(\n (i) => i.id === \"open-workspace-submenu\",\n );\n if (wsItem) {\n // Electron doesn't support dynamic submenu replacement after build,\n // so we dispatch a menu action that opens a workspace picker in the renderer\n wsItem.label = workspaces.length\n ? `Open Workspace (${workspaces.length})`\n : \"Open Workspace\";\n }\n });\n });\n }\n\n // Listen for update status changes to rebuild menu when update is downloaded\n onUpdateStatusChange((status) => {\n if (status.downloaded && !updateDownloaded) {\n updateDownloaded = true;\n rebuildMenu();\n }\n });\n}\n","/**\n * Native desktop notifications for Overdeck events.\n *\n * Event types and their default enabled state (all configurable in Settings):\n * inputNeeded — Agent needs user input (default: on)\n * stuckAgents — Agent has been stuck > threshold (default: on)\n * mergeFailures — Merge specialist failed (default: on)\n * workComplete — Agent signalled work done (default: on)\n * planningDone — Planning session complete (default: off)\n * mergeReady — PR ready for human merge (default: on)\n *\n * Notifications are sent from:\n * 1. Renderer → main via IPC (pan:notify) — for events the frontend detects\n * 2. Main process directly — in future when subscribing to domain events via WS\n */\n\nimport { app, ipcMain, Notification } from \"electron\";\n\nimport { IPC } from \"./main.js\";\nimport { showOrCreateWindow } from \"./main.js\";\nimport { getDesktopSettings } from \"./settings.js\";\nimport { resolveResourcePath } from \"./main.js\";\nimport type { NotificationEventType } from \"./settings.js\";\n\n// ─── Send notification ────────────────────────────────────────────────────────\n\nexport function sendNotification(\n eventType: NotificationEventType,\n title: string,\n body: string,\n): void {\n const settings = getDesktopSettings();\n if (!settings.notifications[eventType]) return;\n\n if (!Notification.isSupported()) return;\n\n const iconPath = resolveResourcePath(\"icon.png\");\n\n const notification = new Notification({\n title,\n body,\n icon: iconPath ?? undefined,\n silent: false,\n });\n\n notification.on(\"click\", () => showOrCreateWindow());\n notification.show();\n}\n\n// ─── IPC handler registration ─────────────────────────────────────────────────\n\nexport function registerNotificationHandlers(): void {\n ipcMain.handle(IPC.NOTIFY, (_event, eventType: unknown, title: unknown, body: unknown) => {\n if (\n typeof eventType === \"string\" &&\n typeof title === \"string\" &&\n typeof body === \"string\"\n ) {\n sendNotification(eventType as NotificationEventType, title, body);\n }\n });\n}\n\n// ─── App lifecycle integration ────────────────────────────────────────────────\n\n/**\n * Called at app.ready to request notification permission on macOS.\n * On Linux/Windows, Notification.isSupported() handles availability.\n */\nexport function initializeNotifications(): void {\n if (!Notification.isSupported()) {\n console.log(\"[desktop/notifications] native notifications not supported on this platform\");\n }\n}\n","/**\n * Auto-start nag flow for the Overdeck desktop app.\n *\n * Flow:\n * Launch 1: Full explanation dialog (warm, inviting)\n * Launch 2-5: In-app toasts dispatched to renderer via \"auto-start-nag:<n>:<max>\" menu action\n * - Shows: \"Reminder N of 5\", Enable button (prominent), Not Yet, Stop Reminding Me\n * After 5 or \"Stop reminding me\": never prompt again (nagDismissed = true)\n * Once enabled: auto-start registered via app.setLoginItemSettings\n *\n * State tracked in DesktopSettings.autoStart via settings.ts.\n */\n\nimport { app, dialog } from \"electron\";\n\nimport { getDesktopSettings, updateDesktopSetting } from \"./settings.js\";\nimport { dispatchMenuAction } from \"./main.js\";\n\nconst NAG_MAX = 5;\n\n// ─── Enable auto-start ────────────────────────────────────────────────────────\n\nexport function enableAutoStart(): void {\n updateDesktopSetting(\"autoStart.enabled\", true);\n updateDesktopSetting(\"autoStart.nagDismissed\", true);\n app.setLoginItemSettings({ openAtLogin: true });\n}\n\nexport function dismissAutoStartNag(): void {\n updateDesktopSetting(\"autoStart.nagDismissed\", true);\n}\n\n// ─── Nag flow ─────────────────────────────────────────────────────────────────\n\nexport function handleAutoStartNag(): void {\n const { autoStart } = getDesktopSettings();\n\n if (autoStart.enabled || autoStart.nagDismissed) return;\n if (autoStart.nagCount >= NAG_MAX) {\n updateDesktopSetting(\"autoStart.nagDismissed\", true);\n return;\n }\n\n const count = autoStart.nagCount + 1;\n updateDesktopSetting(\"autoStart.nagCount\", count);\n\n if (count === 1) {\n // First launch: native dialog\n showFirstLaunchDialog();\n } else {\n // Subsequent launches: in-app toast dispatched to renderer\n // Dispatched with delay to let the window finish loading\n setTimeout(() => {\n dispatchMenuAction(`auto-start-nag:${count}:${NAG_MAX}`);\n }, 3_500);\n }\n}\n\nfunction showFirstLaunchDialog(): void {\n // Delay until window is ready\n setTimeout(() => {\n void dialog\n .showMessageBox({\n type: \"info\",\n title: \"Start Overdeck automatically?\",\n message: \"Keep an eye on your agents — even when you forget to open the app.\",\n detail:\n \"Overdeck can start automatically when you log in, so you never miss an agent \" +\n \"asking for help or a merge that's ready to ship.\\n\\n\" +\n \"You can change this at any time in Settings → Desktop → Auto-start.\",\n buttons: [\"Enable Auto-start\", \"Not Yet\"],\n defaultId: 0,\n cancelId: 1,\n })\n .then(({ response }) => {\n if (response === 0) {\n enableAutoStart();\n }\n });\n }, 2_000);\n}\n","/**\n * overdeck:// custom protocol for serving static frontend assets in packaged builds.\n *\n * In dev mode:\n * BrowserWindow loads from Vite dev server URL (VITE_DEV_SERVER_URL env var).\n * HMR and source maps work normally.\n *\n * In packaged builds:\n * BrowserWindow loads overdeck://app/index.html.\n * This protocol handler serves files from the bundled dist/dashboard/public/.\n * WebSocket connections (ws/rpc, ws/terminal) go to the embedded server port\n * on localhost — the protocol handler only serves static assets.\n *\n * Security:\n * - Path traversal protection: rejects paths containing \"..\"\n * - Only files within the static root are served\n * - Non-existent asset requests return -6 (net::ERR_FILE_NOT_FOUND)\n * - HTML routes (no extension) fall back to index.html for SPA routing\n *\n * CSP:\n * Renderer CSP is configured via vite.config.ts in the frontend.\n * In packaged mode, connect-src must include ws://127.0.0.1:* for WebSocket.\n */\n\nimport * as FS from \"node:fs\";\nimport * as Path from \"node:path\";\n\nimport { protocol } from \"electron\";\n\nimport { DESKTOP_SCHEME, resolveServerStaticDir } from \"./main.js\";\n\nlet registered = false;\n\n// ─── Path resolution ──────────────────────────────────────────────────────────\n\nexport function resolveStaticPath(staticRoot: string, requestUrl: string): string {\n const fallbackIndex = Path.join(staticRoot, \"index.html\");\n\n let url: URL;\n try {\n url = new URL(requestUrl);\n } catch {\n return fallbackIndex;\n }\n\n const rawPath = decodeURIComponent(url.pathname);\n const normalized = Path.posix.normalize(rawPath).replace(/^\\/+/, \"\");\n\n if (normalized.includes(\"..\")) {\n return fallbackIndex;\n }\n\n const requestedPath = normalized.length > 0 ? normalized : \"index.html\";\n const resolved = Path.resolve(staticRoot, requestedPath);\n\n const staticRootResolved = Path.resolve(staticRoot);\n const inRoot =\n resolved === staticRootResolved ||\n resolved.startsWith(staticRootResolved + Path.sep);\n\n if (!inRoot) return fallbackIndex;\n\n // If path has a file extension, serve it (or 404)\n if (Path.extname(resolved)) {\n return resolved;\n }\n\n // SPA route: check for index.html in subdirectory, else root index\n const nestedIndex = Path.join(resolved, \"index.html\");\n if (FS.existsSync(nestedIndex)) return nestedIndex;\n\n return fallbackIndex;\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Register the overdeck:// protocol handler.\n * Must be called after app.ready (but registration via registerSchemesAsPrivileged\n * must happen before app.ready — done in main.ts).\n */\nexport function registerDesktopProtocol(): void {\n if (registered) return;\n\n const staticRoot = resolveServerStaticDir();\n if (!staticRoot) {\n console.error(\n \"[desktop/protocol] Static bundle not found — packaged frontend assets missing. \" +\n \"Run 'npm run build:dashboard' first.\",\n );\n return;\n }\n\n const fallbackIndex = Path.join(staticRoot, \"index.html\");\n\n protocol.registerFileProtocol(DESKTOP_SCHEME, (request, callback) => {\n const candidate = resolveStaticPath(staticRoot, request.url);\n const hasExt = Path.extname(candidate).length > 0;\n\n if (!FS.existsSync(candidate)) {\n if (hasExt) {\n // Asset not found — return 404\n callback({ error: -6 /* net::ERR_FILE_NOT_FOUND */ });\n } else {\n // SPA route — fall back to index\n callback({ path: fallbackIndex });\n }\n return;\n }\n\n callback({ path: candidate });\n });\n\n registered = true;\n console.log(`[desktop/protocol] registered ${DESKTOP_SCHEME}:// serving from ${staticRoot}`);\n}\n","import * as FS from \"node:fs\";\nimport * as OS from \"node:os\";\nimport * as Path from \"node:path\";\n\nimport {\n app,\n BrowserWindow,\n ipcMain,\n protocol,\n shell,\n} from \"electron\";\n\nimport { createTray, destroyTray } from \"./tray.js\";\nimport { loadDesktopSettings, getDesktopSettings, updateDesktopSetting } from \"./settings.js\";\nimport { startServer, stopServer } from \"./server.js\";\nimport { configureApplicationMenu } from \"./menu.js\";\nimport { initializeNotifications, registerNotificationHandlers } from \"./notifications.js\";\nimport { handleAutoStartNag } from \"./autostart.js\";\nimport { registerDesktopProtocol } from \"./protocol.js\";\nimport { initializeAutoUpdater, checkForUpdates, downloadUpdate, quitAndInstall, getUpdateStatus, onUpdateStatusChange } from \"./updater.js\";\n\n// ─── Constants ───────────────────────────────────────────────────────────────\n\nconst ROOT_DIR = Path.resolve(__dirname, \"../../..\");\nconst isDevelopment = Boolean(process.env.VITE_DEV_SERVER_URL);\nconst APP_DISPLAY_NAME = isDevelopment ? \"Overdeck (Dev)\" : \"Overdeck\";\nconst APP_ID = \"com.overdeck.app\";\nconst LINUX_WM_CLASS = isDevelopment ? \"overdeck-dev\" : \"overdeck\";\nexport const DESKTOP_SCHEME = \"overdeck\";\n\n// IPC channel names\nexport const IPC = {\n GET_SERVER_URL: \"pan:get-server-url\",\n GET_WS_URL: \"pan:get-ws-url\",\n PICK_FOLDER: \"pan:pick-folder\",\n OPEN_EXTERNAL: \"pan:open-external\",\n OPEN_TERMINAL_WINDOW: \"pan:open-terminal-window\",\n SET_ALWAYS_ON_TOP: \"pan:set-always-on-top\",\n MENU_ACTION: \"pan:menu-action\",\n GET_DESKTOP_SETTINGS: \"pan:get-desktop-settings\",\n UPDATE_DESKTOP_SETTING: \"pan:update-desktop-setting\",\n NOTIFY: \"pan:notify\",\n GET_UPDATE_STATUS: \"pan:get-update-status\",\n CHECK_FOR_UPDATES: \"pan:check-for-updates\",\n DOWNLOAD_UPDATE: \"pan:download-update\",\n QUIT_AND_INSTALL: \"pan:quit-and-install\",\n RESTART_DASHBOARD: \"pan:restart-dashboard\",\n} as const;\n\n// ─── State ────────────────────────────────────────────────────────────────────\n\nlet mainWindow: BrowserWindow | null = null;\nexport let serverPort = 0;\nexport let serverUrl = \"\";\nexport let serverWsUrl = \"\";\nexport let isQuitting = false;\nconst terminalWindows = new Map<string, BrowserWindow>();\n\n// ─── Single instance lock ─────────────────────────────────────────────────────\n// A second launch must not boot another full app (window, tray, embedded\n// server). Duplicates exit immediately and the running instance is focused\n// via the second-instance event below (PAN-2559).\n\nif (!app.requestSingleInstanceLock()) {\n app.exit(0);\n}\n\napp.on(\"second-instance\", () => showOrCreateWindow());\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nexport function resolveResourcePath(fileName: string): string | null {\n const candidates = [\n // Packaged (electron-builder): resources are in process.resourcesPath/resources/\n Path.join(process.resourcesPath ?? \"\", \"resources\", fileName),\n // npx / global-install: resources are bundled at <package>/resources/\n Path.join(__dirname, \"../resources\", fileName),\n // Dev: resources are in the monorepo\n Path.join(ROOT_DIR, \"apps/desktop/resources\", fileName),\n ];\n for (const candidate of candidates) {\n if (FS.existsSync(candidate)) return candidate;\n }\n return null;\n}\n\nexport function resolveServerEntry(): string {\n if (app.isPackaged) {\n return Path.join(process.resourcesPath ?? \"\", \"server/server.js\");\n }\n // npx / global-install: server bundle is at <package>/server/server.js\n const npxBundle = Path.join(__dirname, \"../server/server.js\");\n if (FS.existsSync(npxBundle)) return npxBundle;\n // Dev mode: server is built in the monorepo root\n return Path.join(ROOT_DIR, \"dist/dashboard/server.js\");\n}\n\nexport function resolveServerStaticDir(): string | null {\n const candidates: string[] = [];\n if (app.isPackaged) {\n candidates.push(Path.join(process.resourcesPath ?? \"\", \"server/public\"));\n } else {\n // npx / global-install: static assets are at <package>/server/public\n candidates.push(Path.join(__dirname, \"../server/public\"));\n // Dev mode fallback\n candidates.push(Path.join(ROOT_DIR, \"dist/dashboard/public\"));\n }\n for (const candidate of candidates) {\n if (FS.existsSync(Path.join(candidate, \"index.html\"))) {\n return candidate;\n }\n }\n return null;\n}\n\nexport function resolveWindowUrl(): string {\n if (isDevelopment) {\n return process.env.VITE_DEV_SERVER_URL!;\n }\n // Serve the window from the embedded server's HTTP origin so the frontend's\n // relative /api fetches and /ws/* sockets are same-origin, exactly like the\n // browser dashboard. The overdeck:// protocol page has no working data\n // layer (relative fetches resolve to the app bundle, PAN-2561) — it is only\n // the pre-server fallback.\n if (serverUrl) {\n return serverUrl;\n }\n return `${DESKTOP_SCHEME}://app/index.html`;\n}\n\nfunction createTerminalWindow(sessionName: string, title: string): BrowserWindow {\n const win = new BrowserWindow({\n width: 900,\n height: 650,\n title,\n webPreferences: {\n preload: Path.join(__dirname, \"preload.js\"),\n contextIsolation: true,\n nodeIntegration: false,\n sandbox: false,\n },\n show: false,\n });\n\n win.once(\"ready-to-show\", () => win.show());\n void win.loadURL(`${resolveWindowUrl()}?terminal=${encodeURIComponent(sessionName)}&title=${encodeURIComponent(title)}`);\n return win;\n}\n\n// ─── IPC: basic handlers ──────────────────────────────────────────────────────\n\nfunction registerIpcHandlers(): void {\n ipcMain.on(IPC.GET_SERVER_URL, (event) => {\n event.returnValue = serverUrl;\n });\n\n ipcMain.on(IPC.GET_WS_URL, (event) => {\n event.returnValue = serverWsUrl;\n });\n\n ipcMain.handle(IPC.PICK_FOLDER, async () => {\n const { dialog } = await import(\"electron\");\n const result = await dialog.showOpenDialog({ properties: [\"openDirectory\"] });\n return result.canceled ? null : (result.filePaths[0] ?? null);\n });\n\n ipcMain.handle(IPC.OPEN_EXTERNAL, async (_event, url: unknown) => {\n if (typeof url !== \"string\") return;\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n return;\n }\n if (parsed.protocol !== \"https:\" && parsed.protocol !== \"http:\") return;\n await shell.openExternal(parsed.toString());\n });\n\n ipcMain.handle(IPC.GET_DESKTOP_SETTINGS, () => getDesktopSettings());\n\n ipcMain.on(IPC.OPEN_TERMINAL_WINDOW, (_event, sessionName: unknown, title: unknown) => {\n if (typeof sessionName !== \"string\" || typeof title !== \"string\") return;\n\n const existing = terminalWindows.get(sessionName);\n if (existing && !existing.isDestroyed()) {\n if (!existing.isVisible()) existing.show();\n existing.focus();\n return;\n }\n\n const win = createTerminalWindow(sessionName, title);\n terminalWindows.set(sessionName, win);\n win.on(\"closed\", () => terminalWindows.delete(sessionName));\n });\n\n ipcMain.on(IPC.SET_ALWAYS_ON_TOP, (_event, value: unknown) => {\n const focused = BrowserWindow.getFocusedWindow();\n if (focused) {\n focused.setAlwaysOnTop(value === true);\n }\n });\n\n ipcMain.handle(IPC.UPDATE_DESKTOP_SETTING, (_event, key: unknown, value: unknown) => {\n if (typeof key !== \"string\") return;\n const updated = updateDesktopSetting(key, value);\n // Apply auto-start setting immediately\n if (updated && key === \"autoStart.enabled\") {\n app.setLoginItemSettings({ openAtLogin: value === true });\n }\n });\n\n // Update IPC handlers\n ipcMain.handle(IPC.GET_UPDATE_STATUS, () => getUpdateStatus());\n\n ipcMain.handle(IPC.CHECK_FOR_UPDATES, async () => {\n try {\n await checkForUpdates();\n } catch (err) {\n console.error(\"[main] checkForUpdates failed:\", err);\n }\n return getUpdateStatus();\n });\n\n ipcMain.handle(IPC.DOWNLOAD_UPDATE, async () => {\n try {\n await downloadUpdate();\n } catch (err) {\n console.error(\"[main] downloadUpdate failed:\", err);\n }\n return getUpdateStatus();\n });\n\n ipcMain.on(IPC.QUIT_AND_INSTALL, () => {\n quitAndInstall();\n });\n\n ipcMain.handle(IPC.RESTART_DASHBOARD, async () => {\n console.log(\"[main] manual dashboard restart requested\");\n stopServer();\n // Brief delay so SIGTERM has a chance to land before we re-spawn.\n await new Promise((resolve) => setTimeout(resolve, 500));\n startServer((port, wsUrl) => {\n serverPort = port;\n serverUrl = `http://127.0.0.1:${port}`;\n serverWsUrl = wsUrl;\n console.log(`[main] dashboard restarted on ${serverUrl}`);\n });\n });\n}\n\n// ─── Window ───────────────────────────────────────────────────────────────────\n\nexport function createWindow(): BrowserWindow {\n const win = new BrowserWindow({\n width: 1400,\n height: 900,\n minWidth: 900,\n minHeight: 600,\n title: APP_DISPLAY_NAME,\n webPreferences: {\n preload: Path.join(__dirname, \"preload.js\"),\n contextIsolation: true,\n nodeIntegration: false,\n sandbox: false,\n },\n show: false,\n });\n\n win.once(\"ready-to-show\", () => win.show());\n\n win.on(\"close\", (event) => {\n if (!isQuitting && process.platform === \"darwin\") {\n event.preventDefault();\n win.hide();\n }\n });\n\n void win.loadURL(resolveWindowUrl());\n return win;\n}\n\nexport function showOrCreateWindow(): void {\n const existing =\n BrowserWindow.getFocusedWindow() ?? mainWindow ?? BrowserWindow.getAllWindows()[0];\n if (existing) {\n if (!existing.isVisible()) existing.show();\n existing.focus();\n return;\n }\n mainWindow = createWindow();\n}\n\nexport function dispatchMenuAction(action: string): void {\n const win = BrowserWindow.getFocusedWindow() ?? mainWindow ?? BrowserWindow.getAllWindows()[0];\n if (!win) {\n showOrCreateWindow();\n setTimeout(() => dispatchMenuAction(action), 500);\n return;\n }\n const send = () => {\n if (win.isDestroyed()) return;\n win.webContents.send(IPC.MENU_ACTION, action);\n if (!win.isVisible()) win.show();\n win.focus();\n };\n if (win.webContents.isLoadingMainFrame()) {\n win.webContents.once(\"did-finish-load\", send);\n } else {\n send();\n }\n}\n\n// ─── Server API helper ────────────────────────────────────────────────────────\n\nexport function callServerApi(path: string, method: string): void {\n if (!serverUrl) return;\n fetch(`${serverUrl}${path}`, { method }).catch((err: unknown) => {\n console.error(\"[desktop] server API call failed:\", err);\n });\n}\n\n// ─── Protocol registration (before app.ready) ─────────────────────────────────\n\nprotocol.registerSchemesAsPrivileged([\n {\n scheme: DESKTOP_SCHEME,\n privileges: {\n standard: true,\n secure: true,\n supportFetchAPI: true,\n corsEnabled: true,\n },\n },\n]);\n\nif (process.platform === \"linux\") {\n app.commandLine.appendSwitch(\"class\", LINUX_WM_CLASS);\n}\n\n// ─── App Lifecycle ────────────────────────────────────────────────────────────\n\napp.on(\"ready\", () => {\n loadDesktopSettings();\n registerIpcHandlers();\n registerNotificationHandlers();\n initializeNotifications();\n configureApplicationMenu();\n registerDesktopProtocol();\n const updateChannel = app.getVersion().includes(\"-canary\") ? \"beta\" : \"latest\";\n initializeAutoUpdater(updateChannel);\n\n if (process.platform === \"win32\") {\n app.setAppUserModelId(APP_ID);\n }\n\n if (process.platform === \"darwin\" && app.dock) {\n const iconPath = resolveResourcePath(\"icon.png\");\n if (iconPath) app.dock.setIcon(iconPath);\n }\n\n createTray();\n\n startServer((port, wsUrl) => {\n serverPort = port;\n serverUrl = `http://127.0.0.1:${port}`;\n serverWsUrl = wsUrl;\n // The ready callback fires again after every server crash-restart. One\n // window per callback multiplied windows under a crash loop (PAN-2570) —\n // reuse the existing window and repoint it at the current server URL.\n if (mainWindow && !mainWindow.isDestroyed()) {\n void mainWindow.loadURL(resolveWindowUrl());\n return;\n }\n mainWindow = createWindow();\n handleAutoStartNag();\n });\n});\n\napp.on(\"window-all-closed\", () => {\n if (process.platform !== \"darwin\") {\n isQuitting = true;\n app.quit();\n }\n});\n\napp.on(\"activate\", () => showOrCreateWindow());\n\napp.on(\"before-quit\", () => {\n isQuitting = true;\n destroyTray();\n stopServer();\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"main.js","names":["Path","app","FS","nativeImage","Menu","app","Tray","Crypto","FS","ChildProcess","app","BrowserWindow","autoUpdater","Menu","app","shell","Notification","dialog","Path","FS","Path","app","FS","BrowserWindow","shell","protocol"],"sources":["../src/settings.ts","../src/tray.ts","../src/server.ts","../src/updater.ts","../src/menu.ts","../src/notifications.ts","../src/autostart.ts","../src/protocol.ts","../src/main.ts"],"sourcesContent":["/**\n * Desktop-specific settings, persisted to userData/desktop-settings.json.\n *\n * Covers: tray appearance, per-event notification toggles, auto-start config.\n * Loaded at app startup; updated via IPC from renderer.\n */\n\nimport * as FS from \"node:fs\";\nimport * as Path from \"node:path\";\n\nimport { app } from \"electron\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface DesktopSettings {\n tray: {\n showBadge: boolean;\n tooltipDetail: \"minimal\" | \"full\";\n };\n notifications: {\n inputNeeded: boolean;\n stuckAgents: boolean;\n mergeFailures: boolean;\n workComplete: boolean;\n planningDone: boolean;\n mergeReady: boolean;\n };\n autoStart: {\n enabled: boolean;\n nagCount: number;\n nagDismissed: boolean;\n };\n}\n\nexport type NotificationEventType = keyof DesktopSettings[\"notifications\"];\n\n// ─── Defaults ─────────────────────────────────────────────────────────────────\n\nconst DEFAULTS: DesktopSettings = {\n tray: {\n showBadge: true,\n tooltipDetail: \"full\",\n },\n notifications: {\n inputNeeded: true,\n stuckAgents: true,\n mergeFailures: true,\n workComplete: true,\n planningDone: false,\n mergeReady: true,\n },\n autoStart: {\n enabled: false,\n nagCount: 0,\n nagDismissed: false,\n },\n};\n\n// ─── State ────────────────────────────────────────────────────────────────────\n\nlet settings: DesktopSettings = deepClone(DEFAULTS);\n\nfunction deepClone<T>(obj: T): T {\n return JSON.parse(JSON.stringify(obj)) as T;\n}\n\nfunction settingsPath(): string {\n return Path.join(app.getPath(\"userData\"), \"desktop-settings.json\");\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\nexport function loadDesktopSettings(): void {\n try {\n const raw = FS.readFileSync(settingsPath(), \"utf8\");\n const parsed = JSON.parse(raw) as Partial<DesktopSettings>;\n settings = {\n tray: { ...DEFAULTS.tray, ...parsed.tray },\n notifications: { ...DEFAULTS.notifications, ...parsed.notifications },\n autoStart: { ...DEFAULTS.autoStart, ...parsed.autoStart },\n };\n } catch {\n settings = deepClone(DEFAULTS);\n }\n}\n\nexport function saveDesktopSettings(): void {\n try {\n FS.writeFileSync(settingsPath(), JSON.stringify(settings, null, 2));\n } catch {\n console.error(\"[desktop] failed to save desktop settings\");\n }\n}\n\nexport function getDesktopSettings(): DesktopSettings {\n return settings;\n}\n\n/**\n * Update a single setting by dotted key (e.g. \"notifications.inputNeeded\").\n * Returns true if the key was found and updated.\n */\nexport function updateDesktopSetting(key: string, value: unknown): boolean {\n const [section, field] = key.split(\".\");\n if (!section || !field) return false;\n const s = settings as unknown as Record<string, Record<string, unknown>>;\n if (!s[section] || !(field in s[section])) return false;\n s[section][field] = value;\n saveDesktopSettings();\n return true;\n}\n","/**\n * System tray for the Overdeck desktop app.\n *\n * Creates a Tray with:\n * - Color-coded icon: green (idle), yellow (working), red (attention needed)\n * - Agent count badge overlay (macOS dock badge)\n * - Rich tooltip: agent count, attention items, last activity\n * - Context menu: Show Dashboard, Start/Stop Cloister, Emergency Stop, Settings, Quit\n *\n * Polls /api/health every 5s for live status.\n */\n\nimport * as Path from \"node:path\";\n\nimport { app, Menu, nativeImage, Tray } from \"electron\";\nimport type { MenuItemConstructorOptions } from \"electron\";\n\nimport { callServerApi, serverUrl, showOrCreateWindow, dispatchMenuAction, isQuitting } from \"./main.js\";\nimport { resolveResourcePath } from \"./main.js\";\nimport { getDesktopSettings } from \"./settings.js\";\n\nexport type AgentStatus = \"idle\" | \"working\" | \"attention\";\n\nlet tray: Tray | null = null;\nlet pollTimer: ReturnType<typeof setInterval> | null = null;\n\n// ─── Icon ─────────────────────────────────────────────────────────────────────\n\nconst STATUS_COLORS: Record<AgentStatus, string> = {\n idle: \"#22c55e\",\n working: \"#f59e0b\",\n attention: \"#ef4444\",\n};\n\nfunction createTrayIcon(status: AgentStatus): Electron.NativeImage {\n const color = STATUS_COLORS[status];\n // 16×16 circle SVG rendered to NativeImage buffer\n const svg = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\">\n <circle cx=\"8\" cy=\"8\" r=\"7\" fill=\"${color}\" stroke=\"#00000033\" stroke-width=\"0.5\"/>\n <ellipse cx=\"8\" cy=\"8\" rx=\"5.5\" ry=\"3.8\" stroke=\"#ffffff44\" stroke-width=\"0.5\" fill=\"none\"/>\n <circle cx=\"8\" cy=\"8\" r=\"2\" fill=\"#ffffff99\"/>\n <circle cx=\"8\" cy=\"8\" r=\"1\" fill=\"#00000066\"/>\n </svg>`;\n\n return nativeImage.createFromBuffer(Buffer.from(svg));\n}\n\n// ─── Tooltip ──────────────────────────────────────────────────────────────────\n\nfunction buildTooltip(agentCount: number, attentionCount: number, lastActivity: string | null): string {\n const settings = getDesktopSettings();\n if (settings.tray.tooltipDetail === \"minimal\") {\n return `Overdeck — ${agentCount} agent${agentCount !== 1 ? \"s\" : \"\"}`;\n }\n\n const lines = [\"Overdeck\"];\n lines.push(`${agentCount} agent${agentCount !== 1 ? \"s\" : \"\"} running`);\n if (attentionCount > 0) {\n lines.push(`⚠ ${attentionCount} need${attentionCount !== 1 ? \"\" : \"s\"} attention`);\n }\n if (lastActivity) {\n lines.push(`Last: ${lastActivity}`);\n }\n return lines.join(\"\\n\");\n}\n\n// ─── Context menu ─────────────────────────────────────────────────────────────\n\nfunction buildContextMenu(): Electron.Menu {\n const template: MenuItemConstructorOptions[] = [\n {\n label: \"Show Dashboard\",\n click: () => showOrCreateWindow(),\n },\n { type: \"separator\" },\n {\n label: \"Start Cloister\",\n click: () => callServerApi(\"/api/cloister/start\", \"POST\"),\n },\n {\n label: \"Stop Cloister\",\n click: () => callServerApi(\"/api/cloister/stop\", \"POST\"),\n },\n {\n label: \"Emergency Stop All\",\n click: () => callServerApi(\"/api/agents/emergency-stop\", \"POST\"),\n },\n { type: \"separator\" },\n {\n label: \"Settings\",\n click: () => {\n showOrCreateWindow();\n dispatchMenuAction(\"open-settings\");\n },\n },\n { type: \"separator\" },\n {\n label: \"Quit\",\n click: () => {\n // isQuitting is set by app.on(\"before-quit\")\n app.quit();\n },\n },\n ];\n return Menu.buildFromTemplate(template);\n}\n\n// ─── Polling ──────────────────────────────────────────────────────────────────\n\ninterface HealthResponse {\n agentCount?: number;\n attentionCount?: number;\n lastActivity?: string;\n status?: string;\n}\n\nasync function refreshTrayStatus(): Promise<void> {\n if (!tray || isQuitting) return;\n const url = serverUrl;\n if (!url) return;\n\n try {\n const resp = await fetch(`${url}/api/health`, { signal: AbortSignal.timeout(3_000) });\n if (!resp.ok) return;\n\n const data = (await resp.json()) as HealthResponse;\n const agentCount = data.agentCount ?? 0;\n const attentionCount = data.attentionCount ?? 0;\n const lastActivity = data.lastActivity ?? null;\n\n const status: AgentStatus =\n attentionCount > 0 ? \"attention\" : agentCount > 0 ? \"working\" : \"idle\";\n\n tray.setImage(createTrayIcon(status));\n tray.setToolTip(buildTooltip(agentCount, attentionCount, lastActivity));\n tray.setContextMenu(buildContextMenu());\n\n // macOS dock badge\n const settings = getDesktopSettings();\n if (settings.tray.showBadge && process.platform === \"darwin\" && app.dock) {\n app.dock.setBadge(agentCount > 0 ? String(agentCount) : \"\");\n }\n } catch {\n // Server not ready yet — silently skip\n }\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\nexport function createTray(): void {\n if (tray) return;\n\n const icon = createTrayIcon(\"idle\");\n tray = new Tray(icon);\n tray.setToolTip(\"Overdeck\");\n tray.setContextMenu(buildContextMenu());\n\n // Single-click opens dashboard on all platforms\n tray.on(\"click\", () => showOrCreateWindow());\n\n // Start polling\n pollTimer = setInterval(() => void refreshTrayStatus(), 5_000);\n void refreshTrayStatus();\n}\n\nexport function destroyTray(): void {\n if (pollTimer) {\n clearInterval(pollTimer);\n pollTimer = null;\n }\n tray?.destroy();\n tray = null;\n}\n","/**\n * Embedded dashboard server management.\n *\n * Spawns dist/dashboard/server.js (or the packaged equivalent) as a child\n * process, passing config via environment variables. Supports exponential-\n * backoff restart on crash. Graceful shutdown on app quit.\n *\n * Bootstrap config passed via env vars:\n * OVERDECK_PORT — TCP port for HTTP + WS\n * OVERDECK_AUTH_TOKEN — random hex token (future: auth middleware)\n * OVERDECK_MODE — \"desktop\" (enables desktop-specific behaviours)\n * OVERDECK_NO_BROWSER — \"1\" (suppresses auto browser open)\n */\n\nimport * as ChildProcess from \"node:child_process\";\nimport * as Crypto from \"node:crypto\";\nimport * as FS from \"node:fs\";\n\nimport { resolveServerEntry, resolveServerStaticDir } from \"./main.js\";\n\n// ─── Constants ────────────────────────────────────────────────────────────────\n\nconst BASE_PORT = 7825;\nconst MAX_RESTART_DELAY_MS = 30_000;\nconst MAX_RESTART_ATTEMPTS = 8;\nconst SIGTERM_GRACE_MS = 3_000;\n\n// ─── State ────────────────────────────────────────────────────────────────────\n\nlet serverProcess: ChildProcess.ChildProcess | null = null;\nlet restartAttempt = 0;\nlet restartTimer: ReturnType<typeof setTimeout> | null = null;\nlet waitInterval: ReturnType<typeof setInterval> | null = null;\nlet quitting = false;\n\n// Callbacks so main.ts can react to server-ready / URL changes\nlet onReadyCallback: ((port: number, wsUrl: string) => void) | null = null;\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction randomHex(bytes: number): string {\n return Crypto.randomBytes(bytes).toString(\"hex\");\n}\n\nfunction resolvePort(): number {\n // Each restart attempt increments the port to avoid EADDRINUSE during quick restarts\n return BASE_PORT + (restartAttempt % 10);\n}\n\n// ─── Start / Stop ─────────────────────────────────────────────────────────────\n\nexport function startServer(onReady: (port: number, wsUrl: string) => void): void {\n // Reset quit flag so manual restarts (after stopServer) can re-spawn.\n // The app-quit path sets isQuitting in main.ts before stopServer, so this\n // only un-latches the flag when the caller intends a real restart.\n quitting = false;\n // A deliberate (re)start gets a fresh crash budget.\n restartAttempt = 0;\n onReadyCallback = onReady;\n spawnServer();\n}\n\nfunction spawnServer(): void {\n if (quitting) return;\n\n const entry = resolveServerEntry();\n if (!FS.existsSync(entry)) {\n console.error(`[desktop/server] Server entry not found: ${entry}`);\n console.error(\"[desktop/server] Run 'npm run build' to build the dashboard server first.\");\n return;\n }\n\n const port = resolvePort();\n const authToken = randomHex(32);\n\n const child = ChildProcess.spawn(\n process.execPath, // Node.js binary (same version Electron bundles)\n [entry],\n {\n env: {\n ...process.env,\n // The server's config reads API_PORT || PORT (src/dashboard/server/config.ts)\n API_PORT: String(port),\n OVERDECK_PORT: String(port),\n // Packaged layout puts the frontend at resources/server/public, which\n // the server cannot derive from its own bundle path (it assumes a\n // repo-root dist/dashboard/public layout).\n OVERDECK_FRONTEND_DIR: resolveServerStaticDir() ?? undefined,\n OVERDECK_AUTH_TOKEN: authToken,\n OVERDECK_MODE: \"desktop\",\n OVERDECK_NO_BROWSER: \"1\",\n // Terminal settings for Claude Code / tmux rendering\n TERM: process.env.TERM || \"xterm-256color\",\n COLORTERM: process.env.COLORTERM || \"truecolor\",\n LANG: process.env.LANG || \"en_US.UTF-8\",\n // process.execPath is the Electron binary; without this flag a\n // packaged app ignores [entry] and boots the full desktop app again,\n // fork-bombing the machine (PAN-2559).\n ELECTRON_RUN_AS_NODE: \"1\",\n } as NodeJS.ProcessEnv,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n },\n );\n\n serverProcess = child;\n\n child.stdout?.on(\"data\", (chunk: Buffer) => {\n process.stdout.write(`[server] ${chunk}`);\n });\n child.stderr?.on(\"data\", (chunk: Buffer) => {\n process.stderr.write(`[server] ${chunk}`);\n });\n\n child.on(\"spawn\", () => {\n console.log(`[desktop/server] spawned pid=${child.pid} port=${port}`);\n // Wait for server to be listening before signalling ready\n waitForServer(`http://127.0.0.1:${port}`, (healthy) => {\n console.log(`[desktop/server] ready on port ${port} (healthy=${String(healthy)})`);\n if (healthy) {\n // A confirmed-healthy boot resets the crash budget so the cap only\n // counts consecutive failures.\n restartAttempt = 0;\n }\n onReadyCallback?.(port, `ws://127.0.0.1:${port}`);\n });\n });\n\n child.on(\"exit\", (code, signal) => {\n serverProcess = null;\n console.warn(`[desktop/server] exited code=${String(code)} signal=${String(signal)}`);\n if (!quitting) {\n scheduleRestart();\n }\n });\n\n child.on(\"error\", (err) => {\n console.error(\"[desktop/server] spawn error:\", err);\n serverProcess = null;\n if (!quitting) scheduleRestart();\n });\n}\n\nfunction waitForServer(url: string, callback: (healthy: boolean) => void, maxMs = 30_000): void {\n // Only one poller may be live: a crash-restart spawns a new child on a new\n // port, and a stale poller's late callback would clobber serverUrl with the\n // dead port (and, pre-PAN-2570, open another window).\n if (waitInterval) clearInterval(waitInterval);\n const start = Date.now();\n const interval = setInterval(() => {\n if (Date.now() - start > maxMs) {\n clearInterval(interval);\n if (waitInterval === interval) waitInterval = null;\n callback(false); // call anyway — server might still come up\n return;\n }\n fetch(url + \"/api/health\", { signal: AbortSignal.timeout(1_000) })\n .then((r) => {\n if (r.ok) {\n clearInterval(interval);\n if (waitInterval === interval) waitInterval = null;\n callback(true);\n }\n })\n .catch(() => {\n /* not ready yet */\n });\n }, 500);\n waitInterval = interval;\n}\n\nfunction scheduleRestart(): void {\n if (quitting) return;\n restartAttempt++;\n if (restartAttempt > MAX_RESTART_ATTEMPTS) {\n console.error(\n `[desktop/server] server failed ${MAX_RESTART_ATTEMPTS} consecutive times — giving up. ` +\n \"Use the app menu's dashboard restart to retry.\",\n );\n return;\n }\n const delay = Math.min(1_000 * Math.pow(2, restartAttempt - 1), MAX_RESTART_DELAY_MS);\n console.log(`[desktop/server] restarting in ${delay}ms (attempt ${restartAttempt})`);\n restartTimer = setTimeout(() => {\n restartTimer = null;\n spawnServer();\n }, delay);\n}\n\n// ─── Graceful Shutdown ────────────────────────────────────────────────────────\n\nexport function stopServer(): void {\n quitting = true;\n\n if (restartTimer) {\n clearTimeout(restartTimer);\n restartTimer = null;\n }\n\n if (waitInterval) {\n clearInterval(waitInterval);\n waitInterval = null;\n }\n\n const child = serverProcess;\n if (!child) return;\n\n serverProcess = null;\n child.kill(\"SIGTERM\");\n\n // Force-kill if it doesn't exit within grace period\n const forceKill = setTimeout(() => {\n try {\n child.kill(\"SIGKILL\");\n } catch {\n /* already dead */\n }\n }, SIGTERM_GRACE_MS);\n forceKill.unref();\n}\n","/** Native desktop updater backed by electron-updater and exact GitHub releases. */\n\nimport { app, BrowserWindow } from 'electron';\nimport { autoUpdater, type UpdateInfo } from 'electron-updater';\nimport type { UpdateChannel, UpdateSnapshot } from '@overdeck/contracts';\n\n// Keep the Electron entrypoint small: importing the contracts runtime would\n// bundle every schema and Effect dependency. The release-manifest verifier\n// guards these literals against packages/contracts/src/update.ts.\nconst OVERDECK_DASHBOARD_PROTOCOL_VERSION = 1;\nconst OVERDECK_AGENT_PROTOCOL_VERSION = 1;\n\nconst FOUR_HOURS_MS = 4 * 60 * 60 * 1000;\nconst DOWNLOAD_RETRY_MS = [0, 1_000, 4_000] as const;\nconst RELEASES_URL = 'https://api.github.com/repos/eltmon/overdeck/releases';\n\nlet checkIntervalId: ReturnType<typeof setInterval> | null = null;\nlet initialized = false;\nlet channel: UpdateChannel = 'stable';\n\nexport let currentStatus: UpdateSnapshot = {\n phase: 'idle',\n installMode: 'desktop',\n channel: 'stable',\n currentVersion: app.getVersion(),\n targetVersion: null,\n releaseName: null,\n releaseNotes: null,\n releaseUrl: null,\n publishedAt: null,\n progressPercent: null,\n error: null,\n lastCheckedAt: null,\n compatibility: 'unknown',\n currentDashboardProtocol: OVERDECK_DASHBOARD_PROTOCOL_VERSION,\n currentAgentProtocol: OVERDECK_AGENT_PROTOCOL_VERSION,\n targetDashboardProtocol: null,\n targetAgentProtocol: null,\n};\n\ntype UpdateStatusCallback = (status: UpdateSnapshot) => void;\nconst statusCallbacks: UpdateStatusCallback[] = [];\n\nexport function onUpdateStatusChange(callback: UpdateStatusCallback): () => void {\n statusCallbacks.push(callback);\n return () => {\n const index = statusCallbacks.indexOf(callback);\n if (index >= 0) statusCallbacks.splice(index, 1);\n };\n}\n\nfunction publish(patch: Partial<UpdateSnapshot>): void {\n currentStatus = { ...currentStatus, ...patch };\n for (const callback of statusCallbacks) callback(currentStatus);\n for (const win of BrowserWindow.getAllWindows()) {\n if (!win.isDestroyed()) win.webContents.send('update-status', currentStatus);\n }\n}\n\nfunction manifestName(): string {\n const prefix = channel === 'canary' ? 'beta' : 'latest';\n if (process.platform === 'darwin') return `${prefix}-mac.yml`;\n if (process.platform === 'linux') return `${prefix}-linux.yml`;\n return `${prefix}.yml`;\n}\n\nasync function configureExactReleaseFeed(): Promise<void> {\n const response = await fetch(RELEASES_URL, { headers: { accept: 'application/vnd.github+json' }, signal: AbortSignal.timeout(10_000) });\n if (!response.ok) throw new Error(`GitHub Releases returned ${response.status}`);\n const releases = await response.json() as Array<{\n tag_name: string;\n name?: string;\n body?: string;\n html_url?: string;\n published_at?: string;\n prerelease?: boolean;\n assets?: Array<{ name: string; browser_download_url: string }>;\n }>;\n const release = releases.find((item) => channel === 'canary' ? item.prerelease : !item.prerelease);\n if (!release) throw new Error(`No ${channel} desktop release is published`);\n const manifest = release.assets?.find((asset) => asset.name === manifestName());\n if (!manifest) throw new Error(`${release.tag_name} is missing ${manifestName()}`);\n\n const manifestResponse = await fetch(manifest.browser_download_url, { signal: AbortSignal.timeout(10_000) });\n if (!manifestResponse.ok) throw new Error(`Update manifest returned ${manifestResponse.status}`);\n const manifestText = await manifestResponse.text();\n const dashboardProtocol = Number(manifestText.match(/^overdeckDashboardProtocol:\\s*(\\d+)/m)?.[1]) || null;\n const agentProtocol = Number(manifestText.match(/^overdeckAgentProtocol:\\s*(\\d+)/m)?.[1]) || null;\n\n publish({\n releaseName: release.name ?? `Overdeck ${release.tag_name}`,\n releaseNotes: release.body ?? 'Release notes are not available for this release.',\n releaseUrl: release.html_url ?? null,\n publishedAt: release.published_at ?? null,\n targetDashboardProtocol: dashboardProtocol,\n targetAgentProtocol: agentProtocol,\n compatibility: dashboardProtocol === OVERDECK_DASHBOARD_PROTOCOL_VERSION && agentProtocol === OVERDECK_AGENT_PROTOCOL_VERSION ? 'compatible' : 'unknown',\n });\n autoUpdater.setFeedURL({\n provider: 'generic',\n url: `https://github.com/eltmon/overdeck/releases/download/${release.tag_name}`,\n });\n autoUpdater.channel = channel === 'canary' ? 'beta' : 'latest';\n}\n\nexport function initializeAutoUpdater(requestedChannel: string = 'latest'): void {\n if (initialized) return;\n initialized = true;\n channel = requestedChannel === 'canary' || requestedChannel === 'beta' ? 'canary' : 'stable';\n publish({ channel });\n autoUpdater.autoDownload = false;\n autoUpdater.autoInstallOnAppQuit = false;\n\n autoUpdater.on('checking-for-update', () => publish({ phase: 'checking', error: null }));\n autoUpdater.on('update-available', (info: UpdateInfo) => publish({\n phase: 'available', targetVersion: info.version, progressPercent: null, error: null, lastCheckedAt: new Date().toISOString(),\n }));\n autoUpdater.on('update-not-available', (info: UpdateInfo) => publish({\n phase: 'current', targetVersion: info.version, progressPercent: null, error: null, lastCheckedAt: new Date().toISOString(),\n }));\n autoUpdater.on('download-progress', (progress) => publish({\n phase: 'downloading', progressPercent: Math.round(progress.percent * 10) / 10,\n }));\n autoUpdater.on('update-downloaded', (info: UpdateInfo) => publish({\n phase: 'ready', targetVersion: info.version, progressPercent: 100, error: null,\n }));\n autoUpdater.on('error', (error: Error) => publish({ phase: 'error', error: error.message }));\n\n setTimeout(() => { void checkForUpdates(); }, 5_000);\n startPeriodicChecks();\n}\n\nexport function startPeriodicChecks(): void {\n checkIntervalId ??= setInterval(() => { void checkForUpdates(); }, FOUR_HOURS_MS);\n}\n\nexport function stopPeriodicChecks(): void {\n if (checkIntervalId) clearInterval(checkIntervalId);\n checkIntervalId = null;\n}\n\nexport async function checkForUpdates(): Promise<UpdateSnapshot> {\n if (currentStatus.phase === 'checking') return getUpdateStatus();\n publish({ phase: 'checking', error: null });\n try {\n await configureExactReleaseFeed();\n await autoUpdater.checkForUpdates();\n } catch (error) {\n publish({ phase: 'error', error: error instanceof Error ? error.message : String(error), lastCheckedAt: new Date().toISOString() });\n }\n return getUpdateStatus();\n}\n\nexport async function downloadUpdate(): Promise<UpdateSnapshot> {\n if (currentStatus.phase !== 'available') return getUpdateStatus();\n for (let attempt = 0; attempt < DOWNLOAD_RETRY_MS.length; attempt += 1) {\n const delay = DOWNLOAD_RETRY_MS[attempt] ?? 0;\n if (delay > 0) await new Promise((resolve) => setTimeout(resolve, delay));\n try {\n publish({ phase: 'downloading', error: null, progressPercent: 0 });\n await autoUpdater.downloadUpdate();\n return getUpdateStatus();\n } catch (error) {\n if (attempt === DOWNLOAD_RETRY_MS.length - 1) {\n publish({ phase: 'error', error: error instanceof Error ? error.message : String(error) });\n }\n }\n }\n return getUpdateStatus();\n}\n\nexport function quitAndInstall(): void {\n if (currentStatus.phase === 'ready') autoUpdater.quitAndInstall(false, true);\n}\n\nexport function getUpdateStatus(): UpdateSnapshot {\n return { ...currentStatus };\n}\n","/**\n * Application menu bar for the Overdeck desktop app.\n *\n * Standard Electron menus: File, Edit, View, Window, Help\n * Plus a Overdeck menu with all orchestration actions.\n *\n * macOS: app name menu with About, Settings, Services etc.\n * Linux/Windows: Settings in File menu.\n */\n\nimport { app, Menu, shell } from \"electron\";\nimport type { MenuItemConstructorOptions } from \"electron\";\n\nimport { callServerApi, showOrCreateWindow, dispatchMenuAction, serverUrl } from \"./main.js\";\nimport { quitAndInstall, onUpdateStatusChange } from \"./updater.js\";\n\n// ─── Workspace submenu (refreshed on open) ────────────────────────────────────\n\ninterface WorkspaceSummary {\n issueId: string;\n title?: string;\n}\n\nasync function fetchActiveWorkspaces(): Promise<WorkspaceSummary[]> {\n if (!serverUrl) return [];\n try {\n const resp = await fetch(`${serverUrl}/api/workspaces`, { signal: AbortSignal.timeout(2_000) });\n if (!resp.ok) return [];\n const data = (await resp.json()) as { workspaces?: WorkspaceSummary[] };\n return data.workspaces?.slice(0, 10) ?? [];\n } catch {\n return [];\n }\n}\n\n// ─── Update menu state ─────────────────────────────────────────────────────────\n\nlet updateDownloaded = false;\n\nfunction rebuildMenu(): void {\n const menu = Menu.buildFromTemplate(buildMenuTemplate());\n Menu.setApplicationMenu(menu);\n\n // Re-attach workspace submenu refresh listener\n const overdeckMenu = menu.items.find((item) => item.label === \"Overdeck\");\n if (overdeckMenu?.submenu) {\n overdeckMenu.submenu.on(\"menu-will-show\", () => {\n void fetchActiveWorkspaces().then((workspaces) => {\n const wsItem = overdeckMenu.submenu?.items.find(\n (i) => i.id === \"open-workspace-submenu\",\n );\n if (wsItem) {\n wsItem.label = workspaces.length\n ? `Open Workspace (${workspaces.length})`\n : \"Open Workspace\";\n }\n });\n });\n }\n}\n\nfunction buildWorkspaceSubmenu(workspaces: WorkspaceSummary[]): MenuItemConstructorOptions[] {\n if (workspaces.length === 0) {\n return [{ label: \"No active workspaces\", enabled: false }];\n }\n return workspaces.map((ws) => ({\n label: ws.title ? `${ws.issueId} — ${ws.title}` : ws.issueId,\n click: () => {\n showOrCreateWindow();\n dispatchMenuAction(`open-workspace:${ws.issueId}`);\n },\n }));\n}\n\n// ─── Menu template ────────────────────────────────────────────────────────────\n\nfunction buildMenuTemplate(): MenuItemConstructorOptions[] {\n const template: MenuItemConstructorOptions[] = [];\n\n // macOS: app menu (first menu = app name on macOS)\n if (process.platform === \"darwin\") {\n template.push({\n label: app.name,\n submenu: [\n { role: \"about\" },\n { type: \"separator\" },\n {\n label: \"Settings...\",\n accelerator: \"CmdOrCtrl+,\",\n click: () => {\n showOrCreateWindow();\n dispatchMenuAction(\"open-settings\");\n },\n },\n { type: \"separator\" },\n { role: \"services\" },\n { type: \"separator\" },\n { role: \"hide\" },\n { role: \"hideOthers\" },\n { role: \"unhide\" },\n { type: \"separator\" },\n { role: \"quit\" },\n ],\n });\n }\n\n // File menu\n template.push({\n label: \"File\",\n submenu: [\n ...(process.platform !== \"darwin\"\n ? [\n {\n label: \"Settings...\",\n accelerator: \"CmdOrCtrl+,\",\n click: () => {\n showOrCreateWindow();\n dispatchMenuAction(\"open-settings\");\n },\n },\n { type: \"separator\" as const },\n ]\n : []),\n { role: process.platform === \"darwin\" ? (\"close\" as const) : (\"quit\" as const) },\n ],\n });\n\n // Edit\n template.push({ role: \"editMenu\" as const });\n\n // View\n template.push({\n label: \"View\",\n submenu: [\n { role: \"reload\" as const },\n { role: \"forceReload\" as const },\n { role: \"toggleDevTools\" as const },\n { type: \"separator\" as const },\n { role: \"resetZoom\" as const },\n { role: \"zoomIn\" as const, accelerator: \"CmdOrCtrl+=\" },\n { role: \"zoomOut\" as const },\n { type: \"separator\" as const },\n { role: \"togglefullscreen\" as const },\n ],\n });\n\n // Window\n template.push({ role: \"windowMenu\" as const });\n\n // Overdeck\n template.push({\n label: \"Overdeck\",\n submenu: [\n {\n label: \"Start Cloister\",\n click: () => callServerApi(\"/api/cloister/start\", \"POST\"),\n },\n {\n label: \"Stop Cloister\",\n click: () => callServerApi(\"/api/cloister/stop\", \"POST\"),\n },\n {\n label: \"Emergency Stop All Agents\",\n click: () => callServerApi(\"/api/agents/emergency-stop\", \"POST\"),\n },\n { type: \"separator\" },\n {\n label: \"Open Workspace\",\n id: \"open-workspace-submenu\",\n submenu: [{ label: \"Loading...\", enabled: false }],\n },\n { type: \"separator\" },\n {\n label: \"Settings...\",\n accelerator: process.platform !== \"darwin\" ? \"CmdOrCtrl+,\" : undefined,\n click: () => {\n showOrCreateWindow();\n dispatchMenuAction(\"open-settings\");\n },\n },\n ],\n });\n\n // Help\n template.push({\n role: \"help\" as const,\n submenu: [\n {\n label: \"Check for Updates...\",\n click: () => {\n showOrCreateWindow();\n dispatchMenuAction(\"open-updater\");\n },\n },\n { type: \"separator\" },\n {\n label: \"Flywheel Documentation\",\n click: () =>\n void shell.openExternal(\n \"https://github.com/eltmon/overdeck/blob/main/docs/FLYWHEEL.md\",\n ),\n },\n {\n label: \"Overdeck on GitHub\",\n click: () =>\n void shell.openExternal(\"https://github.com/eltmon/overdeck\"),\n },\n {\n label: \"Report an Issue\",\n click: () =>\n void shell.openExternal(\"https://github.com/eltmon/overdeck/issues\"),\n },\n ],\n });\n\n // Add \"Install Update and Restart\" item if update is downloaded\n if (updateDownloaded) {\n const helpMenu = template[template.length - 1];\n if (helpMenu && helpMenu.submenu && Array.isArray(helpMenu.submenu)) {\n helpMenu.submenu.push(\n { type: \"separator\" },\n {\n label: \"Install Update and Restart\",\n click: () => {\n quitAndInstall();\n },\n },\n );\n }\n }\n\n return template;\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\nexport function configureApplicationMenu(): void {\n const menu = Menu.buildFromTemplate(buildMenuTemplate());\n Menu.setApplicationMenu(menu);\n\n // Refresh workspace submenu when the Overdeck menu is about to open\n const overdeckMenu = menu.items.find((item) => item.label === \"Overdeck\");\n if (overdeckMenu?.submenu) {\n overdeckMenu.submenu.on(\"menu-will-show\", () => {\n void fetchActiveWorkspaces().then((workspaces) => {\n const wsItem = overdeckMenu.submenu?.items.find(\n (i) => i.id === \"open-workspace-submenu\",\n );\n if (wsItem) {\n // Electron doesn't support dynamic submenu replacement after build,\n // so we dispatch a menu action that opens a workspace picker in the renderer\n wsItem.label = workspaces.length\n ? `Open Workspace (${workspaces.length})`\n : \"Open Workspace\";\n }\n });\n });\n }\n\n // Listen for update status changes to rebuild menu when update is downloaded\n onUpdateStatusChange((status) => {\n if (status.phase === 'ready' && !updateDownloaded) {\n updateDownloaded = true;\n rebuildMenu();\n }\n });\n}\n","/**\n * Native desktop notifications for Overdeck events.\n *\n * Event types and their default enabled state (all configurable in Settings):\n * inputNeeded — Agent needs user input (default: on)\n * stuckAgents — Agent has been stuck > threshold (default: on)\n * mergeFailures — Merge specialist failed (default: on)\n * workComplete — Agent signalled work done (default: on)\n * planningDone — Planning session complete (default: off)\n * mergeReady — PR ready for human merge (default: on)\n *\n * Notifications are sent from:\n * 1. Renderer → main via IPC (pan:notify) — for events the frontend detects\n * 2. Main process directly — in future when subscribing to domain events via WS\n */\n\nimport { app, ipcMain, Notification } from \"electron\";\n\nimport { IPC } from \"./main.js\";\nimport { showOrCreateWindow } from \"./main.js\";\nimport { getDesktopSettings } from \"./settings.js\";\nimport { resolveResourcePath } from \"./main.js\";\nimport type { NotificationEventType } from \"./settings.js\";\n\n// ─── Send notification ────────────────────────────────────────────────────────\n\nexport function sendNotification(\n eventType: NotificationEventType,\n title: string,\n body: string,\n): void {\n const settings = getDesktopSettings();\n if (!settings.notifications[eventType]) return;\n\n if (!Notification.isSupported()) return;\n\n const iconPath = resolveResourcePath(\"icon.png\");\n\n const notification = new Notification({\n title,\n body,\n icon: iconPath ?? undefined,\n silent: false,\n });\n\n notification.on(\"click\", () => showOrCreateWindow());\n notification.show();\n}\n\n// ─── IPC handler registration ─────────────────────────────────────────────────\n\nexport function registerNotificationHandlers(): void {\n ipcMain.handle(IPC.NOTIFY, (_event, eventType: unknown, title: unknown, body: unknown) => {\n if (\n typeof eventType === \"string\" &&\n typeof title === \"string\" &&\n typeof body === \"string\"\n ) {\n sendNotification(eventType as NotificationEventType, title, body);\n }\n });\n}\n\n// ─── App lifecycle integration ────────────────────────────────────────────────\n\n/**\n * Called at app.ready to request notification permission on macOS.\n * On Linux/Windows, Notification.isSupported() handles availability.\n */\nexport function initializeNotifications(): void {\n if (!Notification.isSupported()) {\n console.log(\"[desktop/notifications] native notifications not supported on this platform\");\n }\n}\n","/**\n * Auto-start nag flow for the Overdeck desktop app.\n *\n * Flow:\n * Launch 1: Full explanation dialog (warm, inviting)\n * Launch 2-5: In-app toasts dispatched to renderer via \"auto-start-nag:<n>:<max>\" menu action\n * - Shows: \"Reminder N of 5\", Enable button (prominent), Not Yet, Stop Reminding Me\n * After 5 or \"Stop reminding me\": never prompt again (nagDismissed = true)\n * Once enabled: auto-start registered via app.setLoginItemSettings\n *\n * State tracked in DesktopSettings.autoStart via settings.ts.\n */\n\nimport { app, dialog } from \"electron\";\n\nimport { getDesktopSettings, updateDesktopSetting } from \"./settings.js\";\nimport { dispatchMenuAction } from \"./main.js\";\n\nconst NAG_MAX = 5;\n\n// ─── Enable auto-start ────────────────────────────────────────────────────────\n\nexport function enableAutoStart(): void {\n updateDesktopSetting(\"autoStart.enabled\", true);\n updateDesktopSetting(\"autoStart.nagDismissed\", true);\n app.setLoginItemSettings({ openAtLogin: true });\n}\n\nexport function dismissAutoStartNag(): void {\n updateDesktopSetting(\"autoStart.nagDismissed\", true);\n}\n\n// ─── Nag flow ─────────────────────────────────────────────────────────────────\n\nexport function handleAutoStartNag(): void {\n const { autoStart } = getDesktopSettings();\n\n if (autoStart.enabled || autoStart.nagDismissed) return;\n if (autoStart.nagCount >= NAG_MAX) {\n updateDesktopSetting(\"autoStart.nagDismissed\", true);\n return;\n }\n\n const count = autoStart.nagCount + 1;\n updateDesktopSetting(\"autoStart.nagCount\", count);\n\n if (count === 1) {\n // First launch: native dialog\n showFirstLaunchDialog();\n } else {\n // Subsequent launches: in-app toast dispatched to renderer\n // Dispatched with delay to let the window finish loading\n setTimeout(() => {\n dispatchMenuAction(`auto-start-nag:${count}:${NAG_MAX}`);\n }, 3_500);\n }\n}\n\nfunction showFirstLaunchDialog(): void {\n // Delay until window is ready\n setTimeout(() => {\n void dialog\n .showMessageBox({\n type: \"info\",\n title: \"Start Overdeck automatically?\",\n message: \"Keep an eye on your agents — even when you forget to open the app.\",\n detail:\n \"Overdeck can start automatically when you log in, so you never miss an agent \" +\n \"asking for help or a merge that's ready to ship.\\n\\n\" +\n \"You can change this at any time in Settings → Desktop → Auto-start.\",\n buttons: [\"Enable Auto-start\", \"Not Yet\"],\n defaultId: 0,\n cancelId: 1,\n })\n .then(({ response }) => {\n if (response === 0) {\n enableAutoStart();\n }\n });\n }, 2_000);\n}\n","/**\n * overdeck:// custom protocol for serving static frontend assets in packaged builds.\n *\n * In dev mode:\n * BrowserWindow loads from Vite dev server URL (VITE_DEV_SERVER_URL env var).\n * HMR and source maps work normally.\n *\n * In packaged builds:\n * BrowserWindow loads overdeck://app/index.html.\n * This protocol handler serves files from the bundled dist/dashboard/public/.\n * WebSocket connections (ws/rpc, ws/terminal) go to the embedded server port\n * on localhost — the protocol handler only serves static assets.\n *\n * Security:\n * - Path traversal protection: rejects paths containing \"..\"\n * - Only files within the static root are served\n * - Non-existent asset requests return -6 (net::ERR_FILE_NOT_FOUND)\n * - HTML routes (no extension) fall back to index.html for SPA routing\n *\n * CSP:\n * Renderer CSP is configured via vite.config.ts in the frontend.\n * In packaged mode, connect-src must include ws://127.0.0.1:* for WebSocket.\n */\n\nimport * as FS from \"node:fs\";\nimport * as Path from \"node:path\";\n\nimport { protocol } from \"electron\";\n\nimport { DESKTOP_SCHEME, resolveServerStaticDir } from \"./main.js\";\n\nlet registered = false;\n\n// ─── Path resolution ──────────────────────────────────────────────────────────\n\nexport function resolveStaticPath(staticRoot: string, requestUrl: string): string {\n const fallbackIndex = Path.join(staticRoot, \"index.html\");\n\n let url: URL;\n try {\n url = new URL(requestUrl);\n } catch {\n return fallbackIndex;\n }\n\n const rawPath = decodeURIComponent(url.pathname);\n const normalized = Path.posix.normalize(rawPath).replace(/^\\/+/, \"\");\n\n if (normalized.includes(\"..\")) {\n return fallbackIndex;\n }\n\n const requestedPath = normalized.length > 0 ? normalized : \"index.html\";\n const resolved = Path.resolve(staticRoot, requestedPath);\n\n const staticRootResolved = Path.resolve(staticRoot);\n const inRoot =\n resolved === staticRootResolved ||\n resolved.startsWith(staticRootResolved + Path.sep);\n\n if (!inRoot) return fallbackIndex;\n\n // If path has a file extension, serve it (or 404)\n if (Path.extname(resolved)) {\n return resolved;\n }\n\n // SPA route: check for index.html in subdirectory, else root index\n const nestedIndex = Path.join(resolved, \"index.html\");\n if (FS.existsSync(nestedIndex)) return nestedIndex;\n\n return fallbackIndex;\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Register the overdeck:// protocol handler.\n * Must be called after app.ready (but registration via registerSchemesAsPrivileged\n * must happen before app.ready — done in main.ts).\n */\nexport function registerDesktopProtocol(): void {\n if (registered) return;\n\n const staticRoot = resolveServerStaticDir();\n if (!staticRoot) {\n console.error(\n \"[desktop/protocol] Static bundle not found — packaged frontend assets missing. \" +\n \"Run 'npm run build:dashboard' first.\",\n );\n return;\n }\n\n const fallbackIndex = Path.join(staticRoot, \"index.html\");\n\n protocol.registerFileProtocol(DESKTOP_SCHEME, (request, callback) => {\n const candidate = resolveStaticPath(staticRoot, request.url);\n const hasExt = Path.extname(candidate).length > 0;\n\n if (!FS.existsSync(candidate)) {\n if (hasExt) {\n // Asset not found — return 404\n callback({ error: -6 /* net::ERR_FILE_NOT_FOUND */ });\n } else {\n // SPA route — fall back to index\n callback({ path: fallbackIndex });\n }\n return;\n }\n\n callback({ path: candidate });\n });\n\n registered = true;\n console.log(`[desktop/protocol] registered ${DESKTOP_SCHEME}:// serving from ${staticRoot}`);\n}\n","import * as FS from \"node:fs\";\nimport * as OS from \"node:os\";\nimport * as Path from \"node:path\";\n\nimport {\n app,\n BrowserWindow,\n ipcMain,\n protocol,\n shell,\n} from \"electron\";\n\nimport { createTray, destroyTray } from \"./tray.js\";\nimport { loadDesktopSettings, getDesktopSettings, updateDesktopSetting } from \"./settings.js\";\nimport { startServer, stopServer } from \"./server.js\";\nimport { configureApplicationMenu } from \"./menu.js\";\nimport { initializeNotifications, registerNotificationHandlers } from \"./notifications.js\";\nimport { handleAutoStartNag } from \"./autostart.js\";\nimport { registerDesktopProtocol } from \"./protocol.js\";\nimport { initializeAutoUpdater, checkForUpdates, downloadUpdate, quitAndInstall, getUpdateStatus, onUpdateStatusChange } from \"./updater.js\";\n\n// ─── Constants ───────────────────────────────────────────────────────────────\n\nconst ROOT_DIR = Path.resolve(__dirname, \"../../..\");\nconst isDevelopment = Boolean(process.env.VITE_DEV_SERVER_URL);\nconst APP_DISPLAY_NAME = isDevelopment ? \"Overdeck (Dev)\" : \"Overdeck\";\nconst APP_ID = \"com.overdeck.app\";\nconst LINUX_WM_CLASS = isDevelopment ? \"overdeck-dev\" : \"overdeck\";\nexport const DESKTOP_SCHEME = \"overdeck\";\n\n// IPC channel names\nexport const IPC = {\n GET_SERVER_URL: \"pan:get-server-url\",\n GET_WS_URL: \"pan:get-ws-url\",\n PICK_FOLDER: \"pan:pick-folder\",\n OPEN_EXTERNAL: \"pan:open-external\",\n OPEN_TERMINAL_WINDOW: \"pan:open-terminal-window\",\n SET_ALWAYS_ON_TOP: \"pan:set-always-on-top\",\n MENU_ACTION: \"pan:menu-action\",\n GET_DESKTOP_SETTINGS: \"pan:get-desktop-settings\",\n UPDATE_DESKTOP_SETTING: \"pan:update-desktop-setting\",\n NOTIFY: \"pan:notify\",\n GET_UPDATE_STATUS: \"pan:get-update-status\",\n CHECK_FOR_UPDATES: \"pan:check-for-updates\",\n DOWNLOAD_UPDATE: \"pan:download-update\",\n QUIT_AND_INSTALL: \"pan:quit-and-install\",\n RESTART_DASHBOARD: \"pan:restart-dashboard\",\n} as const;\n\n// ─── State ────────────────────────────────────────────────────────────────────\n\nlet mainWindow: BrowserWindow | null = null;\nexport let serverPort = 0;\nexport let serverUrl = \"\";\nexport let serverWsUrl = \"\";\nexport let isQuitting = false;\nconst terminalWindows = new Map<string, BrowserWindow>();\n\n// ─── Single instance lock ─────────────────────────────────────────────────────\n// A second launch must not boot another full app (window, tray, embedded\n// server). Duplicates exit immediately and the running instance is focused\n// via the second-instance event below (PAN-2559).\n\nif (!app.requestSingleInstanceLock()) {\n app.exit(0);\n}\n\napp.on(\"second-instance\", () => showOrCreateWindow());\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nexport function resolveResourcePath(fileName: string): string | null {\n const candidates = [\n // Packaged (electron-builder): resources are in process.resourcesPath/resources/\n Path.join(process.resourcesPath ?? \"\", \"resources\", fileName),\n // npx / global-install: resources are bundled at <package>/resources/\n Path.join(__dirname, \"../resources\", fileName),\n // Dev: resources are in the monorepo\n Path.join(ROOT_DIR, \"apps/desktop/resources\", fileName),\n ];\n for (const candidate of candidates) {\n if (FS.existsSync(candidate)) return candidate;\n }\n return null;\n}\n\nexport function resolveServerEntry(): string {\n if (app.isPackaged) {\n return Path.join(process.resourcesPath ?? \"\", \"server/server.js\");\n }\n // npx / global-install: server bundle is at <package>/server/server.js\n const npxBundle = Path.join(__dirname, \"../server/server.js\");\n if (FS.existsSync(npxBundle)) return npxBundle;\n // Dev mode: server is built in the monorepo root\n return Path.join(ROOT_DIR, \"dist/dashboard/server.js\");\n}\n\nexport function resolveServerStaticDir(): string | null {\n const candidates: string[] = [];\n if (app.isPackaged) {\n candidates.push(Path.join(process.resourcesPath ?? \"\", \"server/public\"));\n } else {\n // npx / global-install: static assets are at <package>/server/public\n candidates.push(Path.join(__dirname, \"../server/public\"));\n // Dev mode fallback\n candidates.push(Path.join(ROOT_DIR, \"dist/dashboard/public\"));\n }\n for (const candidate of candidates) {\n if (FS.existsSync(Path.join(candidate, \"index.html\"))) {\n return candidate;\n }\n }\n return null;\n}\n\nexport function resolveWindowUrl(): string {\n if (isDevelopment) {\n return process.env.VITE_DEV_SERVER_URL!;\n }\n // Serve the window from the embedded server's HTTP origin so the frontend's\n // relative /api fetches and /ws/* sockets are same-origin, exactly like the\n // browser dashboard. The overdeck:// protocol page has no working data\n // layer (relative fetches resolve to the app bundle, PAN-2561) — it is only\n // the pre-server fallback.\n if (serverUrl) {\n return serverUrl;\n }\n return `${DESKTOP_SCHEME}://app/index.html`;\n}\n\nfunction createTerminalWindow(sessionName: string, title: string): BrowserWindow {\n const win = new BrowserWindow({\n width: 900,\n height: 650,\n title,\n webPreferences: {\n preload: Path.join(__dirname, \"preload.js\"),\n contextIsolation: true,\n nodeIntegration: false,\n sandbox: false,\n },\n show: false,\n });\n\n win.once(\"ready-to-show\", () => win.show());\n void win.loadURL(`${resolveWindowUrl()}?terminal=${encodeURIComponent(sessionName)}&title=${encodeURIComponent(title)}`);\n return win;\n}\n\n// ─── IPC: basic handlers ──────────────────────────────────────────────────────\n\nfunction registerIpcHandlers(): void {\n ipcMain.on(IPC.GET_SERVER_URL, (event) => {\n event.returnValue = serverUrl;\n });\n\n ipcMain.on(IPC.GET_WS_URL, (event) => {\n event.returnValue = serverWsUrl;\n });\n\n ipcMain.handle(IPC.PICK_FOLDER, async () => {\n const { dialog } = await import(\"electron\");\n const result = await dialog.showOpenDialog({ properties: [\"openDirectory\"] });\n return result.canceled ? null : (result.filePaths[0] ?? null);\n });\n\n ipcMain.handle(IPC.OPEN_EXTERNAL, async (_event, url: unknown) => {\n if (typeof url !== \"string\") return;\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n return;\n }\n if (parsed.protocol !== \"https:\" && parsed.protocol !== \"http:\") return;\n await shell.openExternal(parsed.toString());\n });\n\n ipcMain.handle(IPC.GET_DESKTOP_SETTINGS, () => getDesktopSettings());\n\n ipcMain.on(IPC.OPEN_TERMINAL_WINDOW, (_event, sessionName: unknown, title: unknown) => {\n if (typeof sessionName !== \"string\" || typeof title !== \"string\") return;\n\n const existing = terminalWindows.get(sessionName);\n if (existing && !existing.isDestroyed()) {\n if (!existing.isVisible()) existing.show();\n existing.focus();\n return;\n }\n\n const win = createTerminalWindow(sessionName, title);\n terminalWindows.set(sessionName, win);\n win.on(\"closed\", () => terminalWindows.delete(sessionName));\n });\n\n ipcMain.on(IPC.SET_ALWAYS_ON_TOP, (_event, value: unknown) => {\n const focused = BrowserWindow.getFocusedWindow();\n if (focused) {\n focused.setAlwaysOnTop(value === true);\n }\n });\n\n ipcMain.handle(IPC.UPDATE_DESKTOP_SETTING, (_event, key: unknown, value: unknown) => {\n if (typeof key !== \"string\") return;\n const updated = updateDesktopSetting(key, value);\n // Apply auto-start setting immediately\n if (updated && key === \"autoStart.enabled\") {\n app.setLoginItemSettings({ openAtLogin: value === true });\n }\n });\n\n // Update IPC handlers\n ipcMain.handle(IPC.GET_UPDATE_STATUS, () => getUpdateStatus());\n\n ipcMain.handle(IPC.CHECK_FOR_UPDATES, async () => {\n try {\n await checkForUpdates();\n } catch (err) {\n console.error(\"[main] checkForUpdates failed:\", err);\n }\n return getUpdateStatus();\n });\n\n ipcMain.handle(IPC.DOWNLOAD_UPDATE, async () => {\n try {\n await downloadUpdate();\n } catch (err) {\n console.error(\"[main] downloadUpdate failed:\", err);\n }\n return getUpdateStatus();\n });\n\n ipcMain.on(IPC.QUIT_AND_INSTALL, () => {\n quitAndInstall();\n });\n\n ipcMain.handle(IPC.RESTART_DASHBOARD, async () => {\n console.log(\"[main] manual dashboard restart requested\");\n stopServer();\n // Brief delay so SIGTERM has a chance to land before we re-spawn.\n await new Promise((resolve) => setTimeout(resolve, 500));\n startServer((port, wsUrl) => {\n serverPort = port;\n serverUrl = `http://127.0.0.1:${port}`;\n serverWsUrl = wsUrl;\n console.log(`[main] dashboard restarted on ${serverUrl}`);\n });\n });\n}\n\n// ─── Window ───────────────────────────────────────────────────────────────────\n\nexport function createWindow(): BrowserWindow {\n const win = new BrowserWindow({\n width: 1400,\n height: 900,\n minWidth: 900,\n minHeight: 600,\n title: APP_DISPLAY_NAME,\n webPreferences: {\n preload: Path.join(__dirname, \"preload.js\"),\n contextIsolation: true,\n nodeIntegration: false,\n sandbox: false,\n },\n show: false,\n });\n\n win.once(\"ready-to-show\", () => win.show());\n\n win.on(\"close\", (event) => {\n if (!isQuitting && process.platform === \"darwin\") {\n event.preventDefault();\n win.hide();\n }\n });\n\n void win.loadURL(resolveWindowUrl());\n return win;\n}\n\nexport function showOrCreateWindow(): void {\n const existing =\n BrowserWindow.getFocusedWindow() ?? mainWindow ?? BrowserWindow.getAllWindows()[0];\n if (existing) {\n if (!existing.isVisible()) existing.show();\n existing.focus();\n return;\n }\n mainWindow = createWindow();\n}\n\nexport function dispatchMenuAction(action: string): void {\n const win = BrowserWindow.getFocusedWindow() ?? mainWindow ?? BrowserWindow.getAllWindows()[0];\n if (!win) {\n showOrCreateWindow();\n setTimeout(() => dispatchMenuAction(action), 500);\n return;\n }\n const send = () => {\n if (win.isDestroyed()) return;\n win.webContents.send(IPC.MENU_ACTION, action);\n if (!win.isVisible()) win.show();\n win.focus();\n };\n if (win.webContents.isLoadingMainFrame()) {\n win.webContents.once(\"did-finish-load\", send);\n } else {\n send();\n }\n}\n\n// ─── Server API helper ────────────────────────────────────────────────────────\n\nexport function callServerApi(path: string, method: string): void {\n if (!serverUrl) return;\n fetch(`${serverUrl}${path}`, { method }).catch((err: unknown) => {\n console.error(\"[desktop] server API call failed:\", err);\n });\n}\n\n// ─── Protocol registration (before app.ready) ─────────────────────────────────\n\nprotocol.registerSchemesAsPrivileged([\n {\n scheme: DESKTOP_SCHEME,\n privileges: {\n standard: true,\n secure: true,\n supportFetchAPI: true,\n corsEnabled: true,\n },\n },\n]);\n\nif (process.platform === \"linux\") {\n app.commandLine.appendSwitch(\"class\", LINUX_WM_CLASS);\n}\n\n// ─── App Lifecycle ────────────────────────────────────────────────────────────\n\napp.on(\"ready\", () => {\n loadDesktopSettings();\n registerIpcHandlers();\n registerNotificationHandlers();\n initializeNotifications();\n configureApplicationMenu();\n registerDesktopProtocol();\n const updateChannel = app.getVersion().includes(\"-canary\") ? \"beta\" : \"latest\";\n initializeAutoUpdater(updateChannel);\n\n if (process.platform === \"win32\") {\n app.setAppUserModelId(APP_ID);\n }\n\n if (process.platform === \"darwin\" && app.dock) {\n const iconPath = resolveResourcePath(\"icon.png\");\n if (iconPath) app.dock.setIcon(iconPath);\n }\n\n createTray();\n\n startServer((port, wsUrl) => {\n serverPort = port;\n serverUrl = `http://127.0.0.1:${port}`;\n serverWsUrl = wsUrl;\n // The ready callback fires again after every server crash-restart. One\n // window per callback multiplied windows under a crash loop (PAN-2570) —\n // reuse the existing window and repoint it at the current server URL.\n if (mainWindow && !mainWindow.isDestroyed()) {\n void mainWindow.loadURL(resolveWindowUrl());\n return;\n }\n mainWindow = createWindow();\n handleAutoStartNag();\n });\n});\n\napp.on(\"window-all-closed\", () => {\n if (process.platform !== \"darwin\") {\n isQuitting = true;\n app.quit();\n }\n});\n\napp.on(\"activate\", () => showOrCreateWindow());\n\napp.on(\"before-quit\", () => {\n isQuitting = true;\n destroyTray();\n stopServer();\n});\n"],"mappings":"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"}
@@ -23,7 +23,11 @@ const IPC = {
23
23
  GET_DESKTOP_SETTINGS: "pan:get-desktop-settings",
24
24
  UPDATE_DESKTOP_SETTING: "pan:update-desktop-setting",
25
25
  NOTIFY: "pan:notify",
26
- RESTART_DASHBOARD: "pan:restart-dashboard"
26
+ RESTART_DASHBOARD: "pan:restart-dashboard",
27
+ GET_UPDATE_STATUS: "pan:get-update-status",
28
+ CHECK_FOR_UPDATES: "pan:check-for-updates",
29
+ DOWNLOAD_UPDATE: "pan:download-update",
30
+ QUIT_AND_INSTALL: "pan:quit-and-install"
27
31
  };
28
32
  electron.contextBridge.exposeInMainWorld("overdeckBridge", {
29
33
  isDesktopApp: () => true,
@@ -53,7 +57,18 @@ electron.contextBridge.exposeInMainWorld("overdeckBridge", {
53
57
  getDesktopSettings: () => electron.ipcRenderer.invoke(IPC.GET_DESKTOP_SETTINGS),
54
58
  updateDesktopSetting: (key, value) => electron.ipcRenderer.invoke(IPC.UPDATE_DESKTOP_SETTING, key, value),
55
59
  notify: (eventType, title, body) => electron.ipcRenderer.invoke(IPC.NOTIFY, eventType, title, body),
56
- restartDashboard: () => electron.ipcRenderer.invoke(IPC.RESTART_DASHBOARD)
60
+ restartDashboard: () => electron.ipcRenderer.invoke(IPC.RESTART_DASHBOARD),
61
+ getUpdateStatus: () => electron.ipcRenderer.invoke(IPC.GET_UPDATE_STATUS),
62
+ checkForUpdates: () => electron.ipcRenderer.invoke(IPC.CHECK_FOR_UPDATES),
63
+ downloadUpdate: () => electron.ipcRenderer.invoke(IPC.DOWNLOAD_UPDATE),
64
+ quitAndInstall: () => {
65
+ electron.ipcRenderer.send(IPC.QUIT_AND_INSTALL);
66
+ },
67
+ onUpdateStatus: (listener) => {
68
+ const wrapped = (_event, status) => listener(status);
69
+ electron.ipcRenderer.on("update-status", wrapped);
70
+ return () => electron.ipcRenderer.removeListener("update-status", wrapped);
71
+ }
57
72
  });
58
73
  //#endregion
59
74
 
@@ -1 +1 @@
1
- {"version":3,"file":"preload.js","names":["contextBridge","ipcRenderer"],"sources":["../src/preload.ts"],"sourcesContent":["/**\n * Preload script for the Overdeck Electron renderer.\n *\n * Exposes `window.overdeckBridge` via contextBridge.exposeInMainWorld.\n * The renderer uses this bridge to communicate with the main process over IPC.\n *\n * Security model:\n * - contextIsolation: true — renderer JS cannot access Node.js APIs\n * - sandbox: false — allows contextBridge to expose typed APIs\n * - All IPC channels are explicitly listed; no dynamic channel names\n * - External URL validation happens in the main process (ipcMain.handle)\n */\n\nimport { contextBridge, ipcRenderer } from \"electron\";\n\n// ─── IPC channel names (must match main.ts IPC constants) ────────────────────\n\nconst IPC = {\n GET_SERVER_URL: \"pan:get-server-url\",\n GET_WS_URL: \"pan:get-ws-url\",\n PICK_FOLDER: \"pan:pick-folder\",\n OPEN_EXTERNAL: \"pan:open-external\",\n OPEN_TERMINAL_WINDOW: \"pan:open-terminal-window\",\n SET_ALWAYS_ON_TOP: \"pan:set-always-on-top\",\n MENU_ACTION: \"pan:menu-action\",\n GET_DESKTOP_SETTINGS: \"pan:get-desktop-settings\",\n UPDATE_DESKTOP_SETTING: \"pan:update-desktop-setting\",\n NOTIFY: \"pan:notify\",\n RESTART_DASHBOARD: \"pan:restart-dashboard\",\n} as const;\n\n// ─── Types ─────────────────────────────────────────────────────────────────────\n\nexport interface DesktopSettings {\n tray: {\n showBadge: boolean;\n tooltipDetail: \"minimal\" | \"full\";\n };\n notifications: {\n inputNeeded: boolean;\n stuckAgents: boolean;\n mergeFailures: boolean;\n workComplete: boolean;\n planningDone: boolean;\n mergeReady: boolean;\n };\n autoStart: {\n enabled: boolean;\n nagCount: number;\n nagDismissed: boolean;\n };\n}\n\nexport type NotificationEventType = keyof DesktopSettings[\"notifications\"];\n\nexport interface OverdeckBridge {\n /** Returns true — used by renderer to detect desktop vs browser mode */\n isDesktopApp(): boolean;\n\n /** Returns base HTTP server URL (e.g. \"http://127.0.0.1:7825\"), or null */\n getServerUrl(): string | null;\n\n /** Returns base WebSocket URL (e.g. \"ws://127.0.0.1:7825\"), or null */\n getWsUrl(): string | null;\n\n /** Opens a native folder-picker dialog */\n pickFolder(): Promise<string | null>;\n\n /** Opens a URL in the default browser (https/http only) */\n openExternal(url: string): Promise<void>;\n\n /** Opens the current terminal in a dedicated desktop window. */\n openTerminalWindow(sessionName: string, title: string): void;\n\n /** Toggles always-on-top for the focused terminal window. */\n setAlwaysOnTop(value: boolean): void;\n\n /**\n * Registers a listener for menu actions dispatched from the main process.\n * Returns an unsubscribe function.\n *\n * Actions: \"open-settings\", \"open-workspace:<issueId>\",\n * \"auto-start-nag:<count>:<max>\", ...\n */\n onMenuAction(listener: (action: string) => void): () => void;\n\n /** Gets all desktop-specific settings */\n getDesktopSettings(): Promise<DesktopSettings>;\n\n /**\n * Updates a single desktop setting by dotted key path.\n * e.g. updateDesktopSetting(\"notifications.inputNeeded\", true)\n */\n updateDesktopSetting(key: string, value: unknown): Promise<void>;\n\n /**\n * Sends a native OS notification (if permitted by per-event-type settings).\n */\n notify(eventType: NotificationEventType, title: string, body: string): Promise<void>;\n\n /** Stops and restarts the embedded dashboard server in the main process. */\n restartDashboard(): Promise<void>;\n}\n\n// ─── Bridge implementation ────────────────────────────────────────────────────\n\nconst bridge: OverdeckBridge = {\n isDesktopApp: () => true,\n\n getServerUrl: () => {\n const result = ipcRenderer.sendSync(IPC.GET_SERVER_URL);\n return typeof result === \"string\" ? result : null;\n },\n\n getWsUrl: () => {\n const result = ipcRenderer.sendSync(IPC.GET_WS_URL);\n return typeof result === \"string\" ? result : null;\n },\n\n pickFolder: () =>\n ipcRenderer.invoke(IPC.PICK_FOLDER) as Promise<string | null>,\n\n openExternal: (url: string) =>\n ipcRenderer.invoke(IPC.OPEN_EXTERNAL, url) as Promise<void>,\n\n openTerminalWindow: (sessionName: string, title: string) => {\n ipcRenderer.send(IPC.OPEN_TERMINAL_WINDOW, sessionName, title);\n },\n\n setAlwaysOnTop: (value: boolean) => {\n ipcRenderer.send(IPC.SET_ALWAYS_ON_TOP, value);\n },\n\n onMenuAction: (listener: (action: string) => void) => {\n const wrapped = (_event: Electron.IpcRendererEvent, action: unknown) => {\n if (typeof action === \"string\") listener(action);\n };\n ipcRenderer.on(IPC.MENU_ACTION, wrapped);\n return () => ipcRenderer.removeListener(IPC.MENU_ACTION, wrapped);\n },\n\n getDesktopSettings: () =>\n ipcRenderer.invoke(IPC.GET_DESKTOP_SETTINGS) as Promise<DesktopSettings>,\n\n updateDesktopSetting: (key: string, value: unknown) =>\n ipcRenderer.invoke(IPC.UPDATE_DESKTOP_SETTING, key, value) as Promise<void>,\n\n notify: (eventType: NotificationEventType, title: string, body: string) =>\n ipcRenderer.invoke(IPC.NOTIFY, eventType, title, body) as Promise<void>,\n\n restartDashboard: () =>\n ipcRenderer.invoke(IPC.RESTART_DASHBOARD) as Promise<void>,\n};\n\ncontextBridge.exposeInMainWorld(\"overdeckBridge\", bridge);\n\n// ─── Global type augmentation (consumed by renderer TypeScript) ───────────────\n\ndeclare global {\n interface Window {\n overdeckBridge?: OverdeckBridge;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AAiBA,MAAM,MAAM;CACV,gBAAgB;CAChB,YAAY;CACZ,aAAa;CACb,eAAe;CACf,sBAAsB;CACtB,mBAAmB;CACnB,aAAa;CACb,sBAAsB;CACtB,wBAAwB;CACxB,QAAQ;CACR,mBAAmB;CACpB;AA6HDA,SAAAA,cAAc,kBAAkB,kBAAkB;CA/ChD,oBAAoB;CAEpB,oBAAoB;EAClB,MAAM,SAASC,SAAAA,YAAY,SAAS,IAAI,eAAe;AACvD,SAAO,OAAO,WAAW,WAAW,SAAS;;CAG/C,gBAAgB;EACd,MAAM,SAASA,SAAAA,YAAY,SAAS,IAAI,WAAW;AACnD,SAAO,OAAO,WAAW,WAAW,SAAS;;CAG/C,kBACEA,SAAAA,YAAY,OAAO,IAAI,YAAY;CAErC,eAAe,QACbA,SAAAA,YAAY,OAAO,IAAI,eAAe,IAAI;CAE5C,qBAAqB,aAAqB,UAAkB;AAC1D,WAAA,YAAY,KAAK,IAAI,sBAAsB,aAAa,MAAM;;CAGhE,iBAAiB,UAAmB;AAClC,WAAA,YAAY,KAAK,IAAI,mBAAmB,MAAM;;CAGhD,eAAe,aAAuC;EACpD,MAAM,WAAW,QAAmC,WAAoB;AACtE,OAAI,OAAO,WAAW,SAAU,UAAS,OAAO;;AAElD,WAAA,YAAY,GAAG,IAAI,aAAa,QAAQ;AACxC,eAAaA,SAAAA,YAAY,eAAe,IAAI,aAAa,QAAQ;;CAGnE,0BACEA,SAAAA,YAAY,OAAO,IAAI,qBAAqB;CAE9C,uBAAuB,KAAa,UAClCA,SAAAA,YAAY,OAAO,IAAI,wBAAwB,KAAK,MAAM;CAE5D,SAAS,WAAkC,OAAe,SACxDA,SAAAA,YAAY,OAAO,IAAI,QAAQ,WAAW,OAAO,KAAK;CAExD,wBACEA,SAAAA,YAAY,OAAO,IAAI,kBAAkB;CAGW,CAAC"}
1
+ {"version":3,"file":"preload.js","names":["contextBridge","ipcRenderer"],"sources":["../src/preload.ts"],"sourcesContent":["/**\n * Preload script for the Overdeck Electron renderer.\n *\n * Exposes `window.overdeckBridge` via contextBridge.exposeInMainWorld.\n * The renderer uses this bridge to communicate with the main process over IPC.\n *\n * Security model:\n * - contextIsolation: true — renderer JS cannot access Node.js APIs\n * - sandbox: false — allows contextBridge to expose typed APIs\n * - All IPC channels are explicitly listed; no dynamic channel names\n * - External URL validation happens in the main process (ipcMain.handle)\n */\n\nimport { contextBridge, ipcRenderer } from \"electron\";\nimport type { UpdateSnapshot } from '@overdeck/contracts';\n\n// ─── IPC channel names (must match main.ts IPC constants) ────────────────────\n\nconst IPC = {\n GET_SERVER_URL: \"pan:get-server-url\",\n GET_WS_URL: \"pan:get-ws-url\",\n PICK_FOLDER: \"pan:pick-folder\",\n OPEN_EXTERNAL: \"pan:open-external\",\n OPEN_TERMINAL_WINDOW: \"pan:open-terminal-window\",\n SET_ALWAYS_ON_TOP: \"pan:set-always-on-top\",\n MENU_ACTION: \"pan:menu-action\",\n GET_DESKTOP_SETTINGS: \"pan:get-desktop-settings\",\n UPDATE_DESKTOP_SETTING: \"pan:update-desktop-setting\",\n NOTIFY: \"pan:notify\",\n RESTART_DASHBOARD: \"pan:restart-dashboard\",\n GET_UPDATE_STATUS: \"pan:get-update-status\",\n CHECK_FOR_UPDATES: \"pan:check-for-updates\",\n DOWNLOAD_UPDATE: \"pan:download-update\",\n QUIT_AND_INSTALL: \"pan:quit-and-install\",\n} as const;\n\n// ─── Types ─────────────────────────────────────────────────────────────────────\n\nexport interface DesktopSettings {\n tray: {\n showBadge: boolean;\n tooltipDetail: \"minimal\" | \"full\";\n };\n notifications: {\n inputNeeded: boolean;\n stuckAgents: boolean;\n mergeFailures: boolean;\n workComplete: boolean;\n planningDone: boolean;\n mergeReady: boolean;\n };\n autoStart: {\n enabled: boolean;\n nagCount: number;\n nagDismissed: boolean;\n };\n}\n\nexport type NotificationEventType = keyof DesktopSettings[\"notifications\"];\n\nexport interface OverdeckBridge {\n /** Returns true — used by renderer to detect desktop vs browser mode */\n isDesktopApp(): boolean;\n\n /** Returns base HTTP server URL (e.g. \"http://127.0.0.1:7825\"), or null */\n getServerUrl(): string | null;\n\n /** Returns base WebSocket URL (e.g. \"ws://127.0.0.1:7825\"), or null */\n getWsUrl(): string | null;\n\n /** Opens a native folder-picker dialog */\n pickFolder(): Promise<string | null>;\n\n /** Opens a URL in the default browser (https/http only) */\n openExternal(url: string): Promise<void>;\n\n /** Opens the current terminal in a dedicated desktop window. */\n openTerminalWindow(sessionName: string, title: string): void;\n\n /** Toggles always-on-top for the focused terminal window. */\n setAlwaysOnTop(value: boolean): void;\n\n /**\n * Registers a listener for menu actions dispatched from the main process.\n * Returns an unsubscribe function.\n *\n * Actions: \"open-settings\", \"open-workspace:<issueId>\",\n * \"auto-start-nag:<count>:<max>\", ...\n */\n onMenuAction(listener: (action: string) => void): () => void;\n\n /** Gets all desktop-specific settings */\n getDesktopSettings(): Promise<DesktopSettings>;\n\n /**\n * Updates a single desktop setting by dotted key path.\n * e.g. updateDesktopSetting(\"notifications.inputNeeded\", true)\n */\n updateDesktopSetting(key: string, value: unknown): Promise<void>;\n\n /**\n * Sends a native OS notification (if permitted by per-event-type settings).\n */\n notify(eventType: NotificationEventType, title: string, body: string): Promise<void>;\n\n /** Stops and restarts the embedded dashboard server in the main process. */\n restartDashboard(): Promise<void>;\n getUpdateStatus(): Promise<UpdateSnapshot>;\n checkForUpdates(): Promise<UpdateSnapshot>;\n downloadUpdate(): Promise<UpdateSnapshot>;\n quitAndInstall(): void;\n onUpdateStatus(listener: (status: UpdateSnapshot) => void): () => void;\n}\n\n// ─── Bridge implementation ────────────────────────────────────────────────────\n\nconst bridge: OverdeckBridge = {\n isDesktopApp: () => true,\n\n getServerUrl: () => {\n const result = ipcRenderer.sendSync(IPC.GET_SERVER_URL);\n return typeof result === \"string\" ? result : null;\n },\n\n getWsUrl: () => {\n const result = ipcRenderer.sendSync(IPC.GET_WS_URL);\n return typeof result === \"string\" ? result : null;\n },\n\n pickFolder: () =>\n ipcRenderer.invoke(IPC.PICK_FOLDER) as Promise<string | null>,\n\n openExternal: (url: string) =>\n ipcRenderer.invoke(IPC.OPEN_EXTERNAL, url) as Promise<void>,\n\n openTerminalWindow: (sessionName: string, title: string) => {\n ipcRenderer.send(IPC.OPEN_TERMINAL_WINDOW, sessionName, title);\n },\n\n setAlwaysOnTop: (value: boolean) => {\n ipcRenderer.send(IPC.SET_ALWAYS_ON_TOP, value);\n },\n\n onMenuAction: (listener: (action: string) => void) => {\n const wrapped = (_event: Electron.IpcRendererEvent, action: unknown) => {\n if (typeof action === \"string\") listener(action);\n };\n ipcRenderer.on(IPC.MENU_ACTION, wrapped);\n return () => ipcRenderer.removeListener(IPC.MENU_ACTION, wrapped);\n },\n\n getDesktopSettings: () =>\n ipcRenderer.invoke(IPC.GET_DESKTOP_SETTINGS) as Promise<DesktopSettings>,\n\n updateDesktopSetting: (key: string, value: unknown) =>\n ipcRenderer.invoke(IPC.UPDATE_DESKTOP_SETTING, key, value) as Promise<void>,\n\n notify: (eventType: NotificationEventType, title: string, body: string) =>\n ipcRenderer.invoke(IPC.NOTIFY, eventType, title, body) as Promise<void>,\n\n restartDashboard: () =>\n ipcRenderer.invoke(IPC.RESTART_DASHBOARD) as Promise<void>,\n\n getUpdateStatus: () => ipcRenderer.invoke(IPC.GET_UPDATE_STATUS) as Promise<UpdateSnapshot>,\n checkForUpdates: () => ipcRenderer.invoke(IPC.CHECK_FOR_UPDATES) as Promise<UpdateSnapshot>,\n downloadUpdate: () => ipcRenderer.invoke(IPC.DOWNLOAD_UPDATE) as Promise<UpdateSnapshot>,\n quitAndInstall: () => { ipcRenderer.send(IPC.QUIT_AND_INSTALL); },\n onUpdateStatus: (listener) => {\n const wrapped = (_event: Electron.IpcRendererEvent, status: UpdateSnapshot) => listener(status);\n ipcRenderer.on('update-status', wrapped);\n return () => ipcRenderer.removeListener('update-status', wrapped);\n },\n};\n\ncontextBridge.exposeInMainWorld(\"overdeckBridge\", bridge);\n\n// ─── Global type augmentation (consumed by renderer TypeScript) ───────────────\n\ndeclare global {\n interface Window {\n overdeckBridge?: OverdeckBridge;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AAkBA,MAAM,MAAM;CACV,gBAAgB;CAChB,YAAY;CACZ,aAAa;CACb,eAAe;CACf,sBAAsB;CACtB,mBAAmB;CACnB,aAAa;CACb,sBAAsB;CACtB,wBAAwB;CACxB,QAAQ;CACR,mBAAmB;CACnB,mBAAmB;CACnB,mBAAmB;CACnB,iBAAiB;CACjB,kBAAkB;CACnB;AA4IDA,SAAAA,cAAc,kBAAkB,kBAAkB;CAzDhD,oBAAoB;CAEpB,oBAAoB;EAClB,MAAM,SAASC,SAAAA,YAAY,SAAS,IAAI,eAAe;AACvD,SAAO,OAAO,WAAW,WAAW,SAAS;;CAG/C,gBAAgB;EACd,MAAM,SAASA,SAAAA,YAAY,SAAS,IAAI,WAAW;AACnD,SAAO,OAAO,WAAW,WAAW,SAAS;;CAG/C,kBACEA,SAAAA,YAAY,OAAO,IAAI,YAAY;CAErC,eAAe,QACbA,SAAAA,YAAY,OAAO,IAAI,eAAe,IAAI;CAE5C,qBAAqB,aAAqB,UAAkB;AAC1D,WAAA,YAAY,KAAK,IAAI,sBAAsB,aAAa,MAAM;;CAGhE,iBAAiB,UAAmB;AAClC,WAAA,YAAY,KAAK,IAAI,mBAAmB,MAAM;;CAGhD,eAAe,aAAuC;EACpD,MAAM,WAAW,QAAmC,WAAoB;AACtE,OAAI,OAAO,WAAW,SAAU,UAAS,OAAO;;AAElD,WAAA,YAAY,GAAG,IAAI,aAAa,QAAQ;AACxC,eAAaA,SAAAA,YAAY,eAAe,IAAI,aAAa,QAAQ;;CAGnE,0BACEA,SAAAA,YAAY,OAAO,IAAI,qBAAqB;CAE9C,uBAAuB,KAAa,UAClCA,SAAAA,YAAY,OAAO,IAAI,wBAAwB,KAAK,MAAM;CAE5D,SAAS,WAAkC,OAAe,SACxDA,SAAAA,YAAY,OAAO,IAAI,QAAQ,WAAW,OAAO,KAAK;CAExD,wBACEA,SAAAA,YAAY,OAAO,IAAI,kBAAkB;CAE3C,uBAAuBA,SAAAA,YAAY,OAAO,IAAI,kBAAkB;CAChE,uBAAuBA,SAAAA,YAAY,OAAO,IAAI,kBAAkB;CAChE,sBAAsBA,SAAAA,YAAY,OAAO,IAAI,gBAAgB;CAC7D,sBAAsB;AAAE,WAAA,YAAY,KAAK,IAAI,iBAAiB;;CAC9D,iBAAiB,aAAa;EAC5B,MAAM,WAAW,QAAmC,WAA2B,SAAS,OAAO;AAC/F,WAAA,YAAY,GAAG,iBAAiB,QAAQ;AACxC,eAAaA,SAAAA,YAAY,eAAe,iBAAiB,QAAQ;;CAIb,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@overdeck/desktop",
3
- "version": "0.45.16",
3
+ "version": "0.45.19",
4
4
  "description": "Overdeck Command Deck — AI agent monitoring desktop app",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -35,9 +35,9 @@
35
35
  "start": "bun run scripts/start-electron.mjs",
36
36
  "typecheck": "tsc --noEmit",
37
37
  "test": "vitest run --configLoader runner --passWithNoTests",
38
- "dist:linux": "node scripts/prepare-server-resources.mjs && electron-builder --linux AppImage --publish never",
39
- "dist:mac": "node scripts/prepare-server-resources.mjs && electron-builder --mac dmg --arm64 --publish never",
40
- "dist:win": "node scripts/prepare-server-resources.mjs && electron-builder --win nsis --publish never",
38
+ "dist:linux": "node scripts/prepare-server-resources.mjs && electron-builder --linux AppImage --publish never && node scripts/stamp-update-manifests.mjs",
39
+ "dist:mac": "node scripts/prepare-server-resources.mjs && electron-builder --mac dmg zip --arm64 --publish never && node scripts/stamp-update-manifests.mjs",
40
+ "dist:win": "node scripts/prepare-server-resources.mjs && electron-builder --win nsis --publish never && node scripts/stamp-update-manifests.mjs",
41
41
  "postinstall": "electron-rebuild -f -w node-pty 2>/dev/null; exit 0"
42
42
  },
43
43
  "dependencies": {
@@ -58,6 +58,7 @@
58
58
  "build": {
59
59
  "appId": "com.overdeck.app",
60
60
  "productName": "Overdeck",
61
+ "generateUpdatesFilesForAllChannels": true,
61
62
  "publish": {
62
63
  "provider": "github",
63
64
  "owner": "eltmon",
@@ -0,0 +1,62 @@
1
+ import { m as readWorkspacePlanSync, o as init_io, v as init_types, y as subItemsOf } from "./io-Cv9xQTks.js";
2
+ import "effect";
3
+ //#region ../../lib/vbrief/acceptance-criteria.ts
4
+ init_io();
5
+ init_types();
6
+ function getVBriefACStatusSync(workspacePath) {
7
+ const doc = readWorkspacePlanSync(workspacePath);
8
+ if (!doc) return null;
9
+ const allCriteria = extractACFromDocument(doc);
10
+ if (allCriteria.length === 0) return null;
11
+ const itemMap = /* @__PURE__ */ new Map();
12
+ for (const ac of allCriteria) {
13
+ const item = itemMap.get(ac.itemId) ?? {
14
+ itemId: ac.itemId,
15
+ itemTitle: ac.itemTitle,
16
+ completed: 0,
17
+ pending: 0,
18
+ total: 0,
19
+ criteria: []
20
+ };
21
+ item.total++;
22
+ item.criteria.push(ac);
23
+ if (ac.status === "completed" || ac.status === "cancelled") item.completed++;
24
+ else item.pending++;
25
+ itemMap.set(ac.itemId, item);
26
+ }
27
+ const items = [...itemMap.values()];
28
+ const totalCompleted = items.reduce((sum, item) => sum + item.completed, 0);
29
+ const totalPending = items.reduce((sum, item) => sum + item.pending, 0);
30
+ return {
31
+ allCompleted: totalPending === 0,
32
+ items,
33
+ totalCompleted,
34
+ totalPending,
35
+ totalCount: totalCompleted + totalPending
36
+ };
37
+ }
38
+ /**
39
+ * Extract AC from an already-loaded document (avoids re-reading the file).
40
+ */
41
+ function isDeferredOrCancelledItem(item) {
42
+ const status = String(item.status);
43
+ return status === "cancelled" || status === "deferred" || item.metadata?.deferred === true;
44
+ }
45
+ function extractACFromDocument(doc) {
46
+ const criteria = [];
47
+ for (const item of doc.plan.items) {
48
+ if (isDeferredOrCancelledItem(item)) continue;
49
+ for (const sub of subItemsOf(item)) if (sub.metadata?.kind === "acceptance_criterion") criteria.push({
50
+ itemId: item.id,
51
+ itemTitle: item.title,
52
+ subItemId: sub.id,
53
+ title: sub.title,
54
+ status: sub.status
55
+ });
56
+ }
57
+ return criteria;
58
+ }
59
+ //#endregion
60
+ export { getVBriefACStatusSync as n, extractACFromDocument as t };
61
+
62
+ //# sourceMappingURL=acceptance-criteria-DQqRIuQ-.js.map
@@ -1,8 +1,8 @@
1
- import { O as listSessionNames, w as killSession, x as init_tmux } from "./tmux-C3g9VFcg.js";
2
- import { c as init_review_status, s as getReviewStatusSync, u as loadReviewStatuses } from "./review-status-Dc1l4dVz.js";
3
- import { Jn as saveAgentStateSync, Pn as getAgentStateSync, Vn as markAgentStoppedState, t as init_agents } from "./agents-B9GwfCyp.js";
4
- import { c as logDeaconEventSync, o as init_persistent_logger } from "./session-history-DanhhiCw.js";
5
- import { o as selectMergedAdvancingSessions, t as init_reap_terminal_sessions } from "./reap-terminal-sessions-2s-fvilz.js";
1
+ import { S as init_tmux, T as killSession, k as listSessionNames } from "./tmux-C3X2XmV0.js";
2
+ import { c as init_review_status, s as getReviewStatusSync, u as loadReviewStatuses } from "./review-status-By86XA6Z.js";
3
+ import { Fn as getAgentStateSync, Hn as markAgentStoppedState, Yn as saveAgentStateSync, s as init_agents } from "./queries-srgusoi8.js";
4
+ import { c as logDeaconEventSync, o as init_persistent_logger } from "./session-history-DbWnzfo-.js";
5
+ import { o as selectMergedAdvancingSessions, t as init_reap_terminal_sessions } from "./reap-terminal-sessions-CFkuquQj.js";
6
6
  import { Effect } from "effect";
7
7
  //#region ../../lib/cloister/advancing-selfheal.ts
8
8
  init_agents();
@@ -135,4 +135,4 @@ async function runBootAdvancingSelfHeal(deps = defaultBootAdvancingSelfHealDeps)
135
135
  //#endregion
136
136
  export { checkMergedAdvancingSessions, markAdvancingSessionStopped, reconcileInFlightJournals, runBootAdvancingSelfHeal };
137
137
 
138
- //# sourceMappingURL=advancing-selfheal-DJYAvGv-.js.map
138
+ //# sourceMappingURL=advancing-selfheal-BYidNkmp.js.map
@@ -1,5 +1,5 @@
1
- import { Nt as readIssueRecordForWorkspaceSync, kt as init_record } from "./config-yaml-cNOZcEww.js";
2
- import { d as init_agents, m as removeAgentSync, p as listAllAgentsSync } from "./briefing-freshness-DESTCWo-.js";
1
+ import { Nt as readIssueRecordForWorkspaceSync, kt as init_record } from "./config-yaml-BQs6GbD_.js";
2
+ import { c as listAllAgentsSync, l as removeAgentSync, o as init_agents } from "./agents--ljN-wVY.js";
3
3
  //#region ../../lib/cloister/agent-gc.ts
4
4
  init_agents();
5
5
  init_record();
@@ -25,4 +25,4 @@ function pruneTerminalStoppedAgents() {
25
25
  //#endregion
26
26
  export { pruneTerminalStoppedAgents as n, pruneStoppedAgentsForIssue as t };
27
27
 
28
- //# sourceMappingURL=agent-gc-D0wDIeV1.js.map
28
+ //# sourceMappingURL=agent-gc-ClNQLl_V.js.map
@@ -0,0 +1,3 @@
1
+ import { En as clearAgentPausedSync, Fn as getAgentStateSync, In as init_agent_state, On as clearAgentTroubledSync, Zn as setAgentPausedSync } from "./queries-srgusoi8.js";
2
+ init_agent_state();
3
+ export { clearAgentPausedSync, clearAgentTroubledSync, getAgentStateSync, setAgentPausedSync };