toolroll 0.9.54 → 0.9.56

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 (350) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +29 -12
  3. package/dist/action-ledger.d.ts +2 -4
  4. package/dist/action-ledger.js +2 -10
  5. package/dist/agent-onboard.js +4 -1
  6. package/dist/approval-policy.d.ts +5 -5
  7. package/dist/approval-policy.js +4 -4
  8. package/dist/approval-rules-ui.d.ts +3 -2
  9. package/dist/approval-rules-ui.js +17 -22
  10. package/dist/assignment-ui.d.ts +3 -2
  11. package/dist/assignment-ui.js +4 -11
  12. package/dist/backup-ui.d.ts +2 -1
  13. package/dist/backup-ui.js +16 -27
  14. package/dist/brand-mark.d.ts +3 -2
  15. package/dist/brand-mark.js +4 -4
  16. package/dist/browser/workspace.css +1 -1
  17. package/dist/browser/workspace.js +25 -25
  18. package/dist/browser-shell.d.ts +2 -1
  19. package/dist/browser-shell.js +9 -9
  20. package/dist/browser-workspace.d.ts +14 -15
  21. package/dist/browser-workspace.js +0 -3
  22. package/dist/budget-alerts.js +3 -3
  23. package/dist/chat-actions.d.ts +23 -18
  24. package/dist/chat-actions.js +110 -88
  25. package/dist/chat-ask.d.ts +2 -2
  26. package/dist/chat-ask.js +5 -5
  27. package/dist/chat-channel.d.ts +15 -15
  28. package/dist/chat-channel.js +20 -20
  29. package/dist/chat-controls.d.ts +0 -4
  30. package/dist/chat-controls.js +0 -1
  31. package/dist/chat-core.js +1 -1
  32. package/dist/chat-delivery-state.js +2 -2
  33. package/dist/chat-delivery.d.ts +2 -2
  34. package/dist/chat-delivery.js +27 -34
  35. package/dist/chat-flow.js +3 -3
  36. package/dist/chat-messages.d.ts +1 -1
  37. package/dist/chat-messages.js +4 -4
  38. package/dist/chat-migration.d.ts +1 -1
  39. package/dist/chat-migration.js +11 -11
  40. package/dist/chat-polish.d.ts +5 -4
  41. package/dist/chat-polish.js +9 -10
  42. package/dist/chat-rooms.js +3 -3
  43. package/dist/chat-warmth.d.ts +2 -2
  44. package/dist/check-levels-ui.d.ts +2 -1
  45. package/dist/check-levels-ui.js +26 -53
  46. package/dist/cli.d.ts +3 -1
  47. package/dist/cli.js +15 -10
  48. package/dist/coding-shipping-ui.d.ts +2 -1
  49. package/dist/coding-shipping-ui.js +6 -6
  50. package/dist/coding-ui.d.ts +3 -1
  51. package/dist/coding-ui.js +30 -29
  52. package/dist/contracts/chat-actions.d.ts +32 -27
  53. package/dist/contracts/chat-actions.js +29 -11
  54. package/dist/contracts/chat-content.d.ts +1 -1
  55. package/dist/contracts/chat-content.js +1 -1
  56. package/dist/contracts/chat-tables.js +4 -4
  57. package/dist/contracts/cli.d.ts +1 -3
  58. package/dist/contracts/console-api.d.ts +7 -7
  59. package/dist/contracts/console-api.js +13 -12
  60. package/dist/contracts/flow-propose.d.ts +3 -3
  61. package/dist/contracts/flow.d.ts +132 -125
  62. package/dist/contracts/flow.js +27 -12
  63. package/dist/contracts/health.d.ts +1 -3
  64. package/dist/contracts/lead-context.d.ts +1 -1
  65. package/dist/contracts/lead-context.js +2 -2
  66. package/dist/contracts/lead-tools.d.ts +20 -18
  67. package/dist/contracts/lead-tools.js +15 -15
  68. package/dist/contracts/stage-output.d.ts +1 -1
  69. package/dist/contracts/stage-output.js +1 -1
  70. package/dist/contracts/store-json.d.ts +23 -22
  71. package/dist/contracts/store-json.js +17 -17
  72. package/dist/contracts/{teammate-turn.d.ts → subagent-turn.d.ts} +5 -5
  73. package/dist/contracts/{teammate-turn.js → subagent-turn.js} +7 -7
  74. package/dist/control-ui.d.ts +5 -4
  75. package/dist/control-ui.js +9 -34
  76. package/dist/converse.d.ts +23 -23
  77. package/dist/converse.js +28 -28
  78. package/dist/coordinator-proposals.d.ts +2 -2
  79. package/dist/coordinator-proposals.js +1 -1
  80. package/dist/coordinator.js +1 -1
  81. package/dist/credentials-ui.d.ts +5 -4
  82. package/dist/credentials-ui.js +23 -24
  83. package/dist/demo-chat.d.ts +3 -3
  84. package/dist/demo-chat.js +29 -76
  85. package/dist/deprecations.d.ts +12 -0
  86. package/dist/deprecations.js +20 -0
  87. package/dist/desktop-update-gate.js +8 -4
  88. package/dist/desktop-update.js +6 -1
  89. package/dist/discord-chat.js +6 -6
  90. package/dist/discord-settings.d.ts +3 -2
  91. package/dist/discord-settings.js +17 -42
  92. package/dist/dispatch.d.ts +1 -1
  93. package/dist/dispatch.js +1 -1
  94. package/dist/envelope.js +10 -1
  95. package/dist/evidence-pack.d.ts +2 -1
  96. package/dist/evidence-pack.js +45 -49
  97. package/dist/export-ui.d.ts +7 -2
  98. package/dist/export-ui.js +4 -10
  99. package/dist/export.js +7 -7
  100. package/dist/flow-engine.d.ts +2 -2
  101. package/dist/flow-engine.js +21 -21
  102. package/dist/flow-gallery-ui.d.ts +4 -3
  103. package/dist/flow-gallery-ui.js +28 -43
  104. package/dist/flow-insights.d.ts +1 -1
  105. package/dist/flow-share.js +13 -8
  106. package/dist/flow-starters-ui.d.ts +2 -1
  107. package/dist/flow-starters-ui.js +12 -15
  108. package/dist/flow-steps.d.ts +5 -5
  109. package/dist/flow-steps.js +19 -19
  110. package/dist/flow-triggers.d.ts +2 -1
  111. package/dist/flow-triggers.js +17 -15
  112. package/dist/flows-cli.js +1 -1
  113. package/dist/flows-ui.d.ts +4 -3
  114. package/dist/flows-ui.js +38 -49
  115. package/dist/flows.js +21 -20
  116. package/dist/guides.js +112 -6
  117. package/dist/html.d.ts +76 -0
  118. package/dist/html.js +173 -0
  119. package/dist/integrations-ui.d.ts +2 -1
  120. package/dist/integrations-ui.js +19 -23
  121. package/dist/invoke.js +1 -1
  122. package/dist/kits-ui.d.ts +5 -4
  123. package/dist/kits-ui.js +22 -24
  124. package/dist/kits.d.ts +12 -12
  125. package/dist/kits.js +31 -31
  126. package/dist/knowledge-ui.d.ts +6 -5
  127. package/dist/knowledge-ui.js +26 -25
  128. package/dist/{mate-cli.d.ts → lead-cli.d.ts} +11 -11
  129. package/dist/{mate-cli.js → lead-cli.js} +38 -38
  130. package/dist/lead-commitments.js +4 -4
  131. package/dist/lead-context.d.ts +12 -2
  132. package/dist/lead-context.js +15 -17
  133. package/dist/{mate-contract.d.ts → lead-contract.d.ts} +3 -3
  134. package/dist/{mate-contract.js → lead-contract.js} +7 -6
  135. package/dist/{mate-doors.d.ts → lead-doors.d.ts} +6 -6
  136. package/dist/{mate-doors.js → lead-doors.js} +24 -24
  137. package/dist/lead-follow.d.ts +5 -5
  138. package/dist/lead-follow.js +9 -9
  139. package/dist/lead-people.d.ts +5 -5
  140. package/dist/lead-people.js +14 -14
  141. package/dist/{mate-progress.d.ts → lead-progress.d.ts} +9 -9
  142. package/dist/{mate-progress.js → lead-progress.js} +3 -3
  143. package/dist/{mate-tools.d.ts → lead-tools.d.ts} +19 -19
  144. package/dist/{mate-tools.js → lead-tools.js} +109 -101
  145. package/dist/{mate.d.ts → lead.d.ts} +27 -27
  146. package/dist/{mate.js → lead.js} +102 -102
  147. package/dist/ledger-view.d.ts +2 -1
  148. package/dist/ledger-view.js +27 -29
  149. package/dist/limits-ui.d.ts +3 -2
  150. package/dist/limits-ui.js +8 -8
  151. package/dist/live-bus.d.ts +50 -8
  152. package/dist/live-bus.js +133 -50
  153. package/dist/mcp-connect.d.ts +1 -1
  154. package/dist/mcp-connect.js +3 -3
  155. package/dist/mcp-core.js +1 -1
  156. package/dist/mcp-oauth.js +35 -36
  157. package/dist/memory-pass.d.ts +2 -2
  158. package/dist/memory-pass.js +7 -7
  159. package/dist/models-ui.d.ts +2 -1
  160. package/dist/models-ui.js +25 -35
  161. package/dist/monitoring-ui.d.ts +4 -3
  162. package/dist/monitoring-ui.js +13 -26
  163. package/dist/notification-rows.d.ts +2 -2
  164. package/dist/openrouter-models.d.ts +2 -1
  165. package/dist/openrouter-models.js +5 -9
  166. package/dist/operate.d.ts +3 -3
  167. package/dist/operate.js +46 -41
  168. package/dist/people-audit-ui.d.ts +2 -1
  169. package/dist/people-audit-ui.js +13 -13
  170. package/dist/policy-ui.d.ts +3 -2
  171. package/dist/policy-ui.js +17 -23
  172. package/dist/policy.d.ts +1 -1
  173. package/dist/principal.d.ts +1 -1
  174. package/dist/principal.js +2 -2
  175. package/dist/project-delete-ui.d.ts +4 -3
  176. package/dist/project-delete-ui.js +16 -28
  177. package/dist/project-delete.d.ts +1 -1
  178. package/dist/project-delete.js +26 -26
  179. package/dist/project-memory.js +1 -1
  180. package/dist/project-tools.d.ts +1 -1
  181. package/dist/project-tools.js +1 -1
  182. package/dist/pull-request-ui.d.ts +2 -1
  183. package/dist/pull-request-ui.js +17 -32
  184. package/dist/recipe-ui.d.ts +8 -7
  185. package/dist/recipe-ui.js +39 -32
  186. package/dist/release-adapters.d.ts +60 -0
  187. package/dist/release-adapters.js +435 -0
  188. package/dist/release-cli.d.ts +24 -0
  189. package/dist/release-cli.js +181 -0
  190. package/dist/release-gate.d.ts +80 -0
  191. package/dist/release-gate.js +150 -0
  192. package/dist/release-links.d.ts +29 -0
  193. package/dist/release-links.js +102 -0
  194. package/dist/release.d.ts +331 -0
  195. package/dist/release.js +550 -0
  196. package/dist/reply-shape.d.ts +2 -1
  197. package/dist/reply-shape.js +5 -5
  198. package/dist/repository-context-ui.d.ts +2 -1
  199. package/dist/repository-context-ui.js +2 -7
  200. package/dist/request-budget-ui.d.ts +2 -1
  201. package/dist/request-budget-ui.js +16 -16
  202. package/dist/result-review.d.ts +2 -3
  203. package/dist/result-review.js +3 -9
  204. package/dist/retention-ui.d.ts +7 -1
  205. package/dist/retention-ui.js +7 -11
  206. package/dist/retention.js +4 -4
  207. package/dist/scope.d.ts +2 -2
  208. package/dist/scout-report.d.ts +1 -1
  209. package/dist/scout-report.js +1 -1
  210. package/dist/serve.d.ts +11 -2
  211. package/dist/serve.js +105 -99
  212. package/dist/server/chat.d.ts +7 -10
  213. package/dist/server/chat.js +984 -1021
  214. package/dist/server/chrome.d.ts +291 -0
  215. package/dist/server/chrome.js +747 -0
  216. package/dist/server/console.css +2819 -0
  217. package/dist/server/flows.d.ts +2 -6
  218. package/dist/server/flows.js +854 -773
  219. package/dist/server/guards.d.ts +1 -1
  220. package/dist/server/guards.js +8 -5
  221. package/dist/server/handler-context.d.ts +1 -1
  222. package/dist/server/handler-context.js +1 -1
  223. package/dist/server/handler-registry.d.ts +8 -6
  224. package/dist/server/handler-registry.js +14 -2
  225. package/dist/server/http.d.ts +274 -0
  226. package/dist/server/http.js +239 -0
  227. package/dist/server/live.d.ts +34 -0
  228. package/dist/server/live.js +309 -0
  229. package/dist/server/pages.d.ts +2 -6
  230. package/dist/server/pages.js +808 -789
  231. package/dist/server/people-tokens.d.ts +1 -6
  232. package/dist/server/people-tokens.js +581 -573
  233. package/dist/server/remote.d.ts +4 -7
  234. package/dist/server/remote.js +39 -70
  235. package/dist/server/render-chat.d.ts +402 -0
  236. package/dist/server/render-chat.js +927 -0
  237. package/dist/server/render-pages.d.ts +535 -0
  238. package/dist/server/render-pages.js +1277 -0
  239. package/dist/server/render-people.d.ts +32 -0
  240. package/dist/server/render-people.js +121 -0
  241. package/dist/server/render-results.d.ts +744 -0
  242. package/dist/server/render-results.js +2237 -0
  243. package/dist/server/render-settings.d.ts +154 -0
  244. package/dist/server/render-settings.js +580 -0
  245. package/dist/server/render-tasks.d.ts +632 -0
  246. package/dist/server/render-tasks.js +2345 -0
  247. package/dist/server/request-context.d.ts +46 -0
  248. package/dist/server/request-context.js +17 -0
  249. package/dist/server/route-table.d.ts +25 -3
  250. package/dist/server/route-table.js +167 -77
  251. package/dist/server/runtime.d.ts +28 -20
  252. package/dist/server/runtime.js +7 -1
  253. package/dist/server/session.d.ts +138 -0
  254. package/dist/server/session.js +94 -0
  255. package/dist/server/settings.d.ts +1 -5
  256. package/dist/server/settings.js +2186 -2121
  257. package/dist/server/task-views.d.ts +11 -4
  258. package/dist/server/task-views.js +26 -22
  259. package/dist/server/tasks.d.ts +9 -9
  260. package/dist/server/tasks.js +1489 -1420
  261. package/dist/server-telemetry.d.ts +4 -3
  262. package/dist/server-telemetry.js +4 -8
  263. package/dist/setup-guide.d.ts +2 -2
  264. package/dist/skills-ui.d.ts +4 -3
  265. package/dist/skills-ui.js +14 -17
  266. package/dist/skills.d.ts +1 -1
  267. package/dist/skills.js +2 -1
  268. package/dist/slack-chat.d.ts +2 -2
  269. package/dist/slack-chat.js +6 -6
  270. package/dist/slack-settings.d.ts +3 -2
  271. package/dist/slack-settings.js +17 -42
  272. package/dist/spend-ui.d.ts +5 -4
  273. package/dist/spend-ui.js +17 -40
  274. package/dist/spend.d.ts +14 -14
  275. package/dist/spend.js +31 -31
  276. package/dist/sso-ui.d.ts +3 -2
  277. package/dist/sso-ui.js +26 -27
  278. package/dist/storage-ui.d.ts +2 -1
  279. package/dist/storage-ui.js +14 -33
  280. package/dist/store-baseline.d.ts +19 -0
  281. package/dist/store-baseline.js +2873 -0
  282. package/dist/store.d.ts +197 -249
  283. package/dist/store.js +747 -3084
  284. package/dist/subagent-admin.d.ts +53 -0
  285. package/dist/{teammate-admin.js → subagent-admin.js} +37 -37
  286. package/dist/{teammate-desk.d.ts → subagent-desk.d.ts} +22 -23
  287. package/dist/{teammate-desk.js → subagent-desk.js} +26 -35
  288. package/dist/{teammate-memory.d.ts → subagent-memory.d.ts} +17 -17
  289. package/dist/{teammate-memory.js → subagent-memory.js} +41 -41
  290. package/dist/{teammate-question.d.ts → subagent-question.d.ts} +6 -6
  291. package/dist/{teammate-question.js → subagent-question.js} +17 -17
  292. package/dist/{teammate-tools.d.ts → subagent-tools.d.ts} +18 -18
  293. package/dist/{teammate-tools.js → subagent-tools.js} +36 -36
  294. package/dist/{teammate-week.d.ts → subagent-week.d.ts} +10 -10
  295. package/dist/{teammate-week.js → subagent-week.js} +23 -23
  296. package/dist/{teammate-work.d.ts → subagent-work.d.ts} +21 -21
  297. package/dist/{teammate-work.js → subagent-work.js} +50 -50
  298. package/dist/subagents-ui.d.ts +22 -0
  299. package/dist/subagents-ui.js +137 -0
  300. package/dist/{teammates.d.ts → subagents.d.ts} +11 -11
  301. package/dist/{teammates.js → subagents.js} +21 -21
  302. package/dist/subscription-chat.d.ts +13 -13
  303. package/dist/subscription-chat.js +19 -19
  304. package/dist/surface.d.ts +19 -2
  305. package/dist/surface.js +20 -33
  306. package/dist/task-composer.d.ts +2 -1
  307. package/dist/task-composer.js +7 -7
  308. package/dist/task-status.d.ts +4 -3
  309. package/dist/task-status.js +15 -16
  310. package/dist/team-cli.d.ts +1 -0
  311. package/dist/team-cli.js +14 -2
  312. package/dist/team-http.d.ts +5 -9
  313. package/dist/team-http.js +5 -63
  314. package/dist/team-leads.js +13 -13
  315. package/dist/team-runtime.d.ts +2 -2
  316. package/dist/team-runtime.js +8 -8
  317. package/dist/team-ui.d.ts +2 -1
  318. package/dist/team-ui.js +2 -5
  319. package/dist/teams-chat.js +6 -6
  320. package/dist/teams-settings.d.ts +3 -2
  321. package/dist/teams-settings.js +20 -31
  322. package/dist/telegram-flow.d.ts +3 -3
  323. package/dist/telegram-flow.js +3 -3
  324. package/dist/{telegram-mate.d.ts → telegram-lead.d.ts} +5 -5
  325. package/dist/{telegram-mate.js → telegram-lead.js} +27 -27
  326. package/dist/telegram-settings.d.ts +3 -2
  327. package/dist/telegram-settings.js +14 -25
  328. package/dist/telegram-status.js +1 -1
  329. package/dist/telegram-team.d.ts +1 -1
  330. package/dist/telegram-team.js +5 -5
  331. package/dist/telegram.d.ts +1 -1
  332. package/dist/telegram.js +9 -30
  333. package/dist/text-limits.d.ts +18 -18
  334. package/dist/text-limits.js +18 -18
  335. package/dist/toolroll-update-ui.d.ts +3 -2
  336. package/dist/toolroll-update-ui.js +35 -39
  337. package/dist/toolroll-update.d.ts +8 -0
  338. package/dist/toolroll-update.js +45 -8
  339. package/dist/tools-ui.d.ts +5 -2
  340. package/dist/tools-ui.js +31 -31
  341. package/dist/when-html.d.ts +11 -3
  342. package/dist/when-html.js +5 -6
  343. package/dist/workspace-ui.d.ts +4 -2
  344. package/dist/workspace-ui.js +10 -10
  345. package/package.json +1 -2
  346. package/dist/server/shared.d.ts +0 -3151
  347. package/dist/server/shared.js +0 -12001
  348. package/dist/teammate-admin.d.ts +0 -53
  349. package/dist/teammates-ui.d.ts +0 -20
  350. package/dist/teammates-ui.js +0 -166
package/dist/operate.js CHANGED
@@ -22,7 +22,7 @@ import { checkoutPlan, cleanCheckouts, discardCheckout, finishedCheckouts, slimK
22
22
  import { closeOutCheckouts, closeOutRuns, stoppedWords } from "./run-closeout.js";
23
23
  import { MIN_DAYS, RETENTION_KINDS, countWords, dailyRetention, isRetentionKind, lastSweepAt, parsePeriod, periodLabel, periodWords, retentionPlan, sweepWords } from "./retention.js";
24
24
  import { checkPolicy, parseList, policyParts } from "./policy.js";
25
- import { billingOf, budgetHoldWords, budgetLabel, budgetStates, monthNamed, monthOf, spendItems, teammateNames, usd as spendUsd } from "./spend.js";
25
+ import { billingOf, budgetHoldWords, budgetLabel, budgetStates, monthNamed, monthOf, spendItems, subagentNames, usd as spendUsd } from "./spend.js";
26
26
  import { spendCsv } from "./spend-ui.js";
27
27
  import { buildExport, exportSummary, exportZip, writeExportFolder } from "./export.js";
28
28
  import { startBudgetAlerts } from "./budget-alerts.js";
@@ -43,8 +43,8 @@ import { origin, readMonitoring } from "./monitoring-settings.js";
43
43
  import { logEvent } from "./log.js";
44
44
  import { FLOW_EVERY_MS, flowHousekeeping, moveCards, moveCardsAfter } from "./flow-cadence.js";
45
45
  import { readHooksBase, scheduleFromWords } from "./flow-triggers.js";
46
- import { sendTeammateSummaries } from "./teammate-admin.js";
47
- import { runRequestedUndos, sendTeammateWeeklies } from "./teammate-week.js";
46
+ import { sendSubagentSummaries } from "./subagent-admin.js";
47
+ import { runRequestedUndos, sendSubagentWeeklies } from "./subagent-week.js";
48
48
  import { refreshConnections } from "./mcp-connect.js";
49
49
  import { followDiscord } from "./discord.js";
50
50
  import { followTeams } from "./teams.js";
@@ -156,8 +156,8 @@ import { bridgePass, clearBotToken, createTransport, followBridge, hashPairingCo
156
156
  import { scanRepo } from "./capscan.js";
157
157
  import { computeGaps, describeCapability } from "./gaps.js";
158
158
  import { ask as promptAsk, askHidden as promptHidden, confirm as promptConfirm, interactive as promptInteractive, underAgent } from "./prompt.js";
159
- import { runMateCli, answerContextLines } from "./mate-cli.js";
160
- import { confirmCoordinatorProposal, dismissCoordinatorProposal } from "./mate-doors.js";
159
+ import { runLeadCli, answerContextLines } from "./lead-cli.js";
160
+ import { confirmCoordinatorProposal, dismissCoordinatorProposal } from "./lead-doors.js";
161
161
  import { verifyApproverByPassword as approverByPassword } from "./principal.js";
162
162
  import { activeRemote, REMOTE_MESSAGES, REMOTE_REFUSED_FLAGS, REMOTE_SCOPES, RemoteRefusal, remoteAllows, remoteLensOf, remoteProjectOf, remoteRowOf, remoteSecret, reproveRemote, withRemote } from "./operate-remote.js";
163
163
  import { projectAuthority } from "./project-access.js";
@@ -256,9 +256,9 @@ export const OPERATE_HELP = `toolroll — operating the queue
256
256
  toolroll integrations [--json] which integrations work: Connected, Not set up or Broken, and what to do (--saved: no new checks)
257
257
  toolroll monitoring where the audit stream and traces go, and how each destination is doing
258
258
  toolroll check-progress <run> current or final approved check progress
259
- toolroll spend [--month YYYY-MM] [--csv] what agent work cost, by project, person, teammate and model
259
+ toolroll spend [--month YYYY-MM] [--csv] what agent work cost, by project, person, subagent and model
260
260
  toolroll export --out <path> [--zip] everything Toolroll knows, in a folder or .zip (no secrets)
261
- toolroll budget list|set|remove monthly budgets (--all | --project <p> | --person <name> | --teammate <id>) --usd <n> [--alerts-only]
261
+ toolroll budget list|set|remove monthly budgets (--all | --project <p> | --person <name> | --subagent <id>) --usd <n> [--alerts-only]
262
262
  toolroll notifications [quiet|all] how chats reach you: only when you're needed (the default), or every step
263
263
  toolroll notifications digest <HH:MM>|off one evening message: what finished, what waits, what failed
264
264
  toolroll notifications mute|unmute --repo <p> no pings for a project; the console and digest keep it
@@ -370,7 +370,7 @@ External trackers — build what a tracker nominates, under local approvals
370
370
 
371
371
  toolroll claim <id> --runner <name> [--ttl <seconds>]
372
372
  toolroll heartbeat <lease> still working; extends the lease
373
- toolroll release <lease> done with it; fenced if superseded
373
+ toolroll worker release <lease> done with it; fenced if superseded
374
374
  toolroll reap release every lease that ran out
375
375
 
376
376
  toolroll tick --runner <name> --token <t> --repo <path>
@@ -477,7 +477,7 @@ Agents — which provider and model each phase runs on
477
477
  toolroll chat --as <you> [--repo <path>…] [--say "…"] [--end]
478
478
  [--ceiling-usd <n>] [--json] (password at the prompt;
479
479
  --token <t> only for scripts — it lands in shell history)
480
- the mate: one conversation across your projects, the same thread the
480
+ your lead: one conversation across your projects, the same thread the
481
481
  console shows; the password mints a spending session once; it reads
482
482
  and proposes, you confirm cards (confirm N / dismiss N / open N;
483
483
  confirm N yes for an irreversible answer)
@@ -625,7 +625,7 @@ export const OPERATE_VALUE_FLAGS = new Set([
625
625
  // v103: the ledger chain and evidence packs.
626
626
  "checkpoint", "out", "from", "to",
627
627
  // v105: spend and budgets.
628
- "month", "usd", "person", "teammate",
628
+ "month", "usd", "person", "subagent", "teammate",
629
629
  // Sprint 8: the organisation policy.
630
630
  "providers", "models", "tools",
631
631
  "project-root", "schedule", "ceiling", "require",
@@ -869,7 +869,7 @@ async function dispatchOn(command, positional, flags, repoList, base, options, e
869
869
  ...(options.flowFetch === undefined ? {} : { flowFetch: options.flowFetch }),
870
870
  ...(options.integrationIo === undefined ? {} : { integrationIo: options.integrationIo }),
871
871
  ...(options.shouldStop === undefined ? {} : { shouldStop: options.shouldStop }),
872
- ...(options.mateSeams === undefined ? {} : { mateSeams: options.mateSeams }),
872
+ ...(options.leadSeams === undefined ? {} : { leadSeams: options.leadSeams }),
873
873
  ...(options.waitSleep === undefined ? {} : { waitSleep: options.waitSleep }),
874
874
  ...(options.releaseIo === undefined ? {} : { releaseIo: options.releaseIo }),
875
875
  ...(options.installBin === undefined ? {} : { installBin: options.installBin }),
@@ -1023,7 +1023,7 @@ function telegramCanDeliver(context, token) {
1023
1023
  function telegramConversation(context, options = {}) {
1024
1024
  return {
1025
1025
  evidenceRoot: context.evidenceRoot,
1026
- ...(context.mateSeams?.subscriptionRunner === undefined ? {} : { subscriptionRunner: context.mateSeams.subscriptionRunner }),
1026
+ ...(context.leadSeams?.subscriptionRunner === undefined ? {} : { subscriptionRunner: context.leadSeams.subscriptionRunner }),
1027
1027
  // Re-read on every card and every `/task`: the same console-url chat
1028
1028
  // links use, held to an https origin, and — inside `up`, where this
1029
1029
  // process also serves the console — equal to that console's own
@@ -1143,8 +1143,10 @@ async function dispatch(command, positional, flags, context) {
1143
1143
  return claimCommand(positional, flags, context);
1144
1144
  case "heartbeat":
1145
1145
  return leaseCommand("heartbeat", positional, flags, context);
1146
- case "release":
1147
- return leaseCommand("release", positional, flags, context);
1146
+ case "worker":
1147
+ if (positional[0] === "release")
1148
+ return leaseCommand("release", positional.slice(1), flags, context);
1149
+ return fail(context.write, context.json, "worker", "usage", "`toolroll worker release <lease>` returns a lease.", EXIT.usage);
1148
1150
  case "reap":
1149
1151
  return reapCommand(context);
1150
1152
  case "runner":
@@ -1476,18 +1478,20 @@ function claimCommand(positional, flags, context) {
1476
1478
  */
1477
1479
  function leaseCommand(command, positional, flags, context) {
1478
1480
  const { store, write, json, now } = context;
1481
+ // `release` answers as the worker verb it now is (the bare `toolroll release <lease>` form is deprecated).
1482
+ const name = command === "release" ? "worker release" : command;
1479
1483
  const lease = positional[0];
1480
1484
  if (lease === undefined)
1481
- return fail(write, json, command, "usage", `which lease? \`toolroll ${command} <lease>\``, EXIT.usage);
1485
+ return fail(write, json, name, "usage", `which lease? \`toolroll ${name} <lease>\``, EXIT.usage);
1482
1486
  const ttl = readTtl(flags);
1483
1487
  if (ttl === null)
1484
- return fail(write, json, command, "usage", "--ttl takes whole seconds", EXIT.usage);
1488
+ return fail(write, json, name, "usage", "--ttl takes whole seconds", EXIT.usage);
1485
1489
  const result = command === "heartbeat" ? heartbeat(store, lease, now, ttl) : release(store, lease, now);
1486
1490
  if (!result.ok) {
1487
1491
  const message = result.reason === "fenced"
1488
1492
  ? "superseded — another runner holds this task now; stop rather than retry"
1489
1493
  : "no such lease";
1490
- return fail(write, json, command, result.reason, message, EXIT.refused);
1494
+ return fail(write, json, name, result.reason, message, EXIT.refused);
1491
1495
  }
1492
1496
  if (command === "release") {
1493
1497
  const task = store.getTask(String(refExternalId(store, result.claim.taskRef)));
@@ -1496,7 +1500,7 @@ function leaseCommand(command, positional, flags, context) {
1496
1500
  if (task !== null && task.state === "running")
1497
1501
  store.setTaskState(task.id, "queued", now);
1498
1502
  }
1499
- return succeed(write, json, command, { lease: result.claim }, () => [
1503
+ return succeed(write, json, name, { lease: result.claim }, () => [
1500
1504
  command === "heartbeat"
1501
1505
  ? `Still yours until ${result.claim.expiresAt}.`
1502
1506
  : `Released ${result.claim.leaseId}.`,
@@ -2269,25 +2273,25 @@ async function tickCommand(flags, context) {
2269
2273
  const replyPass = housekeeping?.replies ?? { read: 0, taken: 0, problem: null };
2270
2274
  const stepPass = housekeeping?.steps ?? { ran: 0, problems: [] };
2271
2275
  const flowPass = housekeeping?.flows ?? { moved: 0, filed: [], problems: [] };
2272
- // Each AI teammate's daily summary to its manager (v92), once, after 5 pm; its weekly report (v97), Monday mornings.
2276
+ // Each subagent's daily summary to its manager (v92), once, after 5 pm; its weekly report (v97), Monday mornings.
2273
2277
  if (!buildsOnly)
2274
2278
  try {
2275
- sendTeammateSummaries(store, repo, clock());
2276
- sendTeammateWeeklies(store, repo, clock());
2279
+ sendSubagentSummaries(store, repo, clock());
2280
+ sendSubagentWeeklies(store, repo, clock());
2277
2281
  }
2278
2282
  catch (error) {
2279
- flowPass.problems.push(`teammate summaries: ${error instanceof Error ? error.message : "could not send"}`);
2283
+ flowPass.problems.push(`subagent summaries: ${error instanceof Error ? error.message : "could not send"}`);
2280
2284
  }
2281
- // Undoing a teammate's tool call asked for in chat (v97): made here, as the person who asked.
2285
+ // Undoing a subagent's tool call asked for in chat (v97): made here, as the person who asked.
2282
2286
  if (!buildsOnly && context.shouldStop?.() !== true) {
2283
2287
  try {
2284
2288
  await runRequestedUndos(store, {}, clock());
2285
2289
  }
2286
2290
  catch (error) {
2287
- flowPass.problems.push(`teammate undo: ${error instanceof Error ? error.message : "could not run"}`);
2291
+ flowPass.problems.push(`subagent undo: ${error instanceof Error ? error.message : "could not run"}`);
2288
2292
  }
2289
2293
  }
2290
- // Tools connected by signing in: a sign-in that runs out within ten minutes is renewed here, so a build or a teammate's call starts with a fresh one.
2294
+ // Tools connected by signing in: a sign-in that runs out within ten minutes is renewed here, so a build or a subagent's call starts with a fresh one.
2291
2295
  if (!buildsOnly && context.shouldStop?.() !== true) {
2292
2296
  try {
2293
2297
  flowPass.problems.push(...(await refreshConnections(store, [repo], clock(), context.flowStepIo?.fetch === undefined ? {} : { fetcher: context.flowStepIo.fetch })).problems);
@@ -6046,19 +6050,19 @@ async function runWatchLoop(args) {
6046
6050
  : startTelegramFollower({ context, publicUrl: text(flags, "public-url"), signal: followController.signal, progress, label: "watch: bridge", died: { prefix: "watch: ", then: "taps wait for the next watch" } });
6047
6051
  const slackFollower = followSlack({ store, dir: dirname(context.databaseFile), signal: followController.signal,
6048
6052
  readProjects: telegramReadProjects(context), evidenceRoot: context.evidenceRoot,
6049
- ...(context.mateSeams?.subscriptionRunner ? { subscriptionRunner: context.mateSeams.subscriptionRunner } : {}),
6053
+ ...(context.leadSeams?.subscriptionRunner ? { subscriptionRunner: context.leadSeams.subscriptionRunner } : {}),
6050
6054
  origin: () => phoneOrigin(process.env, dirname(context.databaseFile), { serverOrigin: text(flags, "public-url") ?? null }),
6051
6055
  notifications: () => effectivePrimary(process.env, dirname(context.databaseFile), loadBotToken(process.env, context.telegramTokenFile) !== null).channel === "slack",
6052
6056
  }).catch(() => progress("watch: Slack stopped. Check Slack settings before reconnecting."));
6053
6057
  const teamsFollower = followTeams({ store, dir: dirname(context.databaseFile), signal: followController.signal,
6054
6058
  readProjects: telegramReadProjects(context), evidenceRoot: context.evidenceRoot,
6055
- ...(context.mateSeams?.subscriptionRunner ? { subscriptionRunner: context.mateSeams.subscriptionRunner } : {}),
6059
+ ...(context.leadSeams?.subscriptionRunner ? { subscriptionRunner: context.leadSeams.subscriptionRunner } : {}),
6056
6060
  origin: () => phoneOrigin(process.env, dirname(context.databaseFile), { serverOrigin: text(flags, "public-url") ?? null }),
6057
6061
  notifications: () => effectivePrimary(process.env, dirname(context.databaseFile), loadBotToken(process.env, context.telegramTokenFile) !== null).channel === "teams",
6058
6062
  }).catch(() => progress("watch: Teams stopped. Check Teams settings before reconnecting."));
6059
6063
  const discordFollower = followDiscord({ store, dir: dirname(context.databaseFile), signal: followController.signal,
6060
6064
  readProjects: telegramReadProjects(context), evidenceRoot: context.evidenceRoot,
6061
- ...(context.mateSeams?.subscriptionRunner ? { subscriptionRunner: context.mateSeams.subscriptionRunner } : {}),
6065
+ ...(context.leadSeams?.subscriptionRunner ? { subscriptionRunner: context.leadSeams.subscriptionRunner } : {}),
6062
6066
  origin: () => phoneOrigin(process.env, dirname(context.databaseFile), { serverOrigin: text(flags, "public-url") ?? null }),
6063
6067
  notifications: () => effectivePrimary(process.env, dirname(context.databaseFile), loadBotToken(process.env, context.telegramTokenFile) !== null).channel === "discord",
6064
6068
  }).catch(() => progress("watch: Discord stopped. Check Discord settings before reconnecting."));
@@ -9983,7 +9987,7 @@ function describeApproveFailure(reason, id) {
9983
9987
  if (reason === "requester")
9984
9988
  return `you filed ${id}, and this project needs someone else to approve it`;
9985
9989
  if (reason === "person-required")
9986
- return `${id} is protected work: a person has to approve it, not an operating mode, a schedule or an AI teammate`;
9990
+ return `${id} is protected work: a person has to approve it, not an operating mode, a schedule or a subagent`;
9987
9991
  if (reason === "second-approver")
9988
9992
  return `your approval of ${id} is recorded; it is protected work, so a second person needs to approve it (toolroll task approve ${id} as them)`;
9989
9993
  if (reason === "no-approvers") {
@@ -10100,7 +10104,7 @@ async function chatCommand(flags, context) {
10100
10104
  return fail(write, json, "chat", "usage", "Choose --follow or --no-follow.", EXIT.usage);
10101
10105
  const credentials = await askCredentials(flags, context);
10102
10106
  if (credentials === null) {
10103
- return fail(write, json, "chat", "usage", "the mate takes your name and password — `--as <you>` and the hidden prompt; `--token <t>` only where a script must (it lands in shell history)", EXIT.usage);
10107
+ return fail(write, json, "chat", "usage", "the lead takes your name and password — `--as <you>` and the hidden prompt; `--token <t>` only where a script must (it lands in shell history)", EXIT.usage);
10104
10108
  }
10105
10109
  const ceilingGiven = text(flags, "ceiling-usd");
10106
10110
  // The ceiling: the `--repo` list, or the ENROLLED registry beside the
@@ -10116,7 +10120,7 @@ async function chatCommand(flags, context) {
10116
10120
  return fail(write, json, "chat", "registry", "the enrolled-project registry could not be read — name projects with --repo", EXIT.refused);
10117
10121
  repos = loaded.repos;
10118
10122
  }
10119
- const result = await runMateCli({
10123
+ const result = await runLeadCli({
10120
10124
  store,
10121
10125
  databaseFile: context.databaseFile,
10122
10126
  write,
@@ -10127,7 +10131,7 @@ async function chatCommand(flags, context) {
10127
10131
  end: flags.has("end"),
10128
10132
  ...(flags.has("follow") || flags.has("no-follow") ? { follow: flags.has("follow") && !flags.has("no-follow") } : {}),
10129
10133
  ceilingUsd: ceilingGiven === undefined ? undefined : Number(ceilingGiven),
10130
- ...(context.mateSeams === undefined ? {} : { seams: { ...context.mateSeams, clock: context.mateSeams.clock ?? context.clock } }),
10134
+ ...(context.leadSeams === undefined ? {} : { seams: { ...context.leadSeams, clock: context.leadSeams.clock ?? context.clock } }),
10131
10135
  ...(context.evidenceRoot === undefined ? {} : { evidenceRoot: context.evidenceRoot }),
10132
10136
  // The console's own address (and its public one): links there read as "the task", "the result", "Settings → Lead".
10133
10137
  appOrigin: [loadConsoleUrl(process.env, dirname(context.databaseFile)), phoneOrigin(process.env, dirname(context.databaseFile))],
@@ -10694,7 +10698,7 @@ async function projectDeleteCommand(positional, flags, context) {
10694
10698
  `Deleted ${repo}: ${holdingsWords(done.removed)}.`, ...done.left.map(one => `Git kept ${one}.`), `The ledger records it as entry #${done.ledgerId}.`,
10695
10699
  ]);
10696
10700
  }
10697
- /** `spend [--month YYYY-MM] [--csv]` (v105): what agent work cost in a month, by project, person, teammate and model. */
10701
+ /** `spend [--month YYYY-MM] [--csv]` (v105): what agent work cost in a month, by project, person, subagent and model. */
10698
10702
  function spendCommand(flags, context) {
10699
10703
  const command = "spend";
10700
10704
  for (const name of flags.keys())
@@ -10704,7 +10708,7 @@ function spendCommand(flags, context) {
10704
10708
  if (month === null)
10705
10709
  return fail(context.write, context.json, command, "usage", "Use spend --month YYYY-MM.", EXIT.usage);
10706
10710
  const items = spendItems(context.store.handle, month.from, month.to);
10707
- const names = teammateNames(context.store.handle);
10711
+ const names = subagentNames(context.store.handle);
10708
10712
  if (flags.has("csv")) {
10709
10713
  context.write(spendCsv(items, names).replace(/^\ufeff/, "").trimEnd());
10710
10714
  return EXIT.ok;
@@ -10718,7 +10722,7 @@ function spendCommand(flags, context) {
10718
10722
  byProject.set(item.project, (byProject.get(item.project) ?? 0) + (item.microusd ?? 0));
10719
10723
  // v105: subscription work is $0; what binds it is its plan's windows, as the provider last said.
10720
10724
  const limits = context.store.providerLimits();
10721
- const windows = limitsView(limits, [], { project: repo => basename(repo), teammate: id => String(id) }, context.clock())?.tiles ?? [];
10725
+ const windows = limitsView(limits, [], { project: repo => basename(repo), subagent: id => String(id) }, context.clock())?.tiles ?? [];
10722
10726
  // Sized routing: each tier's tasks, time to a result and plan use, and what the tiers saved.
10723
10727
  const tiers = tierReport(context.store.handle, month.from, month.to, items);
10724
10728
  return succeed(context.write, context.json, command, { month: month.name, totalMicrousd: total, unpriced, items: items.length, budgets, limits, tiers: { rows: tiers.rows, saved: tiers.saved } }, () => [
@@ -10821,21 +10825,22 @@ async function budgetCommand(positional, flags, context) {
10821
10825
  const store = context.store;
10822
10826
  if (action === undefined || action === "list") {
10823
10827
  const budgets = store.monthSpend(context.clock()).budgets;
10824
- return succeed(context.write, context.json, command, { budgets }, () => budgets.length === 0 ? ["No budgets. `budget set --all|--project <path>|--person <name>|--teammate <id> --usd <n>` makes one."]
10828
+ return succeed(context.write, context.json, command, { budgets }, () => budgets.length === 0 ? ["No budgets. `budget set --all|--project <path>|--person <name>|--subagent <id> --usd <n>` makes one."]
10825
10829
  : budgets.map(one => `#${one.id} ${budgetLabel(one).replace(/'s$/, "")}: ${spendUsd(one.spentMicrousd)} of ${spendUsd(one.limitMicrousd)} this month (${one.percent}%)${one.hardStop ? ", stops API work at 100%" : ", alerts only"}`));
10826
10830
  }
10827
10831
  if (action !== "set" && action !== "remove")
10828
10832
  return fail(context.write, context.json, command, "usage", "Use budget list, budget set or budget remove.", EXIT.usage);
10829
- const allowed = new Set(["all", "project", "person", "teammate", "usd", "alerts-only", "as", "token", "token-file", "token-env", "db", "json"]);
10833
+ // --teammate: the flag's name before D5, still read this release.
10834
+ const allowed = new Set(["all", "project", "person", "subagent", "teammate", "usd", "alerts-only", "as", "token", "token-file", "token-env", "db", "json"]);
10830
10835
  for (const name of flags.keys())
10831
10836
  if (!allowed.has(name))
10832
10837
  return fail(context.write, context.json, command, "usage", `--${name} is not a budget option.`, EXIT.usage);
10833
10838
  const chosen = [flags.has("all") ? { scope: "installation", key: "*" } : null,
10834
10839
  text(flags, "project") === undefined ? null : { scope: "project", key: resolve(text(flags, "project")) },
10835
10840
  text(flags, "person") === undefined ? null : { scope: "person", key: text(flags, "person") },
10836
- text(flags, "teammate") === undefined ? null : { scope: "teammate", key: text(flags, "teammate") }].filter(one => one !== null);
10841
+ (text(flags, "subagent") ?? text(flags, "teammate")) === undefined ? null : { scope: "subagent", key: (text(flags, "subagent") ?? text(flags, "teammate")) }].filter(one => one !== null);
10837
10842
  if (chosen.length !== 1)
10838
- return fail(context.write, context.json, command, "usage", "Name one of --all, --project <path>, --person <name> or --teammate <id>.", EXIT.usage);
10843
+ return fail(context.write, context.json, command, "usage", "Name one of --all, --project <path>, --person <name> or --subagent <id>.", EXIT.usage);
10839
10844
  const target = chosen[0];
10840
10845
  const acting = await askCredentials(flags, context);
10841
10846
  const verified = acting === null ? null : authenticateApprover(store, acting.name, acting.token);
@@ -10844,9 +10849,9 @@ async function budgetCommand(positional, flags, context) {
10844
10849
  }
10845
10850
  const known = target.scope === "installation" || (target.scope === "project" ? store.knownRepos().includes(target.key)
10846
10851
  : target.scope === "person" ? store.accountFacts().some(one => one.name === target.key && one.revokedAt === null)
10847
- : store.handle.prepare("SELECT 1 FROM teammate WHERE id = ?").get(Number(target.key)) !== undefined);
10852
+ : store.handle.prepare("SELECT 1 FROM subagent WHERE id = ?").get(Number(target.key)) !== undefined);
10848
10853
  if (!known)
10849
- return fail(context.write, context.json, command, "not-found", "That isn't a project, person or teammate here.", EXIT.refused);
10854
+ return fail(context.write, context.json, command, "not-found", "That isn't a project, person or subagent here.", EXIT.refused);
10850
10855
  if (action === "remove") {
10851
10856
  const existing = store.budgets().find(one => one.scope === target.scope && one.key === target.key);
10852
10857
  if (existing === undefined || !store.removeBudget(existing.id, acting.name, context.clock()))
@@ -1,5 +1,6 @@
1
1
  import { type AuditAction } from "./remote-audit.js";
2
2
  import type { ApiTokenRow } from "./store.js";
3
+ import { type Html } from "./html.js";
3
4
  export declare const PEOPLE_AUDIT_CSS: string;
4
5
  export type PersonAuditView = {
5
6
  name: string;
@@ -11,4 +12,4 @@ export type PersonAuditView = {
11
12
  now: Date;
12
13
  };
13
14
  export declare const personHref: (name: string, more?: Record<string, string>) => string;
14
- export declare function personAuditHtml(view: PersonAuditView): string;
15
+ export declare function personAuditHtml(view: PersonAuditView): Html;
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import { basename } from "node:path";
7
7
  import { tokenStatus } from "./remote-audit.js";
8
- const e = (value) => String(value ?? "").replace(/[&<>"']/g, c => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
8
+ import { html } from "./html.js";
9
9
  const stamp = (value) => `${value.slice(0, 16).replace("T", " ")} UTC`;
10
10
  export const PEOPLE_AUDIT_CSS = `.person-audit{max-width:820px;min-width:0;overflow-wrap:anywhere}.person-audit h2{font-size:1.05rem;margin:1.25rem 0 8px}` +
11
11
  `.person-audit .rows{display:grid;border:1px solid var(--so-line);border-radius:10px;overflow:hidden}.person-audit .row-item{padding:10px 14px;border-bottom:1px solid var(--so-line);min-width:0}` +
@@ -17,22 +17,22 @@ export const PEOPLE_AUDIT_CSS = `.person-audit{max-width:820px;min-width:0;overf
17
17
  `@media(max-width:600px){.person-audit .row-item a{min-height:44px}}`;
18
18
  export const personHref = (name, more = {}) => `/people?${new URLSearchParams({ person: name, ...more }).toString()}`;
19
19
  export function personAuditHtml(view) {
20
- const tokens = view.tokens.length === 0 ? `<p class="meta">No API tokens.</p>` : `<div class="rows">${view.tokens.map(one => {
20
+ const tokens = view.tokens.length === 0 ? html `<p class="meta">No API tokens.</p>` : html `<div class="rows">${view.tokens.map(one => {
21
21
  const status = tokenStatus(one, view.now);
22
- return `<div class="row-item" data-token-name="${e(one.name)}"><p><a href="${e(personHref(view.name, { "token-name": one.name }))}">${e(one.name)}</a></p>` +
23
- `<p class="meta">${status === "active" ? "Active" : status === "expired" ? "Expired" : "Revoked"} · ${one.access === "act" ? "Can act" : "Read only"} · ` +
24
- `last used ${one.lastUsedAt === null ? "never" : e(stamp(one.lastUsedAt))}</p></div>`;
25
- }).join("")}</div>`;
26
- const filtered = view.token === null ? "" : `<p class="meta">With ${e(view.token)}</p><p><a href="${e(personHref(view.name))}">All tokens</a></p>`;
27
- const actions = view.actions.length === 0 ? `<p class="meta">No remote actions${view.token === null ? "" : " with this token"}.</p>` : `<div class="rows">${view.actions.map(actionHtml).join("")}</div>`;
28
- const older = view.nextCursor === null ? "" : `<p><a href="${e(personHref(view.name, { ...(view.token === null ? {} : { "token-name": view.token }), before: view.nextCursor }))}">Older actions</a></p>`;
29
- return `<p><a href="/people">People</a></p><h1>${e(view.name)}</h1><section class="person-audit"><h2>API tokens</h2>${tokens}` +
30
- `<h2>Remote activity</h2>${filtered}${actions}${older}</section>`;
22
+ return html `<div class="row-item" data-token-name="${one.name}"><p><a href="${personHref(view.name, { "token-name": one.name })}">${one.name}</a></p>\
23
+ <p class="meta">${status === "active" ? "Active" : status === "expired" ? "Expired" : "Revoked"} · ${one.access === "act" ? "Can act" : "Read only"} · \
24
+ last used ${one.lastUsedAt === null ? "never" : stamp(one.lastUsedAt)}</p></div>`;
25
+ })}</div>`;
26
+ const filtered = view.token === null ? "" : html `<p class="meta">With ${view.token}</p><p><a href="${personHref(view.name)}">All tokens</a></p>`;
27
+ const actions = view.actions.length === 0 ? html `<p class="meta">No remote actions${view.token === null ? "" : " with this token"}.</p>` : html `<div class="rows">${view.actions.map(actionHtml)}</div>`;
28
+ const older = view.nextCursor === null ? "" : html `<p><a href="${personHref(view.name, { ...(view.token === null ? {} : { "token-name": view.token }), before: view.nextCursor })}">Older actions</a></p>`;
29
+ return html `<p><a href="/people">People</a></p><h1>${view.name}</h1><section class="person-audit"><h2>API tokens</h2>${tokens}\
30
+ <h2>Remote activity</h2>${filtered}${actions}${older}</section>`;
31
31
  }
32
32
  function actionHtml(action) {
33
33
  const label = action.outcome === "ok" ? "OK" : action.outcome === "refused" ? "Refused" : "Error";
34
34
  const detail = [stamp(action.at), action.token ?? "unknown token", action.source === "mcp" ? "Agent (MCP)" : "CLI",
35
35
  ...(action.repo === null ? [] : [basename(action.repo)]), ...(action.reason === null ? [] : [action.reason])];
36
- return `<div class="row-item" data-action="${action.id}"><p><span class="outcome outcome-${action.outcome}">${label}</span>${e(action.name)}` +
37
- `${action.taskId === null ? "" : ` · <a href="/t/${encodeURIComponent(action.taskId)}">${e(action.taskId)}</a>`}</p><p class="meta">${detail.map(e).join(" · ")}</p></div>`;
36
+ return html `<div class="row-item" data-action="${action.id}"><p><span class="outcome outcome-${action.outcome}">${label}</span>${action.name}\
37
+ ${action.taskId === null ? "" : html ` · <a href="/t/${encodeURIComponent(action.taskId)}">${action.taskId}</a>`}</p><p class="meta">${detail.join(" · ")}</p></div>`;
38
38
  }
@@ -4,6 +4,7 @@
4
4
  * action ledger. Anyone signed in reads it; an instance operator changes it
5
5
  * with their password.
6
6
  */
7
+ import { type Html } from "./html.js";
7
8
  import type { LedgerEntry } from "./action-ledger.js";
8
9
  import { type SavedPolicy } from "./policy.js";
9
10
  export declare const POLICY_CSS: string;
@@ -14,7 +15,7 @@ export type PolicyView = {
14
15
  /** The project tools here, as a hint for the tools list. */
15
16
  toolNames: string[];
16
17
  };
17
- export declare function policyHtml(view: PolicyView, csrf: string, notice: {
18
+ export declare function policyHtml(view: PolicyView, notice: {
18
19
  said?: string | null;
19
20
  problem?: string | null;
20
- }): string;
21
+ }): Html;
package/dist/policy-ui.js CHANGED
@@ -1,6 +1,12 @@
1
+ /**
2
+ * Settings → Policy (sprint 8): the organisation policy — allowed providers,
3
+ * models and tools, and the permission ceiling — and its history from the
4
+ * action ledger. Anyone signed in reads it; an instance operator changes it
5
+ * with their password.
6
+ */
7
+ import { html, postForm } from "./html.js";
1
8
  import { whenUtc } from "./when-html.js";
2
9
  import { LEVEL_HINTS, LEVEL_NAMES, PERMISSION_LEVELS, POLICY_PROVIDERS, PROVIDER_NAMES, policyParts } from "./policy.js";
3
- const e = (value) => String(value ?? "").replace(/[&<>"']/g, c => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
4
10
  export const POLICY_CSS = `.policy{max-width:720px;min-width:0}.policy dl{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:6px 16px;margin:12px 0 0;font-size:.875rem}` +
5
11
  `.policy dt{color:var(--muted-foreground)}.policy dd{margin:0;overflow-wrap:anywhere}.policy fieldset{border:0;padding:0;margin:20px 0 0;min-width:0}` +
6
12
  `.policy legend{font-weight:600;font-size:.875rem;margin-bottom:8px}.policy legend{padding:0}.policy .providers{display:grid;grid-template-columns:repeat(auto-fill,minmax(132px,1fr));gap:0 16px}.policy .providers label{display:flex;gap:8px;align-items:center;min-height:44px;margin:0;padding:0;font-weight:400}` +
@@ -13,30 +19,18 @@ export const POLICY_CSS = `.policy{max-width:720px;min-width:0}.policy dl{displa
13
19
  function historyItem(entry) {
14
20
  const what = entry.action.replace(/^organisation policy: /, "");
15
21
  const [before, after] = (entry.detail ?? "").split(" → ");
16
- return `<li><span class="what">${e(what.charAt(0).toUpperCase() + what.slice(1))}: ${e(before ?? "")} → <strong>${e(after ?? "")}</strong></span>` +
17
- `<span class="meta">${e(entry.actor)} · ${whenUtc(entry.at)}</span></li>`;
22
+ return html `<li><span class="what">${what.charAt(0).toUpperCase() + what.slice(1)}: ${before ?? ""} → <strong>${after ?? ""}</strong></span><span class="meta">${entry.actor} · ${whenUtc(entry.at)}</span></li>`;
18
23
  }
19
- export function policyHtml(view, csrf, notice) {
20
- const note = notice.problem ? `<p class="problem" role="alert">${e(notice.problem)}</p>` : notice.said ? `<p role="status">${e(notice.said)}</p>` : "";
24
+ export function policyHtml(view, notice) {
25
+ const note = notice.problem ? html `<p class="problem" role="alert">${notice.problem}</p>` : notice.said ? html `<p role="status">${notice.said}</p>` : "";
21
26
  const parts = policyParts(view.policy);
22
- const changed = view.policy.updatedBy === null ? "" : `<p class="meta">Changed by ${e(view.policy.updatedBy)} on ${e((view.policy.updatedAt ?? "").slice(0, 10))}.</p>`;
23
- const current = `<dl><dt>Providers</dt><dd>${e(parts.providers === "any" ? "Any" : parts.providers)}</dd><dt>Models</dt><dd>${e(parts.models === "any" ? "Any" : parts.models)}</dd>` +
24
- `<dt>Tools</dt><dd>${e(parts.tools === "any" ? "Any" : parts.tools)}</dd><dt>Ceiling</dt><dd>${e(parts.ceiling)}</dd></dl>${changed}`;
25
- const history = view.history.length === 0 ? "" : `<details><summary>History (${view.history.length})</summary><ol>${view.history.map(historyItem).join("")}</ol></details>`;
27
+ const changed = view.policy.updatedBy === null ? "" : html `<p class="meta">Changed by ${view.policy.updatedBy} on ${(view.policy.updatedAt ?? "").slice(0, 10)}.</p>`;
28
+ const current = html `<dl><dt>Providers</dt><dd>${parts.providers === "any" ? "Any" : parts.providers}</dd><dt>Models</dt><dd>${parts.models === "any" ? "Any" : parts.models}</dd><dt>Tools</dt><dd>${parts.tools === "any" ? "Any" : parts.tools}</dd><dt>Ceiling</dt><dd>${parts.ceiling}</dd></dl>${changed}`;
29
+ const history = view.history.length === 0 ? "" : html `<details><summary>History (${view.history.length})</summary><ol>${view.history.map(historyItem)}</ol></details>`;
26
30
  if (!view.canChange)
27
- return `<section class="policy">${note}${current}<p class="meta">An instance operator sets the policy.</p>${history}</section>`;
28
- const providers = POLICY_PROVIDERS.map(one => `<label><input type="checkbox" name="provider" value="${one}"${view.policy.providers === null || view.policy.providers.includes(one) ? " checked" : ""}> ${PROVIDER_NAMES[one]}</label>`).join("");
29
- const levels = [...PERMISSION_LEVELS].reverse().map(level => `<label class="choice"><input type="radio" name="ceiling" value="${level}"${view.policy.ceiling === level ? " checked" : ""}>` +
30
- `<span><strong>${LEVEL_NAMES[level]}</strong><small>${e(LEVEL_HINTS[level])}</small></span></label>`).join("");
31
+ return html `<section class="policy">${note}${current}<p class="meta">An instance operator sets the policy.</p>${history}</section>`;
32
+ const providers = POLICY_PROVIDERS.map(one => html `<label><input type="checkbox" name="provider" value="${one}"${view.policy.providers === null || view.policy.providers.includes(one) ? html ` checked` : ""}> ${PROVIDER_NAMES[one]}</label>`);
33
+ const levels = [...PERMISSION_LEVELS].reverse().map(level => html `<label class="choice"><input type="radio" name="ceiling" value="${level}"${view.policy.ceiling === level ? html ` checked` : ""}><span><strong>${LEVEL_NAMES[level]}</strong><small>${LEVEL_HINTS[level]}</small></span></label>`);
31
34
  const toolHint = view.toolNames.length === 0 ? "" : ` Tools here: ${view.toolNames.slice(0, 12).join(", ")}${view.toolNames.length > 12 ? "…" : ""}.`;
32
- return `<section class="policy">${note}${changed}` +
33
- `<form method="post" action="/settings/policy"><input type="hidden" name="csrf" value="${e(csrf)}">` +
34
- `<fieldset><legend>Providers</legend><div class="providers">${providers}</div></fieldset>` +
35
- `<fieldset><legend><label for="policy-models">Models</label></legend><textarea id="policy-models" name="models" spellcheck="false" placeholder="Any model" aria-describedby="policy-models-hint">${e((view.policy.models ?? []).join("\n"))}</textarea>` +
36
- `<span class="hint" id="policy-models-hint">One per line; end with * to allow a family. Blank allows any.</span></fieldset>` +
37
- `<fieldset><legend><label for="policy-tools">Project tools</label></legend><textarea id="policy-tools" name="tools" spellcheck="false" placeholder="Any tool" aria-describedby="policy-tools-hint">${e((view.policy.tools ?? []).join("\n"))}</textarea>` +
38
- `<span class="hint" id="policy-tools-hint">One per line. Blank allows any.${e(toolHint)}</span></fieldset>` +
39
- `<fieldset><legend>Permission ceiling</legend>${levels}</fieldset>` +
40
- `<div class="step-up"><label>Your Toolroll password<input type="password" name="password" autocomplete="current-password" required></label></div>` +
41
- `<button type="submit">Save policy</button></form>${history}</section>`;
35
+ return html `<section class="policy">${note}${changed}${postForm("/settings/policy", html `<fieldset><legend>Providers</legend><div class="providers">${providers}</div></fieldset><fieldset><legend><label for="policy-models">Models</label></legend><textarea id="policy-models" name="models" spellcheck="false" placeholder="Any model" aria-describedby="policy-models-hint">${(view.policy.models ?? []).join("\n")}</textarea><span class="hint" id="policy-models-hint">One per line; end with * to allow a family. Blank allows any.</span></fieldset><fieldset><legend><label for="policy-tools">Project tools</label></legend><textarea id="policy-tools" name="tools" spellcheck="false" placeholder="Any tool" aria-describedby="policy-tools-hint">${(view.policy.tools ?? []).join("\n")}</textarea><span class="hint" id="policy-tools-hint">One per line. Blank allows any.${toolHint}</span></fieldset><fieldset><legend>Permission ceiling</legend>${levels}</fieldset><div class="step-up"><label>Your Toolroll password<input type="password" name="password" autocomplete="current-password" required></label></div><button type="submit">Save policy</button>`)}${history}</section>`;
42
36
  }
package/dist/policy.d.ts CHANGED
@@ -15,7 +15,7 @@
15
15
  * (lowered), or, when there is none, doesn't run (refused); the words say which.
16
16
  *
17
17
  * Every road that admits work asks here: scope approval, the tick before a
18
- * run starts (and build() as the last look), the lead and project chats, teammates and flow steps.
18
+ * run starts (and build() as the last look), the lead and project chats, subagents and flow steps.
19
19
  */
20
20
  import type { Database } from "./store.js";
21
21
  import type { ExecutionProfile } from "./scope.js";
@@ -9,7 +9,7 @@ export type VerifiedApprover = {
9
9
  readonly repos: readonly string[];
10
10
  readonly ceilingDigest: string;
11
11
  };
12
- /** The mate's ceiling digest: the admitted repos IN ORDER, hashed — `rN`
12
+ /** The lead's ceiling digest: the admitted repos IN ORDER, hashed — `rN`
13
13
  * is an index, so two surfaces naming the same repos in a different order
14
14
  * hold different ceilings (slice-2 review, finding 3). */
15
15
  export declare function ceilingDigestOf(repos: readonly string[]): string;
package/dist/principal.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * The branded approver principal (mate arc, ruling 3).
3
3
  *
4
- * Every act the mate proposes and every confirmation an operator taps
4
+ * Every act the lead proposes and every confirmation an operator taps
5
5
  * runs under a principal that PROVES it was authenticated — a password
6
6
  * ceremony, or a cookie session the edge already proved (csrf, role,
7
7
  * generation) and this module re-proves against the approver row. The
@@ -15,7 +15,7 @@
15
15
  */
16
16
  import { createHash } from "node:crypto";
17
17
  import { authenticateAccount } from "./scope.js";
18
- /** The mate's ceiling digest: the admitted repos IN ORDER, hashed — `rN`
18
+ /** The lead's ceiling digest: the admitted repos IN ORDER, hashed — `rN`
19
19
  * is an index, so two surfaces naming the same repos in a different order
20
20
  * hold different ceilings (slice-2 review, finding 3). */
21
21
  export function ceilingDigestOf(repos) {
@@ -4,6 +4,7 @@
4
4
  * then read exactly what goes and confirm with their password (or a fresh
5
5
  * sign-in with the identity provider). The repository itself stays.
6
6
  */
7
+ import { type Html } from "./html.js";
7
8
  import { type ProjectHoldings } from "./project-delete.js";
8
9
  export declare const PROJECT_DELETE_CSS: string;
9
10
  export type ProjectSettingsView = {
@@ -21,9 +22,9 @@ export type ProjectSettingsView = {
21
22
  };
22
23
  };
23
24
  /** The project's page: its path, what Toolroll holds for it, and (for an instance operator) Delete project. */
24
- export declare function projectSettingsHtml(view: ProjectSettingsView, csrf: string, notice: {
25
+ export declare function projectSettingsHtml(view: ProjectSettingsView, notice: {
25
26
  said?: string | null;
26
27
  problem?: string | null;
27
- }): string;
28
+ }): Html;
28
29
  /** The second step: exactly what goes, and the password. */
29
- export declare function projectDeleteConfirmHtml(view: ProjectSettingsView, csrf: string, problem: string | null): string;
30
+ export declare function projectDeleteConfirmHtml(view: ProjectSettingsView, problem: string | null): Html;
@@ -4,8 +4,8 @@
4
4
  * then read exactly what goes and confirm with their password (or a fresh
5
5
  * sign-in with the identity provider). The repository itself stays.
6
6
  */
7
+ import { html, postForm } from "./html.js";
7
8
  import { holdingsWords } from "./project-delete.js";
8
- const e = (value) => String(value ?? "").replace(/[&<>"']/g, c => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
9
9
  export const PROJECT_DELETE_CSS = `.project-settings,.project-delete{max-width:720px;min-width:0}.project-settings .path,.project-delete .path{font-family:var(--font-mono);font-size:.8125rem;overflow-wrap:anywhere;word-break:break-all}` +
10
10
  `.project-settings details.danger-zone{margin-top:24px;border:1px solid var(--border);border-radius:10px;padding:0 14px}.project-settings details.danger-zone summary{min-height:44px;display:flex;align-items:center;cursor:pointer;font-weight:500;color:var(--danger)}` +
11
11
  `.project-settings details.danger-zone[open]{padding-bottom:14px}.project-settings form,.project-delete form{display:grid;gap:12px;margin-top:12px}.project-settings label,.project-delete label{display:grid;gap:4px;font-size:.8125rem;font-weight:500}` +
@@ -16,39 +16,32 @@ export const PROJECT_DELETE_CSS = `.project-settings,.project-delete{max-width:7
16
16
  `.project-settings .builds-at-once .builds-row{display:flex;gap:8px;align-items:stretch}.project-settings .builds-at-once input{width:5.5rem;min-height:44px;margin:0;box-sizing:border-box}.project-settings .builds-at-once button{min-height:44px;margin:0}` +
17
17
  `.project-settings .builds-at-once .meta{margin:0}.project-settings form.remove-project{margin-top:20px;gap:4px}.project-settings form.remove-project button{min-height:44px}.project-settings form.remove-project .meta{margin:0}`;
18
18
  /** Builds at once: one number, one Save. The worker's cap is said only when it lowers the number. */
19
- function buildsHtml(view, csrf) {
19
+ function buildsHtml(view) {
20
20
  const b = view.builds;
21
21
  if (b === undefined)
22
22
  return "";
23
23
  const capped = b.capacity !== null && b.capacity < b.setting ? ` Its worker runs ${b.capacity} at once, so ${b.capacity} for now.` : "";
24
24
  const now = `${b.building} building now.${capped}`;
25
25
  if (!b.canChange)
26
- return `<section class="builds-at-once"><h2>Builds at once</h2><p>Up to ${b.setting}. ${e(now)}</p></section>`;
27
- return `<section class="builds-at-once"><h2>Builds at once</h2><form method="post" action="/settings/project/concurrency">` +
28
- `<input type="hidden" name="csrf" value="${e(csrf)}"><input type="hidden" name="repo" value="${e(view.repo)}">` +
29
- `<div class="builds-row"><input type="number" name="concurrency" min="1" max="64" step="1" inputmode="numeric" value="${b.setting}" aria-label="Builds at once" aria-describedby="builds-now"><button type="submit">Save</button></div>` +
30
- `<p class="meta" id="builds-now">${e(now)}</p></form></section>`;
26
+ return html `<section class="builds-at-once"><h2>Builds at once</h2><p>Up to ${b.setting}. ${now}</p></section>`;
27
+ return html `<section class="builds-at-once"><h2>Builds at once</h2>${postForm("/settings/project/concurrency", html `<div class="builds-row"><input type="number" name="concurrency" min="1" max="64" step="1" inputmode="numeric" value="${b.setting}" aria-label="Builds at once" aria-describedby="builds-now"><button type="submit">Save</button></div><p class="meta" id="builds-now">${now}</p>`, { hidden: { repo: view.repo } })}</section>`;
31
28
  }
32
- const note = (notice) => notice.problem ? `<p class="problem" role="alert">${e(notice.problem)}</p>` : notice.said ? `<p role="status">${e(notice.said)}</p>` : "";
29
+ const note = (notice) => notice.problem ? html `<p class="problem" role="alert">${notice.problem}</p>` : notice.said ? html `<p role="status">${notice.said}</p>` : "";
33
30
  /** The project's page: its path, what Toolroll holds for it, and (for an instance operator) Delete project. */
34
- export function projectSettingsHtml(view, csrf, notice) {
35
- const held = `<p>Toolroll holds ${e(holdingsWords(view.holdings))} for <strong>${e(view.name)}</strong>.</p>`;
36
- const head = `<section class="project-settings">${note(notice)}<p class="path">${e(view.repo)}</p>${held}${buildsHtml(view, csrf)}`;
31
+ export function projectSettingsHtml(view, notice) {
32
+ const held = html `<p>Toolroll holds ${holdingsWords(view.holdings)} for <strong>${view.name}</strong>.</p>`;
33
+ const head = html `${note(notice)}<p class="path">${view.repo}</p>${held}${buildsHtml(view)}`;
37
34
  if (!view.canDelete)
38
- return `${head}<p class="meta">An instance operator can delete a project.</p></section>`;
35
+ return html `<section class="project-settings">${head}<p class="meta">An instance operator can delete a project.</p></section>`;
39
36
  const body = view.running.length > 0
40
- ? `<p class="problem">Its work is running: ${e(view.running.join(", "))}. Stop it before deleting the project.</p>`
41
- : `<p>Removes everything Toolroll holds for ${e(view.name)}. The repository and its own branches stay. There's no undo.</p>` +
42
- `<form method="post" action="/settings/project/delete"><input type="hidden" name="csrf" value="${e(csrf)}"><input type="hidden" name="repo" value="${e(view.repo)}">` +
43
- `<label><span>Type <strong>${e(view.name)}</strong> to continue</span><input name="name" autocomplete="off" autocapitalize="off" spellcheck="false" required></label>` +
44
- `<button type="submit">Continue</button></form>`;
37
+ ? html `<p class="problem">Its work is running: ${view.running.join(", ")}. Stop it before deleting the project.</p>`
38
+ : html `<p>Removes everything Toolroll holds for ${view.name}. The repository and its own branches stay. There's no undo.</p>${postForm("/settings/project/delete", html `<label><span>Type <strong>${view.name}</strong> to continue</span><input name="name" autocomplete="off" autocapitalize="off" spellcheck="false" required></label><button type="submit">Continue</button>`, { hidden: { repo: view.repo } })}`;
45
39
  // Removing is the reversible step: off the lists and the builder, everything kept.
46
- const remove = `<form class="remove-project" method="post" action="/projects/remove"><input type="hidden" name="csrf" value="${e(csrf)}"><input type="hidden" name="repo" value="${e(view.repo)}">` +
47
- `<button type="submit">Remove from Toolroll</button><p class="meta">Its tasks and results stay saved. Add it again to bring it back.</p></form>`;
48
- return `${head}${remove}<details class="danger-zone"${notice.problem ? " open" : ""}><summary>Delete project</summary>${body}</details></section>`;
40
+ const remove = postForm("/projects/remove", html `<button type="submit">Remove from Toolroll</button><p class="meta">Its tasks and results stay saved. Add it again to bring it back.</p>`, { attrs: { class: "remove-project" }, hidden: { repo: view.repo } });
41
+ return html `<section class="project-settings">${head}${remove}<details class="danger-zone"${notice.problem ? html ` open` : ""}><summary>Delete project</summary>${body}</details></section>`;
49
42
  }
50
43
  /** The second step: exactly what goes, and the password. */
51
- export function projectDeleteConfirmHtml(view, csrf, problem) {
44
+ export function projectDeleteConfirmHtml(view, problem) {
52
45
  const h = view.holdings;
53
46
  const count = (n, one, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;
54
47
  const items = [
@@ -56,13 +49,8 @@ export function projectDeleteConfirmHtml(view, csrf, problem) {
56
49
  "The checkouts and branches Toolroll made",
57
50
  ...(h.chats > 0 ? [count(h.chats, "chat")] : []),
58
51
  ...(h.flows > 0 ? [`${count(h.flows, "flow")} and ${count(h.cards, "card")}`] : []),
59
- ...(h.teammates > 0 ? [count(h.teammates, "teammate")] : []),
52
+ ...(h.subagents > 0 ? [count(h.subagents, "subagent")] : []),
60
53
  "Its budgets, settings and knowledge",
61
54
  ];
62
- return `<section class="project-delete">${problem === null ? "" : `<p class="problem" role="alert">${e(problem)}</p>`}` +
63
- `<p>This removes:</p><ul class="removes">${items.map(one => `<li>${e(one)}</li>`).join("")}</ul>` +
64
- `<p>The repository at <span class="path">${e(view.repo)}</span> and its own branches stay. The ledger keeps its history. There's no undo.</p>` +
65
- `<form method="post" action="/settings/project/delete"><input type="hidden" name="csrf" value="${e(csrf)}"><input type="hidden" name="repo" value="${e(view.repo)}"><input type="hidden" name="name" value="${e(view.name)}"><input type="hidden" name="step" value="delete">` +
66
- `<label>Your Toolroll password<input type="password" name="password" autocomplete="current-password"></label>` +
67
- `<div class="actions"><button type="submit" class="danger">Delete project</button><a href="/settings/project?repo=${e(encodeURIComponent(view.repo))}">Cancel</a></div></form></section>`;
55
+ return html `<section class="project-delete">${problem === null ? "" : html `<p class="problem" role="alert">${problem}</p>`}<p>This removes:</p><ul class="removes">${items.map(one => html `<li>${one}</li>`)}</ul><p>The repository at <span class="path">${view.repo}</span> and its own branches stay. The ledger keeps its history. There's no undo.</p>${postForm("/settings/project/delete", html `<label>Your Toolroll password<input type="password" name="password" autocomplete="current-password"></label><div class="actions"><button type="submit" class="danger">Delete project</button><a href="/settings/project?repo=${encodeURIComponent(view.repo)}">Cancel</a></div>`, { hidden: { repo: view.repo, name: view.name, step: "delete" } })}</section>`;
68
56
  }