Skip to content

Commit e1e79f3

Browse files
committed
Merge remote-tracking branch 'origin/main' into fix/chat-ui-refinements
# Conflicts: # apps/pythinker-code/dist-web/.web-bundle-manifest.json # apps/pythinker-code/dist-web/assets/CodeBlockNode-BBzuXnEo.js # apps/pythinker-code/dist-web/assets/CodeBlockNode-Dky7odcN.js # apps/pythinker-code/dist-web/assets/CodeBlockNode-DvjXDaYu.js # apps/pythinker-code/dist-web/assets/DesignSystemView-Drz2kLo0.js # apps/pythinker-code/dist-web/assets/Tooltip-B5geydeU.js # apps/pythinker-code/dist-web/assets/Tooltip-CIGT9w3P.js # apps/pythinker-code/dist-web/assets/Tooltip-CKI8tYnk.js # apps/pythinker-code/dist-web/assets/abnfDiagram-VCTEODGH-CFOXmJ-D.js # apps/pythinker-code/dist-web/assets/abnfDiagram-VCTEODGH-DFvjdFjZ.js # apps/pythinker-code/dist-web/assets/abnfDiagram-VCTEODGH-Du95S4yT.js # apps/pythinker-code/dist-web/assets/arc-DEeVB9of.js # apps/pythinker-code/dist-web/assets/arc-Dak_io8p.js # apps/pythinker-code/dist-web/assets/arc-_NkIYQzw.js # apps/pythinker-code/dist-web/assets/architectureDiagram-5GKGNRK7-B0OjmpuJ.js # apps/pythinker-code/dist-web/assets/architectureDiagram-5GKGNRK7-BRe2lrnT.js # apps/pythinker-code/dist-web/assets/architectureDiagram-5GKGNRK7-DwPbWZbj.js # apps/pythinker-code/dist-web/assets/blockDiagram-NRAW4CY4-CFAES9Zs.js # apps/pythinker-code/dist-web/assets/blockDiagram-NRAW4CY4-CV_y52IC.js # apps/pythinker-code/dist-web/assets/blockDiagram-NRAW4CY4-QmJNXmgz.js # apps/pythinker-code/dist-web/assets/c4Diagram-UCG6FXSJ-6N1YLsPF.js # apps/pythinker-code/dist-web/assets/c4Diagram-UCG6FXSJ-Bep0rAz0.js # apps/pythinker-code/dist-web/assets/c4Diagram-UCG6FXSJ-DpT4j2vI.js # apps/pythinker-code/dist-web/assets/chunk-2Q5K7J3B-BPzI8dOJ.js # apps/pythinker-code/dist-web/assets/chunk-2Q5K7J3B-CtDFYBJ5.js # apps/pythinker-code/dist-web/assets/chunk-2Q5K7J3B-DgM_drcz.js # apps/pythinker-code/dist-web/assets/chunk-5VM5RSS4-CDOmI83E.js # apps/pythinker-code/dist-web/assets/chunk-5VM5RSS4-DipeT3cJ.js # apps/pythinker-code/dist-web/assets/chunk-5VM5RSS4-fHbyolsL.js # apps/pythinker-code/dist-web/assets/chunk-F27PBJKO-BPp-Lkv_.js # apps/pythinker-code/dist-web/assets/chunk-F27PBJKO-CqXmbFOg.js # apps/pythinker-code/dist-web/assets/chunk-F27PBJKO-Rf7FFFKw.js # apps/pythinker-code/dist-web/assets/chunk-G27WJ6UU-BjElWXp6.js # apps/pythinker-code/dist-web/assets/chunk-G27WJ6UU-CljBcQD3.js # apps/pythinker-code/dist-web/assets/chunk-G27WJ6UU-DsvWWmHF.js # apps/pythinker-code/dist-web/assets/chunk-JWPE2WC7-0Q0AhEWa.js # apps/pythinker-code/dist-web/assets/chunk-JWPE2WC7-BMKGkrPR.js # apps/pythinker-code/dist-web/assets/chunk-JWPE2WC7-DnEhu1r7.js # apps/pythinker-code/dist-web/assets/chunk-LCL6LL3I-B-J4MfeL.js # apps/pythinker-code/dist-web/assets/chunk-LCL6LL3I-B3Iw62Gu.js # apps/pythinker-code/dist-web/assets/chunk-LCL6LL3I-B3aML0B_.js # apps/pythinker-code/dist-web/assets/chunk-POPQ4Y6H-CIXepE0R.js # apps/pythinker-code/dist-web/assets/chunk-POPQ4Y6H-Dv84zXjV.js # apps/pythinker-code/dist-web/assets/chunk-POPQ4Y6H-zloRpvh1.js # apps/pythinker-code/dist-web/assets/chunk-SVP7TREG-Bf4O-AL9.js # apps/pythinker-code/dist-web/assets/chunk-SVP7TREG-CIhNfxdS.js # apps/pythinker-code/dist-web/assets/chunk-SVP7TREG-CuVxqgjz.js # apps/pythinker-code/dist-web/assets/chunk-XXDRQBXY-Bijzh1fL.js # apps/pythinker-code/dist-web/assets/chunk-XXDRQBXY-DrNYTeOS.js # apps/pythinker-code/dist-web/assets/chunk-XXDRQBXY-h2aZ6UIF.js # apps/pythinker-code/dist-web/assets/cose-bilkent-JH36ORCC-BaYf70eV.js # apps/pythinker-code/dist-web/assets/cose-bilkent-JH36ORCC-DhpvG0sd.js # apps/pythinker-code/dist-web/assets/cose-bilkent-JH36ORCC-Ds_6cpP6.js # apps/pythinker-code/dist-web/assets/cssMode-CUH-Kh-7.js # apps/pythinker-code/dist-web/assets/cssMode-Cu9rh5RD.js # apps/pythinker-code/dist-web/assets/cssMode-gE7uiuD5.js # apps/pythinker-code/dist-web/assets/cynefin-OW5HDTMX-C-LfKCwU.js # apps/pythinker-code/dist-web/assets/cynefin-OW5HDTMX-CA5kg2Ru.js # apps/pythinker-code/dist-web/assets/cynefin-OW5HDTMX-l1QOd-xU.js # apps/pythinker-code/dist-web/assets/cynefinDiagram-5FMLGOSQ-BAtuFoLf.js # apps/pythinker-code/dist-web/assets/cynefinDiagram-5FMLGOSQ-Bh1egu0X.js # apps/pythinker-code/dist-web/assets/cynefinDiagram-5FMLGOSQ-C8Pyva0u.js # apps/pythinker-code/dist-web/assets/dagre-3AP2YEHR-C8AbAFBs.js # apps/pythinker-code/dist-web/assets/dagre-3AP2YEHR-DU_HKB-3.js # apps/pythinker-code/dist-web/assets/dagre-3AP2YEHR-yqZfirei.js # apps/pythinker-code/dist-web/assets/diagram-S7CK7UJ4-BRe9nod7.js # apps/pythinker-code/dist-web/assets/diagram-S7CK7UJ4-BhCpHRcN.js # apps/pythinker-code/dist-web/assets/diagram-S7CK7UJ4-CL2HcYPz.js # apps/pythinker-code/dist-web/assets/diagram-UQ7AKVKN-Bww-xFDu.js # apps/pythinker-code/dist-web/assets/diagram-UQ7AKVKN-DErCYshf.js # apps/pythinker-code/dist-web/assets/diagram-UQ7AKVKN-gjKWkwak.js # apps/pythinker-code/dist-web/assets/diagram-VSXAHHWV-C_B-ZJz7.js # apps/pythinker-code/dist-web/assets/diagram-VSXAHHWV-DGSPmhBl.js # apps/pythinker-code/dist-web/assets/diagram-VSXAHHWV-DMBcD4On.js # apps/pythinker-code/dist-web/assets/diagram-VX7I27RA-B4ACkkx1.js # apps/pythinker-code/dist-web/assets/diagram-VX7I27RA-CcpH_Mqm.js # apps/pythinker-code/dist-web/assets/diagram-VX7I27RA-m_R0n3VX.js # apps/pythinker-code/dist-web/assets/diagram-Z3DM3KII-BOkAVbVk.js # apps/pythinker-code/dist-web/assets/diagram-Z3DM3KII-BQjPyCxo.js # apps/pythinker-code/dist-web/assets/diagram-Z3DM3KII-CnUTXZMF.js # apps/pythinker-code/dist-web/assets/ebnfDiagram-PWID7BFC-C2XSbCnh.js # apps/pythinker-code/dist-web/assets/ebnfDiagram-PWID7BFC-DV6ofXaq.js # apps/pythinker-code/dist-web/assets/ebnfDiagram-PWID7BFC-su5TjnHi.js # apps/pythinker-code/dist-web/assets/editor.main-BTIX44kH.js # apps/pythinker-code/dist-web/assets/editor.main-C9m6QqV_.js # apps/pythinker-code/dist-web/assets/editor.main-Do96mTXc.js # apps/pythinker-code/dist-web/assets/erDiagram-SSCWMZ5O-8VgAJ3QJ.js # apps/pythinker-code/dist-web/assets/erDiagram-SSCWMZ5O-ClKEQ9ad.js # apps/pythinker-code/dist-web/assets/erDiagram-SSCWMZ5O-Dw8ayruy.js # apps/pythinker-code/dist-web/assets/flowDiagram-A5DVABFB-BQgyQdB-.js # apps/pythinker-code/dist-web/assets/flowDiagram-A5DVABFB-BnvMNKKq.js # apps/pythinker-code/dist-web/assets/flowDiagram-A5DVABFB-DHbXwk5j.js # apps/pythinker-code/dist-web/assets/freemarker2-DpaMPJbW.js # apps/pythinker-code/dist-web/assets/freemarker2-DuXt8BAN.js # apps/pythinker-code/dist-web/assets/freemarker2-yDK1aGoI.js # apps/pythinker-code/dist-web/assets/ganttDiagram-EL5Y4UJY-CQDxrMGn.js # apps/pythinker-code/dist-web/assets/ganttDiagram-EL5Y4UJY-D58Qc6BL.js # apps/pythinker-code/dist-web/assets/ganttDiagram-EL5Y4UJY-DAOyTpNi.js # apps/pythinker-code/dist-web/assets/gitGraphDiagram-WWUBYQGX-BArd2nex.js # apps/pythinker-code/dist-web/assets/gitGraphDiagram-WWUBYQGX-BGurrpyF.js # apps/pythinker-code/dist-web/assets/gitGraphDiagram-WWUBYQGX-CjEs-k-w.js # apps/pythinker-code/dist-web/assets/handlebars-CVaH-T7q.js # apps/pythinker-code/dist-web/assets/handlebars-Cwv8qCNt.js # apps/pythinker-code/dist-web/assets/handlebars-PDBA2ir8.js # apps/pythinker-code/dist-web/assets/html-32K-65Vs.js # apps/pythinker-code/dist-web/assets/html-BUPd72iP.js # apps/pythinker-code/dist-web/assets/html-KtLrsE6h.js # apps/pythinker-code/dist-web/assets/htmlMode-BV7eppqD.js # apps/pythinker-code/dist-web/assets/htmlMode-DTD3vxOt.js # apps/pythinker-code/dist-web/assets/htmlMode-K7PdClha.js # apps/pythinker-code/dist-web/assets/index-B_ztCaEe.js # apps/pythinker-code/dist-web/assets/index-Beom0ErB.js # apps/pythinker-code/dist-web/assets/index-C21Uq2Pg.css # apps/pythinker-code/dist-web/assets/index-C2WWooId.css # apps/pythinker-code/dist-web/assets/index-CUFOMNyI.js # apps/pythinker-code/dist-web/assets/index-CUI9xsln.js # apps/pythinker-code/dist-web/assets/index-CXU9QAFW.js # apps/pythinker-code/dist-web/assets/index-DSS8HA9D.js # apps/pythinker-code/dist-web/assets/index-DaJyk3eE.js # apps/pythinker-code/dist-web/assets/index-VxrL80gU.js # apps/pythinker-code/dist-web/assets/index-kplC8jA9.css # apps/pythinker-code/dist-web/assets/index-otBoqGdB.js # apps/pythinker-code/dist-web/assets/index-uNd-oW5n.js # apps/pythinker-code/dist-web/assets/index10-BO7nIyjR.js # apps/pythinker-code/dist-web/assets/index10-Ck1lMRFE.js # apps/pythinker-code/dist-web/assets/index10-Do5h7u0U.js # apps/pythinker-code/dist-web/assets/index11-CXyzS8DB.js # apps/pythinker-code/dist-web/assets/index11-Cm-Ocx1m.js # apps/pythinker-code/dist-web/assets/index11-DbuNbY9Q.js # apps/pythinker-code/dist-web/assets/index5-CU0dLL6m.js # apps/pythinker-code/dist-web/assets/index5-wGfcGflH.js # apps/pythinker-code/dist-web/assets/index5-wYX4yVjf.js # apps/pythinker-code/dist-web/assets/index6-BB3xpYAf.js # apps/pythinker-code/dist-web/assets/index6-BwNTmFa5.js # apps/pythinker-code/dist-web/assets/index6-zZSe8zAl.js # apps/pythinker-code/dist-web/assets/index7-Bl5iL86c.js # apps/pythinker-code/dist-web/assets/index7-CeBtXuRt.js # apps/pythinker-code/dist-web/assets/index7-DRdIBIMt.js # apps/pythinker-code/dist-web/assets/index8-Bi7iwoqu.js # apps/pythinker-code/dist-web/assets/index8-DRAOMFpX.js # apps/pythinker-code/dist-web/assets/index8-DtL_vLpY.js # apps/pythinker-code/dist-web/assets/infoDiagram-RXCK75RN-C6ikv-Vm.js # apps/pythinker-code/dist-web/assets/infoDiagram-RXCK75RN-CmWgBjvd.js # apps/pythinker-code/dist-web/assets/infoDiagram-RXCK75RN-DAsUjnK_.js # apps/pythinker-code/dist-web/assets/ishikawaDiagram-5VMMS53U-BA25K4R4.js # apps/pythinker-code/dist-web/assets/ishikawaDiagram-5VMMS53U-BI3s7LUn.js # apps/pythinker-code/dist-web/assets/ishikawaDiagram-5VMMS53U-Bdif1tNp.js # apps/pythinker-code/dist-web/assets/javascript-C-85q5tc.js # apps/pythinker-code/dist-web/assets/javascript-CiHuAIMb.js # apps/pythinker-code/dist-web/assets/javascript-DhLeMkNR.js # apps/pythinker-code/dist-web/assets/journeyDiagram-EYS64GPL-CIfSYJfi.js # apps/pythinker-code/dist-web/assets/journeyDiagram-EYS64GPL-CPhGZ2XF.js # apps/pythinker-code/dist-web/assets/journeyDiagram-EYS64GPL-aJcir4g3.js # apps/pythinker-code/dist-web/assets/jsonMode-C0uVPm74.js # apps/pythinker-code/dist-web/assets/jsonMode-D_olTyXe.js # apps/pythinker-code/dist-web/assets/jsonMode-W3mzqTdL.js # apps/pythinker-code/dist-web/assets/kanban-definition-3QL26DDD-BLV765VU.js # apps/pythinker-code/dist-web/assets/kanban-definition-3QL26DDD-CdZyt57x.js # apps/pythinker-code/dist-web/assets/kanban-definition-3QL26DDD-D6PzBXEo.js # apps/pythinker-code/dist-web/assets/layout-B7-fAWXT.js # apps/pythinker-code/dist-web/assets/layout-BNZ9DheH.js # apps/pythinker-code/dist-web/assets/layout-DiZ-5r77.js # apps/pythinker-code/dist-web/assets/linear-B9g9xZ-Y.js # apps/pythinker-code/dist-web/assets/linear-C436T6Ai.js # apps/pythinker-code/dist-web/assets/linear-C6O97xwi.js # apps/pythinker-code/dist-web/assets/liquid-CJtHdLyz.js # apps/pythinker-code/dist-web/assets/liquid-DNdwW4p0.js # apps/pythinker-code/dist-web/assets/liquid-xOcpbfuy.js # apps/pythinker-code/dist-web/assets/lspLanguageFeatures-BxiaBydB.js # apps/pythinker-code/dist-web/assets/lspLanguageFeatures-CW8E-O0n.js # apps/pythinker-code/dist-web/assets/lspLanguageFeatures-CqD6Uysw.js # apps/pythinker-code/dist-web/assets/mdx-BRtdan5t.js # apps/pythinker-code/dist-web/assets/mdx-BXuNBhtU.js # apps/pythinker-code/dist-web/assets/mdx-CPbT9oId.js # apps/pythinker-code/dist-web/assets/mermaid.core-OuxgbeUw.js # apps/pythinker-code/dist-web/assets/mermaid.core-dpvMyTak.js # apps/pythinker-code/dist-web/assets/mermaid.core-s7wyhIOy.js # apps/pythinker-code/dist-web/assets/mindmap-definition-FBJOCRG2-CDR6XGh3.js # apps/pythinker-code/dist-web/assets/mindmap-definition-FBJOCRG2-CtmYA7M1.js # apps/pythinker-code/dist-web/assets/mindmap-definition-FBJOCRG2-Hh6pfQHH.js # apps/pythinker-code/dist-web/assets/pegDiagram-XKGWAZYB-BOUnYBVZ.js # apps/pythinker-code/dist-web/assets/pegDiagram-XKGWAZYB-C-tcscf1.js # apps/pythinker-code/dist-web/assets/pegDiagram-XKGWAZYB-vpZnM-O7.js # apps/pythinker-code/dist-web/assets/pieDiagram-E7YTZNPT-CXlc2ZzT.js # apps/pythinker-code/dist-web/assets/pieDiagram-E7YTZNPT-CxW0m5s5.js # apps/pythinker-code/dist-web/assets/pieDiagram-E7YTZNPT-DxX69_07.js # apps/pythinker-code/dist-web/assets/python-9ci-zw3F.js # apps/pythinker-code/dist-web/assets/python-B6uc32cO.js # apps/pythinker-code/dist-web/assets/python-DzyO9Aoo.js # apps/pythinker-code/dist-web/assets/quadrantDiagram-AXDQQJYC-BKn73fo_.js # apps/pythinker-code/dist-web/assets/quadrantDiagram-AXDQQJYC-BLghvNpN.js # apps/pythinker-code/dist-web/assets/quadrantDiagram-AXDQQJYC-BQxh_ksD.js # apps/pythinker-code/dist-web/assets/railroadDiagram-O6MQD6OU-B327tVxp.js # apps/pythinker-code/dist-web/assets/railroadDiagram-O6MQD6OU-CUzscmcW.js # apps/pythinker-code/dist-web/assets/railroadDiagram-O6MQD6OU-DATB1EEh.js # apps/pythinker-code/dist-web/assets/razor-BzgIK4NZ.js # apps/pythinker-code/dist-web/assets/razor-CzhmMQiQ.js # apps/pythinker-code/dist-web/assets/razor-Dpp--8hU.js # apps/pythinker-code/dist-web/assets/requirementDiagram-EFPCY7ZU-BknDcOem.js # apps/pythinker-code/dist-web/assets/requirementDiagram-EFPCY7ZU-CSOH25Ik.js # apps/pythinker-code/dist-web/assets/requirementDiagram-EFPCY7ZU-cT9hBIVP.js # apps/pythinker-code/dist-web/assets/sankeyDiagram-P5KCCOFB-DNe3S6X8.js # apps/pythinker-code/dist-web/assets/sankeyDiagram-P5KCCOFB-P68itCsu.js # apps/pythinker-code/dist-web/assets/sankeyDiagram-P5KCCOFB-SEvn4fXy.js # apps/pythinker-code/dist-web/assets/sequenceDiagram-WJ2MYXX4-C0WcnuzY.js # apps/pythinker-code/dist-web/assets/sequenceDiagram-WJ2MYXX4-CwUaS7RS.js # apps/pythinker-code/dist-web/assets/sequenceDiagram-WJ2MYXX4-E_7jkkHm.js # apps/pythinker-code/dist-web/assets/sizeCapture-X5ZJPWSS-CZnU0ZDO.js # apps/pythinker-code/dist-web/assets/sizeCapture-X5ZJPWSS-DpAFNf_3.js # apps/pythinker-code/dist-web/assets/sizeCapture-X5ZJPWSS-Syphurhi.js # apps/pythinker-code/dist-web/assets/stateDiagram-HBIQ2CUA-BjHOtf6c.js # apps/pythinker-code/dist-web/assets/stateDiagram-HBIQ2CUA-Bu3ymNkr.js # apps/pythinker-code/dist-web/assets/stateDiagram-HBIQ2CUA-C028TfZ9.js # apps/pythinker-code/dist-web/assets/swimlanes-XN3QIQJK-B5AA3TUN.js # apps/pythinker-code/dist-web/assets/swimlanes-XN3QIQJK-CC7TfnKw.js # apps/pythinker-code/dist-web/assets/swimlanes-XN3QIQJK-CL7m4FJn.js # apps/pythinker-code/dist-web/assets/timeline-definition-24CTP7MA-B6K8mH2t.js # apps/pythinker-code/dist-web/assets/timeline-definition-24CTP7MA-TVKDHpck.js # apps/pythinker-code/dist-web/assets/timeline-definition-24CTP7MA-fxmdT0sb.js # apps/pythinker-code/dist-web/assets/tsMode-AVDpJjwr.js # apps/pythinker-code/dist-web/assets/tsMode-D9-mvcWo.js # apps/pythinker-code/dist-web/assets/tsMode-DC-m6R9D.js # apps/pythinker-code/dist-web/assets/typescript-3_um0hLc.js # apps/pythinker-code/dist-web/assets/typescript-CE8-zCWd.js # apps/pythinker-code/dist-web/assets/typescript-DNBS5ufL.js # apps/pythinker-code/dist-web/assets/vennDiagram-4TSXK5OY-BZLEkyTE.js # apps/pythinker-code/dist-web/assets/vennDiagram-4TSXK5OY-Cwkr1q9t.js # apps/pythinker-code/dist-web/assets/vennDiagram-4TSXK5OY-DeBsmhIP.js # apps/pythinker-code/dist-web/assets/vue.runtime.esm-bundler-BPXwdYup.js # apps/pythinker-code/dist-web/assets/vue.runtime.esm-bundler-BTSms0Ej.js # apps/pythinker-code/dist-web/assets/vue.runtime.esm-bundler-TU1hZ_Mq.js # apps/pythinker-code/dist-web/assets/wardleyDiagram-VM6X3IG4-DDI52-xr.js # apps/pythinker-code/dist-web/assets/wardleyDiagram-VM6X3IG4-EUhW2cdz.js # apps/pythinker-code/dist-web/assets/wardleyDiagram-VM6X3IG4-HOrsWhTQ.js # apps/pythinker-code/dist-web/assets/xml-BrY6ciKs.js # apps/pythinker-code/dist-web/assets/xml-C9Uhlywu.js # apps/pythinker-code/dist-web/assets/xml-CDwOLrjX.js # apps/pythinker-code/dist-web/assets/xychartDiagram-S5SC5T6Z-BY10h7wV.js # apps/pythinker-code/dist-web/assets/xychartDiagram-S5SC5T6Z-BaUzFYB8.js # apps/pythinker-code/dist-web/assets/xychartDiagram-S5SC5T6Z-CPG9gRDI.js # apps/pythinker-code/dist-web/assets/yaml-1rcxNQw9.js # apps/pythinker-code/dist-web/assets/yaml-BATkQN3Z.js # apps/pythinker-code/dist-web/assets/yaml-DJYp6rNm.js # apps/pythinker-code/dist-web/index.html # packages/agent-core-v2/src/features/goal/goalAgentRuntime.ts # packages/agent-core-v2/test/features/goal/goalOps.test.ts
2 parents 40d0947 + 283020c commit e1e79f3

197 files changed

Lines changed: 2997 additions & 1354 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/skills/release/SKILL.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,11 @@ mechanics and failure modes.
1818
- **Private lanes use the push boundary.** `publishedPackages` only lists packages published to npm.
1919
Desktop and VS Code are private workspaces, so `detect-lane-bumps.mjs` compares their versions at
2020
`github.event.before` and `github.sha`.
21-
- **Desktop is tag-driven.** The required `cut-desktop-tag` job creates `desktop-v<version>`, which
22-
starts `desktop-release.yml`.
21+
- **Desktop Stable and Beta are tag-driven.** The required `cut-desktop-tag` job creates
22+
`desktop-v<version>`, which starts `desktop-release.yml`; prerelease versions publish to the
23+
explicit Beta feed.
24+
- **Desktop Nightly is default-branch driven.** `nightly.yml` calls `desktop-release.yml` after each
25+
scheduled main build and publishes a signed Nightly prerelease only when the main commit changed.
2326
- **VS Code is isolated.** `vscode-release.yml` supports `workflow_call` and version-checked manual
2427
dispatch. Existing registry versions are skipped by the publisher scripts, so recovery is safe.
2528

@@ -35,6 +38,7 @@ workspace, set its `private` and changesets policy explicitly and update `flake.
3538
|---|---|---|
3639
| `Release` | every main push after CI + Nix | Detect lane versions, build, run changesets |
3740
| `Cut desktop release tag` | desktop version changed | Required and idempotent; App token makes the tag trigger the desktop workflow |
41+
| `Desktop Nightly` | scheduled main build | Reusable workflow; signed assets and the explicit Nightly update feed |
3842
| `Publish VS Code extension` | extension version changed | Reusable workflow; six VSIX targets, both registries, provenance |
3943
| `Native release artifact` | CLI was published | Six signed/tested zips, checksums, provenance |
4044
| `Publish native release assets` | native builds passed | All-or-nothing immutable upload with `manifest.json` |
@@ -56,6 +60,8 @@ otherwise errors.
5660
genuinely half-published release — read the log; do not blind-rerun.
5761
- **Version PR looks wrong.** Never patch the `changeset-release/main` branch by hand. Fix or add
5862
changesets on `main`; the next workflow run regenerates the PR.
63+
- **Beta or Nightly checks Stable.** GitHub does not infer update channels. Confirm the release is a
64+
prerelease and contains `beta*.yml` or `nightly*.yml`; do not rename Stable manifests.
5965
- **Native builder fails after npm publish succeeded.** npm state is final. Re-run failed jobs from
6066
the same run before any assets upload. A complete asset set is an idempotent no-op. A partial set
6167
must not be filled from a rebuild; keep it or publish a new patch version.
@@ -83,6 +89,7 @@ otherwise errors.
8389

8490
```bash
8591
gh run list --workflow=release.yml --branch=main -L 3 # workflow health
92+
gh run list --workflow=nightly.yml --branch=main -L 3 # Nightly desktop health
8693
gh pr list --search 'ci: release packages in:title' --state open
8794
pnpm release:status # all live lanes
8895
npm view @pymodel/pythinker-code dist-tags --json
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@pymodel/pythinker-desktop": minor
3+
---
4+
5+
Add signed Beta and Nightly desktop update feeds.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@pymodel/pythinker-code": patch
3+
---
4+
5+
Fix repeated server crashes when resuming a session that stopped during a turn.

.github/workflows/desktop-release.yml

Lines changed: 119 additions & 57 deletions
Large diffs are not rendered by default.

.github/workflows/nightly.yml

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: Nightly
22

33
on:
44
schedule:
5-
- cron: '0 3 * * *'
5+
- cron: '17 3 * * *'
66
workflow_dispatch:
77

88
concurrency:
@@ -61,9 +61,18 @@ jobs:
6161
fi
6262
pnpm changeset publish --tag dev --no-git-tag
6363
64+
desktop-nightly:
65+
name: Publish desktop Nightly
66+
if: github.repository_owner == 'PyModel'
67+
uses: ./.github/workflows/desktop-release.yml
68+
with:
69+
publish_nightly: true
70+
secrets: inherit
71+
6472
release-reconciliation:
6573
name: Reconcile release lanes
66-
if: github.repository_owner == 'PyModel'
74+
needs: [publish, desktop-nightly]
75+
if: always() && github.repository_owner == 'PyModel'
6776
runs-on: ubuntu-latest
6877
permissions:
6978
contents: read
@@ -72,6 +81,7 @@ jobs:
7281
- name: Checkout
7382
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pinned from v6.0.2
7483
with:
84+
fetch-depth: 0
7585
persist-credentials: false
7686

7787
- name: Setup Node.js
@@ -83,11 +93,17 @@ jobs:
8393
id: release-status
8494
env:
8595
GITHUB_TOKEN: ${{ github.token }}
96+
UPSTREAM_OK: ${{ needs.publish.result == 'success' && needs.desktop-nightly.result == 'success' }}
8697
shell: bash
8798
run: |
8899
set +e
89100
node scripts/release/release-status.mjs > "$RUNNER_TEMP/release-status.md" 2>&1
90101
status=$?
102+
if [ "$UPSTREAM_OK" != 'true' ]; then
103+
printf '\nNightly publishers did not both succeed. CLI: %s. Desktop: %s.\n' \
104+
'${{ needs.publish.result }}' '${{ needs.desktop-nightly.result }}' >> "$RUNNER_TEMP/release-status.md"
105+
status=1
106+
fi
91107
set -e
92108
cat "$RUNNER_TEMP/release-status.md" >> "$GITHUB_STEP_SUMMARY"
93109
echo "exit_code=$status" >> "$GITHUB_OUTPUT"

apps/desktop/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ Run `pnpm run dist:win` on a native Windows x64 host; cross-building from macOS
7272

7373
Windows artifacts are signed through Azure Artifact Signing when `AZURE_TENANT_ID`, `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`, `AZURE_SIGNING_ENDPOINT`, `AZURE_SIGNING_ACCOUNT`, `AZURE_SIGNING_CERT_PROFILE`, and `AZURE_SIGNING_PUBLISHER_NAME` are all set; they are unsigned when neither signing method is set. The credential variables are read from the environment; the four `AZURE_SIGNING_*` variables map to `azureSignOptions.endpoint`, `azureSignOptions.codeSigningAccountName`, `azureSignOptions.certificateProfileName`, and `azureSignOptions.publisherName`, respectively. Setting only part of either signing method, or setting both methods, is a hard error.
7474

75-
Tagged releases require one complete Windows signing method. CI verifies the installer and packaged app with electron-updater's Authenticode verifier before upload. Both platform jobs also recompute every size and SHA-512 value in `latest.yml` or `latest-mac.yml`. The final job downloads the draft assets and repeats both manifest checks before publication. Manual workflow runs remain private workflow artifacts and cannot publish an unsigned build.
75+
Tagged releases require one complete Windows signing method. CI verifies the installer and packaged app with electron-updater's Authenticode verifier before upload. Both platform jobs also recompute every size and SHA-512 value in the selected channel manifest (`latest`, `beta`, or `nightly`). The final job downloads the draft assets and repeats both manifest checks before publication. Manual workflow runs remain private workflow artifacts and cannot publish an unsigned build.
7676

7777
## Known limitations
7878

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
#!/usr/bin/env node
2+
3+
import { appendFileSync, readFileSync, writeFileSync } from 'node:fs';
4+
import { resolve } from 'node:path';
5+
import { fileURLToPath } from 'node:url';
6+
7+
const channels = new Set(['stable', 'beta', 'nightly']);
8+
const events = new Set(['push', 'workflow_call', 'workflow_dispatch']);
9+
const versionPattern = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*))*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/u;
10+
11+
function desktopChannel(value) {
12+
if (typeof value !== 'string' || !channels.has(value)) {
13+
throw new Error('Desktop release channel must be stable, beta, or nightly.');
14+
}
15+
return value;
16+
}
17+
18+
function parseDesktopVersion(value) {
19+
const match = typeof value === 'string' ? versionPattern.exec(value) : null;
20+
if (match === null) {
21+
throw new Error(`Invalid desktop release version: ${String(value)}`);
22+
}
23+
const major = Number(match[1]);
24+
const minor = Number(match[2]);
25+
const patch = Number(match[3]);
26+
if (![major, minor, patch].every(Number.isSafeInteger)) {
27+
throw new Error(`Desktop release version is outside the safe integer range: ${value}`);
28+
}
29+
return { major, minor, patch, prerelease: match[4] };
30+
}
31+
32+
function desktopVersion(value) {
33+
parseDesktopVersion(value);
34+
return value;
35+
}
36+
37+
function versionChannel(version) {
38+
const prerelease = parseDesktopVersion(version).prerelease;
39+
if (prerelease === undefined) return 'stable';
40+
const channel = prerelease.split('.')[0];
41+
if (channel === 'beta' || channel === 'nightly') return channel;
42+
throw new Error(`Unsupported desktop release channel in ${version}.`);
43+
}
44+
45+
export function desktopReleaseChannel(version) {
46+
return versionChannel(version);
47+
}
48+
49+
function normalizedCommitCount(value) {
50+
const count = typeof value === 'number' ? value : Number(value);
51+
if (!Number.isSafeInteger(count) || count < 1 || String(count) !== String(value)) {
52+
throw new Error(`Invalid main commit count: ${String(value)}`);
53+
}
54+
return count;
55+
}
56+
57+
export function desktopManifestName(channel, platform) {
58+
const resolvedChannel = desktopChannel(channel);
59+
if (platform !== 'mac' && platform !== 'win') throw new Error(`Unsupported desktop platform: ${platform}`);
60+
const prefix = resolvedChannel === 'stable' ? 'latest' : resolvedChannel;
61+
return platform === 'mac' ? `${prefix}-mac.yml` : `${prefix}.yml`;
62+
}
63+
64+
export function nightlyDesktopVersion(packageVersion, commitCount) {
65+
const parsed = parseDesktopVersion(packageVersion);
66+
const patch = parsed.prerelease === undefined ? parsed.patch + 1 : parsed.patch;
67+
if (!Number.isSafeInteger(patch)) throw new Error(`Cannot derive a Nightly version from ${packageVersion}.`);
68+
const next = `${parsed.major}.${parsed.minor}.${patch}`;
69+
return `${next}-nightly.${normalizedCommitCount(commitCount)}`;
70+
}
71+
72+
function previewVersion(packageVersion, channel, commitCount) {
73+
if (channel === 'nightly') return nightlyDesktopVersion(packageVersion, commitCount);
74+
if (versionChannel(packageVersion) === channel) return packageVersion;
75+
const parsed = parseDesktopVersion(packageVersion);
76+
const patch = parsed.prerelease === undefined ? parsed.patch + 1 : parsed.patch;
77+
if (!Number.isSafeInteger(patch)) throw new Error(`Cannot derive a ${channel} version from ${packageVersion}.`);
78+
const next = `${parsed.major}.${parsed.minor}.${patch}`;
79+
if (channel === 'stable') return next;
80+
return `${next}-beta.${normalizedCommitCount(commitCount)}`;
81+
}
82+
83+
export function resolveDesktopRelease(options) {
84+
if (typeof options !== 'object' || options === null) throw new Error('Desktop release options are required.');
85+
const eventName = options.eventName;
86+
if (typeof eventName !== 'string' || !events.has(eventName)) {
87+
throw new Error(`Unsupported desktop release event: ${String(eventName)}`);
88+
}
89+
const packageVersion = desktopVersion(options.packageVersion);
90+
let channel;
91+
let version;
92+
let publish;
93+
94+
if (eventName === 'push') {
95+
channel = versionChannel(packageVersion);
96+
if (channel === 'nightly') {
97+
throw new Error('Nightly desktop releases must come from the scheduled workflow.');
98+
}
99+
version = packageVersion;
100+
publish = true;
101+
const expectedTag = `desktop-v${version}`;
102+
if (options.tagName !== expectedTag) {
103+
throw new Error(`Tag ${String(options.tagName)} does not match apps/desktop/package.json (${version}).`);
104+
}
105+
} else if (eventName === 'workflow_call') {
106+
if (options.publishNightly !== true) {
107+
throw new Error('Reusable desktop releases require explicit Nightly publishing permission.');
108+
}
109+
channel = 'nightly';
110+
version = nightlyDesktopVersion(packageVersion, options.commitCount);
111+
publish = true;
112+
} else {
113+
channel = desktopChannel(options.requestedChannel);
114+
version = previewVersion(packageVersion, channel, options.commitCount);
115+
publish = false;
116+
}
117+
118+
return {
119+
channel,
120+
feedChannel: channel === 'stable' ? 'latest' : channel,
121+
macManifest: desktopManifestName(channel, 'mac'),
122+
prerelease: channel !== 'stable',
123+
publish,
124+
releaseTag: `v${version}`,
125+
version,
126+
winManifest: desktopManifestName(channel, 'win'),
127+
};
128+
}
129+
130+
export function configureDesktopPackage(value, version, channel) {
131+
const resolvedVersion = desktopVersion(version);
132+
const resolvedChannel = desktopChannel(channel);
133+
if (versionChannel(resolvedVersion) !== resolvedChannel) {
134+
throw new Error(`Desktop version ${resolvedVersion} does not belong to the ${resolvedChannel} channel.`);
135+
}
136+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
137+
throw new Error('Desktop package metadata must be an object.');
138+
}
139+
const build = value.build;
140+
if (typeof build !== 'object' || build === null || Array.isArray(build)) {
141+
throw new Error('Desktop package build metadata must be an object.');
142+
}
143+
const publish = build.publish;
144+
if (!Array.isArray(publish) || publish.length === 0) {
145+
throw new Error('Desktop package must define a publish provider.');
146+
}
147+
const provider = publish[0];
148+
if (typeof provider !== 'object' || provider === null || Array.isArray(provider) || provider.provider !== 'github') {
149+
throw new Error('Desktop release publishing must use the GitHub provider.');
150+
}
151+
return {
152+
...value,
153+
version: resolvedVersion,
154+
build: {
155+
...build,
156+
publish: [
157+
{
158+
...provider,
159+
channel: resolvedChannel === 'stable' ? 'latest' : resolvedChannel,
160+
releaseType: resolvedChannel === 'stable' ? 'release' : 'prerelease',
161+
},
162+
...publish.slice(1),
163+
],
164+
},
165+
};
166+
}
167+
168+
function writeOutputs(result) {
169+
const lines = [
170+
`version=${result.version}`,
171+
`tag=${result.releaseTag}`,
172+
`channel=${result.channel}`,
173+
`feed_channel=${result.feedChannel}`,
174+
`mac_manifest=${result.macManifest}`,
175+
`win_manifest=${result.winManifest}`,
176+
`publish=${String(result.publish)}`,
177+
`prerelease=${String(result.prerelease)}`,
178+
];
179+
const output = process.env.GITHUB_OUTPUT;
180+
if (output === undefined || output === '') {
181+
for (const line of lines) process.stdout.write(`${line}\n`);
182+
} else {
183+
appendFileSync(output, `${lines.join('\n')}\n`);
184+
}
185+
}
186+
187+
function main() {
188+
const [command, ...args] = process.argv.slice(2);
189+
if (command === 'resolve' && args.length === 6) {
190+
const [eventName, packageVersion, requestedChannel, tagName, commitCount, publishNightlyValue] = args;
191+
if (!['', 'false', 'true'].includes(publishNightlyValue)) {
192+
throw new Error(`Invalid Nightly publishing permission: ${publishNightlyValue}`);
193+
}
194+
writeOutputs(resolveDesktopRelease({
195+
eventName,
196+
packageVersion,
197+
publishNightly: publishNightlyValue === 'true',
198+
requestedChannel: requestedChannel === '' ? undefined : requestedChannel,
199+
tagName: tagName === '' ? undefined : tagName,
200+
commitCount,
201+
}));
202+
return;
203+
}
204+
if (command === 'configure' && args.length === 3) {
205+
const [path, version, channel] = args;
206+
const packagePath = resolve(path);
207+
const configured = configureDesktopPackage(JSON.parse(readFileSync(packagePath, 'utf8')), version, channel);
208+
writeFileSync(packagePath, `${JSON.stringify(configured, null, 2)}\n`, 'utf8');
209+
return;
210+
}
211+
throw new Error('Usage: desktop-release.mjs resolve <event> <package-version> <channel> <tag> <commit-count> <publish-nightly> | configure <package-json> <version> <channel>');
212+
}
213+
214+
if (process.argv[1] !== undefined && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
215+
try {
216+
main();
217+
} catch (error) {
218+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
219+
process.exitCode = 1;
220+
}
221+
}

apps/desktop/scripts/verify-update-manifest.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { getFileList, parseUpdateInfo } from 'electron-updater/out/providers/Pro
99

1010
export interface VerifyUpdateManifestOptions {
1111
readonly artifactsDir: string
12+
readonly channel?: 'stable' | 'beta' | 'nightly'
1213
readonly expectedVersion: string
1314
readonly platform: 'mac' | 'win'
1415
}
@@ -28,7 +29,8 @@ async function sha512(path: string): Promise<string> {
2829

2930
/** Validate version, file references, sizes, checksums, aliases, and release date. */
3031
export async function verifyUpdateManifest(options: VerifyUpdateManifestOptions): Promise<void> {
31-
const manifestName = options.platform === 'mac' ? 'latest-mac.yml' : 'latest.yml'
32+
const prefix = options.channel === undefined || options.channel === 'stable' ? 'latest' : options.channel
33+
const manifestName = options.platform === 'mac' ? `${prefix}-mac.yml` : `${prefix}.yml`
3234
const manifestPath = join(options.artifactsDir, manifestName)
3335
const raw = await readFile(manifestPath, 'utf8')
3436
const info = parseUpdateInfo(raw, manifestName, pathToFileURL(manifestPath))
@@ -89,12 +91,17 @@ export async function verifyUpdateManifest(options: VerifyUpdateManifestOptions)
8991
}
9092

9193
async function main(): Promise<void> {
92-
const [platform, artifactsDir, expectedVersion] = process.argv.slice(2)
93-
if ((platform !== 'mac' && platform !== 'win') || artifactsDir === undefined || expectedVersion === undefined) {
94-
throw new Error('Usage: verify-update-manifest.ts <mac|win> <artifacts-directory> <version>')
94+
const [platform, artifactsDir, expectedVersion, channel = 'stable'] = process.argv.slice(2)
95+
if (
96+
(platform !== 'mac' && platform !== 'win')
97+
|| artifactsDir === undefined
98+
|| expectedVersion === undefined
99+
|| (channel !== 'stable' && channel !== 'beta' && channel !== 'nightly')
100+
) {
101+
throw new Error('Usage: verify-update-manifest.ts <mac|win> <artifacts-directory> <version> [stable|beta|nightly]')
95102
}
96-
await verifyUpdateManifest({ artifactsDir: resolve(artifactsDir), expectedVersion, platform })
97-
console.log(`${platform} update manifest verified for ${expectedVersion}`)
103+
await verifyUpdateManifest({ artifactsDir: resolve(artifactsDir), channel, expectedVersion, platform })
104+
console.log(`${platform} ${channel} update manifest verified for ${expectedVersion}`)
98105
}
99106

100107
const invokedPath = process.argv[1]

0 commit comments

Comments
 (0)