toolroll 0.9.53 → 0.9.55

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 (254) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +5 -4
  3. package/dist/agent-onboard.js +4 -1
  4. package/dist/approval-rules-ui.d.ts +3 -2
  5. package/dist/approval-rules-ui.js +15 -20
  6. package/dist/assignment-ui.d.ts +3 -2
  7. package/dist/assignment-ui.js +4 -11
  8. package/dist/backup-ui.d.ts +2 -1
  9. package/dist/backup-ui.js +16 -27
  10. package/dist/brand-mark.d.ts +3 -2
  11. package/dist/brand-mark.js +4 -4
  12. package/dist/browser/workspace.css +1 -1
  13. package/dist/browser/workspace.js +25 -25
  14. package/dist/browser-shell.d.ts +2 -1
  15. package/dist/browser-shell.js +9 -9
  16. package/dist/browser-workspace.d.ts +2 -3
  17. package/dist/browser-workspace.js +0 -3
  18. package/dist/chat-ask.js +8 -8
  19. package/dist/chat-core.d.ts +171 -0
  20. package/dist/chat-core.js +288 -0
  21. package/dist/chat-decide.d.ts +10 -8
  22. package/dist/chat-decide.js +26 -21
  23. package/dist/chat-delivery-state.d.ts +51 -75
  24. package/dist/chat-delivery-state.js +140 -170
  25. package/dist/chat-delivery.d.ts +21 -2
  26. package/dist/chat-delivery.js +313 -194
  27. package/dist/chat-flow.d.ts +29 -1
  28. package/dist/chat-flow.js +24 -50
  29. package/dist/chat-messages.d.ts +319 -0
  30. package/dist/chat-messages.js +841 -0
  31. package/dist/chat-migration.d.ts +30 -0
  32. package/dist/chat-migration.js +802 -0
  33. package/dist/chat-polish.d.ts +5 -4
  34. package/dist/chat-polish.js +9 -10
  35. package/dist/chat-quiet.d.ts +3 -0
  36. package/dist/chat-quiet.js +5 -0
  37. package/dist/check-levels-ui.d.ts +2 -1
  38. package/dist/check-levels-ui.js +26 -53
  39. package/dist/cli.d.ts +3 -1
  40. package/dist/cli.js +9 -3
  41. package/dist/coding-shipping-ui.d.ts +2 -1
  42. package/dist/coding-shipping-ui.js +6 -6
  43. package/dist/coding-ui.d.ts +3 -1
  44. package/dist/coding-ui.js +30 -29
  45. package/dist/contracts/chat-content.d.ts +71 -2
  46. package/dist/contracts/chat-content.js +22 -3
  47. package/dist/contracts/chat-tables.d.ts +11 -4
  48. package/dist/contracts/chat-tables.js +165 -87
  49. package/dist/contracts/cli.d.ts +1 -3
  50. package/dist/contracts/console-api.d.ts +1 -1
  51. package/dist/contracts/console-api.js +6 -5
  52. package/dist/contracts/discord-callback.js +2 -1
  53. package/dist/contracts/health.d.ts +1 -3
  54. package/dist/contracts/slack-callback.js +3 -2
  55. package/dist/contracts/store-json.d.ts +1 -5
  56. package/dist/contracts/store-json.js +1 -4
  57. package/dist/contracts/teams-callback.js +2 -1
  58. package/dist/control-ui.d.ts +5 -4
  59. package/dist/control-ui.js +9 -34
  60. package/dist/credentials-ui.d.ts +5 -4
  61. package/dist/credentials-ui.js +23 -24
  62. package/dist/demo-chat.d.ts +3 -3
  63. package/dist/demo-chat.js +29 -76
  64. package/dist/discord-chat.js +19 -38
  65. package/dist/discord-settings.d.ts +3 -2
  66. package/dist/discord-settings.js +21 -55
  67. package/dist/discord.js +4 -14
  68. package/dist/envelope.js +10 -0
  69. package/dist/evidence-pack.d.ts +2 -1
  70. package/dist/evidence-pack.js +45 -49
  71. package/dist/export-ui.d.ts +7 -2
  72. package/dist/export-ui.js +4 -10
  73. package/dist/export.js +4 -4
  74. package/dist/flow-gallery-ui.d.ts +4 -3
  75. package/dist/flow-gallery-ui.js +28 -43
  76. package/dist/flow-starters-ui.d.ts +2 -1
  77. package/dist/flow-starters-ui.js +12 -15
  78. package/dist/flow-triggers.d.ts +1 -0
  79. package/dist/flow-triggers.js +16 -14
  80. package/dist/flows-ui.d.ts +4 -3
  81. package/dist/flows-ui.js +19 -30
  82. package/dist/flows.js +2 -1
  83. package/dist/guides.js +108 -3
  84. package/dist/html.d.ts +76 -0
  85. package/dist/html.js +173 -0
  86. package/dist/integrations-ui.d.ts +2 -1
  87. package/dist/integrations-ui.js +19 -23
  88. package/dist/kits-ui.d.ts +5 -4
  89. package/dist/kits-ui.js +18 -20
  90. package/dist/knowledge-ui.d.ts +6 -5
  91. package/dist/knowledge-ui.js +26 -25
  92. package/dist/lead-context.d.ts +11 -1
  93. package/dist/lead-context.js +6 -8
  94. package/dist/ledger-view.d.ts +2 -1
  95. package/dist/ledger-view.js +27 -29
  96. package/dist/limits-ui.d.ts +2 -1
  97. package/dist/limits-ui.js +7 -7
  98. package/dist/live-bus.d.ts +50 -8
  99. package/dist/live-bus.js +133 -50
  100. package/dist/mate-tools.js +3 -2
  101. package/dist/mcp-oauth.js +35 -36
  102. package/dist/models-ui.d.ts +2 -1
  103. package/dist/models-ui.js +25 -35
  104. package/dist/modes.d.ts +13 -1
  105. package/dist/modes.js +17 -1
  106. package/dist/monitoring-ui.d.ts +4 -3
  107. package/dist/monitoring-ui.js +13 -26
  108. package/dist/notification-rows.d.ts +30 -0
  109. package/dist/notification-rows.js +102 -0
  110. package/dist/openrouter-models.d.ts +2 -1
  111. package/dist/openrouter-models.js +5 -9
  112. package/dist/operate.js +16 -9
  113. package/dist/people-audit-ui.d.ts +2 -1
  114. package/dist/people-audit-ui.js +13 -13
  115. package/dist/plane-review.js +5 -11
  116. package/dist/policy-ui.d.ts +3 -2
  117. package/dist/policy-ui.js +17 -23
  118. package/dist/project-delete-ui.d.ts +4 -3
  119. package/dist/project-delete-ui.js +15 -27
  120. package/dist/project-delete.js +21 -26
  121. package/dist/pull-request-ui.d.ts +2 -1
  122. package/dist/pull-request-ui.js +17 -32
  123. package/dist/recipe-ui.d.ts +8 -7
  124. package/dist/recipe-ui.js +39 -32
  125. package/dist/release-adapters.d.ts +60 -0
  126. package/dist/release-adapters.js +435 -0
  127. package/dist/release-cli.d.ts +24 -0
  128. package/dist/release-cli.js +181 -0
  129. package/dist/release-gate.d.ts +80 -0
  130. package/dist/release-gate.js +150 -0
  131. package/dist/release-links.d.ts +29 -0
  132. package/dist/release-links.js +102 -0
  133. package/dist/release.d.ts +331 -0
  134. package/dist/release.js +550 -0
  135. package/dist/reply-shape.d.ts +2 -1
  136. package/dist/reply-shape.js +5 -5
  137. package/dist/repository-context-ui.d.ts +2 -1
  138. package/dist/repository-context-ui.js +2 -7
  139. package/dist/request-budget-ui.d.ts +2 -1
  140. package/dist/request-budget-ui.js +16 -16
  141. package/dist/result-review.d.ts +2 -3
  142. package/dist/result-review.js +3 -9
  143. package/dist/result-shots.js +2 -2
  144. package/dist/retention-ui.d.ts +7 -1
  145. package/dist/retention-ui.js +7 -11
  146. package/dist/retention.js +1 -1
  147. package/dist/serve.d.ts +11 -2
  148. package/dist/serve.js +69 -58
  149. package/dist/server/chat.d.ts +3 -6
  150. package/dist/server/chat.js +969 -1006
  151. package/dist/server/chrome.d.ts +291 -0
  152. package/dist/server/chrome.js +747 -0
  153. package/dist/server/console.css +2819 -0
  154. package/dist/server/flows.d.ts +2 -6
  155. package/dist/server/flows.js +840 -766
  156. package/dist/server/guards.d.ts +1 -1
  157. package/dist/server/guards.js +5 -2
  158. package/dist/server/handler-context.d.ts +1 -1
  159. package/dist/server/handler-context.js +1 -1
  160. package/dist/server/handler-registry.d.ts +8 -6
  161. package/dist/server/handler-registry.js +14 -2
  162. package/dist/server/http.d.ts +274 -0
  163. package/dist/server/http.js +239 -0
  164. package/dist/server/live.d.ts +34 -0
  165. package/dist/server/live.js +309 -0
  166. package/dist/server/pages.d.ts +2 -6
  167. package/dist/server/pages.js +808 -789
  168. package/dist/server/people-tokens.d.ts +1 -6
  169. package/dist/server/people-tokens.js +580 -572
  170. package/dist/server/remote.d.ts +4 -7
  171. package/dist/server/remote.js +37 -70
  172. package/dist/server/render-chat.d.ts +402 -0
  173. package/dist/server/render-chat.js +927 -0
  174. package/dist/server/render-pages.d.ts +535 -0
  175. package/dist/server/render-pages.js +1277 -0
  176. package/dist/server/render-people.d.ts +32 -0
  177. package/dist/server/render-people.js +121 -0
  178. package/dist/server/render-results.d.ts +744 -0
  179. package/dist/server/render-results.js +2237 -0
  180. package/dist/server/render-settings.d.ts +147 -0
  181. package/dist/server/render-settings.js +577 -0
  182. package/dist/server/render-tasks.d.ts +632 -0
  183. package/dist/server/render-tasks.js +2345 -0
  184. package/dist/server/request-context.d.ts +46 -0
  185. package/dist/server/request-context.js +17 -0
  186. package/dist/server/route-table.d.ts +25 -3
  187. package/dist/server/route-table.js +163 -75
  188. package/dist/server/runtime.d.ts +15 -7
  189. package/dist/server/runtime.js +7 -1
  190. package/dist/server/session.d.ts +138 -0
  191. package/dist/server/session.js +94 -0
  192. package/dist/server/settings.d.ts +1 -5
  193. package/dist/server/settings.js +2182 -2117
  194. package/dist/server/task-views.d.ts +11 -4
  195. package/dist/server/task-views.js +26 -22
  196. package/dist/server/tasks.d.ts +9 -9
  197. package/dist/server/tasks.js +1486 -1418
  198. package/dist/server-telemetry.d.ts +4 -3
  199. package/dist/server-telemetry.js +4 -8
  200. package/dist/skills-ui.d.ts +4 -3
  201. package/dist/skills-ui.js +14 -17
  202. package/dist/skills.d.ts +1 -1
  203. package/dist/skills.js +2 -1
  204. package/dist/slack-api.d.ts +5 -1
  205. package/dist/slack-api.js +2 -2
  206. package/dist/slack-chat.d.ts +9 -4
  207. package/dist/slack-chat.js +29 -52
  208. package/dist/slack-settings.d.ts +3 -2
  209. package/dist/slack-settings.js +23 -57
  210. package/dist/slack.js +6 -16
  211. package/dist/spend-ui.d.ts +2 -1
  212. package/dist/spend-ui.js +9 -32
  213. package/dist/sso-ui.d.ts +3 -2
  214. package/dist/sso-ui.js +26 -27
  215. package/dist/storage-ui.d.ts +2 -1
  216. package/dist/storage-ui.js +14 -33
  217. package/dist/store.d.ts +97 -364
  218. package/dist/store.js +207 -1531
  219. package/dist/surface.js +14 -3
  220. package/dist/task-composer.d.ts +2 -1
  221. package/dist/task-composer.js +7 -7
  222. package/dist/task-status.d.ts +4 -3
  223. package/dist/task-status.js +15 -16
  224. package/dist/team-http.d.ts +5 -9
  225. package/dist/team-http.js +5 -63
  226. package/dist/team-ui.d.ts +2 -1
  227. package/dist/team-ui.js +2 -5
  228. package/dist/teammate-question.js +12 -12
  229. package/dist/teammates-ui.d.ts +5 -4
  230. package/dist/teammates-ui.js +61 -90
  231. package/dist/teams-chat.js +12 -9
  232. package/dist/teams-settings.d.ts +3 -2
  233. package/dist/teams-settings.js +22 -33
  234. package/dist/teams.js +4 -5
  235. package/dist/telegram-flow.js +5 -5
  236. package/dist/telegram-settings.d.ts +3 -2
  237. package/dist/telegram-settings.js +14 -25
  238. package/dist/telegram.js +4 -4
  239. package/dist/toolroll-update-ui.d.ts +3 -2
  240. package/dist/toolroll-update-ui.js +35 -39
  241. package/dist/toolroll-update.d.ts +10 -0
  242. package/dist/toolroll-update.js +50 -3
  243. package/dist/tools-ui.d.ts +5 -2
  244. package/dist/tools-ui.js +31 -31
  245. package/dist/when-html.d.ts +11 -3
  246. package/dist/when-html.js +5 -6
  247. package/dist/workspace-revision.js +3 -1
  248. package/dist/workspace-ui.d.ts +4 -2
  249. package/dist/workspace-ui.js +10 -10
  250. package/package.json +1 -1
  251. package/dist/server/shared.d.ts +0 -3151
  252. package/dist/server/shared.js +0 -12001
  253. package/dist/slack-state.d.ts +0 -13
  254. package/dist/slack-state.js +0 -10
@@ -1,16 +1,17 @@
1
+ import { type Html } from './html.js';
1
2
  import type { ChatSnapshot } from './store.js';
2
3
  /** Presentation only. These are recorded completions, not a claim that
3
4
  * work happened since the user last visited, or that every result passed. */
4
5
  export declare function completedWorkHtml(snapshot: ChatSnapshot, projectLabels: readonly string[], assignments?: Readonly<Record<string, {
5
6
  state: string;
6
- }>>): string;
7
+ }>>): Html;
7
8
  /** Keep activity truthful: the turn's recorded step count is not a
8
9
  * percentage or an invented tool phase. Billing detail stays available. */
9
10
  export declare function chatWorkingHtml(input: {
10
11
  details: string;
11
- stopForm?: string;
12
+ stopForm?: Html;
12
13
  keyed?: boolean;
13
- }): string;
14
+ }): Html;
14
15
  /** Recorded activity is inspectable, not a row of performance badges. */
15
- export declare function chatActivityDetailsHtml(activity: string | null): string;
16
+ export declare function chatActivityDetailsHtml(activity: string | null): Html;
16
17
  export declare const CHAT_POLISH_CSS = "\n /* Quiet workspace surfaces: color signals state, not an AI aesthetic. */\n body { background: var(--background); }\n .chat-workspace .chat-overview, .chat-workspace .chat-plan, .chat-workspace .result-panel,\n .chat-workspace .decide-card { background: var(--card); box-shadow: none; }\n .chat-workspace .result-panel { border-radius: .75rem; }\n .chat-workspace .chat-empty::before { content: none; }\n .chat-workspace .thread .msg.mate::before { background: var(--muted); box-shadow: none; border: 0; }\n .chat-workspace .thread .msg.op { box-shadow: none; border: 0; }\n .chat-workspace .completion-receipt { background: var(--card); box-shadow: none; }\n .chat-workspace .completion-receipt::after { content: none; }\n .chat-workspace .thread { min-height: 0; }\n .chat-workspace .chat-turn-history { margin: 1rem 0; border: 0; border-top: 1px solid var(--border); border-radius: 0; background: transparent; box-shadow: none; padding: 0; }\n .chat-turn-history > summary { display: flex; align-items: center; gap: .75rem; min-height: 44px; cursor: pointer; font-size: .875rem; }\n .chat-turn-history > summary .meta { margin-left: auto; }\n .chat-turn-history .row { display: flex; flex-wrap: wrap; gap: .4rem; }\n .chat-turn-history .row .right { float: none; margin-left: auto; }\n .chat-workspace .task-live-summary { margin-bottom: .75rem; padding-bottom: .25rem; }\n .chat-workspace .task-journey > details, .chat-workspace .result-feedback-history,\n .chat-workspace .chat-limits, .chat-workspace .task-chat-agents {\n border: 0; border-radius: 0; background: transparent; box-shadow: none; padding: 0; margin: .25rem 0;\n }\n .chat-workspace .task-chat-agents > summary { min-height: 44px; cursor: pointer; font-size: .8125rem; color: var(--muted-foreground); }\n .chat-workspace .task-chat-agents p { margin: 0 0 .5rem; }\n .chat-workspace .decide-options .decide-option { border: 0; border-top: 1px solid var(--border); border-radius: 0; background: transparent; padding: .75rem 0; }\n .chat-workspace .chat-plan { padding: 1rem; margin-top: .5rem; }\n .chat-workspace .result-knowledge { margin-bottom: .5rem; }\n .chat-workspace .result-panel [data-result-view] > details { margin-block: 0; padding-block: 0; }\n .chat-workspace .result-panel [data-result-view] > details > summary { min-height: 44px; display: flex; align-items: center; }\n .chat-workspace .result-feedback-history > summary { min-height: 44px; }\n .chat-activity-details { border: 0; background: transparent; box-shadow: none; border-radius: 0; padding: 0; margin: 0; }\n .chat-activity-details > summary { min-height: 44px; display: flex; align-items: center; gap: .4rem; cursor: pointer; color: var(--muted-foreground); font-size: .75rem; }\n .chat-activity-details > summary::after { content: '\u2304'; }\n .chat-activity-details[open] > summary::after { transform: rotate(180deg); }\n .chat-activity-details .chat-activity { gap: .25rem .75rem; }\n .chat-activity-details .chat-activity span { border: 0; padding: 0; border-radius: 0; font: inherit; font-size: .75rem; }\n .chat-workspace .chat-message-foot { justify-content: flex-start; align-items: baseline; margin-top: .25rem; }\n .chat-activity-row { border: 0; background: transparent; box-shadow: none; padding: .5rem 0; min-height: 44px; gap: .65rem; }\n .chat-activity-row details { flex: 1; min-width: 0; border: 0; background: transparent; padding: 0; margin: 0; box-shadow: none; }\n .chat-activity-row summary { min-height: 44px; display: flex; align-items: center; cursor: pointer; width: fit-content; font-size: .875rem; font-weight: 550; }\n .chat-activity-row summary::after { content: '\u2304'; margin-left: .65rem; color: var(--muted-foreground); }\n .chat-activity-row details[open] summary::after { transform: rotate(180deg); }\n .chat-activity-row .meta { padding-bottom: .5rem; overflow-wrap: anywhere; }\n .activity-dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: none; }\n .task-live-summary { border-bottom: 1px solid var(--border); margin-bottom: 1rem; padding-bottom: .5rem; }\n .task-live-summary .task-journey, .task-live-summary .task-control { border: 0; border-radius: 0; box-shadow: none; background: transparent; margin: 0; padding: .65rem 0; }\n .task-live-summary .task-journey h2 { font-size: 1rem; }\n .task-live-summary .agents-strip { margin: .4rem 0; }\n .chat-decisions .chat-section-head { margin-bottom: .5rem; }\n .chat-decisions h2 { font-size: .875rem; font-weight: 550; color: var(--muted-foreground); }\n .decision-context summary { cursor: pointer; min-height: 44px; display: flex; align-items: center; font-size: .8125rem; color: var(--muted-foreground); }\n .decision-context { border: 0; padding: 0; margin: 0; background: transparent; box-shadow: none; }\n .decide-options { display: grid; gap: .5rem; }\n .decide-options .decide-option { margin: 0; padding: .65rem .75rem; border: 1px solid var(--border); border-radius: .75rem; background: var(--background); }\n .decide-options .decide-option .meta { display: block; margin-top: .3rem; overflow-wrap: anywhere; }\n .decide-options .decide-option button { min-height: 44px; max-width: 100%; white-space: normal; text-align: left; }\n .chat-completed { margin-top: 1rem; }\n .chat-completed h3 { font-size: .8125rem; font-weight: 550; margin: 0 0 .25rem; color: var(--muted-foreground); }\n .chat-completed-action { flex: none; font-size: .75rem; white-space: nowrap; color: var(--muted-foreground); }\n .chat-overview .chat-overview-stat { box-shadow: none; border: 0; background: transparent; border-radius: 0; }\n .chat-overview .chat-overview-stat + .chat-overview-stat { border-left: 1px solid var(--border); }\n .result-panel .result-request { border-top: 1px solid var(--border); padding-top: 1rem; }\n .result-panel .result-head h2 { color: var(--foreground); }\n .result-panel .result-request h3 { color: var(--foreground); font-size: .875rem; text-transform: none; letter-spacing: 0; }\n .result-panel .result-feedback-actions { display: flex; justify-content: flex-end; align-items: center; flex-wrap: wrap; gap: .5rem; }\n .result-panel .result-feedback-actions > [data-request-changes] { order: 2; background: var(--foreground); color: var(--background); border-color: var(--foreground); box-shadow: none; }\n .result-panel .result-feedback-actions > button { width: auto; min-height: 44px; white-space: nowrap; }\n .result-panel .result-feedback-actions > .quiet { background: transparent; border-color: transparent; box-shadow: none; }\n .result-panel .result-feedback-actions > button:disabled { opacity: .45; cursor: default; }\n .result-panel .result-feedback-link { display: inline-flex; align-items: center; min-height: 44px; font-size: .8125rem; text-underline-offset: 3px; }\n .result-panel .result-pin > summary, .result-panel .result-notes > summary, .result-panel .result-details > summary { min-height: 44px; }\n .result-panel .diff-comment-form textarea { min-height: 80px; margin: 0; font-size: 1rem; }\n .result-panel .result-attention { background: transparent; border: 0; border-radius: 0; box-shadow: none; padding: 0; margin: .75rem 0; color: var(--foreground); }\n .result-panel .result-attention ul { margin: 0; padding-left: 1.15rem; }\n .result-panel .result-attention ul:has(> li:only-child) { list-style: none; padding-left: 0; }\n .result-panel .result-knowledge { border: 0; border-top: 1px solid var(--border); background: transparent; box-shadow: none; border-radius: 0; padding: .25rem 0; }\n .result-panel .revision-from-comments { border: 0; background: transparent; box-shadow: none; padding: .5rem 0; }\n .result-panel .result-pin summary { font-size: .8125rem; min-height: 44px; display: flex; align-items: center; cursor: pointer; }\n .result-panel .diff-comment-limit { font-size: .75rem; }\n .chat-main .chat-plan { box-shadow: none; }\n .proposal-review-notes { margin: .5rem 0; padding-left: 1.15rem; overflow-wrap: anywhere; }\n .proposal-review-notes li + li { margin-top: .5rem; }\n .proposal-actions .button-link, .proposal-actions button { min-height: 44px; white-space: nowrap; }\n .chat-stop-confirm > summary { min-height: 44px; display: flex; align-items: center; cursor: pointer; }\n .task-chat-title-line h1, .task-main-title, .resume-target, .resume-ceremony, .proposal-task_action, .chat-stop-confirm { overflow-wrap: anywhere; }\n .chat-main .chat-task-back { display: none; }\n .knowledge-library .knowledge-intro { padding: .5rem 0 1.25rem; margin-bottom: 1.5rem; border-bottom: 1px solid var(--border); }\n .knowledge-library .knowledge-instructions { line-height: 1.65; margin-bottom: .5rem; }\n .knowledge-library .knowledge-editor summary { width: fit-content; font-size: .875rem; font-weight: 550; }\n .knowledge-library .knowledge-editor, .knowledge-library > details { border: 0; border-bottom: 1px solid var(--border); border-radius: 0; box-shadow: none; background: transparent; padding: 0; }\n .knowledge-library .knowledge-editor { border-bottom: 0; }\n .knowledge-library .card { border: 0; border-bottom: 1px solid var(--border); border-radius: 0; padding: 0 0 .5rem; margin: 0; box-shadow: none; background: transparent; }\n .knowledge-library .card[open] { padding-bottom: 1rem; }\n .knowledge-library .card > summary { padding: .85rem 0; }\n @media (prefers-reduced-motion: no-preference) {\n .chat-activity-row .activity-dot { animation: quiet-activity 1.8s ease-in-out infinite; }\n .chat-completed .chat-overview-item { transition: background-color 140ms ease; }\n @keyframes quiet-activity { 50% { opacity: .35; } }\n }\n @media (max-width: 600px) {\n .chat-workspace .result-panel { background: transparent; border-radius: 0; }\n .chat-completed .chat-overview-item { align-items: flex-start; flex-wrap: wrap; gap: .35rem; }\n .chat-completed .chat-overview-copy { flex-basis: 65%; }\n .task-live-summary .task-journey { gap: .5rem; }\n }\n";
@@ -1,4 +1,4 @@
1
- const escape = (value) => value.replace(/[&<>"']/g, char => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[char]));
1
+ import { html } from './html.js';
2
2
  /** Presentation only. These are recorded completions, not a claim that
3
3
  * work happened since the user last visited, or that every result passed. */
4
4
  export function completedWorkHtml(snapshot, projectLabels, assignments) {
@@ -7,23 +7,22 @@ export function completedWorkHtml(snapshot, projectLabels, assignments) {
7
7
  && !task.historyProblem && !task.otherActive?.length
8
8
  && task.dispatch?.condition !== 'waiting' : assignments[task.id]?.state === 'complete').slice(0, 3);
9
9
  if (!completed.length)
10
- return '';
11
- return `<section class="chat-completed" aria-label="Completed work"><h3>Completed work</h3>` + completed.map(task => {
12
- return `<a class="chat-overview-item" href="/t/${encodeURIComponent(task.rootId ?? task.id)}"><span class="chat-overview-copy"><strong>${escape(task.title)}</strong><span>${escape(projectLabels[task.repoIndex] ?? `Project ${task.repoIndex + 1}`)}</span></span><span class="chat-completed-action">Open task <span aria-hidden="true">→</span></span></a>`;
13
- }).join('') + '</section>';
10
+ return html ``;
11
+ return html `<section class="chat-completed" aria-label="Completed work"><h3>Completed work</h3>${completed.map(task => {
12
+ return html `<a class="chat-overview-item" href="/t/${encodeURIComponent(task.rootId ?? task.id)}"><span class="chat-overview-copy"><strong>${task.title}</strong><span>${projectLabels[task.repoIndex] ?? `Project ${task.repoIndex + 1}`}</span></span><span class="chat-completed-action">Open task <span aria-hidden="true">→</span></span></a>`;
13
+ })}</section>`;
14
14
  }
15
15
  /** Keep activity truthful: the turn's recorded step count is not a
16
16
  * percentage or an invented tool phase. Billing detail stays available. */
17
17
  export function chatWorkingHtml(input) {
18
- return `<div class="chat-thinking chat-activity-row" id="latest" role="status"${input.keyed ? ' data-key="pending"' : ''}>` +
19
- `<span class="activity-dot" aria-hidden="true"></span><details><summary>Working<span class="activity-ellipsis" aria-hidden="true">…</span></summary><p class="meta">${escape(input.details)}</p></details>` +
20
- (input.stopForm ?? '') + '</div>';
18
+ const activity = html `<span class="activity-dot" aria-hidden="true"></span><details><summary>Working<span class="activity-ellipsis" aria-hidden="true">…</span></summary><p class="meta">${input.details}</p></details>`;
19
+ return html `<div class="chat-thinking chat-activity-row" id="latest" role="status"${input.keyed ? html ` data-key="pending"` : ''}>${activity}${input.stopForm}</div>`;
21
20
  }
22
21
  /** Recorded activity is inspectable, not a row of performance badges. */
23
22
  export function chatActivityDetailsHtml(activity) {
24
23
  if (!activity)
25
- return '';
26
- return `<details class="chat-activity-details"><summary>Activity</summary><div class="chat-activity" aria-label="work performed">${activity.split(' · ').map(one => `<span>${escape(one)}</span>`).join('')}</div></details>`;
24
+ return html ``;
25
+ return html `<details class="chat-activity-details"><summary>Activity</summary><div class="chat-activity" aria-label="work performed">${activity.split(' · ').map(one => html `<span>${one}</span>`)}</div></details>`;
27
26
  }
28
27
  // Local primitives adapted from the researched interaction patterns. No
29
28
  // imported kit, animation runtime, simulated progress, or changed authority.
@@ -11,6 +11,9 @@ export type QuietView = {
11
11
  also?: PhoneTaskLink[];
12
12
  target?: DecideTarget;
13
13
  };
14
+ /** What still wants a person who pairs after it landed, in every chat app: an open decision or an unresolved attention
15
+ * notice. Routine history is never replayed to a chat that just arrived (store.ts WANTED_BEFORE_PAIRING, the same rule). */
16
+ export declare function wantedBeforePairing(row: Pick<Notification, "dedupeKey" | "pushClass" | "resolvedAt">): boolean;
14
17
  export declare function needsPerson(row: Pick<Notification, "dedupeKey" | "kind" | "pushClass" | "recipient">): boolean;
15
18
  /** A fact about one placed task: in quiet mode it lands on that task's card. */
16
19
  export declare function isTaskFact(row: Pick<Notification, "taskRef" | "taskId" | "project">): row is typeof row & {
@@ -20,6 +20,11 @@ const NEEDED_KINDS = new Set([
20
20
  "acceptance-ready", "acceptance-evidence", "flow-decision", "flow-card", "plan-ready", "report-ready",
21
21
  "stale-approval", "security-release", "secret-detected",
22
22
  ]);
23
+ /** What still wants a person who pairs after it landed, in every chat app: an open decision or an unresolved attention
24
+ * notice. Routine history is never replayed to a chat that just arrived (store.ts WANTED_BEFORE_PAIRING, the same rule). */
25
+ export function wantedBeforePairing(row) {
26
+ return row.resolvedAt === null && (row.dedupeKey.startsWith("decision:") || row.pushClass === "attention");
27
+ }
23
28
  export function needsPerson(row) {
24
29
  if (row.recipient !== null)
25
30
  return true;
@@ -2,6 +2,7 @@
2
2
  * words. One choice (Quick, Full, Off) and the quick command beside the full
3
3
  * one. Every change takes the password and lands in the ledger; approving a
4
4
  * command restates exactly what will run, unattended, before the yes. */
5
+ import { type Html } from "./html.js";
5
6
  import { type CheckLevel } from "./check-levels.js";
6
7
  import type { VerifyCommand } from "./store.js";
7
8
  export type CheckSettingsView = {
@@ -26,5 +27,5 @@ export type CheckSettingsView = {
26
27
  said: string | null;
27
28
  problem: string | null;
28
29
  };
29
- export declare function checkSettingsHtml(view: CheckSettingsView): string;
30
+ export declare function checkSettingsHtml(view: CheckSettingsView): Html;
30
31
  export declare const CHECK_SETTINGS_CSS = "\n .check-settings { display: grid; gap: .6rem; max-width: 40rem; }\n .check-settings p, .check-settings h2 { margin: 0; }\n .check-settings h2 { font-size: 1rem; }\n .check-settings fieldset { border: 0; margin: 0; padding: 0; display: grid; gap: .25rem; }\n .check-settings legend { font-weight: 600; margin-bottom: .25rem; }\n .check-settings label { display: grid; gap: .25rem; }\n .check-settings .check-choice { display: flex; align-items: flex-start; gap: .6rem; min-height: 44px; padding: .35rem 0; cursor: pointer; }\n .check-settings .check-choice > span { display: grid; gap: .1rem; }\n .check-settings .check-choice input { margin-top: .2rem; }\n .check-settings input[type=password], .check-settings input[type=number], .check-settings textarea { min-height: 44px; box-sizing: border-box; width: 100%; }\n .check-settings button { min-height: 44px; justify-self: start; }\n .check-settings .check-command { overflow-wrap: anywhere; }\n .check-terms { margin: 0; padding-left: 1.1rem; display: grid; gap: .25rem; }\n @media (max-width: 480px) { .check-settings button { justify-self: stretch; } }\n .follow-ups .follow-up-list { margin: .4rem 0; padding-left: 1.1rem; display: grid; gap: .25rem; }\n .follow-ups .follow-up-acts, .follow-ups .follow-up-act { display: flex; flex-wrap: wrap; gap: .5rem; margin: .5rem 0 0; }\n .follow-ups .follow-up-act { margin: 0; }\n .follow-ups button { min-height: 44px; }\n @media (max-width: 480px) { .follow-ups .follow-up-acts, .follow-ups .follow-up-act { display: grid; } .follow-ups .follow-up-act button { width: 100%; } }\n";
@@ -2,51 +2,33 @@
2
2
  * words. One choice (Quick, Full, Off) and the quick command beside the full
3
3
  * one. Every change takes the password and lands in the ledger; approving a
4
4
  * command restates exactly what will run, unattended, before the yes. */
5
+ import { html, postForm } from "./html.js";
5
6
  import { CHECK_LEVELS, CHECK_LEVEL_HINTS, CHECK_LEVEL_WORDS } from "./check-levels.js";
6
7
  import { BATCH_HINT } from "./batch-policy.js";
7
- const escape = (text) => text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
8
8
  export function checkSettingsHtml(view) {
9
- const hidden = (act) => `<input type="hidden" name="csrf" value="${escape(view.csrf)}"><input type="hidden" name="repo" value="${escape(view.repo)}"><input type="hidden" name="act" value="${act}">`;
10
- const password = `<label>Password<input type="password" name="password" autocomplete="current-password" required></label>`;
9
+ const post = (act, body, attrs) => postForm("/settings/checks", body, { attrs, hidden: { repo: view.repo, act } });
10
+ const password = html `<label>Password<input type="password" name="password" autocomplete="current-password" required></label>`;
11
11
  const notes = [
12
- view.said === null ? "" : `<p class="notice" role="status">${escape(view.said)}</p>`,
13
- view.problem === null ? "" : `<p class="problem" role="alert">${escape(view.problem)}</p>`,
14
- ].join("");
15
- const head = `<p class="meta">${escape(view.name)} · <span class="mono">${escape(view.repo)}</span></p>${notes}`;
16
- const command = (one) => `<p class="mono check-command">${escape(one.command)}</p><p class="meta">Up to ${Math.round(one.timeoutMs / 1000)}s · approved by ${escape(one.approvedBy)}</p>`;
12
+ view.said === null ? "" : html `<p class="notice" role="status">${view.said}</p>`,
13
+ view.problem === null ? "" : html `<p class="problem" role="alert">${view.problem}</p>`,
14
+ ];
15
+ const head = html `<p class="meta">${view.name} · <span class="mono">${view.repo}</span></p>${notes}`;
16
+ const command = (one) => html `<p class="mono check-command">${one.command}</p><p class="meta">Up to ${Math.round(one.timeoutMs / 1000)}s · approved by ${one.approvedBy}</p>`;
17
17
  // Quick with no quick command runs the full check; say so where the choice is made.
18
18
  const fallsBack = view.level === "quick" && view.quick === null;
19
- const current = `<p><strong>${CHECK_LEVEL_WORDS[view.level]}</strong> · ${escape(CHECK_LEVEL_HINTS[view.level])}</p>` +
20
- (fallsBack ? `<p class="meta">No quick command yet, so builds run the full check.</p>` : "") +
21
- (view.level !== "full" && view.full !== null ? `<p class="meta">The full check still runs when a pull request opens, and Merge waits for it.</p>` : "");
19
+ const current = html `<p><strong>${CHECK_LEVEL_WORDS[view.level]}</strong> · ${CHECK_LEVEL_HINTS[view.level]}</p>${fallsBack ? html `<p class="meta">No quick command yet, so builds run the full check.</p>` : ""}${view.level !== "full" && view.full !== null ? html `<p class="meta">The full check still runs when a pull request opens, and Merge waits for it.</p>` : ""}`;
22
20
  if (!view.canChange) {
23
- return head + `<section class="card check-settings">${current}</section>` + batchHtml(view, "") + reviewHtml(view, "") +
24
- `<section class="card check-settings"><h2>Quick check</h2>${view.quick === null ? `<p class="meta">None yet.</p>` : command(view.quick)}</section>` +
25
- `<section class="card check-settings"><h2>Full check</h2>${view.full === null ? `<p class="meta">None yet.</p>` : command(view.full)}</section>`;
21
+ return html `${head}<section class="card check-settings">${current}</section>${batchHtml(view, null)}${reviewHtml(view, null)}<section class="card check-settings"><h2>Quick check</h2>${view.quick === null ? html `<p class="meta">None yet.</p>` : command(view.quick)}</section><section class="card check-settings"><h2>Full check</h2>${view.full === null ? html `<p class="meta">None yet.</p>` : command(view.full)}</section>`;
26
22
  }
27
- const choices = CHECK_LEVELS.map(level => `<label class="check-choice"><input type="radio" name="level" value="${level}"${level === view.level ? " checked" : ""}>` +
28
- `<span><strong>${CHECK_LEVEL_WORDS[level]}</strong><span class="meta">${escape(CHECK_LEVEL_HINTS[level])}</span></span></label>`).join("");
29
- const levelForm = `<form method="post" action="/settings/checks" class="card check-settings">${hidden("level")}` +
30
- `<fieldset><legend>After each build</legend>${choices}</fieldset>` +
31
- (fallsBack ? `<p class="meta">No quick command yet, so Quick runs the full check.</p>` : "") +
32
- `<p class="meta">A task can choose its own checks when it's filed. With Quick or Off, the full check runs when a pull request opens, and Merge waits for it unless you merge anyway.</p>` +
33
- `${password}<button type="submit">Save</button></form>`;
34
- const quickTerms = `<ul class="check-terms"><li>Runs unattended right after every Quick build, in that build's checkout.</li><li>Plain environment: no credentials.</li><li>A failure shows on the result; nothing is blocked.</li></ul>`;
35
- const quickForm = (value, submit) => `<form method="post" action="/settings/checks" class="check-settings">${hidden("quick")}` +
36
- `<label>Command<textarea name="command" rows="2" maxlength="2000" spellcheck="false" class="mono" required>${escape(value)}</textarea></label>` +
37
- `<label>Time limit (seconds)<input type="number" name="timeout" min="1" max="3600" value="${view.quick === null ? 180 : Math.round(view.quick.timeoutMs / 1000)}" required></label>` +
38
- `${quickTerms}${password}<button type="submit">${submit}</button></form>`;
23
+ const choices = CHECK_LEVELS.map(level => html `<label class="check-choice"><input type="radio" name="level" value="${level}"${level === view.level ? html ` checked` : ""}><span><strong>${CHECK_LEVEL_WORDS[level]}</strong><span class="meta">${CHECK_LEVEL_HINTS[level]}</span></span></label>`);
24
+ const levelForm = post("level", html `<fieldset><legend>After each build</legend>${choices}</fieldset>${fallsBack ? html `<p class="meta">No quick command yet, so Quick runs the full check.</p>` : ""}<p class="meta">A task can choose its own checks when it's filed. With Quick or Off, the full check runs when a pull request opens, and Merge waits for it unless you merge anyway.</p>${password}<button type="submit">Save</button>`, { class: "card check-settings" });
25
+ const quickTerms = html `<ul class="check-terms"><li>Runs unattended right after every Quick build, in that build's checkout.</li><li>Plain environment: no credentials.</li><li>A failure shows on the result; nothing is blocked.</li></ul>`;
26
+ const quickForm = (value, submit) => post("quick", html `<label>Command<textarea name="command" rows="2" maxlength="2000" spellcheck="false" class="mono" required>${value}</textarea></label><label>Time limit (seconds)<input type="number" name="timeout" min="1" max="3600" value="${view.quick === null ? 180 : Math.round(view.quick.timeoutMs / 1000)}" required></label>${quickTerms}${password}<button type="submit">${submit}</button>`, { class: "check-settings" });
39
27
  const quick = view.quick === null
40
- ? `<section class="card check-settings"><h2>Quick check</h2>` +
41
- (view.suggestion === null ? `<p class="meta">Typecheck and the tests near the change, ideally under 2 minutes.</p>` : `<p class="meta">Suggested from the project's scripts. Edit it before approving.</p>`) +
42
- quickForm(view.suggestion ?? "", "Approve quick check") + `</section>`
43
- : `<section class="card check-settings"><h2>Quick check</h2>${command(view.quick)}` +
44
- `<details class="settings-more"><summary>Change quick check</summary>${quickForm(view.quick.command, "Approve change")}</details>` +
45
- `<details class="settings-more"><summary>Remove quick check</summary><form method="post" action="/settings/checks" class="check-settings">${hidden("quick-clear")}` +
46
- `<p class="meta">Quick builds run the full check instead.</p>${password}<button type="submit" class="danger">Remove</button></form></details></section>`;
47
- const full = `<details class="settings-more"><summary>Full check</summary><section class="card check-settings">` +
48
- (view.full === null ? `<p class="meta">None yet. Approve one with <span class="mono">toolroll verify set --repo … --command "…"</span>.</p>` : command(view.full)) + `</section></details>`;
49
- return head + levelForm + batchHtml(view, password) + reviewHtml(view, password) + quick + full;
28
+ ? html `<section class="card check-settings"><h2>Quick check</h2>${view.suggestion === null ? html `<p class="meta">Typecheck and the tests near the change, ideally under 2 minutes.</p>` : html `<p class="meta">Suggested from the project's scripts. Edit it before approving.</p>`}${quickForm(view.suggestion ?? "", "Approve quick check")}</section>`
29
+ : html `<section class="card check-settings"><h2>Quick check</h2>${command(view.quick)}<details class="settings-more"><summary>Change quick check</summary>${quickForm(view.quick.command, "Approve change")}</details><details class="settings-more"><summary>Remove quick check</summary>${post("quick-clear", html `<p class="meta">Quick builds run the full check instead.</p>${password}<button type="submit" class="danger">Remove</button>`, { class: "check-settings" })}</details></section>`;
30
+ const full = html `<details class="settings-more"><summary>Full check</summary><section class="card check-settings">${view.full === null ? html `<p class="meta">None yet. Approve one with <span class="mono">toolroll verify set --repo … --command "…"</span>.</p>` : command(view.full)}</section></details>`;
31
+ return html `${head}${levelForm}${batchHtml(view, password)}${reviewHtml(view, password)}${quick}${full}`;
50
32
  }
51
33
  /** Batch checks: one state and one sentence; turning it on restates exactly what happens, behind a disclosure with the password. */
52
34
  function batchHtml(view, password) {
@@ -54,17 +36,11 @@ function batchHtml(view, password) {
54
36
  if (batch === undefined)
55
37
  return "";
56
38
  const act = batch.on ? "Turn off" : "Turn on";
57
- const terms = batch.on ? `<p class="meta">Each result runs its own full check again. Results already waiting are checked now.</p>`
58
- : `<ul class="check-terms"><li>When results finish within 10 minutes of each other, they're merged in a temporary checkout and the full check runs once.</li>` +
59
- `<li>If it fails, the batch is split until the result that breaks it is found. Results that conflict are checked on their own.</li>` +
60
- `<li>Nothing is merged into a real branch. Each result still lands on its own.</li></ul>`;
61
- const change = password === "" ? ""
62
- : `<details class="settings-more"><summary>${act}</summary><form method="post" action="/settings/checks" class="check-settings">` +
63
- `<input type="hidden" name="csrf" value="${escape(view.csrf)}"><input type="hidden" name="repo" value="${escape(view.repo)}"><input type="hidden" name="act" value="batch">` +
64
- `<input type="hidden" name="on" value="${batch.on ? "0" : "1"}">${terms}${password}<button type="submit">${act}</button></form></details>`;
65
- return `<section class="card check-settings" data-batch-checks="${batch.on ? "on" : "off"}"><h2>Batch checks</h2>` +
66
- `<p><strong>${batch.on ? "On" : "Off"}</strong> · ${escape(BATCH_HINT)}</p>` +
67
- (batch.on && view.level !== "full" ? `<p class="meta">Only Full checks are batched.</p>` : "") + `${change}</section>`;
39
+ const terms = batch.on ? html `<p class="meta">Each result runs its own full check again. Results already waiting are checked now.</p>`
40
+ : html `<ul class="check-terms"><li>When results finish within 10 minutes of each other, they're merged in a temporary checkout and the full check runs once.</li><li>If it fails, the batch is split until the result that breaks it is found. Results that conflict are checked on their own.</li><li>Nothing is merged into a real branch. Each result still lands on its own.</li></ul>`;
41
+ const change = password === null ? ""
42
+ : html `<details class="settings-more"><summary>${act}</summary>${postForm("/settings/checks", html `${terms}${password}<button type="submit">${act}</button>`, { attrs: { class: "check-settings" }, hidden: { repo: view.repo, act: "batch", on: batch.on ? "0" : "1" } })}</details>`;
43
+ return html `<section class="card check-settings" data-batch-checks="${batch.on ? "on" : "off"}"><h2>Batch checks</h2><p><strong>${batch.on ? "On" : "Off"}</strong> · ${BATCH_HINT}</p>${batch.on && view.level !== "full" ? html `<p class="meta">Only Full checks are batched.</p>` : ""}${change}</section>`;
68
44
  }
69
45
  /** Automatic review: one state, one sentence, and its one change behind a disclosure (the password). */
70
46
  function reviewHtml(view, password) {
@@ -73,12 +49,9 @@ function reviewHtml(view, password) {
73
49
  return "";
74
50
  const state = review.on ? (review.source === "hands-off" ? "On while hands-off lasts" : "On") : "Off";
75
51
  const act = review.on ? "Turn off" : "Turn on";
76
- const change = password === "" ? ""
77
- : `<details class="settings-more"><summary>${act}</summary><form method="post" action="/settings/checks" class="check-settings">` +
78
- `<input type="hidden" name="csrf" value="${escape(view.csrf)}"><input type="hidden" name="repo" value="${escape(view.repo)}"><input type="hidden" name="act" value="review">` +
79
- `<input type="hidden" name="on" value="${review.on ? "0" : "1"}">${password}<button type="submit">${act}</button></form></details>`;
80
- return `<section class="card check-settings" data-review-switch="${review.on ? "on" : "off"}"><h2>Automatic review</h2>` +
81
- `<p><strong>${state}</strong> · One read-only review after each build passes its checks. Only high findings send it back, once.</p>${change}</section>`;
52
+ const change = password === null ? ""
53
+ : html `<details class="settings-more"><summary>${act}</summary>${postForm("/settings/checks", html `${password}<button type="submit">${act}</button>`, { attrs: { class: "check-settings" }, hidden: { repo: view.repo, act: "review", on: review.on ? "0" : "1" } })}</details>`;
54
+ return html `<section class="card check-settings" data-review-switch="${review.on ? "on" : "off"}"><h2>Automatic review</h2><p><strong>${state}</strong> · One read-only review after each build passes its checks. Only high findings send it back, once.</p>${change}</section>`;
82
55
  }
83
56
  export const CHECK_SETTINGS_CSS = `
84
57
  .check-settings { display: grid; gap: .6rem; max-width: 40rem; }
package/dist/cli.d.ts CHANGED
@@ -15,6 +15,7 @@ import { type SessionCliOptions } from "./session-cli.js";
15
15
  import { type TeamCliOptions } from "./team-cli.js";
16
16
  import { type RemoteExecOptions } from "./remote-exec.js";
17
17
  import { type UpdateCliDeps } from "./toolroll-update-cli.js";
18
+ import { type ReleaseCliDeps } from "./release-cli.js";
18
19
  import { type OperateOptions } from "./operate.js";
19
20
  export type CliOptions = {
20
21
  roots: string[];
@@ -40,7 +41,7 @@ export type ParseResult = {
40
41
  error: string;
41
42
  };
42
43
  type Write = (line: string) => void;
43
- export declare const HELP = "toolroll \u2014 a control plane for unattended coding agents\n\nUsage\n toolroll [path...] report what is in flight\n toolroll pulls report what is waiting on a person\n toolroll graph report which work graph is already here\n toolroll repos list connected repositories, and how to adjust\n toolroll repos add <path> add a project; it is in the console at once (no path: the repo you are in)\n toolroll repos remove <path>\n take it out of the console; its tasks and results stay\n toolroll repos add-from-github <owner/name> --root <dir>\n preview, then clone and connect (--yes)\n toolroll link put `toolroll` on your PATH\n toolroll unlink take it off again\n toolroll update update to the latest release: verified, drained, undoable\n (preview first; --yes, --now, --at HH:MM, --rollback)\n toolroll contract the machine contract: envelope version + capabilities\n (--commands dumps the declared command guide)\n toolroll skills install --claude-code [--dir <path>]\n install this binary's guides for Claude Code (preview first)\n toolroll skills install teach a repo's agents this queue exists (preview first)\n toolroll skills list the guides this exact binary serves\n toolroll skills get <name> print one guide (version-matched, never stale)\n toolroll demo a seeded throwaway sandbox \u2014 see it working in 90 seconds\n toolroll up app + builder for every saved project \u2014 the normal start\n toolroll onboard make your agent Toolroll's lead: add this repo, install its skill (--yes), offer starter flows (--starter)\n toolroll status running, queued, ready results, release check and plan windows\n toolroll health recent server latency, write waits and live streams\n toolroll integrations which integrations work, and what to do about the ones that don't (--json)\n toolroll session native coding sessions through the running service\n toolroll connect save a private connection to your central service\n toolroll lead named leads on the connected service\n toolroll lead token mint your lead agent's credential: its own work pings nobody\n toolroll lead say \"<text>\" (the lead) one short message in its person's chat\n toolroll conversation shared and private conversations on that service\n toolroll chat --lead <id> --conversation <id> central chat (use --local for local chat)\n\nOperating the queue \u2014 `toolroll task` prints the whole surface,\nand any queue command + --help prints it too\n toolroll approver add <name>\n mint the credential that lets a person say yes\n toolroll ready what could be dispatched right now\n toolroll check-progress <run> current or final approved check progress\n toolroll task wait <id> [--timeout <seconds>]\n wait for this attempt to finish or need a person\n toolroll task add <title> queue work\n toolroll task scope <id> --goal <text>\n state what success is; approval binds to it\n toolroll task approve <id>\n the yes \u2014 nothing builds without one\n toolroll claim <id> --runner <name>\n toolroll heartbeat <lease> / release <lease> / reap\n toolroll tick --runner <name> --token <t> --repo <path>\n one unattended pass over the ready set\n toolroll flows list | show <id> | create | edit | trigger | script | card | archive\n set up and inspect flows (writes preview until --yes)\n toolroll chat --as <you> --token <t>\n talk to the mate: one conversation across every\n project; it proposes, you confirm (--say \"\u2026\" for one turn)\n toolroll serve --repo <path> advanced: run only the console\n (--editor vscode with --runner: file links\n open in VS Code on the device you browse from)\n toolroll watch / daemon install advanced: run only the builder\n toolroll reconcile recover what the last stretch left behind\n\nWith nothing connected it reports everything it can find below the working\ndirectory. Once you connect repositories it reports those instead.\n\nOptions\n --all report everything discoverable, ignoring connected repos\n --depth <n> directory levels to descend (default: 6)\n --hidden include dot-directories, which are skipped by default\n --dirty also read each working tree for uncommitted files\n --local branches only: no network, no pull requests, no issues\n --json emit a machine-readable envelope instead of a report\n -o <file> also write that envelope to a file (requires --json)\n -h, --help show this\n\nNothing is written, installed, or configured. Every repository is read\nthrough git with --no-optional-locks, so a scan never contends for the\nindex lock with an editor you have open.\n\n--dirty is off by default because reading a working tree is unbounded\nwork: on a large or cloud-synced repo `git status` can take minutes,\nwhere listing branches takes milliseconds.\n\nlink shows what it would do and changes nothing; add --yes to apply it,\nor --to <dir> to choose where. It never uses sudo, never leaves your\nhome directory, and never touches a file it did not create.";
44
+ export declare const HELP = "toolroll \u2014 a control plane for unattended coding agents\n\nUsage\n toolroll [path...] report what is in flight\n toolroll pulls report what is waiting on a person\n toolroll graph report which work graph is already here\n toolroll repos list connected repositories, and how to adjust\n toolroll repos add <path> add a project; it is in the console at once (no path: the repo you are in)\n toolroll repos remove <path>\n take it out of the console; its tasks and results stay\n toolroll repos add-from-github <owner/name> --root <dir>\n preview, then clone and connect (--yes)\n toolroll link put `toolroll` on your PATH\n toolroll unlink take it off again\n toolroll update update to the latest release: verified, drained, undoable\n (preview first; --yes, --now, --at HH:MM, --rollback)\n toolroll release <branch> release Toolroll itself: gate, your approval, deploy, merge, tag, publish\n (resumable: rerun to continue; --help)\n toolroll contract the machine contract: envelope version + capabilities\n (--commands dumps the declared command guide)\n toolroll skills install --claude-code [--dir <path>]\n install this binary's guides for Claude Code (preview first)\n toolroll skills install teach a repo's agents this queue exists (preview first)\n toolroll skills list the guides this exact binary serves\n toolroll skills get <name> print one guide (version-matched, never stale)\n toolroll demo a seeded throwaway sandbox \u2014 see it working in 90 seconds\n toolroll up app + builder for every saved project \u2014 the normal start\n toolroll onboard make your agent Toolroll's lead: add this repo, install its skill (--yes), offer starter flows (--starter)\n toolroll status running, queued, ready results, release check and plan windows\n toolroll health recent server latency, write waits and live streams\n toolroll integrations which integrations work, and what to do about the ones that don't (--json)\n toolroll session native coding sessions through the running service\n toolroll connect save a private connection to your central service\n toolroll lead named leads on the connected service\n toolroll lead token mint your lead agent's credential: its own work pings nobody\n toolroll lead say \"<text>\" (the lead) one short message in its person's chat\n toolroll conversation shared and private conversations on that service\n toolroll chat --lead <id> --conversation <id> central chat (use --local for local chat)\n\nOperating the queue \u2014 `toolroll task` prints the whole surface,\nand any queue command + --help prints it too\n toolroll approver add <name>\n mint the credential that lets a person say yes\n toolroll ready what could be dispatched right now\n toolroll check-progress <run> current or final approved check progress\n toolroll task wait <id> [--timeout <seconds>]\n wait for this attempt to finish or need a person\n toolroll task add <title> queue work\n toolroll task scope <id> --goal <text>\n state what success is; approval binds to it\n toolroll task approve <id>\n the yes \u2014 nothing builds without one\n toolroll claim <id> --runner <name>\n toolroll heartbeat <lease> / worker release <lease> / reap\n toolroll tick --runner <name> --token <t> --repo <path>\n one unattended pass over the ready set\n toolroll flows list | show <id> | create | edit | trigger | script | card | archive\n set up and inspect flows (writes preview until --yes)\n toolroll chat --as <you> --token <t>\n talk to the mate: one conversation across every\n project; it proposes, you confirm (--say \"\u2026\" for one turn)\n toolroll serve --repo <path> advanced: run only the console\n (--editor vscode with --runner: file links\n open in VS Code on the device you browse from)\n toolroll watch / daemon install advanced: run only the builder\n toolroll reconcile recover what the last stretch left behind\n\nWith nothing connected it reports everything it can find below the working\ndirectory. Once you connect repositories it reports those instead.\n\nOptions\n --all report everything discoverable, ignoring connected repos\n --depth <n> directory levels to descend (default: 6)\n --hidden include dot-directories, which are skipped by default\n --dirty also read each working tree for uncommitted files\n --local branches only: no network, no pull requests, no issues\n --json emit a machine-readable envelope instead of a report\n -o <file> also write that envelope to a file (requires --json)\n -h, --help show this\n\nNothing is written, installed, or configured. Every repository is read\nthrough git with --no-optional-locks, so a scan never contends for the\nindex lock with an editor you have open.\n\n--dirty is off by default because reading a working tree is unbounded\nwork: on a large or cloud-synced repo `git status` can take minutes,\nwhere listing branches takes milliseconds.\n\nlink shows what it would do and changes nothing; add --yes to apply it,\nor --to <dir> to choose where. It never uses sudo, never leaves your\nhome directory, and never touches a file it did not create.";
44
45
  export declare function parseArgs(argv: readonly string[]): ParseResult;
45
46
  /** The commands that operate the queue rather than report on the world. */
46
47
  /** Every verb the CLI routes OUTSIDE runOperate. Exported (with
@@ -65,6 +66,7 @@ export type MainOptions = {
65
66
  clone?: typeof cloneGithubRepo;
66
67
  };
67
68
  update?: UpdateCliDeps;
69
+ release?: ReleaseCliDeps;
68
70
  };
69
71
  /**
70
72
  * Strip `-o <file>` / `--output <file>` before dispatch. The flag belongs to
package/dist/cli.js CHANGED
@@ -35,6 +35,7 @@ import { detectGraphs, chooseBackend, setupOptions, LABELS as GRAPH_LABELS, } fr
35
35
  import { openStore, openStoreReadOnly, databasePath } from "./store.js";
36
36
  import { PACKAGE_VERSION } from "./version.js";
37
37
  import { runUpdateCommand } from "./toolroll-update-cli.js";
38
+ import { runReleaseCommand } from "./release-cli.js";
38
39
  import { runOperate, OPERATE_HELP } from "./operate.js";
39
40
  import { renderReport, renderPulls, renderGraph } from "./render.js";
40
41
  import { readRemote } from "./remote.js";
@@ -57,6 +58,8 @@ Usage
57
58
  toolroll unlink take it off again
58
59
  toolroll update update to the latest release: verified, drained, undoable
59
60
  (preview first; --yes, --now, --at HH:MM, --rollback)
61
+ toolroll release <branch> release Toolroll itself: gate, your approval, deploy, merge, tag, publish
62
+ (resumable: rerun to continue; --help)
60
63
  toolroll contract the machine contract: envelope version + capabilities
61
64
  (--commands dumps the declared command guide)
62
65
  toolroll skills install --claude-code [--dir <path>]
@@ -92,7 +95,7 @@ and any queue command + --help prints it too
92
95
  toolroll task approve <id>
93
96
  the yes — nothing builds without one
94
97
  toolroll claim <id> --runner <name>
95
- toolroll heartbeat <lease> / release <lease> / reap
98
+ toolroll heartbeat <lease> / worker release <lease> / reap
96
99
  toolroll tick --runner <name> --token <t> --repo <path>
97
100
  one unattended pass over the ready set
98
101
  toolroll flows list | show <id> | create | edit | trigger | script | card | archive
@@ -196,7 +199,7 @@ export function parseArgs(argv) {
196
199
  * which answers as `scan`. */
197
200
  export const TOP_LEVEL_COMMANDS = [
198
201
  "", "pulls", "graph", "repos", "repos add", "repos remove", "repos add-from-github",
199
- "link", "unlink", "update", "contract", "skills list", "skills get", "skills install", "demo",
202
+ "link", "unlink", "update", "release", "contract", "skills list", "skills get", "skills install", "demo",
200
203
  ...SESSION_CLI_ACTIONS.map(action => `session ${action}`),
201
204
  ...TEAM_CLI_ACTIONS,
202
205
  ];
@@ -230,7 +233,7 @@ export const OPERATE_COMMANDS = new Set([
230
233
  "models",
231
234
  "claim",
232
235
  "heartbeat",
233
- "release",
236
+ "worker",
234
237
  "reap",
235
238
  "enroll",
236
239
  "grants",
@@ -417,6 +420,9 @@ async function dispatch(argv, write, mainOptions) {
417
420
  }
418
421
  if (first === "update")
419
422
  return runUpdateCommand(rest, write, mainOptions.update);
423
+ if (first === "release") {
424
+ return runReleaseCommand(rest, write, { releaseLease: lease => runOperate("worker", ["release", ...lease], write, mainOptions.operate ?? {}), ...mainOptions.release });
425
+ }
420
426
  if (first === "contract")
421
427
  return runContractCommand(rest, write);
422
428
  if (first === "session")
@@ -1,4 +1,5 @@
1
1
  import type { CodingHandoffPreview } from './coding-handoff.js';
2
+ import { type Html } from './html.js';
2
3
  /** A reviewable handoff. The next screen retains the existing approval terms. */
3
- export declare function codingShippingHtml(preview: CodingHandoffPreview, csrf: string, values?: URLSearchParams, error?: string): string;
4
+ export declare function codingShippingHtml(preview: CodingHandoffPreview, values?: URLSearchParams, error?: string): Html;
4
5
  export declare const CODING_SHIPPING_CSS = ".coding-workspace.coding-handoff{display:block;max-width:780px}.coding-handoff h1{margin-bottom:12px}.coding-handoff>p{margin-top:12px}.coding-workspace .coding-check{display:flex;align-items:center;gap:10px;min-height:44px}.coding-workspace .coding-check input{width:20px;height:20px;flex:0 0 20px}";
@@ -1,12 +1,12 @@
1
- const escape = (value) => value.replace(/[&<>"']/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
1
+ import { html, postForm } from './html.js';
2
2
  /** A reviewable handoff. The next screen retains the existing approval terms. */
3
- export function codingShippingHtml(preview, csrf, values, error) {
4
- const hidden = (name, value) => `<input type="hidden" name="${name}" value="${escape(value)}">`;
3
+ export function codingShippingHtml(preview, values, error) {
5
4
  const goal = values?.get('goal') ?? preview.title;
6
5
  const criteria = values?.get('acceptance') ?? '';
7
6
  const notice = error?.startsWith('Review screenshots are not ready.')
8
- ? `<p class="coding-notice" role="alert">Screenshots needed. Ask Codex to capture and commit them, then try again.</p><details><summary>Technical details</summary><p>${escape(error)}</p></details>`
9
- : error ? `<p class="coding-notice" role="alert">${escape(error)}</p>` : '';
10
- return `<section class="coding-workspace coding-handoff" data-coding-session=""><h1>Review for shipping</h1><p>${escape(preview.title)}</p>${notice}<form method="post" action="/code/${escape(preview.sessionId)}/ship" class="coding-start">${hidden('csrf', csrf)}${hidden('base', preview.base)}${hidden('candidate', preview.candidate)}${hidden('title', preview.title)}<label>Outcome<textarea name="goal" rows="4" required maxlength="16000">${escape(goal)}</textarea></label><label>Checks for review<textarea name="acceptance" rows="4" required maxlength="16000" placeholder="One observable result per line">${escape(criteria)}</textarea></label><label class="coding-check"><input type="checkbox" name="visual" value="yes"${values?.get('visual') === 'yes' ? ' checked' : ''}>Include desktop and phone screenshots</label><details><summary>${preview.changedPaths.length} changed files · commit ${escape(preview.candidate.slice(0, 8))}</summary><ul>${preview.changedPaths.map(path => `<li><code>${escape(path)}</code></li>`).join('')}</ul><p class="coding-meta">Review covers the complete change from ${escape(preview.base)} to ${escape(preview.candidate)}.</p></details><p class="coding-terms">Verification and publication use this project’s existing approval process.</p><button type="submit">Create review task</button></form><p><a href="/code/${escape(preview.sessionId)}">Back to coding</a></p></section>`;
7
+ ? html `<p class="coding-notice" role="alert">Screenshots needed. Ask Codex to capture and commit them, then try again.</p><details><summary>Technical details</summary><p>${error}</p></details>`
8
+ : error ? html `<p class="coding-notice" role="alert">${error}</p>` : '';
9
+ const form = postForm(`/code/${preview.sessionId}/ship`, html `<label>Outcome<textarea name="goal" rows="4" required maxlength="16000">${goal}</textarea></label><label>Checks for review<textarea name="acceptance" rows="4" required maxlength="16000" placeholder="One observable result per line">${criteria}</textarea></label><label class="coding-check"><input type="checkbox" name="visual" value="yes"${values?.get('visual') === 'yes' ? html ` checked` : ''}>Include desktop and phone screenshots</label><details><summary>${preview.changedPaths.length} changed files · commit ${preview.candidate.slice(0, 8)}</summary><ul>${preview.changedPaths.map(path => html `<li><code>${path}</code></li>`)}</ul><p class="coding-meta">Review covers the complete change from ${preview.base} to ${preview.candidate}.</p></details><p class="coding-terms">Verification and publication use this project’s existing approval process.</p><button type="submit">Create review task</button>`, { attrs: { class: 'coding-start' }, hidden: { base: preview.base, candidate: preview.candidate, title: preview.title } });
10
+ return html `<section class="coding-workspace coding-handoff" data-coding-session=""><h1>Review for shipping</h1><p>${preview.title}</p>${notice}${form}<p><a href="/code/${preview.sessionId}">Back to coding</a></p></section>`;
11
11
  }
12
12
  export const CODING_SHIPPING_CSS = `.coding-workspace.coding-handoff{display:block;max-width:780px}.coding-handoff h1{margin-bottom:12px}.coding-handoff>p{margin-top:12px}.coding-workspace .coding-check{display:flex;align-items:center;gap:10px;min-height:44px}.coding-workspace .coding-check input{width:20px;height:20px;flex:0 0 20px}`;
@@ -1,4 +1,5 @@
1
1
  import type { CodingSession, CodingSnapshot } from './coding-types.js';
2
+ import { type Html } from './html.js';
2
3
  export declare function codingWorkspaceHtml(input: {
3
4
  projects: {
4
5
  path: string;
@@ -6,12 +7,13 @@ export declare function codingWorkspaceHtml(input: {
6
7
  }[];
7
8
  sessions: CodingSession[];
8
9
  selected: CodingSnapshot | null;
10
+ /** The page script posts with it (data-coding-csrf); the forms get theirs from postForm. */
9
11
  csrf: string;
10
12
  project: string | null;
11
13
  error?: string;
12
14
  draft?: string;
13
15
  available: boolean;
14
16
  owner?: string;
15
- }): string;
17
+ }): Html;
16
18
  export declare const CODING_CSS = "\n.coding-workspace{display:grid;grid-template-columns:224px minmax(0,1fr);gap:24px;max-width:1220px;min-width:0;margin:0 auto;overflow-wrap:anywhere}\n.coding-workspace *{box-sizing:border-box}.coding-workspace [hidden]{display:none!important}.coding-workspace h1{font-size:1.45rem;line-height:1.25;margin:0}.coding-workspace h2{font-size:1rem;margin:0}.coding-workspace h3{font-size:1rem;margin:0 0 12px}.coding-workspace p{margin:0 0 12px}.coding-main,.coding-sidebar{min-width:0}.coding-meta,.coding-workspace small{font-size:.83rem;color:var(--muted-foreground,#747981)}\n.coding-sidebar-top,.coding-header,.coding-state-row,.coding-actions{display:flex;align-items:center;gap:12px;justify-content:space-between}.coding-sidebar-top{margin-bottom:12px}.coding-header{align-items:flex-start;margin-bottom:16px}.coding-header .coding-meta{margin:6px 0 0}.coding-session-detail{max-width:220px}.coding-session-detail summary{font-size:.82rem}.coding-session-detail dl{font-size:.82rem}.coding-session-detail dt{font-weight:600;margin-top:8px}.coding-session-detail dd{margin:2px 0 0}.coding-state-row{flex-wrap:wrap;padding:10px 0;border-bottom:1px solid var(--border,#353940);margin-bottom:12px}.coding-state-row p{margin:0;font-weight:600}.coding-state-row form{margin:0}\n.coding-session-list{max-height:74vh;overflow:auto}.coding-sidebar ul{list-style:none;margin:0;padding:0;display:grid;gap:4px}.coding-sidebar nav a{display:block;padding:12px;border-radius:10px;text-decoration:none;color:inherit}.coding-sidebar nav a:hover,.coding-sidebar nav a[aria-current]{background:var(--muted,#22252a)}.coding-sidebar nav a[aria-current]{box-shadow:inset 3px 0 var(--primary,#90bfa7)}.coding-sidebar nav span,.coding-sidebar nav small{display:block}.coding-sidebar nav [data-coding-status-for]{display:inline}.coding-sidebar nav small{margin-top:5px}.coding-mobile-sessions{display:none}\n.coding-workspace form:not(.coding-state-row form){display:grid;gap:12px;margin:0}.coding-workspace label{display:grid;gap:6px;font-size:.9rem;font-weight:500}.coding-workspace input,.coding-workspace textarea,.coding-workspace select{width:100%;min-width:0;max-width:100%;font-size:16px;border:1px solid var(--border,#353940);border-radius:8px;padding:11px 12px;background:var(--background,#14161a);color:var(--foreground,#f0f1f3)}.coding-workspace textarea{resize:vertical;line-height:1.5}.coding-workspace input:focus-visible,.coding-workspace textarea:focus-visible,.coding-workspace select:focus-visible,.coding-workspace button:focus-visible,.coding-workspace summary:focus-visible,.coding-workspace a:focus-visible{outline:2px solid var(--ring,#79acff);outline-offset:3px}.coding-workspace button,.coding-link-button{min-height:44px;padding:10px 15px;white-space:nowrap;border-radius:8px;max-width:100%;font-size:.9rem}.coding-workspace button{justify-self:start;cursor:pointer}.coding-workspace button:disabled{cursor:default;opacity:.55}.coding-workspace .coding-secondary{background:transparent;color:inherit;border:1px solid var(--border,#353940)}.coding-link-button{display:inline-flex;align-items:center;justify-content:center;text-decoration:none;color:inherit;border:1px solid var(--border,#353940)}.coding-workspace summary{cursor:pointer;min-height:44px;padding:12px 0}.coding-actions{justify-content:flex-start;flex-wrap:wrap}.coding-start{max-width:680px}.coding-options label+label{margin-top:12px}.coding-terms{font-size:.9rem;line-height:1.6;margin:0!important}.coding-empty{padding:28px 0}\n.coding-notice{border:1px solid var(--destructive,#bd7674);border-radius:10px;padding:12px;line-height:1.5;margin-bottom:16px}.coding-conversation{max-height:min(58vh,640px);min-height:140px;overflow:auto;overscroll-behavior:contain;scrollbar-gutter:stable;padding:4px 10px 12px 0}.coding-message{padding:14px 0}.coding-message strong{display:block;font-size:.78rem;margin-bottom:7px;color:var(--muted-foreground,#747981)}.coding-user{margin:8px 0 8px 32px;padding:14px 16px;border-radius:12px;background:var(--muted,#22252a)}.coding-text{white-space:pre-wrap;line-height:1.6;overflow-wrap:anywhere}.coding-tool{border-bottom:1px solid var(--border,#353940);font-size:.88rem}.coding-workspace pre{white-space:pre-wrap;overflow-wrap:anywhere;max-width:100%;font-size:.8rem;line-height:1.5;margin:0 0 12px}.coding-tool summary{display:list-item}.coding-tool summary span{margin-left:6px}.coding-composer{border-top:1px solid var(--border,#353940);padding-top:14px;margin-top:8px!important}.coding-composer label{font-size:.83rem}.coding-request{border:1px solid var(--border,#353940);border-radius:12px;padding:16px;margin:12px 0}.coding-permission-detail{max-height:240px;overflow:auto}.coding-changes{margin-top:20px;border-top:1px solid var(--border,#353940)}.coding-changes pre{max-height:min(50dvh,480px);overflow:auto}.coding-change-meta{font-size:.8rem}.coding-workspace #coding-latest{margin:8px 0}.coding-workspace #coding-connection{line-height:1.4}\n.coding-workspace .coding-start>button[type=\"submit\"],.coding-workspace .coding-composer button[type=\"submit\"]{background:var(--primary,#171717);color:var(--primary-foreground,#fff);border:1px solid var(--primary,#171717);font-weight:600;box-shadow:none}.coding-workspace .coding-start>button[type=\"submit\"]:not(:disabled):hover,.coding-workspace .coding-composer button[type=\"submit\"]:not(:disabled):hover{filter:brightness(.9)}\n.coding-workspace:not([data-coding-session=\"\"]) .coding-main{display:flex;flex-direction:column;min-height:calc(100dvh - 180px)}.coding-main>.coding-header,.coding-main>.coding-state-row,.coding-main>#coding-requests,.coding-main>.coding-composer,.coding-main>.coding-changes,.coding-main>.coding-notice,.coding-main>#coding-connection{flex-shrink:0}.coding-workspace:not([data-coding-session=\"\"]) .coding-conversation{flex:1 1 120px;min-height:100px;max-height:min(58dvh,640px)}.coding-workspace .coding-tool{margin:4px 0;padding:0 10px;border-radius:8px}.coding-workspace .coding-session-detail{margin:0;padding:0 10px;flex-shrink:0}.coding-workspace .coding-changes{padding:0 12px;margin-top:12px}.coding-text strong{display:inline;font-size:inherit;color:inherit;margin:0;font-weight:600}.coding-composer textarea{min-height:72px;max-height:180px}\n@media(max-width:760px){.coding-workspace{grid-template-columns:minmax(0,1fr);gap:16px}.coding-sidebar{border-bottom:1px solid var(--border,#353940);padding-bottom:12px}.coding-sidebar-top h2,.coding-session-list{display:none}.coding-sidebar-top{float:right;margin:0 0 8px 12px}.coding-mobile-sessions{display:block;max-width:100%;margin:0}.coding-mobile-sessions>nav{max-height:260px;overflow:auto;clear:both}.coding-mobile-sessions>summary{min-height:44px}.coding-header{display:block}.coding-session-detail{max-width:none;margin-top:4px}.coding-session-detail summary{padding:10px 0}.coding-conversation{max-height:52vh;min-height:100px;padding-right:4px}.coding-user{margin-left:16px}.coding-workspace h1{font-size:1.25rem}.coding-workspace button,.coding-link-button{padding:10px 12px}.coding-start{max-width:none}}\n@media(max-width:760px){.coding-workspace:not([data-coding-session=\"\"]) .coding-main{min-height:calc(100dvh - 245px - env(safe-area-inset-bottom,0px))}.coding-workspace:not([data-coding-session=\"\"]) .coding-conversation{min-height:80px}.coding-header{display:flex;gap:12px}.coding-header>div{min-width:0;flex:1}.coding-header:has(.coding-session-detail[open]){flex-wrap:wrap}.coding-workspace .coding-session-detail{max-width:100px}.coding-workspace .coding-session-detail[open]{max-width:100%;flex-basis:100%}.coding-session-detail summary{white-space:nowrap}.coding-workspace .coding-mobile-sessions{padding:0 12px}.coding-composer{gap:8px!important}.coding-composer textarea{height:72px}.coding-state-row{padding:8px 0;margin-bottom:8px}}\n@media(prefers-reduced-motion:reduce){.coding-workspace *{scroll-behavior:auto!important}}\n";
17
19
  export declare function codingWorkspaceScript(): string;