@repo-os/repoos 0.5.77 → 0.5.78

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 (176) hide show
  1. package/dist/.build-info.json +2 -2
  2. package/dist/.build-stamp.json +1 -1
  3. package/dist/cli/api-route-parity.js +3 -0
  4. package/dist/cli/help.js +10 -4
  5. package/dist/cli/index.js +7 -1
  6. package/dist/commands/control-api.d.ts +65 -2
  7. package/dist/commands/control-api.js +201 -50
  8. package/dist/commands/message-input.test.d.ts +1 -0
  9. package/dist/commands/message-input.test.js +61 -0
  10. package/dist/core/active-task-card.d.ts +5 -0
  11. package/dist/core/active-task-card.js +9 -0
  12. package/dist/core/config.js +14 -0
  13. package/dist/core/db.d.ts +5 -1
  14. package/dist/core/db.js +7 -3
  15. package/dist/core/doctor.js +4 -2
  16. package/dist/core/frontmatter.js +7 -1
  17. package/dist/core/git-maintenance-lsof.test.d.ts +1 -0
  18. package/dist/core/git-maintenance-lsof.test.js +32 -0
  19. package/dist/core/git-maintenance.d.ts +2 -0
  20. package/dist/core/git-maintenance.js +10 -5
  21. package/dist/core/indexer.js +7 -8
  22. package/dist/core/providers/spend.d.ts +36 -5
  23. package/dist/core/providers/spend.js +130 -4
  24. package/dist/core/repo-log.js +18 -8
  25. package/dist/core/shot-fixtures.d.ts +1 -1
  26. package/dist/core/shot-fixtures.js +11 -0
  27. package/dist/core/shot-targets.d.ts +1 -1
  28. package/dist/core/shot-targets.js +1 -0
  29. package/dist/core/task-body-normalize.test.js +10 -0
  30. package/dist/core/task-dependencies.js +22 -8
  31. package/dist/core/task-dependencies.test.js +46 -0
  32. package/dist/core/task-summary.d.ts +149 -0
  33. package/dist/core/task-summary.js +269 -0
  34. package/dist/core/task-summary.test.d.ts +1 -0
  35. package/dist/core/task-summary.test.js +247 -0
  36. package/dist/core/task.d.ts +6 -0
  37. package/dist/core/task.js +14 -1
  38. package/dist/core/types.d.ts +1 -0
  39. package/dist/server/agents.d.ts +9 -0
  40. package/dist/server/agents.js +75 -2
  41. package/dist/server/handoff-status.d.ts +16 -0
  42. package/dist/server/handoff-status.js +14 -0
  43. package/dist/server/handoff.d.ts +2 -0
  44. package/dist/server/handoff.js +38 -2
  45. package/dist/server/integration-orchestrator.d.ts +1 -0
  46. package/dist/server/integration-orchestrator.js +56 -40
  47. package/dist/server/live-index.js +13 -6
  48. package/dist/server/remote-validation.d.ts +96 -10
  49. package/dist/server/remote-validation.js +463 -55
  50. package/dist/server/routes/agents.js +1 -1
  51. package/dist/server/routes/model-providers.js +21 -7
  52. package/dist/server/routes/tasks.d.ts +12 -0
  53. package/dist/server/routes/tasks.js +60 -0
  54. package/dist/server/server.js +3 -1
  55. package/dist/server/task-summary.d.ts +131 -0
  56. package/dist/server/task-summary.js +754 -0
  57. package/dist/server/write.js +4 -6
  58. package/dist/ui/assets/{ActivityIndicator-meIh-6Qi.js → ActivityIndicator-BXn8FXaH.js} +1 -1
  59. package/dist/ui/assets/{AgentModelControl-CUkgcahC.js → AgentModelControl-B7Nx53jN.js} +1 -1
  60. package/dist/ui/assets/{AgentModelModal-et6vEhsU.js → AgentModelModal-BexSFFvC.js} +1 -1
  61. package/dist/ui/assets/AgentsView-BDSUKKjx.js +2 -0
  62. package/dist/ui/assets/AgentsView-fw80jZzb.css +1 -0
  63. package/dist/ui/assets/{ChatAgentModelChip-BzJzDKcR.js → ChatAgentModelChip-BH10OXwQ.js} +1 -1
  64. package/dist/ui/assets/ChecksView-7XjxxvM3.js +0 -0
  65. package/dist/ui/assets/{ChecksView-CfIhwy6Z.css → ChecksView-bW4060TJ.css} +1 -1
  66. package/dist/ui/assets/ClientBoardView-BtvaFmwp.js +1 -0
  67. package/dist/ui/assets/ClientBoardView-CMI5835W.css +1 -0
  68. package/dist/ui/assets/ContextView-BfO-Io9R.js +4 -0
  69. package/dist/ui/assets/DashboardView-CAu2hv1W.js +1 -0
  70. package/dist/ui/assets/{DeploymentsView-CA2ix9_g.js → DeploymentsView-DB-oQnih.js} +1 -1
  71. package/dist/ui/assets/{DevView-DKXpK5Qm.js → DevView-DK0NFETd.js} +2 -2
  72. package/dist/ui/assets/DiffView-DLg3DKTC.css +1 -0
  73. package/dist/ui/assets/DiffView-Doamf-Ib.js +6 -0
  74. package/dist/ui/assets/{FailedTestsList-DoNaF-o9.js → FailedTestsList-dqUj2Jdc.js} +1 -1
  75. package/dist/ui/assets/{InputsView-BPPJ8S6Y.js → InputsView-CIi6y1tW.js} +1 -1
  76. package/dist/ui/assets/{LoginView-DRz4GxbQ.js → LoginView-DHoT2wvS.js} +1 -1
  77. package/dist/ui/assets/{PageSkeleton-DJRKj5JK.js → PageSkeleton-BYwhPs40.js} +1 -1
  78. package/dist/ui/assets/{ReleasesView-9CdkP52G.js → ReleasesView-DMoT64oC.js} +1 -1
  79. package/dist/ui/assets/SecretsView-DHitDUwx.js +1 -0
  80. package/dist/ui/assets/SettingsView-Doem5tZO.js +4 -0
  81. package/dist/ui/assets/StoriesView-C092wbH4.js +1 -0
  82. package/dist/ui/assets/{VoiceDictate-CaVNQ8T-.js → VoiceDictate-BpAHW05d.js} +1 -1
  83. package/dist/ui/assets/WorkView-6a0gN2l-.css +1 -0
  84. package/dist/ui/assets/WorkView-DUltG-o6.js +2 -0
  85. package/dist/ui/assets/{abnfDiagram-VCTEODGH-Ck9-3iLy.js → abnfDiagram-VCTEODGH-Ck6r7miV.js} +1 -1
  86. package/dist/ui/assets/{architectureDiagram-5GKGNRK7-DvIcrrgC.js → architectureDiagram-5GKGNRK7-Cc0fxIsa.js} +1 -1
  87. package/dist/ui/assets/{blockDiagram-I7D4REHJ-BBoLST3y.js → blockDiagram-I7D4REHJ-0UOj4eU8.js} +1 -1
  88. package/dist/ui/assets/{c4Diagram-7LVT6UL2-BwLvPA3Y.js → c4Diagram-7LVT6UL2-DFVZRqEB.js} +1 -1
  89. package/dist/ui/assets/channel-Cvd9RE10.js +1 -0
  90. package/dist/ui/assets/check-setup-C5eaJYZG.js +2 -0
  91. package/dist/ui/assets/{chunk-2E4U76K2-DlpqhXPk.js → chunk-2E4U76K2-Bx7GZJ_K.js} +1 -1
  92. package/dist/ui/assets/{chunk-4HAMMTFA-CMuDIy4-.js → chunk-4HAMMTFA-CcFh7yhX.js} +1 -1
  93. package/dist/ui/assets/{chunk-75Z2AOVW-DhkKqPZ5.js → chunk-75Z2AOVW-Ch_fDxwg.js} +1 -1
  94. package/dist/ui/assets/{chunk-CLGD4ZFX-DnkSMImP.js → chunk-CLGD4ZFX-C3H0Sy2V.js} +1 -1
  95. package/dist/ui/assets/{chunk-DU6HZSFF-DQZ0voqQ.js → chunk-DU6HZSFF-Ip5LXuND.js} +1 -1
  96. package/dist/ui/assets/{chunk-F27PBJKO-D-StCPR-.js → chunk-F27PBJKO-BC2HCExR.js} +1 -1
  97. package/dist/ui/assets/{chunk-GMAD6QVW-DckjY_zL.js → chunk-GMAD6QVW-D9vexk6P.js} +1 -1
  98. package/dist/ui/assets/{chunk-GVQU2GXP-PYIT2Paz.js → chunk-GVQU2GXP-DlFChOLd.js} +1 -1
  99. package/dist/ui/assets/{chunk-IMKFNOWR-Cu9qJHw-.js → chunk-IMKFNOWR-BwF7XF8L.js} +1 -1
  100. package/dist/ui/assets/{chunk-L3NEJ4N5-QR-rX4_C.js → chunk-L3NEJ4N5-8JuimrXI.js} +1 -1
  101. package/dist/ui/assets/{chunk-OSK3NFVY-DjPVqKWu.js → chunk-OSK3NFVY-CEk1_jZZ.js} +1 -1
  102. package/dist/ui/assets/{chunk-P2QGCYS3-Pd3RjwwS.js → chunk-P2QGCYS3-BFEJ9oIY.js} +1 -1
  103. package/dist/ui/assets/{chunk-POPQ4Y6H-0jyMzUgO.js → chunk-POPQ4Y6H-ls4xuYo0.js} +1 -1
  104. package/dist/ui/assets/{chunk-PWAF6VOD-Chfp_-3p.js → chunk-PWAF6VOD-DAVLljYp.js} +1 -1
  105. package/dist/ui/assets/{chunk-SHT3W25Y-mEcj_J6I.js → chunk-SHT3W25Y-59nxdP_9.js} +1 -1
  106. package/dist/ui/assets/{chunk-SVP7TREG-BHj54rWr.js → chunk-SVP7TREG-BexCXvFB.js} +1 -1
  107. package/dist/ui/assets/{chunk-TICWLB2K-BwMt47bo.js → chunk-TICWLB2K-BOEpbfJI.js} +1 -1
  108. package/dist/ui/assets/{chunk-TLUHSLCS-x0ftTd9l.js → chunk-TLUHSLCS-BE4liAeZ.js} +2 -2
  109. package/dist/ui/assets/classDiagram-ZZMXUADV-0Lpgi9vA.js +1 -0
  110. package/dist/ui/assets/classDiagram-v2-VYDZK3BY-0Lpgi9vA.js +1 -0
  111. package/dist/ui/assets/{cynefinDiagram-5FMLGOSQ-DVfYNG_Q.js → cynefinDiagram-5FMLGOSQ-BEuH8cqK.js} +1 -1
  112. package/dist/ui/assets/{dagre-GXQ25YYZ-BwdC4qcS.js → dagre-GXQ25YYZ-DJHX7JRM.js} +1 -1
  113. package/dist/ui/assets/{diagram-S7CK7UJ4-JsR51qhB.js → diagram-S7CK7UJ4-CeD3KxHQ.js} +1 -1
  114. package/dist/ui/assets/{diagram-UQ7AKVKN-CMCQSDr3.js → diagram-UQ7AKVKN-B_Ur_853.js} +1 -1
  115. package/dist/ui/assets/{diagram-VSXAHHWV-CATH5Oj6.js → diagram-VSXAHHWV-k1B3tbTY.js} +1 -1
  116. package/dist/ui/assets/{diagram-VX7I27RA-CRoGHmrx.js → diagram-VX7I27RA-CaI3Gi6a.js} +1 -1
  117. package/dist/ui/assets/{diagram-Z3DM3KII-DqIuKOHQ.js → diagram-Z3DM3KII-AaNCy6XD.js} +1 -1
  118. package/dist/ui/assets/{docs-itn09t2R.js → docs-Clr18AxL.js} +1 -1
  119. package/dist/ui/assets/{ebnfDiagram-PWID7BFC-JH3ONDVx.js → ebnfDiagram-PWID7BFC-CWdQLfbF.js} +1 -1
  120. package/dist/ui/assets/{erDiagram-RLTQ6QDP-B52JimpF.js → erDiagram-RLTQ6QDP-vPkf6OX1.js} +1 -1
  121. package/dist/ui/assets/flowDiagram-HODETNUW-DzaFBPZM.js +1 -0
  122. package/dist/ui/assets/{ganttDiagram-EL5Y4UJY-DtzMq4WU.js → ganttDiagram-EL5Y4UJY-Dfc5nDxI.js} +1 -1
  123. package/dist/ui/assets/{gitGraphDiagram-WWUBYQGX--zc1RQj5.js → gitGraphDiagram-WWUBYQGX-Bq9a64PC.js} +1 -1
  124. package/dist/ui/assets/index-CqzMM_H1.css +1 -0
  125. package/dist/ui/assets/index-CrnbCa_P.js +92 -0
  126. package/dist/ui/assets/{infoDiagram-27XIBGKW-Dgs4ctzL.js → infoDiagram-27XIBGKW-CW8gtkIj.js} +1 -1
  127. package/dist/ui/assets/{ishikawaDiagram-5VMMS53U-_XEyi_2-.js → ishikawaDiagram-5VMMS53U-Bc1Rh_cU.js} +1 -1
  128. package/dist/ui/assets/{journeyDiagram-3NMN7TZE-BfGm01Ob.js → journeyDiagram-3NMN7TZE-BjWhklZZ.js} +1 -1
  129. package/dist/ui/assets/{kanban-definition-UXKFOSKX-CPqlsZTO.js → kanban-definition-UXKFOSKX-EJjRtvtc.js} +1 -1
  130. package/dist/ui/assets/{line-Daci5s3-.js → line-DsEqHdtk.js} +1 -1
  131. package/dist/ui/assets/{mermaid-parser.core-B63iqALO.js → mermaid-parser.core-BN01g6kH.js} +1 -1
  132. package/dist/ui/assets/{mermaid.core-UAYoj9xv.js → mermaid.core-BZVMMw-7.js} +3 -3
  133. package/dist/ui/assets/{mindmap-definition-YA3MSWOX-Cicc-w7u.js → mindmap-definition-YA3MSWOX-C4KW0x2p.js} +1 -1
  134. package/dist/ui/assets/{pegDiagram-XKGWAZYB-D1vcq36-.js → pegDiagram-XKGWAZYB-CWCksxEq.js} +1 -1
  135. package/dist/ui/assets/{pieDiagram-E7YTZNPT-DbdAXTmP.js → pieDiagram-E7YTZNPT-DIa72Gin.js} +1 -1
  136. package/dist/ui/assets/{quadrantDiagram-AXDQQJYC-CwEe9IOq.js → quadrantDiagram-AXDQQJYC-Cr3_KoMM.js} +1 -1
  137. package/dist/ui/assets/{railroadDiagram-O6MQD6OU-DxnMJSjL.js → railroadDiagram-O6MQD6OU-BblfxqzT.js} +1 -1
  138. package/dist/ui/assets/repo-W2udocGJ.js +61 -0
  139. package/dist/ui/assets/{requirementDiagram-BXWQKSXE-DLZTh0DQ.js → requirementDiagram-BXWQKSXE-DiDG9q-4.js} +1 -1
  140. package/dist/ui/assets/{sankeyDiagram-P5KCCOFB-CKa0Wfge.js → sankeyDiagram-P5KCCOFB-6DsHJ6Jh.js} +1 -1
  141. package/dist/ui/assets/{sequenceDiagram-WJ2MYXX4-CLOzcRmp.js → sequenceDiagram-WJ2MYXX4-CnA-wuKZ.js} +1 -1
  142. package/dist/ui/assets/{stateDiagram-D77RDMKH-DLppA560.js → stateDiagram-D77RDMKH-B1qC_HH_.js} +1 -1
  143. package/dist/ui/assets/stateDiagram-v2-MP3YSRHH-D7Wr-Zwf.js +1 -0
  144. package/dist/ui/assets/{swimlanes-42K2YHIH-CUzM6buM.js → swimlanes-42K2YHIH-CqZkLtRu.js} +1 -1
  145. package/dist/ui/assets/swimlanesDiagram-VR7AAH4N-CJtYP160.js +8 -0
  146. package/dist/ui/assets/time-8KGOT2-i.js +1 -0
  147. package/dist/ui/assets/{timeline-definition-24CTP7MA-CFPVvRBQ.js → timeline-definition-24CTP7MA-Be1h9Gq-.js} +1 -1
  148. package/dist/ui/assets/{vennDiagram-4TSXK5OY-ClsTZQ-R.js → vennDiagram-4TSXK5OY-BE5YDpBV.js} +1 -1
  149. package/dist/ui/assets/{wardleyDiagram-VM6X3IG4-DptKnpwr.js → wardleyDiagram-VM6X3IG4-D_s2lGsn.js} +1 -1
  150. package/dist/ui/assets/{xychartDiagram-S5SC5T6Z-Dvh3iz4C.js → xychartDiagram-S5SC5T6Z-BgsF5rLo.js} +1 -1
  151. package/dist/ui/index.html +12 -12
  152. package/dist/ui/sw.js +2 -2
  153. package/package.json +1 -1
  154. package/dist/ui/assets/AgentsView-DBPauK5u.css +0 -1
  155. package/dist/ui/assets/AgentsView-DeLoWSNP.js +0 -2
  156. package/dist/ui/assets/ChecksView-BUgESAfB.js +0 -0
  157. package/dist/ui/assets/ContextView-CaBC_G25.js +0 -4
  158. package/dist/ui/assets/DashboardView-CTAcrmmA.js +0 -1
  159. package/dist/ui/assets/DiffView-CcApcT6t.css +0 -1
  160. package/dist/ui/assets/DiffView-DWH1j5lq.js +0 -4
  161. package/dist/ui/assets/SecretsView-DrqlfzYZ.js +0 -1
  162. package/dist/ui/assets/SettingsView-I4YgjcHZ.js +0 -4
  163. package/dist/ui/assets/StoriesView-wndp3gL4.js +0 -1
  164. package/dist/ui/assets/WorkView-C7qnLKcz.css +0 -1
  165. package/dist/ui/assets/WorkView-DmktA6bI.js +0 -2
  166. package/dist/ui/assets/channel-CgG7lExa.js +0 -1
  167. package/dist/ui/assets/check-setup-DSNmeJ0T.js +0 -2
  168. package/dist/ui/assets/classDiagram-ZZMXUADV-D0qxKPH9.js +0 -1
  169. package/dist/ui/assets/classDiagram-v2-VYDZK3BY-D0qxKPH9.js +0 -1
  170. package/dist/ui/assets/flowDiagram-HODETNUW-Cwac_MIE.js +0 -1
  171. package/dist/ui/assets/index-DITWgaOj.css +0 -1
  172. package/dist/ui/assets/index-Dr8tocJH.js +0 -68
  173. package/dist/ui/assets/repo-BuEVC7d2.js +0 -59
  174. package/dist/ui/assets/stateDiagram-v2-MP3YSRHH-Cw7KAXTy.js +0 -1
  175. package/dist/ui/assets/swimlanesDiagram-VR7AAH4N-BdgqSlq8.js +0 -8
  176. package/dist/ui/assets/time-CKklpdEK.js +0 -1
@@ -1,5 +1,5 @@
1
1
  {
2
- "hash": "00d00bc38a262d4e2656d0b83e58441ebf83f85bb8a15fb25355d96683336177",
3
- "version": "0.5.77",
2
+ "hash": "38040072cfd2b545bf2a27d69bffb1b2a29dede01df01ee2dd8cde7388833fa9",
3
+ "version": "0.5.78",
4
4
  "devUi": false
5
5
  }
@@ -1,3 +1,3 @@
1
1
  {
2
- "generatedAt": "2026-11-06T06:24:32.457Z"
2
+ "generatedAt": "2026-11-08T14:57:15.392Z"
3
3
  }
@@ -18,6 +18,8 @@ const CONTROL_API_MATCHERS = [
18
18
  re("POST", /^\/api\/tasks\/([^/]+)\/needs-input\/dismiss$/.source),
19
19
  re("POST", /^\/api\/tasks\/([^/]+)\/review\/again$/.source),
20
20
  re("GET", /^\/api\/tasks\/([^/]+)\/landable$/.source),
21
+ // `repoos summary <id>` generates or retries the post-merge summary (#1041).
22
+ re("POST", /^\/api\/tasks\/([^/]+)\/summary$/.source),
21
23
  // Non-blocking handoff outcome for `repoos handoff-status` (#1028).
22
24
  re("GET", /^\/api\/tasks\/([^/]+)\/handoff-status$/.source),
23
25
  re("POST", /^\/api\/tasks\/([^/]+)\/preview$/.source),
@@ -78,6 +80,7 @@ const UI_ONLY_MATCHERS = [
78
80
  // Task-card-only explicit cancellation of a stalled, registered local check.
79
81
  re("POST", /^\/api\/tasks\/([^/]+)\/cancel-check$/.source),
80
82
  // Read-only task engineering-report history used by the Dev tab.
83
+ re("GET", /^\/api\/tasks\/([^/]+)\/summary$/.source),
81
84
  re("GET", /^\/api\/tasks\/([^/]+)\/engineering-reports$/.source),
82
85
  re("GET", /^\/api\/tasks\/([^/]+)\/engineering-reports\/([^/]+)$/.source),
83
86
  under("/api/auth/"),
package/dist/cli/help.js CHANGED
@@ -83,9 +83,9 @@ const GROUPS = [
83
83
  },
84
84
  {
85
85
  name: "send-back",
86
- usage: 'send-back <id> (--message "…" | --message-file <file|->) [--json] [--port N]',
86
+ usage: 'send-back <id> [text | - | --message "…" | --file <path> | --message-file <path>] [--json] [--port N]',
87
87
  desc: "Return a task to the engineer, start work, then queue feedback",
88
- flags: "--message · --message-file · --json · --port",
88
+ flags: "--message · --file · --message-file · --json · --port",
89
89
  },
90
90
  {
91
91
  name: "start",
@@ -113,9 +113,9 @@ const GROUPS = [
113
113
  },
114
114
  {
115
115
  name: "message",
116
- usage: 'message <id> "<text>" [--now] [--json] [--port N]',
116
+ usage: 'message <id> [text | - | --message "…" | --file <path> | --message-file <path>] [--now] [--json] [--port N]',
117
117
  desc: "Send a follow-up to the task agent; queues while busy, --now interrupts the turn and delivers immediately (POST /api/tasks/:id/message)",
118
- flags: "--now · --json · --port",
118
+ flags: "--message · --file · --message-file · --now · --json · --port",
119
119
  },
120
120
  {
121
121
  name: "review",
@@ -160,6 +160,12 @@ const GROUPS = [
160
160
  desc: "Read-only checks before merging (verdict, lock, worktree, merge, scope, main) — exits non-zero when the task is not ready to merge (GET /api/tasks/:id/landable)",
161
161
  flags: "--json · --port",
162
162
  },
163
+ {
164
+ name: "summary",
165
+ usage: "summary <id> [--json] [--port N]",
166
+ desc: "Generate or retry the post-merge PM summary report (POST /api/tasks/:id/summary)",
167
+ flags: "--json · --port",
168
+ },
163
169
  {
164
170
  name: "handoff-status",
165
171
  usage: "handoff-status <id> [--json] [--port N]",
package/dist/cli/index.js CHANGED
@@ -27,7 +27,7 @@ import { cmdCertify } from "../commands/certify.js";
27
27
  import { cmdSupport } from "../commands/support.js";
28
28
  import { cmdService } from "../commands/service.js";
29
29
  import { cmdServers } from "../commands/servers.js";
30
- import { cmdAgentsRunning, cmdConfig, cmdDone, cmdLandable, cmdHandoffStatus, cmdMessage, cmdSendBack, cmdOverride, cmdPause, cmdPreview, cmdDev, cmdReview, cmdVerify, cmdRunners, cmdStart, cmdSync, cmdStats, cmdDecisions, cmdApi, cmdIntegration, cmdAttention, cmdHold, cmdUnhold, } from "../commands/control-api.js";
30
+ import { cmdAgentsRunning, cmdConfig, cmdDone, cmdLandable, cmdHandoffStatus, cmdSummary, cmdMessage, cmdSendBack, cmdOverride, cmdPause, cmdPreview, cmdDev, cmdReview, cmdVerify, cmdRunners, cmdStart, cmdSync, cmdStats, cmdDecisions, cmdApi, cmdIntegration, cmdAttention, cmdHold, cmdUnhold, } from "../commands/control-api.js";
31
31
  import { cmdWatch } from "../commands/watch.js";
32
32
  import { cmdDriver } from "../commands/driver.js";
33
33
  import { cmdBundle } from "../commands/bundle.js";
@@ -333,6 +333,12 @@ function main() {
333
333
  process.exitCode = code;
334
334
  });
335
335
  break;
336
+ case "summary":
337
+ void cmdSummary(rest).then((code) => {
338
+ if (code !== 0)
339
+ process.exitCode = code;
340
+ });
341
+ break;
336
342
  case "override":
337
343
  void cmdOverride(rest).then((code) => {
338
344
  if (code !== 0)
@@ -1,11 +1,36 @@
1
1
  import { RepoOsApi, type ApiJson } from "../cli/repoos-api.js";
2
+ interface CommonOpts {
3
+ json: boolean;
4
+ wait: boolean;
5
+ now: boolean;
6
+ port?: number;
7
+ /** `repoos hold`: free-text reason the task was parked (#0960). */
8
+ reason?: string;
9
+ /** `repoos hold`: ISO-8601 auto-release time (#0960). */
10
+ until?: string;
11
+ /** `repoos hold`: who placed the hold (#0960); defaults to "driver" server-side. */
12
+ by?: string;
13
+ /** `repoos unhold --start`: start the engineer after releasing the hold (#0960). */
14
+ start?: boolean;
15
+ /** `repoos start --force`: start even a held task (#0960). */
16
+ force?: boolean;
17
+ }
2
18
  export declare function cmdStart(args: string[]): Promise<number>;
3
19
  export declare function cmdHold(args: string[]): Promise<number>;
4
20
  export declare function cmdUnhold(args: string[]): Promise<number>;
5
21
  export declare function cmdPause(args: string[]): Promise<number>;
6
- export declare function cmdMessage(args: string[]): Promise<number>;
22
+ /** Parse message sources before contacting the server, preserving option values verbatim. */
23
+ export declare function parseMessageInput(args: string[], stdin?: {
24
+ isTTY: boolean;
25
+ read: () => string;
26
+ }): {
27
+ id: string;
28
+ text: string;
29
+ opts: CommonOpts;
30
+ };
31
+ export declare function cmdMessage(args: string[], stdin?: Parameters<typeof parseMessageInput>[1]): Promise<number>;
7
32
  /** `repoos send-back` — reopen, start the engineer, then deliver feedback. */
8
- export declare function cmdSendBack(args: string[]): Promise<number>;
33
+ export declare function cmdSendBack(args: string[], stdin?: Parameters<typeof parseMessageInput>[1]): Promise<number>;
9
34
  export declare function cmdReviewAgain(args: string[]): Promise<number>;
10
35
  export declare function cmdReview(args: string[]): Promise<number>;
11
36
  type ReviewWaitApi = Pick<RepoOsApi, "requestOk">;
@@ -94,6 +119,44 @@ export declare function cmdHandoffStatus(args: string[]): Promise<number>;
94
119
  * exit 0 only when every check passes. Never edits, commits or moves status.
95
120
  */
96
121
  export declare function cmdLandable(args: string[]): Promise<number>;
122
+ /** Shape of `GET /api/tasks/:id/summary` used by `repoos summary`. */
123
+ export interface TaskSummaryState {
124
+ ok: boolean;
125
+ exists: boolean;
126
+ markdown: string | null;
127
+ status: "idle" | "pending" | "done" | "failed";
128
+ statusAt: string | null;
129
+ /** Anchor for this `pending` run (ISO); mirrors server payload. */
130
+ pendingSince: string | null;
131
+ reportMtimeMs: number | null;
132
+ error: string | null;
133
+ }
134
+ /**
135
+ * Terminal poll states: `done`/`failed`, a committed report while status is
136
+ * `idle` (lost write), or `pending` long enough that the server coerces to `done`
137
+ * (API returns `done` when stale; this mirrors that for older servers).
138
+ */
139
+ export declare function isSummaryPollTerminal(state: Pick<TaskSummaryState, "status" | "exists" | "statusAt" | "pendingSince" | "reportMtimeMs">, expectedRunAt?: string | null): boolean;
140
+ /**
141
+ * Poll a summary status fetcher until terminal. A committed report (`exists`)
142
+ * with non-`pending` status is terminal (including `idle` when the done-status
143
+ * write was lost). While `pending`, keep polling even when the file exists —
144
+ * a retry may still be running. Persistent fetch failures fail fast.
145
+ */
146
+ export declare function pollSummaryState(getState: () => Promise<TaskSummaryState>, opts?: {
147
+ label?: string;
148
+ timeoutMs?: number;
149
+ intervalMs?: number;
150
+ maxConsecutiveErrors?: number;
151
+ expectedRunAt?: string | null;
152
+ }): Promise<TaskSummaryState>;
153
+ /**
154
+ * `repoos summary <id>` (#1041): queue the post-merge PM summary report and
155
+ * wait for its terminal state. Requires a released (`done`) task — the normal
156
+ * path runs it automatically after Move to done; this is the visible retry.
157
+ * Never touches the task status.
158
+ */
159
+ export declare function cmdSummary(args: string[]): Promise<number>;
97
160
  /** Shared server-down check for tests. */
98
161
  export declare function controlApiServerDownMessage(port: number): Promise<string>;
99
162
  export {};
@@ -8,6 +8,7 @@ import { effectiveEngineerFromApi } from "../cli/effective-from-api.js";
8
8
  import { HANDOFF_DEADLINE_MS } from "../server/handoff.js";
9
9
  import { fmtDuration } from "./driver.js";
10
10
  import { agentModelAllowRows, pauseAllowlistEntry, resumeAllowlistEntry, pairPauseInfo, } from "../core/budget-allowlist.js";
11
+ import { effectiveSummaryJobStatus } from "../core/task-summary.js";
11
12
  function parseCommonFlags(args) {
12
13
  const opts = { json: false, wait: false, now: false };
13
14
  const rest = [];
@@ -276,18 +277,88 @@ export async function cmdPause(args) {
276
277
  return 1;
277
278
  }
278
279
  }
279
- export async function cmdMessage(args) {
280
- const { rest, opts, error } = parseCommonFlags(args);
281
- if (error) {
282
- printErr(error);
283
- return 1;
280
+ /** Parse message sources before contacting the server, preserving option values verbatim. */
281
+ export function parseMessageInput(args, stdin = { isTTY: process.stdin.isTTY, read: () => readFileSync(0, "utf8") }) {
282
+ const opts = { json: false, wait: false, now: false };
283
+ let id;
284
+ let source;
285
+ let value = "";
286
+ const choose = (next, input) => {
287
+ if (source && !(source === "inline" && next === "inline"))
288
+ throw new Error("Use only one message input source");
289
+ value = source === "inline" ? `${value} ${input}` : input;
290
+ source = next;
291
+ };
292
+ for (let i = 0; i < args.length; i++) {
293
+ const arg = args[i];
294
+ if (arg === "--json")
295
+ opts.json = true;
296
+ else if (arg === "--now")
297
+ opts.now = true;
298
+ else if (arg === "--port") {
299
+ const port = Number(args[++i]);
300
+ if (!Number.isInteger(port) || port <= 0)
301
+ throw new Error("--port requires a positive integer");
302
+ opts.port = port;
303
+ }
304
+ else if (arg === "--message" || arg === "--file" || arg === "--message-file") {
305
+ const input = args[++i];
306
+ if (input === undefined || input.startsWith("--"))
307
+ throw new Error(`${arg} requires a value`);
308
+ choose(arg === "--message" ? "message" : "file", input);
309
+ }
310
+ else if (arg.startsWith("--"))
311
+ throw new Error(`Unknown flag: ${arg}`);
312
+ else if (!id) {
313
+ if (arg === "-")
314
+ throw new Error("A task id is required before stdin");
315
+ id = arg;
316
+ }
317
+ else if (arg === "-")
318
+ choose("stdin", "-");
319
+ else
320
+ choose("inline", arg);
284
321
  }
285
- const id = rest[0];
286
- const text = rest.slice(1).join(" ").trim();
287
- if (!id || !text) {
288
- printErr('Usage: repoos message <id> "<text>" [--now] [--json] [--port N]');
322
+ if (!id)
323
+ throw new Error("A task id is required");
324
+ if (!source && !stdin.isTTY)
325
+ source = "stdin";
326
+ if (source === "stdin" || (source === "file" && value === "-")) {
327
+ if (stdin.isTTY)
328
+ throw new Error("No stdin provided; pipe a message or use a heredoc");
329
+ try {
330
+ value = stdin.read();
331
+ }
332
+ catch (e) {
333
+ throw new Error(`Could not read stdin: ${e.message}`);
334
+ }
335
+ }
336
+ else if (source === "file") {
337
+ try {
338
+ value = readFileSync(value, "utf8");
339
+ }
340
+ catch (e) {
341
+ throw new Error(`Could not read message file: ${e.message}`);
342
+ }
343
+ }
344
+ const text = value.trim();
345
+ if (!text)
346
+ throw new Error("Message cannot be empty or whitespace-only");
347
+ return { id, text, opts };
348
+ }
349
+ function messageSummary(text) {
350
+ return `${text.length} characters: ${text.split(/\r?\n/, 1)[0]}`;
351
+ }
352
+ export async function cmdMessage(args, stdin) {
353
+ let input;
354
+ try {
355
+ input = parseMessageInput(args, stdin);
356
+ }
357
+ catch (e) {
358
+ printErr(e.message);
289
359
  return 1;
290
360
  }
361
+ const { id, text, opts } = input;
291
362
  const api = new RepoOsApi({ port: opts.port });
292
363
  try {
293
364
  await ensureServer(api);
@@ -301,14 +372,14 @@ export async function cmdMessage(args) {
301
372
  const why = data.now
302
373
  ? (data.reason ?? "agent still busy after the interrupt")
303
374
  : "agent busy — delivers when the turn ends";
304
- console.log(c.green(" ✓ ") + c.dim(`message queued to #${id} (${why})`));
375
+ console.log(c.green(" ✓ ") + c.dim(`message queued to #${id} (${why}) — ${messageSummary(text)}`));
305
376
  }
306
377
  else if (data?.deliveredNow) {
307
378
  const how = data.interrupted ? " (interrupted the running turn)" : " (agent was idle)";
308
- console.log(c.green(" ✓ ") + c.dim(`message delivered now to #${id}${how}`));
379
+ console.log(c.green(" ✓ ") + c.dim(`message delivered now to #${id}${how} — ${messageSummary(text)}`));
309
380
  }
310
381
  else {
311
- console.log(c.green(" ✓ ") + c.dim(`message sent to #${id}`));
382
+ console.log(c.green(" ✓ ") + c.dim(`message sent to #${id} — ${messageSummary(text)}`));
312
383
  }
313
384
  return 0;
314
385
  }
@@ -318,49 +389,20 @@ export async function cmdMessage(args) {
318
389
  }
319
390
  }
320
391
  /** `repoos send-back` — reopen, start the engineer, then deliver feedback. */
321
- export async function cmdSendBack(args) {
322
- const { rest, opts, error } = parseCommonFlags(args);
323
- if (error) {
324
- printErr(error);
392
+ export async function cmdSendBack(args, stdin) {
393
+ let input;
394
+ try {
395
+ input = parseMessageInput(args, stdin);
396
+ }
397
+ catch (e) {
398
+ printErr(e.message);
325
399
  return 1;
326
400
  }
401
+ const { id, text: message, opts } = input;
327
402
  if (opts.now) {
328
403
  printErr("repoos send-back does not support --now; feedback is queued after the engineer starts");
329
404
  return 1;
330
405
  }
331
- let id;
332
- let message;
333
- let messageFile;
334
- for (let i = 0; i < rest.length; i++) {
335
- if (rest[i] === "--message")
336
- message = rest[++i];
337
- else if (rest[i] === "--message-file")
338
- messageFile = rest[++i];
339
- else if (!id)
340
- id = rest[i];
341
- else {
342
- printErr(`Unexpected argument: ${rest[i]}`);
343
- return 1;
344
- }
345
- }
346
- if (message !== undefined && messageFile !== undefined) {
347
- printErr("Use --message or --message-file, not both");
348
- return 1;
349
- }
350
- if (messageFile !== undefined) {
351
- try {
352
- message = messageFile === "-" ? readFileSync(0, "utf8") : readFileSync(messageFile, "utf8");
353
- }
354
- catch (e) {
355
- printErr(`Could not read message file: ${e.message}`);
356
- return 1;
357
- }
358
- }
359
- message = message?.trim();
360
- if (!id || !message) {
361
- printErr('Usage: repoos send-back <id> (--message "…" | --message-file <file|->) [--json] [--port N]');
362
- return 1;
363
- }
364
406
  const api = new RepoOsApi({ port: opts.port });
365
407
  try {
366
408
  await ensureServer(api);
@@ -370,7 +412,7 @@ export async function cmdSendBack(args) {
370
412
  else {
371
413
  console.log(c.green(" ✓ ") + c.dim(`moved #${id} to active`));
372
414
  console.log(c.green(" ✓ ") + c.dim(`started engineer on #${id}`));
373
- console.log(c.green(" ✓ ") + c.dim(`queued message to #${id}`));
415
+ console.log(c.green(" ✓ ") + c.dim(`queued message to #${id} — ${messageSummary(message)}`));
374
416
  }
375
417
  return 0;
376
418
  }
@@ -1602,6 +1644,115 @@ export async function cmdLandable(args) {
1602
1644
  return 1;
1603
1645
  }
1604
1646
  }
1647
+ /**
1648
+ * Terminal poll states: `done`/`failed`, a committed report while status is
1649
+ * `idle` (lost write), or `pending` long enough that the server coerces to `done`
1650
+ * (API returns `done` when stale; this mirrors that for older servers).
1651
+ */
1652
+ export function isSummaryPollTerminal(state, expectedRunAt) {
1653
+ if (state.status === "failed")
1654
+ return true;
1655
+ // A successful POST starts a specific run. Do not let an unchanged idle/done
1656
+ // record and its previous report satisfy that run's poll.
1657
+ if (expectedRunAt) {
1658
+ const runAt = Date.parse(expectedRunAt);
1659
+ if (!Number.isFinite(runAt))
1660
+ return false;
1661
+ if (state.status === "pending") {
1662
+ const pendingAt = Date.parse(state.pendingSince ?? state.statusAt ?? "");
1663
+ if (!Number.isFinite(pendingAt) || pendingAt < runAt)
1664
+ return false;
1665
+ }
1666
+ else {
1667
+ const stateAt = Date.parse(state.statusAt ?? "");
1668
+ if (!Number.isFinite(stateAt) || stateAt < runAt)
1669
+ return false;
1670
+ if (state.exists && state.reportMtimeMs !== null && state.reportMtimeMs < runAt)
1671
+ return false;
1672
+ }
1673
+ }
1674
+ if (state.status === "done")
1675
+ return true;
1676
+ const eff = effectiveSummaryJobStatus(state.status, state.statusAt ?? "", state.exists, Date.now(), state.reportMtimeMs ?? null, state.pendingSince);
1677
+ if (eff === "done")
1678
+ return true;
1679
+ if (state.exists && state.status !== "pending")
1680
+ return true;
1681
+ return false;
1682
+ }
1683
+ const SUMMARY_MAX_GET_ERRORS = 10;
1684
+ /**
1685
+ * Poll a summary status fetcher until terminal. A committed report (`exists`)
1686
+ * with non-`pending` status is terminal (including `idle` when the done-status
1687
+ * write was lost). While `pending`, keep polling even when the file exists —
1688
+ * a retry may still be running. Persistent fetch failures fail fast.
1689
+ */
1690
+ export async function pollSummaryState(getState, opts = {}) {
1691
+ const maxConsecutiveErrors = opts.maxConsecutiveErrors ?? SUMMARY_MAX_GET_ERRORS;
1692
+ let consecutiveGetErrors = 0;
1693
+ const label = opts.label ?? "summary";
1694
+ return pollUntil(async () => {
1695
+ let state;
1696
+ try {
1697
+ state = await getState();
1698
+ }
1699
+ catch (err) {
1700
+ consecutiveGetErrors += 1;
1701
+ if (consecutiveGetErrors >= maxConsecutiveErrors)
1702
+ throw new RepoOsApiError(`summary status unreachable (${label}): ${err.message}`, 503);
1703
+ return null;
1704
+ }
1705
+ consecutiveGetErrors = 0;
1706
+ return isSummaryPollTerminal(state, opts.expectedRunAt) ? state : null;
1707
+ }, { label, timeoutMs: opts.timeoutMs ?? 900_000, intervalMs: opts.intervalMs ?? 2_000 });
1708
+ }
1709
+ /**
1710
+ * `repoos summary <id>` (#1041): queue the post-merge PM summary report and
1711
+ * wait for its terminal state. Requires a released (`done`) task — the normal
1712
+ * path runs it automatically after Move to done; this is the visible retry.
1713
+ * Never touches the task status.
1714
+ */
1715
+ export async function cmdSummary(args) {
1716
+ const { rest, opts, error } = parseCommonFlags(args);
1717
+ if (error) {
1718
+ printErr(error);
1719
+ return 1;
1720
+ }
1721
+ const id = rest[0];
1722
+ if (!id) {
1723
+ printErr("Usage: repoos summary <id> [--json] [--port N]");
1724
+ return 1;
1725
+ }
1726
+ const api = new RepoOsApi({ port: opts.port });
1727
+ try {
1728
+ await ensureServer(api);
1729
+ const queued = await api.request("POST", `/api/tasks/${id}/summary`, {});
1730
+ if (queued.status === 404)
1731
+ throw new RepoOsApiError(`Task #${id} not found`, 404, queued.body);
1732
+ if (queued.status !== 202 || !queued.body.ok)
1733
+ throw new RepoOsApiError(queued.body.error ?? `summary refused (${queued.status})`, queued.status, queued.body);
1734
+ if (!opts.json)
1735
+ console.log(c.dim(` … generating post-merge summary for #${id}`));
1736
+ const expectedRunAt = typeof queued.body.pendingSince === "string" ? queued.body.pendingSince : null;
1737
+ const final = await pollSummaryState(async () => (await api.requestOk("GET", `/api/tasks/${id}/summary`)), { label: `summary for #${id}`, expectedRunAt });
1738
+ const eff = effectiveSummaryJobStatus(final.status, final.statusAt ?? "", final.exists, Date.now(), final.reportMtimeMs ?? null, final.pendingSince);
1739
+ const success = eff === "done" || (final.exists && eff !== "failed" && eff !== "pending");
1740
+ if (opts.json) {
1741
+ printJson(final);
1742
+ return success ? 0 : 1;
1743
+ }
1744
+ if (success) {
1745
+ console.log(c.green(` ✓ #${id} summary ready: work/reports/${id}/summary.md`));
1746
+ return 0;
1747
+ }
1748
+ printErr(final.error ?? `summary generation failed for #${id}`);
1749
+ return 1;
1750
+ }
1751
+ catch (e) {
1752
+ printErr(e.message);
1753
+ return 1;
1754
+ }
1755
+ }
1605
1756
  /** Shared server-down check for tests. */
1606
1757
  export async function controlApiServerDownMessage(port) {
1607
1758
  const { baseUrl } = resolveControlPlaneBase({ port });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,61 @@
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
+ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { cmdMessage, cmdSendBack, parseMessageInput } from "./control-api.js";
6
+ import { RepoOsApi } from "../cli/repoos-api.js";
7
+ const dir = mkdtempSync(join(tmpdir(), "repoos-message-input-"));
8
+ const file = join(dir, "brief.txt");
9
+ writeFileSync(file, " first line\nsecond line \n");
10
+ const piped = { isTTY: false, read: () => " first line\nsecond line \n" };
11
+ const terminal = {
12
+ isTTY: true,
13
+ read: () => {
14
+ throw new Error("unexpected read");
15
+ },
16
+ };
17
+ afterEach(() => vi.restoreAllMocks());
18
+ process.on("exit", () => rmSync(dir, { recursive: true, force: true }));
19
+ describe.each([
20
+ ["message", cmdMessage],
21
+ ["send-back", cmdSendBack],
22
+ ])("%s message input", (_name, command) => {
23
+ it.each([
24
+ [["first", "line"], "first line"],
25
+ [["-"], "first line\nsecond line"],
26
+ [[], "first line\nsecond line"],
27
+ [["--file", file], "first line\nsecond line"],
28
+ [["--message-file", file], "first line\nsecond line"],
29
+ [["--message", "first line"], "first line"],
30
+ [["--message-file", "-"], "first line\nsecond line"],
31
+ ])("accepts %j", async (args, expected) => {
32
+ vi.spyOn(RepoOsApi.prototype, "health").mockResolvedValue(true);
33
+ const request = vi.spyOn(RepoOsApi.prototype, "requestOk").mockResolvedValue({});
34
+ vi.spyOn(console, "log").mockImplementation(() => { });
35
+ expect(await command(["123", ...args], piped)).toBe(0);
36
+ expect(request).toHaveBeenCalledWith("POST", expect.any(String), expect.objectContaining(_name === "message" ? { text: expected } : { message: expected }));
37
+ });
38
+ it.each([
39
+ ["text", "--file", file],
40
+ ["-"],
41
+ ["--unknown"],
42
+ ["--message", " "],
43
+ ["--file", join(dir, "missing")],
44
+ ["--message"],
45
+ ["--message", "x", "extra"],
46
+ ])("rejects %j before the API", async (...args) => {
47
+ expect(() => parseMessageInput(["123", ...args], terminal)).toThrow();
48
+ const health = vi.spyOn(RepoOsApi.prototype, "health");
49
+ vi.spyOn(console, "error").mockImplementation(() => { });
50
+ expect(await command(["123", ...args], terminal)).toBe(1);
51
+ expect(health).not.toHaveBeenCalled();
52
+ });
53
+ it("reports the text length and first line", async () => {
54
+ vi.spyOn(RepoOsApi.prototype, "health").mockResolvedValue(true);
55
+ const request = vi.spyOn(RepoOsApi.prototype, "requestOk").mockResolvedValue({});
56
+ const log = vi.spyOn(console, "log").mockImplementation(() => { });
57
+ expect(await command(["123", "--message", "hello\nworld"])).toBe(0);
58
+ expect(request).toHaveBeenCalledWith("POST", expect.any(String), expect.objectContaining(_name === "message" ? { text: "hello\nworld" } : { message: "hello\nworld" }));
59
+ expect(log.mock.calls.flat().join(" ")).toContain("11 characters: hello");
60
+ });
61
+ });
@@ -56,6 +56,11 @@ export interface ActiveTaskCardInput {
56
56
  localQueueTitle: string | null;
57
57
  remotePoolQueueLabel: string | null;
58
58
  remotePoolQueueTitle: string | null;
59
+ /** A live local check slot can prove a run when no check history row exists. */
60
+ localCheckSlot?: {
61
+ ageMs: number;
62
+ heartbeatAgeMs: number;
63
+ } | null;
59
64
  busyChild: string | null;
60
65
  stuckSilenceMs?: number;
61
66
  checkStallMs?: number;
@@ -99,6 +99,15 @@ export function resolveActiveEngineerCardHint(input) {
99
99
  blocksAgentInterrupt: true,
100
100
  };
101
101
  }
102
+ if (input.localCheckSlot) {
103
+ const elapsed = formatCardDuration(input.localCheckSlot.ageMs);
104
+ return {
105
+ label: `running checks · check · ${elapsed}`,
106
+ title: `A local check slot is held for this task · running for ${elapsed}.`,
107
+ cls: "tc-reviewing",
108
+ blocksAgentInterrupt: true,
109
+ };
110
+ }
102
111
  const check = input.check;
103
112
  if (check?.running && checkHeartbeating(check, input.nowMs, stallMs)) {
104
113
  const elapsed = formatCardDuration(msSince(check.startedAt, input.nowMs) ?? 0);
@@ -235,6 +235,7 @@ export const DEFAULT_CONFIG = {
235
235
  worktreeWarnThreshold: 20,
236
236
  board: {
237
237
  agentPopoverCloseSeconds: 15,
238
+ doneCollapsedByDefault: true,
238
239
  },
239
240
  whisper: {
240
241
  provider: "none",
@@ -1561,6 +1562,9 @@ export function loadConfig(rootArg, options = {}) {
1561
1562
  cfg.worktreeWarnThreshold = Math.floor(worktreeWarnThreshold);
1562
1563
  else if (typeof worktreeWarnThreshold === "string" && /^\d+$/.test(worktreeWarnThreshold))
1563
1564
  cfg.worktreeWarnThreshold = Number(worktreeWarnThreshold);
1565
+ const doneCollapsedByDefault = get("board.doneCollapsedByDefault");
1566
+ if (typeof doneCollapsedByDefault === "boolean")
1567
+ cfg.board = { ...cfg.board, doneCollapsedByDefault };
1564
1568
  const popoverClose = get("board.agentPopoverCloseSeconds");
1565
1569
  const popoverCloseNum = typeof popoverClose === "number"
1566
1570
  ? popoverClose
@@ -2901,6 +2905,15 @@ export function getConfigSchema() {
2901
2905
  ],
2902
2906
  description: "Advisory ceiling on registered git worktrees. Above it, the Control page's Codebase card turns amber and the server logs a `repoos gc` reminder. Never blocks a task.",
2903
2907
  },
2908
+ {
2909
+ key: "board.doneCollapsedByDefault",
2910
+ label: "Start Done collapsed",
2911
+ type: "boolean",
2912
+ tier: "live",
2913
+ restartRequired: false,
2914
+ default: true,
2915
+ description: "Collapse Done for browsers without a saved board preference.",
2916
+ },
2904
2917
  {
2905
2918
  key: "board.agentPopoverCloseSeconds",
2906
2919
  label: "Agent popover auto-close",
@@ -4027,6 +4040,7 @@ export const SUPPORTED_TOML_KEYS = [
4027
4040
  "cto.nudgeCooldownMs",
4028
4041
  "skillSuggestions",
4029
4042
  "worktreeWarnThreshold",
4043
+ "board.doneCollapsedByDefault",
4030
4044
  "board.agentPopoverCloseSeconds",
4031
4045
  "dev.inspector.enabled",
4032
4046
  "dev.inspector.editorCommand",
package/dist/core/db.d.ts CHANGED
@@ -198,7 +198,11 @@ export declare class RepoOSDb {
198
198
  * rounds for a candidate's completed work without a cross join.
199
199
  */
200
200
  getTaskSessionTypeCounts(taskIds: string[], sessionTypes: string[]): Map<string, number>;
201
- /** Retrieve all sessions for a task. */
201
+ /**
202
+ * Retrieve all sessions for a task. Returns [] when SQLite is unavailable
203
+ * (no DB). Throws on query/prepare failures so callers can distinguish
204
+ * "no rows" from a broken read (#1041).
205
+ */
202
206
  getTaskSessions(taskId: string): SessionRecord[];
203
207
  /** Aggregate stats for a task (all sessions and review rounds). */
204
208
  getTaskStats(taskId: string): TaskStats | null;
package/dist/core/db.js CHANGED
@@ -452,7 +452,11 @@ export class RepoOSDb {
452
452
  }
453
453
  return out;
454
454
  }
455
- /** Retrieve all sessions for a task. */
455
+ /**
456
+ * Retrieve all sessions for a task. Returns [] when SQLite is unavailable
457
+ * (no DB). Throws on query/prepare failures so callers can distinguish
458
+ * "no rows" from a broken read (#1041).
459
+ */
456
460
  getTaskSessions(taskId) {
457
461
  if (!this.available || !this.db)
458
462
  return [];
@@ -461,8 +465,8 @@ export class RepoOSDb {
461
465
  .prepare("SELECT * FROM sessions WHERE taskId = ? ORDER BY startedAt DESC")
462
466
  .all(taskId);
463
467
  }
464
- catch {
465
- return [];
468
+ catch (err) {
469
+ throw new Error(`getTaskSessions failed: ${err.message}`);
466
470
  }
467
471
  }
468
472
  /** Aggregate stats for a task (all sessions and review rounds). */