toolroll 0.9.67 → 0.9.68

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 (236) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/acp-client.js +1 -3
  3. package/dist/agent-board.d.ts +0 -1
  4. package/dist/agent-board.js +0 -1
  5. package/dist/agent-fence.js +3 -0
  6. package/dist/browser/workspace.css +1 -1
  7. package/dist/browser/workspace.js +31 -29
  8. package/dist/builder.js +3 -26
  9. package/dist/canonical-json.d.ts +14 -0
  10. package/dist/canonical-json.js +36 -0
  11. package/dist/chat-actions.js +1 -2
  12. package/dist/chat-admission.d.ts +19 -0
  13. package/dist/chat-admission.js +22 -0
  14. package/dist/chat-channel.js +1 -1
  15. package/dist/chat-delivery.js +0 -1
  16. package/dist/chat-follow.d.ts +17 -0
  17. package/dist/chat-follow.js +32 -0
  18. package/dist/chat-messages.js +0 -1
  19. package/dist/chat-settings-page.d.ts +23 -0
  20. package/dist/chat-settings-page.js +41 -0
  21. package/dist/claim.js +3 -6
  22. package/dist/claude-print.d.ts +12 -0
  23. package/dist/claude-print.js +34 -0
  24. package/dist/cli-http.d.ts +0 -7
  25. package/dist/cli-http.js +0 -21
  26. package/dist/cli.d.ts +3 -10
  27. package/dist/cli.js +15 -24
  28. package/dist/coding-provider.js +2 -1
  29. package/dist/coding-workspace.js +1 -1
  30. package/dist/{team-cli.d.ts → connect-cli.d.ts} +5 -9
  31. package/dist/connect-cli.js +331 -0
  32. package/dist/containment.d.ts +0 -2
  33. package/dist/containment.js +2 -6
  34. package/dist/contracts/chat-actions.d.ts +0 -1
  35. package/dist/contracts/chat-content.d.ts +0 -4
  36. package/dist/contracts/chat-content.js +0 -2
  37. package/dist/contracts/chat-tables.d.ts +0 -1
  38. package/dist/contracts/cli.d.ts +0 -62
  39. package/dist/contracts/cli.js +0 -4
  40. package/dist/contracts/coding-workspace.js +1 -1
  41. package/dist/contracts/contract.js +1 -1
  42. package/dist/contracts/flow-card.d.ts +0 -1
  43. package/dist/contracts/flow-propose.d.ts +0 -1
  44. package/dist/contracts/flow-send.js +1 -1
  45. package/dist/contracts/gateway-tools.d.ts +0 -1
  46. package/dist/contracts/integration-metadata.js +1 -1
  47. package/dist/contracts/lead-tools.d.ts +0 -1
  48. package/dist/contracts/memory-pass.d.ts +0 -1
  49. package/dist/contracts/memory-pass.js +1 -1
  50. package/dist/contracts/plan.js +1 -1
  51. package/dist/contracts/project-knowledge.js +1 -1
  52. package/dist/contracts/project-skills.d.ts +0 -1
  53. package/dist/contracts/proof.js +1 -1
  54. package/dist/contracts/recipes.js +1 -1
  55. package/dist/contracts/scope.js +1 -1
  56. package/dist/contracts/scout-report.js +1 -1
  57. package/dist/contracts/teams-callback.d.ts +0 -1
  58. package/dist/converse.d.ts +0 -6
  59. package/dist/converse.js +0 -19
  60. package/dist/daemon.js +3 -2
  61. package/dist/decision.d.ts +1 -3
  62. package/dist/deprecations.d.ts +12 -4
  63. package/dist/deprecations.js +27 -4
  64. package/dist/desktop-update-recovery.js +1 -1
  65. package/dist/desktop-update.js +3 -2
  66. package/dist/discord-chat.js +4 -15
  67. package/dist/discord-settings.d.ts +2 -6
  68. package/dist/discord-settings.js +10 -38
  69. package/dist/discord.js +12 -23
  70. package/dist/evidence.d.ts +0 -1
  71. package/dist/evidence.js +0 -3
  72. package/dist/exec.d.ts +0 -9
  73. package/dist/exec.js +3 -81
  74. package/dist/flow-actions.js +0 -3
  75. package/dist/flow-draft.d.ts +1 -3
  76. package/dist/flow-draft.js +15 -28
  77. package/dist/flow-scripts.d.ts +1 -8
  78. package/dist/flow-scripts.js +0 -13
  79. package/dist/flow-send.js +1 -1
  80. package/dist/flow-share.d.ts +0 -2
  81. package/dist/flow-share.js +1 -6
  82. package/dist/flow-steps.js +1 -1
  83. package/dist/flow-triggers.d.ts +0 -1
  84. package/dist/flow-triggers.js +1 -2
  85. package/dist/flow-unattended.js +1 -12
  86. package/dist/flows.js +1 -1
  87. package/dist/health-cli.d.ts +2 -2
  88. package/dist/health-cli.js +1 -1
  89. package/dist/invoke.d.ts +0 -3
  90. package/dist/invoke.js +5 -3
  91. package/dist/is-record.d.ts +2 -0
  92. package/dist/is-record.js +2 -0
  93. package/dist/lead-doors.d.ts +1 -5
  94. package/dist/lead-doors.js +0 -4
  95. package/dist/lead-tools.js +3 -6
  96. package/dist/live-detail.d.ts +0 -1
  97. package/dist/live-detail.js +0 -6
  98. package/dist/live-terminal.js +2 -1
  99. package/dist/live.d.ts +0 -1
  100. package/dist/live.js +1 -2
  101. package/dist/mcp-core.js +0 -2
  102. package/dist/memory-pass.js +1 -1
  103. package/dist/model-catalog.js +1 -1
  104. package/dist/modes.js +1 -12
  105. package/dist/operate.js +383 -750
  106. package/dist/password-bearer.d.ts +0 -2
  107. package/dist/password-bearer.js +0 -4
  108. package/dist/peek-cli.js +2 -1
  109. package/dist/peek.d.ts +2 -26
  110. package/dist/peek.js +2 -168
  111. package/dist/phase-routing.d.ts +0 -4
  112. package/dist/phase-routing.js +2 -21
  113. package/dist/planner-source.d.ts +2 -1
  114. package/dist/planner-source.js +2 -12
  115. package/dist/planner.js +2 -14
  116. package/dist/process-tree.d.ts +0 -2
  117. package/dist/process-tree.js +0 -10
  118. package/dist/project-delete.d.ts +0 -2
  119. package/dist/project-delete.js +1 -3
  120. package/dist/project-learning.d.ts +0 -14
  121. package/dist/project-skills.d.ts +0 -1
  122. package/dist/project-skills.js +0 -14
  123. package/dist/project-tools.js +2 -10
  124. package/dist/provider.d.ts +0 -17
  125. package/dist/provider.js +0 -26
  126. package/dist/pulls.d.ts +0 -2
  127. package/dist/pulls.js +0 -9
  128. package/dist/release-adapters.d.ts +3 -0
  129. package/dist/release-adapters.js +15 -5
  130. package/dist/remote-exec.d.ts +3 -2
  131. package/dist/remote-exec.js +3 -2
  132. package/dist/repos.d.ts +0 -5
  133. package/dist/repos.js +2 -10
  134. package/dist/repository-context.d.ts +0 -2
  135. package/dist/repository-context.js +0 -4
  136. package/dist/restart-certification.d.ts +0 -2
  137. package/dist/restart-certification.js +1 -5
  138. package/dist/result-follow-ups.d.ts +1 -4
  139. package/dist/result-follow-ups.js +1 -6
  140. package/dist/result-review.d.ts +0 -2
  141. package/dist/result-review.js +0 -2
  142. package/dist/retention.d.ts +0 -1
  143. package/dist/retention.js +0 -1
  144. package/dist/review-context.d.ts +0 -1
  145. package/dist/review-context.js +1 -12
  146. package/dist/reviewer.d.ts +0 -16
  147. package/dist/reviewer.js +0 -18
  148. package/dist/run-closeout.js +2 -1
  149. package/dist/scope.js +1 -13
  150. package/dist/scout-browser.js +2 -1
  151. package/dist/serve.js +2 -2
  152. package/dist/server/console.css +5 -133
  153. package/dist/server/flows.js +2 -2
  154. package/dist/server/http.d.ts +2 -0
  155. package/dist/server/http.js +5 -0
  156. package/dist/server/pages.js +2 -2
  157. package/dist/server/people-tokens.js +3 -4
  158. package/dist/server/remote.js +1 -6
  159. package/dist/server/render-chat.d.ts +9 -2
  160. package/dist/server/render-chat.js +53 -7
  161. package/dist/server/render-results.d.ts +0 -6
  162. package/dist/server/render-results.js +3 -9
  163. package/dist/server/render-tasks.js +4 -5
  164. package/dist/server/route-table.d.ts +0 -4
  165. package/dist/server/route-table.js +0 -6
  166. package/dist/server/settings.js +23 -23
  167. package/dist/server/task-views.js +1 -1
  168. package/dist/server/tasks.js +3 -3
  169. package/dist/setup-guide.d.ts +2 -2
  170. package/dist/skills.d.ts +0 -6
  171. package/dist/skills.js +0 -8
  172. package/dist/slack-chat.d.ts +1 -7
  173. package/dist/slack-chat.js +5 -18
  174. package/dist/slack-settings.d.ts +2 -6
  175. package/dist/slack-settings.js +10 -38
  176. package/dist/slack.js +12 -23
  177. package/dist/sleep.d.ts +2 -0
  178. package/dist/sleep.js +2 -0
  179. package/dist/store.d.ts +0 -73
  180. package/dist/store.js +3 -177
  181. package/dist/structured-output.d.ts +0 -1
  182. package/dist/structured-output.js +0 -1
  183. package/dist/subagent-work.d.ts +0 -2
  184. package/dist/subagent-work.js +0 -2
  185. package/dist/subagents.d.ts +1 -3
  186. package/dist/subagents.js +20 -33
  187. package/dist/subscription-chat.d.ts +1 -3
  188. package/dist/subscription-chat.js +2 -3
  189. package/dist/task-sizing.d.ts +1 -4
  190. package/dist/task-sizing.js +3 -10
  191. package/dist/teams-chat.js +4 -11
  192. package/dist/teams.js +12 -19
  193. package/dist/telegram-lead.js +2 -2
  194. package/dist/telegram.js +1 -1
  195. package/dist/token-notices.d.ts +0 -11
  196. package/dist/token-notices.js +1 -5
  197. package/dist/tool-secret-store.d.ts +0 -3
  198. package/dist/tool-secret-store.js +2 -11
  199. package/dist/toolroll-update.d.ts +0 -2
  200. package/dist/toolroll-update.js +2 -2
  201. package/dist/verification-evidence.d.ts +0 -1
  202. package/dist/verification-evidence.js +0 -1
  203. package/dist/webhooks.d.ts +0 -1
  204. package/dist/webhooks.js +0 -1
  205. package/dist/workspace-ui.d.ts +0 -8
  206. package/dist/workspace-ui.js +1 -6
  207. package/package.json +1 -1
  208. package/dist/contracts/native-census.d.ts +0 -42
  209. package/dist/contracts/native-census.js +0 -36
  210. package/dist/process-recovery-anchor.d.ts +0 -75
  211. package/dist/process-recovery-anchor.js +0 -188
  212. package/dist/process-recovery-coalition.d.ts +0 -25
  213. package/dist/process-recovery-coalition.js +0 -105
  214. package/dist/process-recovery-eligibility.d.ts +0 -179
  215. package/dist/process-recovery-eligibility.js +0 -174
  216. package/dist/process-recovery-native.d.ts +0 -161
  217. package/dist/process-recovery-native.js +0 -595
  218. package/dist/process-recovery-provenance.d.ts +0 -163
  219. package/dist/process-recovery-provenance.js +0 -292
  220. package/dist/process-recovery-services.d.ts +0 -56
  221. package/dist/process-recovery-services.js +0 -191
  222. package/dist/process-recovery-settlement.d.ts +0 -32
  223. package/dist/process-recovery-settlement.js +0 -133
  224. package/dist/process-recovery.d.ts +0 -41
  225. package/dist/process-recovery.js +0 -91
  226. package/dist/session-brief.d.ts +0 -8
  227. package/dist/session-brief.js +0 -24
  228. package/dist/session-cli.d.ts +0 -42
  229. package/dist/session-cli.js +0 -271
  230. package/dist/session-contract.d.ts +0 -183
  231. package/dist/session-contract.js +0 -122
  232. package/dist/session-remote.d.ts +0 -19
  233. package/dist/session-remote.js +0 -86
  234. package/dist/session-service.d.ts +0 -22
  235. package/dist/session-service.js +0 -157
  236. package/dist/team-cli.js +0 -692
@@ -39,14 +39,14 @@
39
39
  :root {
40
40
  --background: var(--so-ground); --foreground: var(--so-ink); --card: var(--so-paper);
41
41
  --muted: var(--so-raised); --muted-foreground: var(--so-muted); --border: var(--so-line); --input: var(--so-input-line);
42
- --primary: var(--so-accent); --primary-foreground: var(--so-on-accent); --secondary: var(--so-raised); --secondary-foreground: var(--so-ink);
43
- --accent: var(--so-soft); --destructive: var(--so-danger); --destructive-strong: var(--so-danger); --destructive-soft: var(--so-danger-soft);
42
+ --primary: var(--so-accent); --primary-foreground: var(--so-on-accent); --secondary: var(--so-raised);
43
+ --accent: var(--so-soft); --destructive: var(--so-danger); --destructive-soft: var(--so-danger-soft);
44
44
  --success: var(--so-success); --success-soft: var(--so-success-soft); --warning: var(--so-warning); --warning-soft: var(--so-warning-soft);
45
45
  --running: var(--so-info); --running-soft: var(--so-info-soft); --ring: var(--so-signal);
46
46
  /* The accent means one thing: a person is needed (approve, answer, decide). */
47
47
  --brand: var(--so-attention); --brand-foreground: var(--so-on-attention); --brand-soft: var(--so-attention-soft);
48
48
  --glass: var(--so-paper); --glass-strong: var(--so-paper); --glass-border: var(--so-line); --glass-highlight: transparent;
49
- --ambient-one: transparent; --ambient-two: transparent; --user-message: var(--so-user-bubble);
49
+ --user-message: var(--so-user-bubble);
50
50
  --surface: var(--so-paper); --ok: var(--so-success); --danger: var(--so-danger); --fg-muted: var(--so-muted);
51
51
  --shadow: none; --shadow-overlay: var(--so-shadow-overlay);
52
52
  --radius: 0.625rem;
@@ -58,7 +58,7 @@
58
58
  and a long press never selects a control's label. Text stays selectable. */
59
59
  html { -webkit-tap-highlight-color: transparent; }
60
60
  a, button, summary, [role=button], [role=tab] { touch-action: manipulation; }
61
- button, [role=button], [role=tab], .task-context-chip, .approval-chip, .verdict-chip { -webkit-user-select: none; user-select: none; }
61
+ button, [role=button], [role=tab], .task-context-chip, .verdict-chip { -webkit-user-select: none; user-select: none; }
62
62
  ::selection { background: var(--so-selection); color: var(--so-ink); }
63
63
  ::placeholder { color: var(--muted-foreground); }
64
64
  body {
@@ -73,23 +73,9 @@
73
73
  /* Scrollbars belong to the theme, not the platform default. */
74
74
  * { scrollbar-width: thin; scrollbar-color: var(--border) transparent; }
75
75
 
76
- .topbar {
77
- position: sticky; top: 0; z-index: 10;
78
- border-bottom: 1px solid var(--border); background: var(--background);
79
- }
80
- .topbar-inner {
81
- max-width: 44rem; margin-inline: auto; padding: 0 1.25rem; height: 3.25rem;
82
- display: flex; align-items: center; gap: 1.25rem;
83
- }
84
76
  .brand { font-weight: 600; letter-spacing: -0.01em; color: var(--foreground); text-decoration: none;
85
77
  display: flex; align-items: center; height: 100%; }
86
78
  .brand .dot { color: var(--muted-foreground); }
87
- .topbar nav { display: flex; gap: .25rem; margin-left: auto; height: 100%; }
88
- .topbar nav a {
89
- color: var(--muted-foreground); text-decoration: none; font-size: 0.8125rem; font-weight: 500;
90
- display: flex; align-items: center; padding: 0 .625rem; transition: color .15s;
91
- }
92
- @media (hover: hover) and (pointer: fine) { .topbar nav a:hover { color: var(--foreground); } }
93
79
  main { max-width: 44rem; margin-inline: auto; padding: 1.75rem 1.25rem 4rem; }
94
80
  h1 { font-size: 1.25rem; font-weight: 600; letter-spacing: -0.02em; margin: 0 0 .25rem; line-height: 1.3; }
95
81
  h1 .meta { font-weight: 400; letter-spacing: 0; }
@@ -118,13 +104,6 @@
118
104
  /* Utility classes replacing the old inline style= attributes. */
119
105
  .tight { margin-top: 0; }
120
106
  .card > h2:first-child, .card > h3:first-child { margin-top: 0; }
121
- .w-xs { width: 4.5rem; } .w-sm { width: 8rem; } .w-md { width: 12rem; } .w-lg { width: 14rem; }
122
- .field-cap { width: 100%; max-width: 28rem; }
123
- .field-cap-sm { width: 100%; max-width: 22rem; }
124
- .field-cap-lg { width: 100%; max-width: 34rem; }
125
- .prewrap { white-space: pre-wrap; }
126
- .wrap-any { overflow-wrap: anywhere; }
127
- .grab { cursor: grab; user-select: none; }
128
107
 
129
108
  .palette {
130
109
  position: fixed; top: 18vh; left: 50%; transform: translateX(-50%); width: min(32rem, 90vw);
@@ -298,12 +277,6 @@
298
277
  main:has(.task-intake) { max-width: 68rem; }
299
278
  .task-intake { width: min(100%, 50rem); margin: clamp(1rem, 5vh, 4rem) auto 0; }
300
279
  .task-intake-hero { text-align: center; margin: 0 auto 1.35rem; max-width: 38rem; }
301
- .task-intake-mark {
302
- display: grid; place-items: center; width: 3rem; height: 3rem; margin: 0 auto .85rem;
303
- border: 1px solid var(--border); border-radius: .75rem;
304
- background: var(--card); box-shadow: var(--so-pill-shadow);
305
- font: 600 .72rem/1 var(--font-mono); letter-spacing: -.05em;
306
- }
307
280
  .task-intake-hero h1 { margin: 0; font-size: clamp(1.65rem, 4vw, 2.2rem); letter-spacing: -.045em; }
308
281
  .task-intake-hero p { margin: .45rem 0 0; color: var(--muted-foreground); }
309
282
  .task-composer { padding: .75rem; border-radius: 1.45rem; box-shadow: var(--shadow-overlay); }
@@ -461,8 +434,6 @@
461
434
  .cockpit-row-meta { display: block; color: var(--muted-foreground); font-size: .68rem; overflow-wrap: anywhere; }
462
435
  .cockpit-why { display: block; color: var(--muted-foreground); font-size: .7rem; line-height: 1.35; overflow-wrap: anywhere; }
463
436
  .refusal-back a { display: inline-flex; align-items: center; min-height: 44px; min-width: 44px; }
464
- .cockpit-primary { margin: .85rem 0 1rem; }
465
- .cockpit-primary .button-link { white-space: nowrap; }
466
437
  .cockpit-detail { min-width: 0; }
467
438
  .cockpit-head h1 { margin: .2rem 0 .55rem; color: var(--foreground); font-size: clamp(1.2rem, 2vw, 1.45rem); font-weight: 600; line-height: 1.3; letter-spacing: -.025em; overflow-wrap: anywhere; }
468
439
  .cockpit-chips { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem .75rem; margin: 0 0 .35rem; }
@@ -500,22 +471,6 @@
500
471
  .cockpit-proof-group { margin: .65rem 0; border: 1px solid var(--glass-border); border-radius: calc(var(--radius) - 3px); background: color-mix(in srgb, var(--glass-strong) 62%, transparent); }
501
472
  .cockpit-proof-group > summary { padding: .65rem .75rem; cursor: pointer; font-weight: 550; }
502
473
  .cockpit-proof-group .criterion-matrix { padding: 0 .75rem .7rem; }
503
- .cockpit-proof-summary { display: grid; gap: .25rem; margin: .15rem 0 .75rem; line-height: 1.45; }
504
- .cockpit-proof-summary strong { font-size: .88rem; }
505
- .cockpit-evidence-group { margin: .65rem 0 0; scroll-margin-top: 6rem; border: 1px solid var(--glass-border); border-radius: calc(var(--radius) - 3px); overflow: hidden; }
506
- .cockpit-evidence-group > summary { display: flex; align-items: center; justify-content: space-between; gap: .75rem; padding: .75rem; cursor: pointer; list-style: none; font-weight: 550; }
507
- .cockpit-evidence-group > summary::-webkit-details-marker { display: none; }
508
- .cockpit-evidence-group > summary > span { flex: none; white-space: nowrap; }
509
- .cockpit-evidence-group > summary small { min-width: 0; color: var(--muted-foreground); font-size: .7rem; font-weight: 400; text-align: right; }
510
- .cockpit-evidence-group [data-primary-evidence] { scroll-margin-top: 6rem; }
511
- .cockpit-evidence-body { padding: 0 .75rem .75rem; border-top: 1px solid var(--glass-border); }
512
- .cockpit-accept { margin-top: .65rem; padding-top: .65rem; border-top: 1px solid var(--glass-border); }
513
- .cockpit-accept > summary { cursor: pointer; font-size: .78rem; font-weight: 600; }
514
- .cockpit-accept[open] > summary { margin-bottom: .55rem; }
515
- .cockpit-accept p { margin: 0 0 .55rem; }
516
- .cockpit-accept-form { display: flex; gap: .5rem; margin: 0; }
517
- .cockpit-accept-form input { margin: 0; min-width: 10rem; }
518
- .cockpit-accept-form button { flex: none; background: var(--primary); color: var(--primary-foreground); border-color: var(--primary); font-weight: 600; }
519
474
  .diff-review {
520
475
  overflow: hidden; margin: .8rem 0 .5rem; border: 1px solid var(--glass-border);
521
476
  border-radius: var(--radius); background: color-mix(in srgb, var(--card) 82%, transparent);
@@ -757,7 +712,6 @@
757
712
  margin: 0; color: var(--foreground); font-size: 1.0625rem; line-height: 1.35;
758
713
  letter-spacing: -.015em;
759
714
  }
760
- .project-add-intro { margin: .25rem 0 0; max-width: 36rem; }
761
715
  .project-add-actions {
762
716
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
763
717
  gap: .625rem; margin-top: 1rem;
@@ -822,7 +776,6 @@
822
776
  .nav-group > summary .chevron { width: .875rem; height: .875rem; flex: none; transition: transform .15s; }
823
777
  .nav-group[open] > summary .chevron { transform: rotate(180deg); }
824
778
  .nav-group .nav-group-items { display: flex; flex-direction: column; gap: .125rem; margin: .125rem 0 .25rem; }
825
- .side .nav-settings { margin-top: .375rem; }
826
779
  .side nav a {
827
780
  position: relative;
828
781
  display: flex; align-items: center; gap: .625rem; padding: .4375rem .625rem; min-height: 2.125rem;
@@ -1051,15 +1004,6 @@
1051
1004
  .task-control[data-task-control="paused"] .eyebrow { color: var(--destructive); }
1052
1005
  .resume-ceremony .row { margin: .3rem 0; }
1053
1006
  @media (max-width: 480px) { .task-control { align-items: stretch; } .task-control-form, .task-control-button { width: 100%; } .task-control-form .task-control-button { width: 100%; } }
1054
- .review-attempts { margin: .55rem 0 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: .2rem; font-size: .8125rem; }
1055
- .review-attempts li { display: flex; flex-wrap: wrap; align-items: baseline; gap: .2rem .45rem; min-width: 0; }
1056
- .review-attempts .review-attempt-ordinal { font-weight: 600; }
1057
- .review-attempts .meta { min-width: 0; overflow-wrap: anywhere; }
1058
- .review-retry-actions { display: flex; align-items: center; flex-wrap: wrap; gap: .55rem; margin-top: .65rem; }
1059
- .review-retry-actions form.inline { margin: 0; }
1060
- .review-retry-button { width: auto; max-width: 100%; white-space: normal; }
1061
- .review-retry-form .review-retry-button { background: var(--primary); color: var(--primary-foreground); border-color: var(--primary); font-weight: 600; }
1062
- .review-retry-button[disabled] { opacity: .65; cursor: not-allowed; }
1063
1007
  .proof-review-actions { display: flex; align-items: center; flex-wrap: wrap; gap: .55rem; margin: .65rem 0; }
1064
1008
  .proof-review-actions > .button-link { flex: none; }
1065
1009
  details.proof-exception { margin: 0; }
@@ -1113,9 +1057,6 @@
1113
1057
  .approve-form .recap { margin: .125rem 0 .5rem; }
1114
1058
  .approval-card { padding: 1.2rem 1.3rem; border-radius: calc(var(--radius) + 3px); }
1115
1059
  .approval-card .ceremony-head { align-items: flex-start; padding-bottom: .85rem; border-bottom: 1px solid var(--glass-border); }
1116
- .approval-title { display: grid; gap: .15rem; }
1117
- .approval-kicker { color: var(--muted-foreground); font: 500 .66rem/1.3 var(--font-mono); letter-spacing: .06em; text-transform: uppercase; }
1118
- .approval-title strong { font-size: 1.05rem; letter-spacing: -.02em; }
1119
1060
  .approval-label { display: block; margin: .9rem 0 .2rem; color: var(--muted-foreground); font: 500 12px/1.4 var(--font-sans); }
1120
1061
  .approval-label::first-letter, .planner-plan .eyebrow::first-letter { text-transform: uppercase; }
1121
1062
  .planner-plan .eyebrow { font: 500 12px/1.4 var(--font-sans); letter-spacing: 0; text-transform: none; }
@@ -1124,7 +1065,6 @@
1124
1065
  .approval-boundary { padding: .7rem .8rem; border: 1px solid var(--glass-border); border-radius: calc(var(--radius) - 3px); background: color-mix(in srgb, var(--muted) 45%, transparent); }
1125
1066
  .approval-boundary .approval-label { margin: 0 0 .2rem; }
1126
1067
  .approval-boundary p { margin: 0; color: var(--muted-foreground); font-size: .8rem; overflow-wrap: anywhere; }
1127
- .approval-chips { display: flex; flex-wrap: wrap; gap: .4rem; margin: .8rem 0 .3rem; }
1128
1068
  /* The filed contract (contract handoff, task 1): preserved, amended, or
1129
1069
  drafted from nothing — the same panel on the task page, in the
1130
1070
  ceremony, and on /next, so an amendment is never a surprise after the
@@ -1189,7 +1129,6 @@
1189
1129
  .task-chat-agents-aside .agents-summary { font-size: .74rem; }
1190
1130
  .task-chat-agents-aside .agents-badges { margin-top: .35rem; }
1191
1131
  .task-chat-agents-aside .agents-roles { grid-template-columns: 1fr; }
1192
- .approval-chip { padding: .25rem .6rem; border: 1px solid var(--glass-border); border-radius: 999px; color: var(--muted-foreground); background: var(--glass); font-size: .7rem; }
1193
1132
  .approval-confirm { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: .75rem; align-items: end; margin-top: .9rem; padding-top: .8rem; border-top: 1px solid var(--glass-border); }
1194
1133
  .approval-confirm label { margin: 0; }
1195
1134
  .approval-card .approval-confirm button { min-height: 44px; white-space: nowrap; }
@@ -1508,7 +1447,6 @@
1508
1447
  .result-facts dd form { margin: .35rem 0 0; }
1509
1448
  .result-stat { margin: .2rem 0 .4rem; }
1510
1449
  .result-files { margin: .4rem 0 .6rem; }
1511
- .result-label { display: block; margin: .5rem 0 .25rem; font: 600 .6875rem/1.3 var(--font-mono); color: var(--muted-foreground); letter-spacing: .06em; text-transform: uppercase; }
1512
1450
  .result-verdict { align-items: center; }
1513
1451
  .result-verdict > .meta { min-width: 0; overflow-wrap: anywhere; }
1514
1452
  .result-request { margin-top: 1.1rem; padding-top: .9rem; border-top: 1px solid var(--border); }
@@ -1532,7 +1470,6 @@
1532
1470
  .result-links a { font-weight: 550; }
1533
1471
  .result-links a + a::before { content: "·"; margin-right: .5rem; color: var(--muted-foreground); font-weight: 400; }
1534
1472
  .cockpit-detail .result-panel { margin: .85rem 0; }
1535
- .cockpit-accept { margin-top: .75rem; }
1536
1473
  /* The result beside the conversation (package 3): with room, two
1537
1474
  columns and no second auxiliary panel; without it, the dedicated
1538
1475
  result view with Back to chat and the conversation out of the way. */
@@ -1572,29 +1509,17 @@
1572
1509
  toggle share one wrapping line; an opened disclosure is wider than
1573
1510
  its summary, so it wraps onto its own full-width line by itself. */
1574
1511
  .work-row-status { display: flex; flex-wrap: wrap; align-items: center; gap: .25rem .875rem; }
1575
- .work-details, .work-details[open] { max-width: 100%; margin: 0; padding: 0; border: 0; background: none; border-radius: 0; }
1576
- .work-details > summary {
1577
- list-style: none; cursor: pointer; display: inline-flex; align-items: center; gap: .25rem;
1578
- min-height: 2.25rem; padding: 0 .25rem; margin: 0 -.25rem; border-radius: .375rem;
1579
- font-size: .8125rem; font-weight: 500; color: var(--muted-foreground);
1580
- }
1581
- .work-details > summary::-webkit-details-marker { display: none; }
1582
- .work-details > summary::before { content: "▸"; }
1583
- .work-details[open] > summary::before { content: "▾"; }
1584
- @media (hover: hover) and (pointer: fine) { .work-details > summary:hover { color: var(--foreground); } }
1585
1512
  .work-title { display: block; font-weight: 600; font-size: .9375rem; line-height: 1.35; color: var(--foreground); text-decoration: none; overflow-wrap: anywhere; }
1586
1513
  @media (hover: hover) and (pointer: fine) { .work-title:hover { text-decoration: underline; } }
1587
1514
  .work-meta { display: flex; flex-wrap: wrap; gap: .25rem .625rem; margin: .25rem 0 0; font-size: .75rem; color: var(--muted-foreground); }
1588
1515
  .work-meta .mono { overflow-wrap: anywhere; }
1589
1516
  .project-label { display: inline-block; padding: 0 .4rem; border: 1px solid var(--border); border-radius: 999px; font-size: .6875rem; line-height: 1.5; color: var(--foreground); }
1590
1517
  .work-detail { margin: 0 0 .25rem; font-size: .8125rem; line-height: 1.45; color: var(--muted-foreground); overflow-wrap: anywhere; }
1591
- .work-details .work-id { margin: 0 0 .25rem; }
1592
1518
  .work-action { display: inline-flex; align-items: center; min-height: 44px; font-size: .8125rem; font-weight: 500; }
1593
1519
  .work-empty { padding: 2rem 0 1rem; max-width: 34rem; }
1594
1520
  .work-empty p { margin: 0 0 .75rem; color: var(--muted-foreground); line-height: 1.5; }
1595
1521
  .work-empty .row { display: flex; align-items: center; gap: 1rem; }
1596
1522
  .work-empty .row > a { display: inline-flex; align-items: center; min-height: 44px; }
1597
- .work-bound { margin-top: .75rem; }
1598
1523
  .status-line { display: inline-flex; align-items: center; gap: .45rem; min-width: 0; font-size: .875rem; font-weight: 600; color: var(--foreground); }
1599
1524
  .status-line .status-label { min-width: 0; overflow-wrap: anywhere; }
1600
1525
  .status-line .status-dot { flex: none; width: .5rem; height: .5rem; border-radius: 50%; background: var(--muted-foreground); }
@@ -1629,7 +1554,7 @@
1629
1554
  .work-row { grid-template-columns: minmax(0, 1fr); gap: .125rem; padding: .625rem 0; }
1630
1555
  .work-head { align-items: center; }
1631
1556
  .work-head h1 { margin-bottom: 0; }
1632
- .work-details > summary, .work-action { min-height: 44px; }
1557
+ .work-action { min-height: 44px; }
1633
1558
  /* The tools control sits at the head's right edge on every width, so
1634
1559
  its menu keeps the desk's right-aligned anchor here too. A phone
1635
1560
  override once re-anchored it at left: 0, which pushed the opened
@@ -1771,7 +1696,6 @@
1771
1696
  /* Caution without a claim on the operator: quiet, not magenta. */
1772
1697
  .dot-warn { background: var(--muted-foreground); }
1773
1698
  .dot-off { background: var(--muted-foreground); opacity: .5; }
1774
- .dot-bad { background: var(--destructive); }
1775
1699
  .pulse { animation: pulse 2s ease-in-out infinite; }
1776
1700
  .dot-ok.pulse { background: var(--running); }
1777
1701
  @keyframes pulse { 50% { opacity: .35; } }
@@ -1811,7 +1735,6 @@
1811
1735
 
1812
1736
  @media (max-width: 640px) {
1813
1737
  button, form.option button { min-height: 2.75rem; }
1814
- .topbar nav a { padding: 0 .5rem; }
1815
1738
  }
1816
1739
 
1817
1740
  /* The board: lanes as columns, the pipeline left to right. */
@@ -1935,23 +1858,6 @@
1935
1858
  .control-room-head h1 { font-size: 1.375rem; margin-top: .08rem; }
1936
1859
  .control-room-head .actions { display: flex; gap: .45rem; flex-wrap: wrap; justify-content: flex-end; }
1937
1860
  .control-room-head .actions a { text-decoration: none; }
1938
- .command-metrics {
1939
- display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
1940
- gap: .625rem; margin: .85rem 0 1.5rem;
1941
- }
1942
- .command-metric {
1943
- border: 1px solid var(--border); border-radius: calc(var(--radius) - 2px); background: var(--card);
1944
- padding: .8rem .9rem; min-width: 0;
1945
- }
1946
- .command-metric .value { display: block; margin-top: .25rem; font-size: 1.5rem; font-weight: 600; line-height: 1.15; font-variant-numeric: tabular-nums; font-family: var(--font-mono); }
1947
- .command-metric .label { display: flex; align-items: center; gap: .4rem; font-weight: 500; font-size: .8125rem; }
1948
- .command-metric .label::before {
1949
- content: ""; width: .375rem; height: .375rem; border-radius: var(--so-radius-full);
1950
- background: var(--muted-foreground); flex: none;
1951
- }
1952
- .command-metric .detail { display: block; color: var(--muted-foreground); font-size: .6875rem; margin-top: .18rem; }
1953
- .command-metric.attention .label::before { background: var(--brand); }
1954
- .command-metric.live .label::before { background: var(--running); }
1955
1861
  .workspace-pulse { margin: .4rem 0 1.5rem; display: grid; grid-template-columns: repeat(auto-fill, minmax(19rem, 1fr)); gap: .625rem; }
1956
1862
  /* The workspace card: name and status word, four counts, the same counts
1957
1863
  as a bar, and the one tap to its board. Neutral border always; the
@@ -1980,17 +1886,8 @@
1980
1886
  .task-chat-context-head { display: flex; align-items: center; justify-content: space-between; gap: .65rem; }
1981
1887
  .task-chat-context h2 { margin: .7rem 0 .25rem; color: var(--foreground); font-size: 1rem; line-height: 1.35; letter-spacing: -.025em; }
1982
1888
  .task-chat-context > .meta { margin: 0; overflow-wrap: anywhere; font-size: .65rem; }
1983
- .task-chat-status { display: grid; gap: .2rem; margin-top: .85rem; padding: .7rem; border-radius: calc(var(--radius) - 3px); background: var(--warning-soft); }
1984
- .task-chat-status.ready { background: var(--running-soft); }
1985
- .task-chat-status strong { font-size: .75rem; }
1986
- .task-chat-status span { color: var(--muted-foreground); font-size: .68rem; line-height: 1.45; }
1987
- .task-chat-facts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .4rem; margin: .65rem 0; }
1988
- .task-chat-facts div { min-width: 0; padding: .45rem .5rem; border-radius: calc(var(--radius) - 5px); background: color-mix(in srgb, var(--muted) 58%, transparent); }
1989
- .task-chat-facts dt { color: var(--muted-foreground); font: 400 .58rem/1.2 var(--font-mono); text-transform: uppercase; letter-spacing: .04em; }
1990
- .task-chat-facts dd { margin: .15rem 0 0; font-size: .68rem; overflow-wrap: anywhere; }
1991
1889
  .task-chat-overview-actions { display: grid; justify-items: start; gap: .55rem; margin-top: .75rem; }
1992
1890
  .task-chat-overview-link { display: block; font-size: .72rem; text-decoration: none; }
1993
- .task-chat-recovery-link { min-height: 2rem; padding-inline: .7rem; font-size: .7rem; }
1994
1891
  #task-chat-live { display: grid; gap: 1rem; margin-bottom: 1.1rem; }
1995
1892
  .task-journey {
1996
1893
  position: relative; overflow: hidden; padding: 1rem 1.05rem;
@@ -1998,7 +1895,6 @@
1998
1895
  background: var(--card);
1999
1896
  }
2000
1897
  .task-journey::after { content: ""; position: absolute; width: 9rem; height: 9rem; right: -4rem; top: -5rem; border-radius: 50%; background: color-mix(in srgb, var(--accent) 8%, transparent); filter: blur(12px); pointer-events: none; }
2001
- .task-journey-head { position: relative; z-index: 1; display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; }
2002
1898
  .task-journey h2 { margin: .2rem 0 0; font-size: 1rem; letter-spacing: -.025em; }
2003
1899
  .task-journey > ol { position: relative; z-index: 1; display: grid; grid-template-columns: repeat(5,minmax(0,1fr)); gap: 0; margin: 1rem 0 .75rem; padding: 0; list-style: none; }
2004
1900
  .task-journey > ol::before { content: ""; position: absolute; top: .7rem; left: 10%; right: 10%; height: 1px; background: var(--border); }
@@ -2026,8 +1922,6 @@
2026
1922
  .task-control-copy { min-width: 0; overflow-wrap: anywhere; }
2027
1923
  [data-primary-action] { max-width: 100%; }
2028
1924
 
2029
- .task-live-build { display: flex; align-items: center; flex-wrap: wrap; gap: .3rem; margin: .75rem 0 0; font-size: .72rem; }
2030
- .task-live-build .live-dot { width: .45rem; height: .45rem; border-radius: 50%; background: var(--success); box-shadow: 0 0 0 4px color-mix(in srgb,var(--success) 10%,transparent); }
2031
1925
  .chat-action-card { padding: 0; overflow: hidden; }
2032
1926
  .chat-action-card:not(details) { padding: 1rem 1.05rem; }
2033
1927
  .chat-action-card > summary { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: 1rem 1.05rem; cursor: pointer; list-style: none; }
@@ -2036,9 +1930,6 @@
2036
1930
  .chat-action-card > summary strong { font-size: .92rem; }
2037
1931
  .chat-action-card > summary small { color: var(--muted-foreground); font-size: .68rem; font-weight: 400; }
2038
1932
  .chat-action-card[open] > summary { border-bottom: 1px solid var(--border); }
2039
- .chat-run-details { padding: .65rem .75rem; border: 1px solid var(--border); border-radius: calc(var(--radius) - 3px); background: color-mix(in srgb,var(--muted) 45%,transparent); }
2040
- .chat-run-details > summary { color: var(--muted-foreground); cursor: pointer; font-size: .68rem; }
2041
- .chat-run-details > .meta { margin-bottom: 0; }
2042
1933
  .chat-decisions { display: grid; gap: .7rem; }
2043
1934
  .chat-section-head { margin: .15rem .15rem 0; }
2044
1935
  .chat-section-head h2 { margin: .2rem 0 0; font-size: 1rem; }
@@ -2484,9 +2375,6 @@
2484
2375
  .workspace-bar .seg.building { background: var(--running); }
2485
2376
  .workspace-bar .seg.waiting { background: var(--muted-foreground); }
2486
2377
  .workspace-bar .seg.queued { background: color-mix(in srgb, var(--muted-foreground) 45%, transparent); }
2487
- .attention-stack { display: grid; gap: .55rem; }
2488
- .attention-stack .decide-card { margin: 0; }
2489
- .attention-stack .q { display: flex; gap: .5rem; align-items: center; justify-content: space-between; }
2490
2378
  .wb-rail-head {
2491
2379
  display: flex; align-items: flex-end; justify-content: space-between; gap: .75rem;
2492
2380
  padding: 0 .15rem .6rem; border-bottom: 1px solid var(--border);
@@ -2512,13 +2400,11 @@
2512
2400
  @media (max-width: 720px) {
2513
2401
  .control-room-head { display: block; }
2514
2402
  .control-room-head .actions { justify-content: flex-start; margin-top: .75rem; }
2515
- .command-metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
2516
2403
  .workspace-pulse { grid-template-columns: 1fr; }
2517
2404
  .workspace-stats .pulse-stat { padding: .375rem .125rem; font-size: .625rem; letter-spacing: -.01em; }
2518
2405
  .permission-toggle { grid-template-columns: 1fr; }
2519
2406
  .task-intake { margin-top: .25rem; }
2520
2407
  .task-intake-hero { text-align: left; margin-bottom: .9rem; }
2521
- .task-intake-mark { display: none; }
2522
2408
  .task-composer { margin-inline: 0; padding: .55rem; border-radius: 1.15rem; }
2523
2409
  .task-prompt textarea { min-height: 7.25rem; padding: .75rem; font-size: 1rem; }
2524
2410
  .task-composer-footer { flex-wrap: wrap; }
@@ -2534,8 +2420,6 @@
2534
2420
  .agents-roles { grid-template-columns: 1fr; }
2535
2421
  .agents-role-row { grid-template-columns: 1fr; }
2536
2422
  .approval-card { padding: 1rem; }
2537
- /* The orientation block says the wait on a phone; the kicker would say it twice. */
2538
- .approval-card .approval-kicker { display: none; }
2539
2423
  .approval-boundaries, .approval-confirm { grid-template-columns: 1fr; }
2540
2424
  /* The approval stays IN FLOW on phones: the password field comes
2541
2425
  first and the button follows it — never a sticky control floating
@@ -2570,20 +2454,10 @@
2570
2454
  .queue-card p.row { padding: 0; border-bottom: none; }
2571
2455
  .tracks { margin-top: 1.5rem; }
2572
2456
  .tracks > .hint { margin-bottom: .75rem; }
2573
- .track-row { margin-bottom: .6rem; }
2574
- .track-row p { display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; }
2575
- .track-row p .right { margin-left: auto; }
2576
- .track-row a { text-decoration: none; }
2577
- @media (hover: hover) and (pointer: fine) { .track-row a:hover { text-decoration: underline; } }
2578
- .track-strip { display: inline-flex; gap: .3rem; align-items: center; vertical-align: middle; }
2579
2457
  .fire {
2580
2458
  display: inline-block; width: .65rem; height: .65rem; border-radius: 50%;
2581
2459
  background: var(--muted);
2582
2460
  }
2583
- .fire-ok { background: var(--success); }
2584
- .fire-bad { background: var(--destructive); }
2585
- .fire-live { background: var(--running); animation: pulse 1.6s ease-in-out infinite; }
2586
- .fire-skip { background: transparent; border: 1.5px solid var(--muted); }
2587
2461
 
2588
2462
  /* the phone: fingers, not cursors — tested at 320/390px */
2589
2463
  input, select, textarea { font-size: 16px; }
@@ -2646,8 +2520,6 @@ button { min-height: 44px; }
2646
2520
  .cockpit-disclosure { padding: 0; }
2647
2521
  .cockpit-disclosure > summary { padding: .8rem; }
2648
2522
  .cockpit-disclosure-body { padding: 0 .8rem .85rem; }
2649
- .cockpit-accept-form { display: grid; }
2650
- .cockpit-accept-form button { width: 100%; }
2651
2523
  .diff-review { margin-inline: -.1rem; }
2652
2524
  .diff-review-bar { padding-left: .7rem; }
2653
2525
  .diff-file > summary { padding-inline: .7rem; }
@@ -26,7 +26,7 @@ import { keyStatus } from "../keys.js";
26
26
  import { kitPageHtml, kitsGalleryHtml } from "../kits-ui.js";
27
27
  import { addKitGithubTrigger, addKitSample, kitOf, setUpKit } from "../kits.js";
28
28
  import { connectionsOf } from "../mcp-connect.js";
29
- import { authorizedProject, projectName, rowVisible } from "../project.js";
29
+ import { authorizedProject, projectName, } from "../project.js";
30
30
  import { recipeAnswersFromForm, recipeDefinitionPreviewHtml, recipeEditorHtml, recipeFromForm, recipeLibraryHtml, recipeRunHtml, recipeScript, workflowPreviewHtml } from "../recipe-ui.js";
31
31
  import { createWorkflowPreview, exportRecipe, findRecipe, importRecipe, launchWorkflow, prepareRecipeRun, RecipeError, savedRecipes, saveWorkflowRecipe, starterRecipes, workflowPreview } from "../recipes.js";
32
32
  import { isAlive as runnerAlive } from "../runner.js";
@@ -43,7 +43,7 @@ import { redirect, respond, SAFETY, taskHref } from "./http.js";
43
43
  import { html } from "../html.js";
44
44
  import {} from "./session.js";
45
45
  export function createFlowsHandlers(runtime) {
46
- const { store, clock, sendScreen, chromeFor, consoleProjects, options, visible, flowRooms, identify, liveCeiling, providerHome, toolHome, restricted, evidenceRoot, authenticateApprover, authenticateCredential, projectOf, unscopedMode, ceiling, authorizeMutation } = runtime;
46
+ const { store, clock, sendScreen, chromeFor, consoleProjects, options, visible, liveCeiling, providerHome, toolHome, restricted, evidenceRoot, authenticateApprover, authenticateCredential, projectOf, unscopedMode, ceiling, authorizeMutation } = runtime;
47
47
  // ---- reads ---------------------------------------------------------------
48
48
  /** A gallery template's page (Flows → New → Use this): its questions, the preview of these answers, and Create. */
49
49
  function sendGalleryUse(response, who, template, projects, repo, given, name, notice, said = null) {
@@ -245,6 +245,8 @@ export declare function goOutside(response: ServerResponse, to: string, words: s
245
245
  path: string;
246
246
  secure: boolean;
247
247
  }): void;
248
+ /** A form's answer: back to `page` (which may carry its own query) saying `said` or `problem` words, then an anchor. */
249
+ export declare function sayTo(response: ServerResponse, page: string, anchor?: string): (key: "said" | "problem", words: string, at?: string) => void;
248
250
  export declare function redirect(response: ServerResponse, to: string): void;
249
251
  export declare const taskChatHref: (taskId: string) => string;
250
252
  /** A project's own lead thread (v77). */
@@ -115,6 +115,11 @@ export function goOutside(response, to, words, bind) {
115
115
  "content-security-policy": "default-src 'none'; style-src 'unsafe-inline'; frame-ancestors 'none'; base-uri 'none'; form-action 'none'" });
116
116
  response.end(htmlString(html `<!doctype html><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta http-equiv="refresh" content="0;url=${to}"><title>Toolroll</title>${HANDOFF_STYLE}<p>${words} <a href="${to}">Continue</a></p>`));
117
117
  }
118
+ /** A form's answer: back to `page` (which may carry its own query) saying `said` or `problem` words, then an anchor. */
119
+ export function sayTo(response, page, anchor = "") {
120
+ const join = page.includes("?") ? "&" : "?";
121
+ return (key, words, at = anchor) => redirect(response, `${page}${join}${key}=${encodeURIComponent(words)}${at}`);
122
+ }
118
123
  export function redirect(response, to) {
119
124
  // Set as a header (not only in writeHead) so the answer can be read back after: a refused act's redirect names it.
120
125
  response.setHeader("Location", to);
@@ -38,7 +38,7 @@ import { healthWords } from "../server-telemetry.js";
38
38
  import { tally } from "../summary.js";
39
39
  import { serveInstallAsset } from "./pages-assets.js";
40
40
  import { healthz } from "./remote-hooks.js";
41
- import { projectChip, refuse, regionScript, relativeAge, screen, settingsRows, transcriptScript, WORKSPACE_STYLE, workToolRows } from "./chrome.js";
41
+ import { refuse, regionScript, screen, settingsRows, WORKSPACE_STYLE, workToolRows } from "./chrome.js";
42
42
  import { redirect, respond, safeReturn, SAFETY, taskHref } from "./http.js";
43
43
  import { assignmentOf } from "../assignment.js";
44
44
  import { assignmentTaskStatusOf } from "../assignment-presentation.js";
@@ -500,7 +500,7 @@ export function createPagesHandlers(runtime) {
500
500
  return sendScreen(response, 200, screen("Agents", fallback, { chrome: chromeFor(project, "runs"), workspace: { view } }));
501
501
  }
502
502
  async function fleetPage(ctx) {
503
- const { url, who, response, project } = ctx;
503
+ const { url, response, project } = ctx;
504
504
  // Cross-project by design: which agent is on which project is the
505
505
  // question, so this screen is a survey — reads are still filtered
506
506
  // through the ceiling before a card renders. No project needed.
@@ -9,9 +9,8 @@ import { projectName } from "../project.js";
9
9
  import { requestLimitsHtml, tokenLimitWords } from "../request-budget-ui.js";
10
10
  import { authenticateAccount } from "../scope.js";
11
11
  import { SSO_CALLBACK } from "../sso-settings.js";
12
- import { requestContext } from "./request-context.js";
13
12
  import { personChip, refuse, screen } from "./chrome.js";
14
- import { redirect, respond } from "./http.js";
13
+ import { redirect, respond, sayTo } from "./http.js";
15
14
  /** people handlers, moved without changing their route bodies. */
16
15
  import { randomBytes, timingSafeEqual } from "node:crypto";
17
16
  import {} from "node:http";
@@ -40,7 +39,7 @@ export function sameVisitCheck(authTime, startedAt, now) {
40
39
  return Math.min(Math.max(at, startedAt), now);
41
40
  }
42
41
  export function createPeopleHandlers(runtime) {
43
- const { settingsUpdates, joinBySource, ssoOffer, ssoVisits, ssoSettings, options, clock, ssoHandoffs, managedRepos, store, visible, admissionList, sendScreen, chromeFor, restricted, sessions, consoleProjects, authenticateApprover, authenticateCredential, bustBadge, projectOf, signInLinks, linkKey, fromThisComputer, recordSignIn, passwordAllowed, arrival, defaultProject, cookieSecure, consoleOrigin, providerFor, joinSourceOf, signInBudget, signInActor, minutesWords } = runtime;
42
+ const { joinBySource, ssoOffer, ssoVisits, ssoSettings, options, clock, ssoHandoffs, managedRepos, store, visible, admissionList, sendScreen, chromeFor, restricted, sessions, consoleProjects, authenticateApprover, authenticateCredential, bustBadge, projectOf, signInLinks, linkKey, fromThisComputer, recordSignIn, passwordAllowed, arrival, defaultProject, cookieSecure, consoleOrigin, providerFor, joinSourceOf, signInBudget, signInActor, minutesWords } = runtime;
44
43
  // v101: Settings → Sessions & tokens: where you're signed in, and your API tokens. An instance operator can see everyone's.
45
44
  async function sessionsPage(ctx) {
46
45
  const { url, who, response, project } = ctx;
@@ -167,7 +166,7 @@ ${reauth(html `<label>Password <input type="password" name="token" autocomplete=
167
166
  if (who.via !== "cookie")
168
167
  return refuse(response, who, 403, "Sign in in a browser to manage sessions and tokens.", "/settings");
169
168
  const operator = store.isInstanceOperator(who.name);
170
- const back = (key, words) => redirect(response, `/settings/sessions?${body.get("everyone") === "1" && operator ? "everyone=1&" : ""}${key}=${encodeURIComponent(words)}`);
169
+ const back = sayTo(response, `/settings/sessions${body.get("everyone") === "1" && operator ? "?everyone=1" : ""}`);
171
170
  const action = body.get("action") ?? "";
172
171
  if (action === "end-session") {
173
172
  const target = store.webSessions(null).find(one => one.idHash === (body.get("session") ?? ""));
@@ -7,9 +7,7 @@ import { CodingWorkspace } from '../coding-workspace.js';
7
7
  import { createMcpHttp } from "../mcp-http.js";
8
8
  import { createOAuthHttp, resourceMetadataUrl } from "../mcp-oauth.js";
9
9
  import { reproveRemote } from "../operate-remote.js";
10
- import { rowVisible } from "../project.js";
11
10
  import { RequestBudget, SourceAdmission } from "../request-budget.js";
12
- import { createSessionRunner } from '../session-remote.js';
13
11
  import {} from "../store.js";
14
12
  import { handleTeamHttp, TEAM_PASSWORD_REVERIFY_MS } from '../team-http.js';
15
13
  import { handleTeamsHttp } from "../teams.js";
@@ -17,7 +15,7 @@ import { flowHook, telegramHook } from "./remote-hooks.js";
17
15
  import { respond } from "./http.js";
18
16
  import {} from "./session.js";
19
17
  export function createRemoteHandlers(runtime) {
20
- const { identify, store, admitPasswordSource, admitBearer, allowedHost, team, teamBrowserReply, requestBudget, options, coding, codingProjects, liveCeiling, clock, evidenceRoot, consoleOrigin, managedRepos, joinSourceOf, ssoSettings, oauthTokenBudget, hookContext, teamsSourceBudget, teamsTenantBudget } = runtime;
18
+ const { identify, store, admitPasswordSource, admitBearer, allowedHost, team, teamBrowserReply, requestBudget, options, clock, evidenceRoot, consoleOrigin, managedRepos, joinSourceOf, ssoSettings, oauthTokenBudget, hookContext, teamsSourceBudget, teamsTenantBudget } = runtime;
21
19
  /**
22
20
  * The full principal a live `so_` API token stands for: its person and their generation, the token's own read/act
23
21
  * scope, its project limit narrowed by the person's current access, and its id. Every route that accepts an API token
@@ -97,15 +95,12 @@ export function createRemoteHandlers(runtime) {
97
95
  return who?.via === 'cookie' ? teamBrowserReply(reply, actor, who.session.csrf) : reply;
98
96
  },
99
97
  });
100
- // D7: native session operations are remote commands too (`session …` over /api/cli), run by the session owner.
101
- const sessionRunner = createSessionRunner({ store, workspace: coding, projects: codingProjects, projectAllowed: repo => rowVisible(liveCeiling(), repo), clock });
102
98
  // Remote CLI: one live API token names the person; the shared command boundary decides everything else.
103
99
  const cliEndpoint = (request, response) => handleCliHttp(request, response, {
104
100
  admit: principal => requestBudget.admit(principal.tokenId, "api"),
105
101
  authenticate: tokenPrincipalOf,
106
102
  run: async () => options.cliRunner ?? (await import("../operate.js")).runOperateAs ?? null,
107
103
  modeOf: options.cliModeOf,
108
- session: sessionRunner,
109
104
  store,
110
105
  });
111
106
  const mcpHttp = createMcpHttp({ store, clock, evidenceRoot, ...(options.requestBudgetClock === undefined ? {} : { requestBudgetClock: options.requestBudgetClock }), signedIn: request => identify(request, true, true) !== null, admit: person => requestBudget.admit(person.principal.tokenId, "mcp"),
@@ -142,9 +142,16 @@ export declare function leadPromptStarters(focus?: TaskChatFocus | null): Html;
142
142
  export declare function chatHeading(copy: string, projectCount: number, showProjectToggle?: boolean): Html;
143
143
  /** A deliberately small rich-text grammar for model copy. The reply is shaped first (the lead's voice, enforced:
144
144
  * no headers, at most three bold anchors, labelled links, no internal ids unless `asked` wanted them); input is
145
- * escaped before tags are introduced: bullets, numbered steps, bold, inline code and labelled http(s) links are
146
- * presentation only—never executable HTML. */
145
+ * escaped before tags are introduced: bullets, numbered steps, bold, inline code, fenced code blocks and labelled
146
+ * http(s) links are presentation only—never executable HTML. */
147
147
  export declare function renderChatText(raw: string, asked?: string): Html;
148
+ /** A line comment from the chat Workbench (src/browser/line-steer.tsx steerMessage): On "<file, as a JSON string>":<line>
149
+ * [ (removed line)] (run #<n>): <comment>, then a blank line and "> <the line>" (just ">" for a blank line, once the
150
+ * chat POST has trimmed the message), always last.
151
+ * The words the lead reads stay exactly as sent; the thread shows them in three parts: a small reference line, the quoted
152
+ * code as written, and the comment. Null for any other message, which keeps its own plain rendering. Everything is
153
+ * escaped by the html template. */
154
+ export declare function steerCommentHtml(text: string): Html | null;
148
155
  export declare function chatActivity(activity: string | null): Html;
149
156
  export declare const CHAT_UI_SCRIPT: string;
150
157
  /** Provider, model, and limits under ONE disclosure (UI polish
@@ -33,7 +33,6 @@ import { chatMoney, oneLineOf, relativeAge, safePrUrl, screen, sentenceCase, str
33
33
  import { chatResultHref, projectChatHref, taskChatHref, taskHref } from "./http.js";
34
34
  import {} from "./render-pages.js";
35
35
  import { completionReceiptCard } from "./render-results.js";
36
- import {} from "./render-settings.js";
37
36
  import { agentsAvailabilityHtml, agentsBadgesHtml, agentsStripHtml, agentsSummaryWords, agentsWhyHtml, approvalSheetHtml, checkProgressHtml, consentClosedHtml, consentDoorOf, decisionAnswerCard, dispatchHeadline, milestoneProgressHtml, planRevisionLedgerHtml, profileWords, sizeConsequence, taskControlDetailsHtml, taskStatusCard, taskViewSwitch } from "./render-tasks.js";
38
37
  /** The installation fact that the lead was turned on by default, once (its value: the provider). */
39
38
  export const LEAD_BY_DEFAULT_FACT = "lead-on-by-default";
@@ -260,8 +259,8 @@ export function chatHeading(copy, projectCount, showProjectToggle = true) {
260
259
  }
261
260
  /** A deliberately small rich-text grammar for model copy. The reply is shaped first (the lead's voice, enforced:
262
261
  * no headers, at most three bold anchors, labelled links, no internal ids unless `asked` wanted them); input is
263
- * escaped before tags are introduced: bullets, numbered steps, bold, inline code and labelled http(s) links are
264
- * presentation only—never executable HTML. */
262
+ * escaped before tags are introduced: bullets, numbered steps, bold, inline code, fenced code blocks and labelled
263
+ * http(s) links are presentation only—never executable HTML. */
265
264
  export function renderChatText(raw, asked) {
266
265
  const inline = replyHtmlInline;
267
266
  const text = shapeReply(raw, { appOrigin: requestContext.getStore()?.appOrigins ?? null, ...(asked === undefined ? {} : { asked }) });
@@ -281,7 +280,31 @@ export function renderChatText(raw, asked) {
281
280
  out.push(list.tag === "ul" ? html `<ul>${list.items}</ul>` : html `<ol>${list.items}</ol>`);
282
281
  list = null;
283
282
  };
283
+ // A fenced block (shaping leaves it exactly as written) is shown as written: escaped, never parsed.
284
+ let fence = null;
285
+ const closeFence = () => {
286
+ const open = fence;
287
+ if (open === null)
288
+ return;
289
+ out.push(html `<pre class="chat-code"${open.lang === "" ? "" : html ` data-lang="${open.lang}"`}><code>${open.lines.join("\n")}</code></pre>`);
290
+ fence = null;
291
+ };
284
292
  for (const line of lines) {
293
+ const open = fence;
294
+ if (open !== null) {
295
+ if (/^\s{0,3}```\s*$/.test(line))
296
+ closeFence();
297
+ else
298
+ open.lines.push(line);
299
+ continue;
300
+ }
301
+ const fenceMark = /^\s{0,3}```\s*([A-Za-z0-9_+.#-]{0,24})\s*$/.exec(line);
302
+ if (fenceMark !== null) {
303
+ flushParagraph();
304
+ closeList();
305
+ fence = { lang: fenceMark[1], lines: [] };
306
+ continue;
307
+ }
285
308
  const bullet = /^\s*[-*]\s+(.+)$/.exec(line);
286
309
  const numbered = /^\s*(\d{1,9})[.)]\s+(.+)$/.exec(line);
287
310
  if (bullet !== null || numbered !== null) {
@@ -304,10 +327,35 @@ export function renderChatText(raw, asked) {
304
327
  paragraph.push(line);
305
328
  }
306
329
  }
330
+ // An unclosed fence still shows its lines as code: the reply may have been cut.
307
331
  flushParagraph();
308
332
  closeList();
333
+ closeFence();
309
334
  return html `<div class="chat-copy">${out}</div>`;
310
335
  }
336
+ /** A line comment from the chat Workbench (src/browser/line-steer.tsx steerMessage): On "<file, as a JSON string>":<line>
337
+ * [ (removed line)] (run #<n>): <comment>, then a blank line and "> <the line>" (just ">" for a blank line, once the
338
+ * chat POST has trimmed the message), always last.
339
+ * The words the lead reads stay exactly as sent; the thread shows them in three parts: a small reference line, the quoted
340
+ * code as written, and the comment. Null for any other message, which keeps its own plain rendering. Everything is
341
+ * escaped by the html template. */
342
+ export function steerCommentHtml(text) {
343
+ const steer = /^On ("(?:[^"\\\n]|\\.){1,1200}"):(\d{1,9})( \(removed line\))? \(run #(\d{1,12})\): ([\s\S]+)\n\n>(?: ([^\n]*))?$/.exec(text);
344
+ if (steer === null)
345
+ return null;
346
+ // A blank line's quote ("> ") reaches the store as ">": the chat POST trims the message (src/server/chat.ts).
347
+ const [, quoted, line, removed, run, comment, code = ""] = steer;
348
+ let path;
349
+ try {
350
+ path = JSON.parse(quoted);
351
+ }
352
+ catch {
353
+ return null;
354
+ }
355
+ if (typeof path !== "string" || path === "" || /[\u0000-\u001f]/.test(path) || comment.trim() === "")
356
+ return null;
357
+ return html `<div class="chat-steer" data-steer-comment><p class="chat-steer-ref">${path}:${line}${removed === undefined ? "" : " · removed line"} · run #${run}</p>${code === "" ? "" : html `<pre class="chat-steer-code"><code>${code}</code></pre>`}<p class="chat-steer-note" style="white-space:pre-wrap">${comment}</p></div>`;
358
+ }
311
359
  export function chatActivity(activity) {
312
360
  return chatActivityDetailsHtml(activity);
313
361
  }
@@ -351,8 +399,6 @@ export function chatLimitsHtml(facts) {
351
399
  return html `<details class="chat-limits"><summary>Model &amp; limits<span class="meta">${facts.subscription ? "membership" : facts.provider}</span></summary><div class="chat-budget">${rows}</div></details>`;
352
400
  }
353
401
  export function chatPage(chrome, data) {
354
- const formFacts = { keyFacts: data.keyFacts, openrouterModels: data.openrouterModels, ...(data.liveModels === undefined ? {} : { liveModels: data.liveModels }), csrf: data.csrf,
355
- returnTo: data.focusTask === null ? "/chat" : taskChatHref(data.focusTask.id) };
356
402
  const parts = [
357
403
  data.focusTask === null
358
404
  ? chatHeading("", data.projects.length, data.enabled.ok)
@@ -803,7 +849,7 @@ export function leadBrowserMessages(rows, back, target = { task: null, project:
803
849
  return rows.messages.map(message => ({
804
850
  id: message.id, role: message.role, text: message.text,
805
851
  html: htmlString(message.role === 'operator'
806
- ? (() => { const queued = rows.queue?.get(message.id); return html `${message.text === "" ? "" : html `<p>${message.text}</p>`}${queued === undefined ? "" : queuedStateHtml(queued, back)}`; })()
852
+ ? (() => { const queued = rows.queue?.get(message.id); return html `${message.text === "" ? "" : steerCommentHtml(message.text) ?? html `<p>${message.text}</p>`}${queued === undefined ? "" : queuedStateHtml(queued, back)}`; })()
807
853
  : html `${renderChatText(message.text, askedBy.get(message.id))}${asked(message.turn)}`),
808
854
  activity: message.activity, createdAt: message.createdAt,
809
855
  ...(() => {
@@ -882,7 +928,7 @@ export function leadThreadHtml(data) {
882
928
  asked = message.text;
883
929
  // A message that waits its turn (the lead queue) says so, with Remove while it still waits — as in the React thread.
884
930
  const queued = data.queue?.get(message.id);
885
- thread.push(html `<div class="msg op" data-message-role="operator" data-key="m${message.id}">${message.text === "" ? "" : html `<p style="white-space:pre-wrap">${message.text}</p>`}${queued === undefined ? "" : queuedStateHtml(queued, returnTo)}</div>`);
931
+ thread.push(html `<div class="msg op" data-message-role="operator" data-key="m${message.id}">${message.text === "" ? "" : steerCommentHtml(message.text) ?? html `<p style="white-space:pre-wrap">${message.text}</p>`}${queued === undefined ? "" : queuedStateHtml(queued, returnTo)}</div>`);
886
932
  continue;
887
933
  }
888
934
  const cards = message.turn === null ? [] : (byTurn.get(message.turn) ?? []);