toolroll 0.9.55 → 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 (200) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +24 -8
  3. package/dist/action-ledger.d.ts +2 -4
  4. package/dist/action-ledger.js +2 -10
  5. package/dist/approval-policy.d.ts +5 -5
  6. package/dist/approval-policy.js +4 -4
  7. package/dist/approval-rules-ui.js +2 -2
  8. package/dist/browser/workspace.js +5 -5
  9. package/dist/browser-workspace.d.ts +12 -12
  10. package/dist/budget-alerts.js +3 -3
  11. package/dist/chat-actions.d.ts +23 -18
  12. package/dist/chat-actions.js +110 -88
  13. package/dist/chat-ask.d.ts +2 -2
  14. package/dist/chat-ask.js +5 -5
  15. package/dist/chat-channel.d.ts +15 -15
  16. package/dist/chat-channel.js +20 -20
  17. package/dist/chat-controls.d.ts +0 -4
  18. package/dist/chat-controls.js +0 -1
  19. package/dist/chat-core.js +1 -1
  20. package/dist/chat-delivery-state.js +2 -2
  21. package/dist/chat-delivery.d.ts +2 -2
  22. package/dist/chat-delivery.js +27 -34
  23. package/dist/chat-flow.js +3 -3
  24. package/dist/chat-messages.d.ts +1 -1
  25. package/dist/chat-messages.js +4 -4
  26. package/dist/chat-migration.d.ts +1 -1
  27. package/dist/chat-migration.js +11 -11
  28. package/dist/chat-rooms.js +3 -3
  29. package/dist/chat-warmth.d.ts +2 -2
  30. package/dist/cli.d.ts +1 -1
  31. package/dist/cli.js +6 -7
  32. package/dist/contracts/chat-actions.d.ts +32 -27
  33. package/dist/contracts/chat-actions.js +29 -11
  34. package/dist/contracts/chat-content.d.ts +1 -1
  35. package/dist/contracts/chat-content.js +1 -1
  36. package/dist/contracts/chat-tables.js +4 -4
  37. package/dist/contracts/console-api.d.ts +6 -6
  38. package/dist/contracts/console-api.js +7 -7
  39. package/dist/contracts/flow-propose.d.ts +3 -3
  40. package/dist/contracts/flow.d.ts +132 -125
  41. package/dist/contracts/flow.js +27 -12
  42. package/dist/contracts/lead-context.d.ts +1 -1
  43. package/dist/contracts/lead-context.js +2 -2
  44. package/dist/contracts/lead-tools.d.ts +20 -18
  45. package/dist/contracts/lead-tools.js +15 -15
  46. package/dist/contracts/stage-output.d.ts +1 -1
  47. package/dist/contracts/stage-output.js +1 -1
  48. package/dist/contracts/store-json.d.ts +23 -22
  49. package/dist/contracts/store-json.js +17 -17
  50. package/dist/contracts/{teammate-turn.d.ts → subagent-turn.d.ts} +5 -5
  51. package/dist/contracts/{teammate-turn.js → subagent-turn.js} +7 -7
  52. package/dist/converse.d.ts +23 -23
  53. package/dist/converse.js +28 -28
  54. package/dist/coordinator-proposals.d.ts +2 -2
  55. package/dist/coordinator-proposals.js +1 -1
  56. package/dist/coordinator.js +1 -1
  57. package/dist/deprecations.d.ts +12 -0
  58. package/dist/deprecations.js +20 -0
  59. package/dist/desktop-update-gate.js +8 -4
  60. package/dist/desktop-update.js +6 -1
  61. package/dist/discord-chat.js +6 -6
  62. package/dist/dispatch.d.ts +1 -1
  63. package/dist/dispatch.js +1 -1
  64. package/dist/envelope.js +0 -1
  65. package/dist/export-ui.js +1 -1
  66. package/dist/export.js +7 -7
  67. package/dist/flow-engine.d.ts +2 -2
  68. package/dist/flow-engine.js +21 -21
  69. package/dist/flow-insights.d.ts +1 -1
  70. package/dist/flow-share.js +13 -8
  71. package/dist/flow-steps.d.ts +5 -5
  72. package/dist/flow-steps.js +19 -19
  73. package/dist/flow-triggers.d.ts +1 -1
  74. package/dist/flow-triggers.js +1 -1
  75. package/dist/flows-cli.js +1 -1
  76. package/dist/flows-ui.js +20 -20
  77. package/dist/flows.js +19 -19
  78. package/dist/guides.js +4 -3
  79. package/dist/invoke.js +1 -1
  80. package/dist/kits-ui.js +5 -5
  81. package/dist/kits.d.ts +12 -12
  82. package/dist/kits.js +31 -31
  83. package/dist/{mate-cli.d.ts → lead-cli.d.ts} +11 -11
  84. package/dist/{mate-cli.js → lead-cli.js} +38 -38
  85. package/dist/lead-commitments.js +4 -4
  86. package/dist/lead-context.d.ts +2 -2
  87. package/dist/lead-context.js +9 -9
  88. package/dist/{mate-contract.d.ts → lead-contract.d.ts} +3 -3
  89. package/dist/{mate-contract.js → lead-contract.js} +7 -6
  90. package/dist/{mate-doors.d.ts → lead-doors.d.ts} +6 -6
  91. package/dist/{mate-doors.js → lead-doors.js} +24 -24
  92. package/dist/lead-follow.d.ts +5 -5
  93. package/dist/lead-follow.js +9 -9
  94. package/dist/lead-people.d.ts +5 -5
  95. package/dist/lead-people.js +14 -14
  96. package/dist/{mate-progress.d.ts → lead-progress.d.ts} +9 -9
  97. package/dist/{mate-progress.js → lead-progress.js} +3 -3
  98. package/dist/{mate-tools.d.ts → lead-tools.d.ts} +19 -19
  99. package/dist/{mate-tools.js → lead-tools.js} +109 -101
  100. package/dist/{mate.d.ts → lead.d.ts} +27 -27
  101. package/dist/{mate.js → lead.js} +102 -102
  102. package/dist/limits-ui.d.ts +1 -1
  103. package/dist/limits-ui.js +1 -1
  104. package/dist/mcp-connect.d.ts +1 -1
  105. package/dist/mcp-connect.js +3 -3
  106. package/dist/mcp-core.js +1 -1
  107. package/dist/memory-pass.d.ts +2 -2
  108. package/dist/memory-pass.js +7 -7
  109. package/dist/notification-rows.d.ts +2 -2
  110. package/dist/operate.d.ts +3 -3
  111. package/dist/operate.js +35 -34
  112. package/dist/policy.d.ts +1 -1
  113. package/dist/principal.d.ts +1 -1
  114. package/dist/principal.js +2 -2
  115. package/dist/project-delete-ui.js +1 -1
  116. package/dist/project-delete.d.ts +1 -1
  117. package/dist/project-delete.js +26 -26
  118. package/dist/project-memory.js +1 -1
  119. package/dist/project-tools.d.ts +1 -1
  120. package/dist/project-tools.js +1 -1
  121. package/dist/recipe-ui.js +1 -1
  122. package/dist/retention.js +4 -4
  123. package/dist/scope.d.ts +2 -2
  124. package/dist/scout-report.d.ts +1 -1
  125. package/dist/scout-report.js +1 -1
  126. package/dist/serve.js +40 -45
  127. package/dist/server/chat.d.ts +4 -4
  128. package/dist/server/chat.js +97 -97
  129. package/dist/server/chrome.js +3 -3
  130. package/dist/server/flows.js +69 -62
  131. package/dist/server/guards.js +3 -3
  132. package/dist/server/http.d.ts +3 -3
  133. package/dist/server/http.js +1 -1
  134. package/dist/server/live.js +6 -6
  135. package/dist/server/people-tokens.js +1 -1
  136. package/dist/server/remote.js +12 -10
  137. package/dist/server/render-chat.d.ts +30 -30
  138. package/dist/server/render-chat.js +33 -33
  139. package/dist/server/render-pages.js +2 -2
  140. package/dist/server/render-settings.d.ts +7 -0
  141. package/dist/server/render-settings.js +3 -0
  142. package/dist/server/route-table.js +9 -7
  143. package/dist/server/runtime.d.ts +13 -13
  144. package/dist/server/settings.js +14 -13
  145. package/dist/server/tasks.js +6 -5
  146. package/dist/setup-guide.d.ts +2 -2
  147. package/dist/slack-chat.d.ts +2 -2
  148. package/dist/slack-chat.js +6 -6
  149. package/dist/spend-ui.d.ts +3 -3
  150. package/dist/spend-ui.js +9 -9
  151. package/dist/spend.d.ts +14 -14
  152. package/dist/spend.js +31 -31
  153. package/dist/store-baseline.d.ts +19 -0
  154. package/dist/store-baseline.js +2873 -0
  155. package/dist/store.d.ts +197 -249
  156. package/dist/store.js +747 -3084
  157. package/dist/subagent-admin.d.ts +53 -0
  158. package/dist/{teammate-admin.js → subagent-admin.js} +37 -37
  159. package/dist/{teammate-desk.d.ts → subagent-desk.d.ts} +22 -23
  160. package/dist/{teammate-desk.js → subagent-desk.js} +26 -35
  161. package/dist/{teammate-memory.d.ts → subagent-memory.d.ts} +17 -17
  162. package/dist/{teammate-memory.js → subagent-memory.js} +41 -41
  163. package/dist/{teammate-question.d.ts → subagent-question.d.ts} +6 -6
  164. package/dist/{teammate-question.js → subagent-question.js} +17 -17
  165. package/dist/{teammate-tools.d.ts → subagent-tools.d.ts} +18 -18
  166. package/dist/{teammate-tools.js → subagent-tools.js} +36 -36
  167. package/dist/{teammate-week.d.ts → subagent-week.d.ts} +10 -10
  168. package/dist/{teammate-week.js → subagent-week.js} +23 -23
  169. package/dist/{teammate-work.d.ts → subagent-work.d.ts} +21 -21
  170. package/dist/{teammate-work.js → subagent-work.js} +50 -50
  171. package/dist/subagents-ui.d.ts +22 -0
  172. package/dist/{teammates-ui.js → subagents-ui.js} +45 -45
  173. package/dist/{teammates.d.ts → subagents.d.ts} +11 -11
  174. package/dist/{teammates.js → subagents.js} +21 -21
  175. package/dist/subscription-chat.d.ts +13 -13
  176. package/dist/subscription-chat.js +19 -19
  177. package/dist/surface.d.ts +19 -2
  178. package/dist/surface.js +7 -31
  179. package/dist/team-cli.d.ts +1 -0
  180. package/dist/team-cli.js +14 -2
  181. package/dist/team-leads.js +13 -13
  182. package/dist/team-runtime.d.ts +2 -2
  183. package/dist/team-runtime.js +8 -8
  184. package/dist/teams-chat.js +6 -6
  185. package/dist/telegram-flow.d.ts +3 -3
  186. package/dist/telegram-flow.js +3 -3
  187. package/dist/{telegram-mate.d.ts → telegram-lead.d.ts} +5 -5
  188. package/dist/{telegram-mate.js → telegram-lead.js} +27 -27
  189. package/dist/telegram-status.js +1 -1
  190. package/dist/telegram-team.d.ts +1 -1
  191. package/dist/telegram-team.js +5 -5
  192. package/dist/telegram.d.ts +1 -1
  193. package/dist/telegram.js +9 -30
  194. package/dist/text-limits.d.ts +18 -18
  195. package/dist/text-limits.js +18 -18
  196. package/dist/toolroll-update.d.ts +8 -0
  197. package/dist/toolroll-update.js +45 -8
  198. package/package.json +1 -2
  199. package/dist/teammate-admin.d.ts +0 -53
  200. package/dist/teammates-ui.d.ts +0 -21
@@ -12,15 +12,15 @@ import { isSubscriptionChatProvider } from "../converse.js";
12
12
  import {} from "../dispatch.js";
13
13
  import { firstTaskJourney, START_COMMAND } from "../first-run.js";
14
14
  import { FLOW_HREF } from "../flow-engine.js";
15
- import { PROPOSAL_WAIT_REASON, proposalActGate } from "../mate-doors.js";
16
- import {} from "../mate-progress.js";
17
- import { MATE_MESSAGE_MAX_CHARS } from "../mate.js";
15
+ import { PROPOSAL_WAIT_REASON, proposalActGate } from "../lead-doors.js";
16
+ import {} from "../lead-progress.js";
17
+ import { LEAD_MESSAGE_MAX_CHARS } from "../lead.js";
18
18
  import {} from "../needs-you.js";
19
19
  import { projectName } from "../project.js";
20
20
  import { replyHtmlInline, shapeReply } from "../reply-shape.js";
21
21
  import { RESULT_REVIEW_SCRIPT } from "../result-review.js";
22
22
  import {} from "../scope.js";
23
- import { MATE_ASK_OTHER } from "../store.js";
23
+ import { LEAD_ASK_OTHER } from "../store.js";
24
24
  import {} from "../task-control.js";
25
25
  import { assignmentStageOf, stageOfCode } from "../task-status.js";
26
26
  import {} from "../team-chat-authorization.js";
@@ -207,7 +207,7 @@ export function chatFleetOverview(snapshot, projects, interactive) {
207
207
  : html `<a href="/board?scope=all" class="chat-overview-link">open board</a>`}</div><div class="chat-overview-stats"><a href="/work?view=needs-you" class="chat-overview-stat attention"><b>${needsYou}${snapshot.attentionCount?.saturated ? "+" : ""}</b><span>need you</span></a><a href="/board?scope=all" class="chat-overview-stat live"><b>${running}</b><span>building</span></a><a href="/board?scope=all&amp;view=order" class="chat-overview-stat"><b>${queued}</b><span>queued</span></a><a href="/work" class="chat-overview-stat"><b>${done}</b><span>finished today</span></a></div>${rows.length === 0 ? html `<p class="chat-overview-clear">${needsYou === 0 && running === 0 ? html `<span class="dot dot-ok"></span>No tasks are waiting and no builds are running.` : html `<a href="/work">Open Tasks to inspect current work.</a>`}</p>` : html `<div class="chat-overview-items">${rows}</div>`}${completedWorkHtml(snapshot, projects.map(project => project.label), snapshot.assignmentStates)}${saturated ? html `<p class="meta chat-overview-note">Some items aren’t shown. Open Work to see more.</p>` : ""}</section>`;
208
208
  }
209
209
  /**
210
- * The mate's project rail: one bounded pulse per admitted project, plus
210
+ * The lead's project rail: one bounded pulse per admitted project, plus
211
211
  * two roads that preserve the plane's contracts. "ask" sends the stable
212
212
  * rN alias the model already sees; "board" uses the existing POST switch
213
213
  * instead of smuggling a project change through a GET parameter.
@@ -234,7 +234,7 @@ export function chatProjectRail(projects, inert) {
234
234
  return html `<aside class="chat-projects" id="chat-project-panel" aria-label="projects in this conversation"><div class="chat-projects-head"><h2>Projects</h2><span class="badge">${projects.length}</span><button type="button" class="chat-project-close quiet" aria-label="close projects">×</button></div><div class="chat-project-list">${rows}</div></aside>`;
235
235
  }
236
236
  /** Spend-authorized one-click questions: ordinary /chat posts, not a new door. */
237
- export function matePromptStarters(focus = null) {
237
+ export function leadPromptStarters(focus = null) {
238
238
  const prompts = focus === null
239
239
  ? [
240
240
  ["brief me", "Brief me on what needs my attention, what is building, and the highest-leverage next action across every project."],
@@ -380,8 +380,8 @@ export function chatPage(chrome, data) {
380
380
  if (!data.canManage) {
381
381
  parts.push(html `<div class="card chat-readonly"><strong>Read-only view</strong><p class="meta">An approver can start the unified conversation and confirm its proposed actions. You can still open every live card and project board here.</p></div>`);
382
382
  }
383
- if (data.canManage && data.mateMint !== undefined)
384
- parts.push(data.mateMint);
383
+ if (data.canManage && data.leadMint !== undefined)
384
+ parts.push(data.leadMint);
385
385
  if (data.canManage && data.coordinatorProposals !== undefined)
386
386
  parts.push(data.coordinatorProposals);
387
387
  parts.push(data.focusTask === null && !data.catchUp ? html `<details class="chat-fleet-context">${chatOverviewSummaryHtml(data.projects, data.fleetSnapshot?.attentionCount)}${chatFleetOverview(data.fleetSnapshot, data.projects, false)}</details>` : "", chatLimitsHtml({ provider: config.provider, model: config.model, turnsToday: data.turnsToday, dailyTurns: config.dailyTurns, subscription, weekly: subscription ? null : { spent: data.weeklySpent, ceiling: config.weeklyCeilingMicrousd } }));
@@ -437,7 +437,7 @@ export function chatAckPage(chrome, turn, nonce) {
437
437
  ], "\n"), { chrome });
438
438
  }
439
439
  /** The card that starts a conversation: the one password ceremony (mate arc §1). */
440
- export function mateMintCard(enabled, returnTo = "/chat") {
440
+ export function leadMintCard(enabled, returnTo = "/chat") {
441
441
  const subscription = enabled.billing === "subscription";
442
442
  return html `<div class="card mate-mint" id="latest">\n<p><strong>Start a conversation</strong></p>\n${postForm("/chat/mate/mint", joinHtml([
443
443
  html `<div class="mate-terms">`,
@@ -617,7 +617,7 @@ export function proposalCardParts(view, inert, decision, returnTo = null) {
617
617
  const options = decision === null
618
618
  ? html `<p class="meta">The decision is gone</p>`
619
619
  : html `<ul class="answer-options">${decision.options.map(one => html `<li${one.id === pick ? html ` class="picked"` : ""}><strong>${one.label}</strong>${one.reversible ? "" : html ` <span class="badge">Irreversible</span>`}${one.id === decision.recommendation ? html ` <span class="meta">— the builder recommends this</span>` : ""}${one.id === pick ? html ` <span class="meta">— proposed</span>` : ""}<p class="meta">${one.consequence}</p></li>`)}</ul>`;
620
- what = html `<h3>Answer <a href="/d/${decisionId}">decision #${decisionId}</a> on <a href="${taskHref(task)}">${task}</a></h3>${decision === null ? "" : html `<p class="proposal-summary">${decision.question}</p>`}${options}<div class="proposal-rationale"><span class="eyebrow">proposed answer</span><strong>${text("optionLabel")}</strong><p>${text("rationale")}</p></div><p class="meta proposal-disclosure">${payload["readConsequences"] === true ? `${view.by.mate ? "The mate" : "The coordinator"} read every consequence but not the builder's recommendation` : `${view.by.mate ? "The mate" : "The coordinator"} did not read the consequences`}. You see both here${decision !== null && decision.state !== "open" ? " · this decision is no longer open" : ""}.</p>`;
620
+ what = html `<h3>Answer <a href="/d/${decisionId}">decision #${decisionId}</a> on <a href="${taskHref(task)}">${task}</a></h3>${decision === null ? "" : html `<p class="proposal-summary">${decision.question}</p>`}${options}<div class="proposal-rationale"><span class="eyebrow">proposed answer</span><strong>${text("optionLabel")}</strong><p>${text("rationale")}</p></div><p class="meta proposal-disclosure">${payload["readConsequences"] === true ? `${view.by.mate ? "The lead" : "The coordinator"} read every consequence but not the builder's recommendation` : `${view.by.mate ? "The lead" : "The coordinator"} did not read the consequences`}. You see both here${decision !== null && decision.state !== "open" ? " · this decision is no longer open" : ""}.</p>`;
621
621
  }
622
622
  else {
623
623
  what = html `<h3>Cancel <a href="${taskHref(task)}">${task}</a></h3><p class="proposal-summary">${text("reason")}</p>${facts(["project", mono(repoId)])}`;
@@ -686,10 +686,10 @@ export function proposalCardParts(view, inert, decision, returnTo = null) {
686
686
  };
687
687
  return { html: cardHtml, card };
688
688
  }
689
- export function mateProposalCard(proposal, inert, decision, returnTo = null) {
690
- return mateProposalCardParts(proposal, inert, decision, returnTo).html;
689
+ export function leadProposalCard(proposal, inert, decision, returnTo = null) {
690
+ return leadProposalCardParts(proposal, inert, decision, returnTo).html;
691
691
  }
692
- export function mateProposalCardParts(proposal, inert, decision, returnTo = null) {
692
+ export function leadProposalCardParts(proposal, inert, decision, returnTo = null) {
693
693
  return proposalCardParts({ id: proposal.id, kind: proposal.kind, payload: proposal.payload, state: proposal.state, outcome: proposal.outcome, by: { mate: true }, actionBase: "/chat/proposal" }, inert, decision, returnTo);
694
694
  }
695
695
  /** Review and inline team cards share the same server-decided controls and reasons. The team API renders these
@@ -698,7 +698,7 @@ export function teamProposalCardParts(store, actor, proposal, snapshot, csrf, de
698
698
  const gate = proposalActGate(store, actor, proposal.thread, now, provider);
699
699
  const reason = !gate.ok ? gate.said : null;
700
700
  const back = '/chat?conversation=' + encodeURIComponent(snapshot.selected.id);
701
- const parts = withFormToken(csrf, () => mateProposalCardParts(proposal, reason !== null, decision, back));
701
+ const parts = withFormToken(csrf, () => leadProposalCardParts(proposal, reason !== null, decision, back));
702
702
  if (reason !== null && reason !== PROPOSAL_WAIT_REASON && proposal.state === 'pending') {
703
703
  parts.html = replaceMarkup(parts.html, new RegExp(htmlString(html `${PROPOSAL_WAIT_REASON}`).replace(/[.*+?^${}()|[\]\\]/g, "\\$&")), () => html `${reason}`);
704
704
  parts.card = { ...parts.card, note: reason };
@@ -717,7 +717,7 @@ export function coordinatorProposalsSection(proposals, decisions, now, heading =
717
717
  export const REPLACED_THREAD_DIVIDER = "New conversation — the projects I can reach changed";
718
718
  /** The replaced thread as the React conversation renders it: words and card outcomes, no buttons. */
719
719
  export function replacedBrowserMessages(previous) {
720
- return mateBrowserMessages({ ...previous, pending: null, ask: null, asks: new Map() }, null).map(message => ({
720
+ return leadBrowserMessages({ ...previous, pending: null, ask: null, asks: new Map() }, null).map(message => ({
721
721
  ...message, cardsHtml: "",
722
722
  cards: (message.cards ?? []).map(card => ({ ...card, primary: null, dismissable: false })),
723
723
  }));
@@ -732,20 +732,20 @@ export function replacedThreadHtml(previous) {
732
732
  }
733
733
  /** The lead's question as buttons: each option sends itself as the next message (the same POST as a typed one);
734
734
  * "Something else" moves to the composer. Nothing shows while a reply is running. */
735
- export function mateAskHtml(ask, target, composer, open = true) {
735
+ export function leadAskHtml(ask, target, composer, open = true) {
736
736
  if (!open)
737
737
  return html `<div class="so-owner-ask" data-ask="${ask.turn}"><p class="so-owner-ask-question"><strong>${ask.question}</strong></p></div>`;
738
738
  const hidden = target.task !== null ? { task: target.task } : target.project !== null ? { project: target.project } : {};
739
- return html `<div class="so-owner-ask" data-ask="${ask.turn}"><p class="so-owner-ask-question"><strong>${ask.question}</strong></p><div class="so-owner-ask-options" role="group" aria-label="Answer options">${ask.options.map(option => postForm("/chat", html `<button type="submit" name="message" value="${option}" class="so-suggestion quiet">${option}</button>`, { attrs: { class: "inline" }, hidden }))}<label for="${composer}" class="so-suggestion so-owner-ask-other">${MATE_ASK_OTHER}</label></div></div>`;
739
+ return html `<div class="so-owner-ask" data-ask="${ask.turn}"><p class="so-owner-ask-question"><strong>${ask.question}</strong></p><div class="so-owner-ask-options" role="group" aria-label="Answer options">${ask.options.map(option => postForm("/chat", html `<button type="submit" name="message" value="${option}" class="so-suggestion quiet">${option}</button>`, { attrs: { class: "inline" }, hidden }))}<label for="${composer}" class="so-suggestion so-owner-ask-other">${LEAD_ASK_OTHER}</label></div></div>`;
740
740
  }
741
741
  /** The thread's messages as the React conversation renders them, each
742
742
  * with the proposal cards its turn produced; `back` is where a card's
743
743
  * confirm or dismiss returns. */
744
- export function mateBrowserMessages(rows, back, target = { task: null, project: null }) {
744
+ export function leadBrowserMessages(rows, back, target = { task: null, project: null }) {
745
745
  const open = rows.pending === null ? rows.ask?.turn ?? null : null;
746
746
  const asked = (turn) => {
747
747
  const ask = turn === null ? undefined : rows.asks?.get(turn);
748
- return ask === undefined ? "" : mateAskHtml(ask, target, target.task === null ? "lead-message" : "task-message", ask.turn === open);
748
+ return ask === undefined ? "" : leadAskHtml(ask, target, target.task === null ? "lead-message" : "task-message", ask.turn === open);
749
749
  };
750
750
  // The owner's message each reply answers: when it asked for ids, the reply keeps them.
751
751
  const askedBy = new Map();
@@ -759,7 +759,7 @@ export function mateBrowserMessages(rows, back, target = { task: null, project:
759
759
  ...(() => {
760
760
  // Under the lead's message only, as the server-rendered thread does: the person's message shares the turn.
761
761
  const parts = message.turn === null || message.role === 'operator' ? [] : rows.proposals.filter(one => one.turn === message.turn)
762
- .map(one => mateProposalCardParts(one, rows.pending !== null, rows.decisions.get(typeof one.payload['decision'] === 'number' ? one.payload['decision'] : -1) ?? null, back));
762
+ .map(one => leadProposalCardParts(one, rows.pending !== null, rows.decisions.get(typeof one.payload['decision'] === 'number' ? one.payload['decision'] : -1) ?? null, back));
763
763
  return { cardsHtml: htmlString(joinHtml(parts.map(one => one.html))), cards: parts.map(one => one.card) };
764
764
  })(),
765
765
  }));
@@ -770,7 +770,7 @@ export function mateBrowserMessages(rows, back, target = { task: null, project:
770
770
  * decisions the cards name, and the task lens's own facts — and nothing
771
771
  * that merely ticks: no relative age, no freshly minted nonce, no csrf.
772
772
  * Equal versions mean equal fragments; the poll fetches nothing else. */
773
- export function mateChatVersion(rows) {
773
+ export function leadChatVersion(rows) {
774
774
  const focus = rows.focusTask;
775
775
  const facts = [
776
776
  rows.messages.map(one => [one.id, one.role, one.turn]),
@@ -805,7 +805,7 @@ export function mateChatVersion(rows) {
805
805
  * populated thread. Every child carries a stable `data-key` so the page
806
806
  * keeps an unchanged node (its open disclosures, its focus) and replaces
807
807
  * only what the server rendered differently. The composer is never here. */
808
- export function mateThreadHtml(data) {
808
+ export function leadThreadHtml(data) {
809
809
  const returnTo = data.focusTask === null ? "/chat" : taskChatHref(data.focusTask.id);
810
810
  const parts = [];
811
811
  if (data.problem !== null)
@@ -834,7 +834,7 @@ export function mateThreadHtml(data) {
834
834
  }
835
835
  const cards = message.turn === null ? [] : (byTurn.get(message.turn) ?? []);
836
836
  const ask = message.turn === null ? undefined : data.asks?.get(message.turn);
837
- thread.push(html `<div class="msg mate" data-message-role="assistant" data-key="m${message.id}"${message.id === latestReply ? html ` id="latest"` : ""}>${renderChatText(message.text, asked)}${ask === undefined ? "" : mateAskHtml(ask, { task: data.focusTask?.id ?? null, project: data.chatProject ?? null }, "chat-message", data.pending === null && data.ask?.turn === ask.turn)}${cards.map(one => mateProposalCard(one, inert, data.decisions.get(typeof one.payload["decision"] === "number" ? one.payload["decision"] : -1) ?? null, data.focusTask === null ? null : returnTo))}<div class="chat-message-foot">${chatActivity(message.activity)}<time datetime="${message.createdAt}">${relativeAge(message.createdAt, data.now)}</time></div></div>`);
837
+ thread.push(html `<div class="msg mate" data-message-role="assistant" data-key="m${message.id}"${message.id === latestReply ? html ` id="latest"` : ""}>${renderChatText(message.text, asked)}${ask === undefined ? "" : leadAskHtml(ask, { task: data.focusTask?.id ?? null, project: data.chatProject ?? null }, "chat-message", data.pending === null && data.ask?.turn === ask.turn)}${cards.map(one => leadProposalCard(one, inert, data.decisions.get(typeof one.payload["decision"] === "number" ? one.payload["decision"] : -1) ?? null, data.focusTask === null ? null : returnTo))}<div class="chat-message-foot">${chatActivity(message.activity)}<time datetime="${message.createdAt}">${relativeAge(message.createdAt, data.now)}</time></div></div>`);
838
838
  }
839
839
  parts.push(html `<div class="thread" data-key="thread" data-chat-list>\n${joinHtml(thread, "\n")}\n</div>`);
840
840
  if (data.pending !== null) {
@@ -850,10 +850,10 @@ export function mateThreadHtml(data) {
850
850
  }
851
851
  /** What follows the composer (package 2): the starters of an EMPTY thread
852
852
  * sit under the box; once the first message lands they move above it. */
853
- export function mateAfterComposerHtml(data) {
854
- return html `<div id="chat-after-composer" data-chat-region="after">${data.messages.length === 0 && data.pending === null ? matePromptStarters(data.focusTask) : ""}</div>`;
853
+ export function leadAfterComposerHtml(data) {
854
+ return html `<div id="chat-after-composer" data-chat-region="after">${data.messages.length === 0 && data.pending === null ? leadPromptStarters(data.focusTask) : ""}</div>`;
855
855
  }
856
- export function matePage(chrome, data) {
856
+ export function leadPage(chrome, data) {
857
857
  const subscription = isSubscriptionChatProvider(data.config.provider);
858
858
  const chatProject = data.focusTask === null ? data.chatProject ?? null : null;
859
859
  const returnTo = data.focusTask !== null ? taskChatHref(data.focusTask.id) : chatProject !== null ? projectChatHref(chatProject) : "/chat";
@@ -888,16 +888,16 @@ export function matePage(chrome, data) {
888
888
  const lastMessage = data.messages.at(-1);
889
889
  const latestReply = data.pending === null && lastMessage?.role === "assistant" ? lastMessage.id : null;
890
890
  conversation.push(replacedThreadHtml(data.previous));
891
- conversation.push(mateThreadHtml({ ...data, problem: null, chatProject }));
891
+ conversation.push(leadThreadHtml({ ...data, problem: null, chatProject }));
892
892
  conversation.push(
893
893
  // The New update action (package 2): hidden until a live update lands
894
894
  // while the reader is above the latest message; a real button, so the
895
895
  // keyboard reaches it. Only this act moves the reader.
896
896
  html `<div class="chat-new-update-holder"><button type="button" class="chat-new-update" id="chat-new-update" hidden>New update ↓</button></div>`, postForm("/chat", joinHtml([
897
- html `<label>Message<textarea id="chat-message" name="message" rows="1" maxlength="${MATE_MESSAGE_MAX_CHARS}" placeholder="${data.focusTask !== null ? "Ask about this task…" : chatProject !== null ? `Ask about ${projectName(chatProject)}…` : "Describe what you want done…"}"></textarea></label>`,
897
+ html `<label>Message<textarea id="chat-message" name="message" rows="1" maxlength="${LEAD_MESSAGE_MAX_CHARS}" placeholder="${data.focusTask !== null ? "Ask about this task…" : chatProject !== null ? `Ask about ${projectName(chatProject)}…` : "Describe what you want done…"}"></textarea></label>`,
898
898
  html `<button type="submit" aria-label="${data.pending === null ? "send message" : "wait for the current reply before sending"}"${data.pending === null ? "" : html ` disabled`}>Send</button>`,
899
899
  ], "\n"), {
900
- attrs: { class: "card composer", id: latestReply === null && data.pending === null ? "latest" : "chat-composer", "aria-label": "message the mate", "data-chat-session": data.session.id, "data-chat-task": data.focusTask?.id ?? "", "data-chat-user": data.session.approver, "data-chat-busy": data.pending === null ? "0" : "1", "data-chat-version": mateChatVersion(data), "data-chat-approval": data.focusTask?.approval?.digest ?? "" },
900
+ attrs: { class: "card composer", id: latestReply === null && data.pending === null ? "latest" : "chat-composer", "aria-label": "message the lead", "data-chat-session": data.session.id, "data-chat-task": data.focusTask?.id ?? "", "data-chat-user": data.session.approver, "data-chat-busy": data.pending === null ? "0" : "1", "data-chat-version": leadChatVersion(data), "data-chat-approval": data.focusTask?.approval?.digest ?? "" },
901
901
  hidden: { request: randomBytes(16).toString("hex"), "request-session": data.session.id, task: data.focusTask?.id, project: chatProject, result: data.resultRunId },
902
902
  }),
903
903
  // Concise pass (2026-09-13): the status line speaks only when there is
@@ -906,13 +906,13 @@ export function matePage(chrome, data) {
906
906
  html `<p class="meta composer-hint" id="chat-connection" role="status" aria-live="polite">${data.pending === null ? "" : "Reply in progress. You can draft your next message or come back later."}</p>`,
907
907
  // The explicit reconnection (package 2): shown only once the session
908
908
  // or sign-in changed under this page; it reloads on the reader's act.
909
- html `<p class="meta composer-hint" id="chat-reconnect" hidden><button type="button" class="quiet">Reconnect</button></p>`, mateAfterComposerHtml(data), controlsHtml);
909
+ html `<p class="meta composer-hint" id="chat-reconnect" hidden><button type="button" class="quiet">Reconnect</button></p>`, leadAfterComposerHtml(data), controlsHtml);
910
910
  return screen("chat", chatWorkspace(joinHtml(conversation, "\n"), data.projects, false, data.focusTask, data.resultPanel ?? null), { chrome, functional: { script: CHAT_CONTINUITY_SCRIPT + CHAT_UI_SCRIPT + (data.focusTask === null ? "" : RESULT_REVIEW_SCRIPT), fetches: true },
911
911
  workspace: {
912
912
  conversation: {
913
- sessionId: data.session.id, user: data.session.approver, version: mateChatVersion(data),
914
- messages: mateBrowserMessages(data, data.focusTask === null && chatProject === null ? null : returnTo, { task: data.focusTask?.id ?? null, project: chatProject }),
915
- pendingTurnId: data.pending?.id ?? null, requestId: randomBytes(16).toString('hex'), maxChars: MATE_MESSAGE_MAX_CHARS,
913
+ sessionId: data.session.id, user: data.session.approver, version: leadChatVersion(data),
914
+ messages: leadBrowserMessages(data, data.focusTask === null && chatProject === null ? null : returnTo, { task: data.focusTask?.id ?? null, project: chatProject }),
915
+ pendingTurnId: data.pending?.id ?? null, requestId: randomBytes(16).toString('hex'), maxChars: LEAD_MESSAGE_MAX_CHARS,
916
916
  taskId: data.focusTask?.id ?? null, resultRunId: data.resultRunId ?? null, project: chatProject,
917
917
  ...(data.previous == null ? {} : { previous: { messages: replacedBrowserMessages(data.previous) } }),
918
918
  },
@@ -551,9 +551,9 @@ export function workPage(chrome, data) {
551
551
  ? html `<div class="work-empty" data-work-empty="${data.view}"><p>${data.previous ? 'There are no more tasks on this page.' : current.empty}</p>${data.view === 'all' && !data.previous ? html `<p><a class="button-link" href="${chrome.chat === true ? '/chat' : '/tasks/new'}">${chrome.chat === true ? 'Start in chat' : 'Add a task'}</a></p>` : html `<a href="${href('all')}">See all tasks →</a>`}</div>`
552
552
  : html `<div class="work-list">${data.work.items.map(rowHtml)}</div>`;
553
553
  const pages = !data.previous && data.work.nextCursor === null ? null : html `<nav class="row work-pagination" aria-label="Task pages">${data.previous ? html `<a class="button-link" href="${href(data.view)}">First page</a>` : null}${data.work.nextCursor === null ? null : html `<a class="button-link" rel="next" href="${href(data.view, data.work.nextCursor)}">Next page</a>`}</nav>`;
554
- const tools = html `<details class="work-tools"><summary>Work tools${CHEVRON_ICON}</summary><nav class="work-tools-menu">${[['/inbox', 'Inbox'], ...(chrome.code ? [['/code', 'Coding sessions']] : []), ['/board', 'Board'], ['/board?view=order', 'Order'], ['/tasks', 'Task list'], ['/recipes', 'Recipes'], ...(chrome.projectScoped ? [] : [['/workbench', 'Portfolio']]), ['/ledger', 'Action ledger']]
554
+ const tools = html `<details class="work-tools"><summary>Work tools${CHEVRON_ICON}</summary><nav class="work-tools-menu">${[['/inbox', 'Inbox'], ['/board', 'Board'], ['/board?view=order', 'Order'], ['/tasks', 'Task list'], ['/recipes', 'Recipes'], ...(chrome.projectScoped ? [] : [['/workbench', 'Portfolio']]), ['/ledger', 'Action ledger']]
555
555
  .map(([path, label]) => html `<a href="${path}">${label}</a>`)}</nav></details>`;
556
- const toolLinks = [['/inbox', 'Inbox'], ...(chrome.code ? [['/code', 'Coding sessions']] : []), ['/board', 'Board'], ['/board?view=order', 'Order'], ['/tasks', 'Task list'], ['/recipes', 'Recipes'], ...(chrome.projectScoped ? [] : [['/workbench', 'Portfolio']])];
556
+ const toolLinks = [['/inbox', 'Inbox'], ['/board', 'Board'], ['/board?view=order', 'Order'], ['/tasks', 'Task list'], ['/recipes', 'Recipes'], ...(chrome.projectScoped ? [] : [['/workbench', 'Portfolio']])];
557
557
  const view = {
558
558
  kind: 'tasks',
559
559
  tabs: WORK_VIEWS.map(one => ({ label: one.label, href: href(one.key), count: data.work.totals[one.key], active: one.key === data.view })),
@@ -44,6 +44,13 @@ export declare function leadSettingsHtml(data: {
44
44
  when: string;
45
45
  until: string;
46
46
  }[];
47
+ /** D5: the lead's subagents in the projects this person can use: name and role, project, and whether it's paused. */
48
+ subagents?: {
49
+ id: number;
50
+ label: string;
51
+ project: string;
52
+ paused: boolean;
53
+ }[];
47
54
  }): Html;
48
55
  /** The lead's full settings (Settings → Lead → Advanced): provider, model, limits and a direct API key. */
49
56
  export declare function leadConfigForm(current: import("../store.js").ChatConfig | null, data: LeadFormFacts): Html;
@@ -53,6 +53,9 @@ export function leadSettingsHtml(data) {
53
53
  // What the lead promised to follow up on; it reports each once, here in chat, and drops it after 7 days.
54
54
  (data.promises ?? []).length === 0 ? "" : html `<section class="card lead-promises" data-lead-promises><h2>Promises</h2><ul class="lead-promise-list">${data.promises.map(one => html `<li data-promise="${one.id}"><p>${one.what}</p><p class="meta">${one.when.charAt(0).toUpperCase() + one.when.slice(1)} · <span class="nowrap">until ${new Date(one.until).toLocaleDateString("en-US", { month: "short", day: "numeric", timeZone: "UTC" })}</span></p>\
55
55
  ${postForm("/settings/lead/promise/cancel", html `<button type="submit" class="secondary">Cancel</button>`, { attrs: { class: "inline" }, returnTo, hidden: { promise: one.id } })}</li>`)}</ul></section>`,
56
+ // D5: the lead's subagents, one line each; everything else about one is on its own page.
57
+ data.subagents === undefined ? "" : html `<section class="card lead-subagents" id="subagents" data-lead-subagents><h2>Subagents</h2>${data.subagents.length === 0 ? html `<p class="meta">None yet. Your lead can ask one for help once it's here.</p>`
58
+ : html `<ul class="lead-subagent-list">${data.subagents.map(one => html `<li data-subagent="${one.id}"><a href="/settings/lead/subagents/${one.id}">${one.label}</a><span class="meta">${one.project}${one.paused ? " · Paused" : ""}</span></li>`)}</ul>`}<p><a class="button-link" href="/settings/lead/subagents?add=1#add" data-add-subagent>Add subagent</a></p></section>`,
56
59
  html `<details class="lead-advanced" data-lead-advanced><summary>Advanced</summary>`,
57
60
  leadConfigForm(config, facts),
58
61
  config === null ? "" : postForm("/chat/config", html `<input type="password" name="token" placeholder="your password" autocomplete="current-password" aria-label="Your password"><button type="submit" class="secondary">Turn the lead off</button>`, { attrs: { class: "inline" }, returnTo, hidden: { off: "1" } }),
@@ -148,9 +148,11 @@ get("flows", "flows.gallery", "^\\/flows\\/new\\/[a-z-]{1,40}$", { limited: "res
148
148
  get("flows", "flows.page", "/flows", { limited: "resource", ...free });
149
149
  get("flows", "kits", "/kits");
150
150
  get("flows", "kit.page", "^\\/kits\\/[a-z-]{1,40}$", { sample: "/kits/support" });
151
- get("flows", "teammates", "/teammates", free);
152
- get("flows", "teammate.page", `^\\/teammates\\/[1-9][0-9]{0,9}$`, { ...free, sample: "/teammates/1" });
153
- get("flows", "teammate.soul-file", `^\\/teammates\\/[1-9][0-9]{0,9}\\/soul\\.md$`, { ...free, sample: "/teammates/1/soul.md" });
151
+ get("flows", "subagents", "/settings/lead/subagents", free);
152
+ // D5: subagents were teammates; old links to them land on the same pages under Settings → Lead.
153
+ get("flows", "subagents.legacy", "^\\/teammates(\\/[1-9][0-9]{0,9}(\\/soul\\.md)?)?$", { ...free, sample: "/teammates/1" });
154
+ get("flows", "subagent.page", `^\\/settings\\/lead\\/subagents\\/[1-9][0-9]{0,9}$`, { ...free, sample: "/settings/lead/subagents/1" });
155
+ get("flows", "subagent.soul-file", `^\\/settings\\/lead\\/subagents\\/[1-9][0-9]{0,9}\\/soul\\.md$`, { ...free, sample: "/settings/lead/subagents/1/soul.md" });
154
156
  get("flows", "flow.insights", `^\\/flows\\/${FLOW}\\/insights$`, { project: "flow", limited: "resource", ...free, sample: "/flows/1/insights" });
155
157
  get("flows", "flow.run", `^\\/flows\\/${FLOW}\\/runs\\/${FLOW}\\/${FLOW}$`, { project: "flow", limited: "resource", ...free, sample: "/flows/1/runs/2/3" });
156
158
  get("flows", "flow.export", `^\\/flows\\/${FLOW}\\/export$`, { project: "flow", limited: "resource", ...free, sample: "/flows/1/export" });
@@ -222,11 +224,11 @@ post("settings", "settings.skills-import", "/settings/skills/import", { project:
222
224
  post("settings", "settings.skills-change", "/settings/skills/change", { project: "form", limited: "unscoped", ...approver("Sign in as an approver to manage skills.", "/settings/skills") });
223
225
  for (const one of ["setup", "sample", "github"])
224
226
  post("flows", `kit.${one}`, `^\\/kits\\/[a-z-]{1,40}\\/${one}$`, { project: "form", sample: `/kits/support/${one}`, ...approver("Sign in as an approver to set kits up.", "/kits") });
225
- const TEAMMATES = anyApprover("Sign in as an approver to look after teammates.", "/teammates");
226
- post("flows", "teammate.new", "/teammates/new", { project: "form", ...TEAMMATES });
227
+ const SUBAGENTS = anyApprover("Sign in as an approver to look after subagents.", "/settings/lead/subagents");
228
+ post("flows", "subagent.new", "/settings/lead/subagents/new", { project: "form", ...SUBAGENTS });
227
229
  for (const one of ["soul", "state", "note", "settings", "summary", "tools", "memory", "routines", "week"])
228
- post("flows", `teammate.${one}`, `^\\/teammates\\/[1-9][0-9]{0,9}\\/${one}$`, { sample: `/teammates/1/${one}`, ...TEAMMATES });
229
- post("flows", "teammate.answer", "^\\/teammates\\/questions\\/[1-9][0-9]{0,9}\\/answer$", { sample: "/teammates/questions/1/answer" });
230
+ post("flows", `subagent.${one}`, `^\\/settings\\/lead\\/subagents\\/[1-9][0-9]{0,9}\\/${one}$`, { sample: `/settings/lead/subagents/1/${one}`, ...SUBAGENTS });
231
+ post("flows", "subagent.answer", "^\\/settings\\/lead\\/subagents\\/questions\\/[1-9][0-9]{0,9}\\/answer$", { sample: "/settings/lead/subagents/questions/1/answer" });
230
232
  post("flows", "flows.gallery-create", "^\\/flows\\/new\\/[a-z-]{1,40}$", { project: "form", limited: "resource", sample: "/flows/new/triage" });
231
233
  // Flow editing answers JSON: a caller without approver standing hears it in the editor's own format.
232
234
  const CREATE_FLOWS = approver("Sign in as an approver to create flows.", "/flows");
@@ -24,8 +24,8 @@ import { type ProviderConnection } from "../provider-connection.js";
24
24
  import { type ProviderId } from "../provider.js";
25
25
  import { authenticateApprover as checkApproverPassword, type Scope } from "../scope.js";
26
26
  import { SourceBudget } from "../sign-in-guard.js";
27
- import type { CoordinatorProposal, DirectChatProviderId, MateMessage, MateProposal, MateTurn, PlanRevision, TaskFamily, TaskRef } from "../store.js";
28
- import { type Decision, type MateAsk, type MateThreadScope, type Run, type Store, type Task, type TaskState } from "../store.js";
27
+ import type { CoordinatorProposal, DirectChatProviderId, LeadMessage, LeadProposal, LeadTurn, PlanRevision, TaskFamily, TaskRef } from "../store.js";
28
+ import { type Decision, type LeadAsk, type LeadThreadScope, type Run, type Store, type Task, type TaskState } from "../store.js";
29
29
  import type { TeamChatProviderResolver } from '../team-chat-authorization.js';
30
30
  import type { TeamResponse } from '../team-contract.js';
31
31
  import { createTeamRuntime } from '../team-runtime.js';
@@ -108,24 +108,24 @@ export interface ServerRuntime {
108
108
  flowRooms: FlowRooms;
109
109
  providerHome: string;
110
110
  toolHome: string;
111
- matePrincipal: (who: Who & {
111
+ leadPrincipal: (who: Who & {
112
112
  via: "cookie";
113
113
  }) => VerifiedApprover | null;
114
- chatScopeOf: (focusTask: TaskChatFocus | null, chatProject: string | null) => MateThreadScope;
114
+ chatScopeOf: (focusTask: TaskChatFocus | null, chatProject: string | null) => LeadThreadScope;
115
115
  liveTurns: Map<number, LiveTurn>;
116
116
  /** A lead reply started on a thread: pages with that conversation open follow it (server/live.ts). */
117
117
  liveTurnStarted: (thread: number) => void;
118
- mateConversationRows: (who: Who & {
118
+ leadConversationRows: (who: Who & {
119
119
  via: "cookie";
120
120
  }, principal: VerifiedApprover, focusTask: TaskChatFocus | null, now: Date, chatProject?: string | null) => {
121
- messages: MateMessage[];
122
- proposals: MateProposal[];
121
+ messages: LeadMessage[];
122
+ proposals: LeadProposal[];
123
123
  decisions: Map<number, Decision>;
124
124
  coordinatorProposals: CoordinatorProposal[];
125
- pending: MateTurn | null;
126
- recent: MateTurn[];
127
- ask: MateAsk | null;
128
- asks: Map<number, MateAsk>;
125
+ pending: LeadTurn | null;
126
+ recent: LeadTurn[];
127
+ ask: LeadAsk | null;
128
+ asks: Map<number, LeadAsk>;
129
129
  previous: ReplacedThread | null;
130
130
  };
131
131
  demoLeadHere: () => DemoLead | null;
@@ -137,7 +137,7 @@ export interface ServerRuntime {
137
137
  teamChatProvider: TeamChatProviderResolver;
138
138
  pullRequestTargetOf: (runId: number) => string | null;
139
139
  chatEnablement: () => ChatEnablement;
140
- startMateConversation: (who: Who & {
140
+ startLeadConversation: (who: Who & {
141
141
  via: "cookie";
142
142
  }, principal: VerifiedApprover, enabled: Extract<ChatEnablement, {
143
143
  ok: true;
@@ -185,7 +185,7 @@ export interface ServerRuntime {
185
185
  status: number;
186
186
  message: string;
187
187
  } | null;
188
- mateSaid: Map<string, {
188
+ leadSaid: Map<string, {
189
189
  turn: number | null;
190
190
  message: string;
191
191
  }>;
@@ -8,8 +8,8 @@ import { skillTestResult } from "../project-skills.js";
8
8
  import { discoverTools, projectToolsOf } from "../project-tools.js";
9
9
  import { publishingOf } from '../pull-request-flow.js';
10
10
  import { skillTestFeedbackHtml } from "../skills-ui.js";
11
- import { nameOf } from "../teammate-admin.js";
12
- import { grantTool } from "../teammate-tools.js";
11
+ import { labelOf, nameOf } from "../subagent-admin.js";
12
+ import { grantTool } from "../subagent-tools.js";
13
13
  import { learningHtml } from "../workspace-ui.js";
14
14
  import { adapterPolicy } from "./route-policy.js";
15
15
  import { createHash } from "node:crypto";
@@ -100,7 +100,7 @@ import { skillsHtml, skillsScript } from "../skills-ui.js";
100
100
  import { checkSlackCredentials, clearSlackCredentials, loadSlackCredentials, saveSlackCredentials, SLACK_MANIFEST, SlackError } from "../slack-api.js";
101
101
  import { slackSettingsHtml } from "../slack-settings.js";
102
102
  import { spendCsv, spendHtml } from "../spend-ui.js";
103
- import { budgetStates, monthNamed, spendItems, usd as spendUsd, teammateNames as teammateNamesOf } from "../spend.js";
103
+ import { budgetStates, monthNamed, spendItems, usd as spendUsd, subagentNames as subagentNamesOf } from "../spend.js";
104
104
  import { removeSsoSettings, saveSsoSettings, SSO_CALLBACK, ssoChangeWords } from "../sso-settings.js";
105
105
  import { ssoSettingsHtml } from "../sso-ui.js";
106
106
  import { lastSweep, saveSweep, storageSweepOff } from "../storage-sweep.js";
@@ -129,23 +129,23 @@ export function createSettingsHandlers(runtime) {
129
129
  if (month === null)
130
130
  return refuse(response, who, 400, "Choose a month like 2026-09.", "/spend");
131
131
  const items = spendItems(store.handle, month.from, month.to);
132
- const teammates = store.teammates([...new Set([...managedRepos(), ...store.knownRepos()])]);
133
- const teammateNames = teammateNamesOf(store.handle);
132
+ const subagents = store.subagents([...new Set([...managedRepos(), ...store.knownRepos()])]);
133
+ const subagentNames = subagentNamesOf(store.handle);
134
134
  if (url.searchParams.get("format") === "csv") {
135
135
  response.writeHead(200, { "content-type": "text/csv; charset=utf-8", "content-disposition": `attachment; filename="standing-orders-spend-${month.name}.csv"`, "cache-control": "no-store", "x-content-type-options": "nosniff" });
136
- return void response.end(spendCsv(items, teammateNames));
136
+ return void response.end(spendCsv(items, subagentNames));
137
137
  }
138
138
  const shift = (by) => { const at = new Date(`${month.name}-01T00:00:00.000Z`); at.setUTCMonth(at.getUTCMonth() + by); return at.toISOString().slice(0, 7); };
139
139
  const projects = consoleProjects();
140
140
  const people = store.accountFacts().filter(one => one.revokedAt === null).map(one => one.name);
141
141
  const view = {
142
142
  month: month.name, previous: shift(-1), next: month.name === current.name ? null : shift(1),
143
- items, budgets: budgetStates(store.budgets(), items), teammateNames, csrf: who.via === "cookie" ? who.session.csrf : "",
143
+ items, budgets: budgetStates(store.budgets(), items), subagentNames, csrf: who.via === "cookie" ? who.session.csrf : "",
144
144
  targets: [
145
145
  { value: "installation:*", label: "Everything", group: "Everything" },
146
146
  ...projects.map(repo => ({ value: `project:${repo}`, label: projectName(repo), group: "Projects" })),
147
147
  ...people.map(name => ({ value: `person:${name}`, label: name, group: "People" })),
148
- ...teammates.map(one => ({ value: `teammate:${one.id}`, label: `${teammateNames.get(one.id) ?? one.handle} (${projectName(one.repo)})`, group: "Teammates" })),
148
+ ...subagents.map(one => ({ value: `subagent:${one.id}`, label: `${subagentNames.get(one.id) ?? one.handle} (${projectName(one.repo)})`, group: "Subagents" })),
149
149
  ],
150
150
  };
151
151
  return sendScreen(response, 200, screen("Spend", spendHtml(view, { said: url.searchParams.get("said"), problem: url.searchParams.get("problem") }), { chrome: chromeFor(null, "spend") }));
@@ -536,7 +536,8 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
536
536
  const promises = openCommitments(store, who.name, 50).map(one => ({ id: one.id, what: one.what, when: conditionWords(store, one.condition), until: one.expiresAt }));
537
537
  return sendScreen(response, 200, screen("Lead", leadSettingsHtml({ config, facts, words: leadWords(), signedIn, command: agentSignInCommand(),
538
538
  said: url.searchParams.get("said"), saved: url.searchParams.get("saved") === "1", identity: leadIdentityOf(store, who.name), promises,
539
- about: aboutYouOf(store, who.name), aboutSaved: url.searchParams.get("saved") === "about" }), { chrome: chromeFor(project, "settings") }));
539
+ about: aboutYouOf(store, who.name), aboutSaved: url.searchParams.get("saved") === "about",
540
+ subagents: store.subagents(consoleProjects().filter(repo => store.accountCanAccess(who.name, repo))).map(one => ({ id: one.id, label: labelOf(one), project: projectName(one.repo), paused: one.state === "paused" })) }), { chrome: chromeFor(project, "settings") }));
540
541
  }
541
542
  async function telegramPage(ctx) {
542
543
  const { response, now, project } = ctx;
@@ -754,16 +755,16 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
754
755
  const back = (key, words) => redirect(response, `/spend?${key}=${encodeURIComponent(words)}`);
755
756
  if (!authenticateApprover(who, body.get("password") ?? "").ok)
756
757
  return back("problem", "Enter your Toolroll password to change a budget.");
757
- const target = /^(installation|project|person|teammate):(.+)$/.exec(body.get("target") ?? "");
758
+ const target = /^(installation|project|person|subagent):(.+)$/.exec(body.get("target") ?? "");
758
759
  if (target === null)
759
760
  return back("problem", "Choose what the budget is for.");
760
761
  const scope = target[1], key = target[2];
761
762
  const known = scope === "installation" ? key === "*"
762
763
  : scope === "project" ? consoleProjects().includes(key)
763
764
  : scope === "person" ? store.accountFacts().some(one => one.name === key && one.revokedAt === null)
764
- : store.teammates([...new Set([...managedRepos(), ...store.knownRepos()])]).some(one => String(one.id) === key);
765
+ : store.subagents([...new Set([...managedRepos(), ...store.knownRepos()])]).some(one => String(one.id) === key);
765
766
  if (!known)
766
- return back("problem", "That isn't a project, person or teammate here.");
767
+ return back("problem", "That isn't a project, person or subagent here.");
767
768
  const label = budgetLabel({ scope, key }).replace(/'s$/, "");
768
769
  if (body.get("action") === "remove") {
769
770
  const existing = store.budgets().find(one => one.scope === scope && one.key === key);
@@ -2315,7 +2316,7 @@ ${modeWords(terms)
2315
2316
  return done(back, "problem", finished.said);
2316
2317
  // From a kit: its teammate may use the tool now (reading freely, the rest after a person approves each call).
2317
2318
  const set = kit === null || !kit.tools.some(one => one.tool === service.id) ? null : kitInstalled(store, kit, visit.repo);
2318
- if (set !== null && store.teammateGrant(set.mate.id, service.id) === null) {
2319
+ if (set !== null && store.subagentGrant(set.mate.id, service.id) === null) {
2319
2320
  const granted = await grantTool(store, set.mate, service.id, visit.by, now, { toolHome });
2320
2321
  if (granted.ok)
2321
2322
  return done(back, "said", `${service.label} is connected to ${to}, and ${nameOf(set.mate)} can use it: reading freely, the rest after you approve each call.`);
@@ -1,4 +1,5 @@
1
1
  import { handlersOf } from './handler-registry.js';
2
+ import { DEPRECATED_PAGE } from "../deprecations.js";
2
3
  /** tasks handlers, moved without changing their route bodies. */
3
4
  import { html, htmlString, joinHtml, postForm, textHtml } from "../html.js";
4
5
  import { createHash, randomBytes, randomUUID } from "node:crypto";
@@ -38,7 +39,7 @@ import { fileAddTestsTask, requestFollowUpChecks } from '../result-follow-ups.js
38
39
  import { commentSourceKey, isRevisionFeedback, parseResultTab, resultReturnTarget } from "../result-review.js";
39
40
  import { isAlive as runnerAlive } from "../runner.js";
40
41
  import { acceptanceLinesToInput, acceptanceToLines, approvalOf, approve as approveScope, hasFreshIdentitySignIn, modeFilingCoverage, PLACEHOLDER_RUBRIC, proposeGuarded } from "../scope.js";
41
- import { teammateNames as teammateNamesOf } from "../spend.js";
42
+ import { subagentNames as subagentNamesOf } from "../spend.js";
42
43
  import { verifiedAuthor } from "../store.js";
43
44
  import { runCostWords } from "../summary.js";
44
45
  import { requestTaskStop, resumeTaskStop } from "../task-control.js";
@@ -54,7 +55,7 @@ import { ACCEPT_ANYWAY_NEEDS_REASON, editorFileHref, proofBundleView, rankReview
54
55
  import { approvalFormDigest, approveRefusalWords, checkProgressHtml, consentDoorOf, decisionPage, nonceHashOf, PermissionsWouldChange, requirementsFromEditor, resumeDigestOf, taskOf } from "./render-tasks.js";
55
56
  import {} from "./session.js";
56
57
  export function createTasksHandlers(runtime) {
57
- const { store, peekSay, peekCache, PEEK_CACHE_TTL_MS, peekInFlight, PEEK_GLOBAL_INFLIGHT, peekBySession, PEEK_SESSION_INFLIGHT, clock, peekName, PEEK_FRAGMENT_BYTES, peekEvict, evidenceRoot, sendScreen, chromeFor, visible, consumeApprovalNonce, authenticateApprover, options, mintApprovalNonce, taskScreen, unscopedMode, admissionList, planViewOf, revisionViewOf, runIsTaskResult, matePrincipal, familyOf, armTaskResume, runIsLive, revisionLedgerOf, resultDetailOf, reviewFactsFor, taskRepoOf, runVisible, restricted, codingActorAllowed, codingProjectAllowed, managedRepos, routeViewOf, workAccess, firstRunStepsNow, revisionDocOf, failureOf, dockedConversation, planContractViewOf, familiesInView, explainAttempt, taskChatFocus, familyTasksInView, taskRooms, identify, liveCeiling, projectOf, ceiling, bustBadge, revisionDestination } = runtime;
58
+ const { store, peekSay, peekCache, PEEK_CACHE_TTL_MS, peekInFlight, PEEK_GLOBAL_INFLIGHT, peekBySession, PEEK_SESSION_INFLIGHT, clock, peekName, PEEK_FRAGMENT_BYTES, peekEvict, evidenceRoot, sendScreen, chromeFor, visible, consumeApprovalNonce, authenticateApprover, options, mintApprovalNonce, taskScreen, unscopedMode, admissionList, planViewOf, revisionViewOf, runIsTaskResult, leadPrincipal, familyOf, armTaskResume, runIsLive, revisionLedgerOf, resultDetailOf, reviewFactsFor, taskRepoOf, runVisible, restricted, codingActorAllowed, codingProjectAllowed, managedRepos, routeViewOf, workAccess, firstRunStepsNow, revisionDocOf, failureOf, dockedConversation, planContractViewOf, familiesInView, explainAttempt, taskChatFocus, familyTasksInView, taskRooms, identify, liveCeiling, projectOf, ceiling, bustBadge, revisionDestination } = runtime;
58
59
  /** A row admitted the address but its path parameter does not parse: the console's unknown page. */
59
60
  function noPage(who, response) {
60
61
  return refuse(response, who, 404, "There's no page at this address.", "/chat");
@@ -92,7 +93,7 @@ export function createTasksHandlers(runtime) {
92
93
  const codeProject = selected?.repo ?? requestedProject ?? project;
93
94
  const chrome = chromeFor(codeProject, 'code');
94
95
  const content = codingWorkspaceHtml({ owner: who.name, projects: chrome.projects ?? [], sessions: runtime.coding?.list(actor).filter(s => codingProjectAllowed(s.repo)) ?? [], selected: selected && runtime.coding ? runtime.coding.snapshot(selected.id, actor) : null, csrf: who.session.csrf, project: codeProject, available: runtime.coding !== null, ...(runtime.codingProblem ? { error: runtime.codingProblem } : {}) });
95
- return sendScreen(response, 200, screen('Code', content, { chrome, functional: { script: codingWorkspaceScript(), fetches: true } }));
96
+ return sendScreen(response, 200, screen('Code', html `<p class="problem" role="status" data-deprecated="session">${DEPRECATED_PAGE.session}</p>${content}`, { chrome, functional: { script: codingWorkspaceScript(), fetches: true } }));
96
97
  }
97
98
  catch (error) {
98
99
  if (!codingActorAllowed(actor))
@@ -245,7 +246,7 @@ export function createTasksHandlers(runtime) {
245
246
  now,
246
247
  // v105: subscription windows and monthly budgets, for whoever runs the installation.
247
248
  limits: store.isInstanceOperator(who.name)
248
- ? limitsView(store.providerLimits(), store.budgets().length === 0 ? [] : store.monthSpendCached(now).budgets, { project: projectName, teammate: id => teammateNamesOf(store.handle).get(id) ?? `Teammate ${id}` }, now)
249
+ ? limitsView(store.providerLimits(), store.budgets().length === 0 ? [] : store.monthSpendCached(now).budgets, { project: projectName, subagent: id => subagentNamesOf(store.handle).get(id) ?? `Subagent ${id}` }, now)
249
250
  : null,
250
251
  });
251
252
  // One project's Tasks dock that project's own conversation (v77).
@@ -2227,7 +2228,7 @@ export function createTasksHandlers(runtime) {
2227
2228
  case "complete": {
2228
2229
  const body = readForm(posted, CONSOLE_FORMS.taskComplete);
2229
2230
  // The row admits only an approver's browser session; the principal re-proves that session's standing.
2230
- const principal = who.via === "cookie" ? matePrincipal(who) : null;
2231
+ const principal = who.via === "cookie" ? leadPrincipal(who) : null;
2231
2232
  if (principal === null)
2232
2233
  return refuse(response, who, 403, "Your access changed. Sign in again.");
2233
2234
  const digest = body.get("receipt") ?? "";
@@ -15,10 +15,10 @@ export declare function detectPreparation(repo: string): {
15
15
  } | null;
16
16
  /** Browser installation may write only this known file inside its project. */
17
17
  export declare function previewProjectInstructions(repo: string): {
18
+ content?: never;
18
19
  ok: false;
19
20
  message: string;
20
21
  plan?: never;
21
- content?: never;
22
22
  fingerprint?: never;
23
23
  installed?: never;
24
24
  } | {
@@ -30,10 +30,10 @@ export declare function previewProjectInstructions(repo: string): {
30
30
  installed: boolean;
31
31
  };
32
32
  export declare function addProjectInstructions(repo: string, fingerprint: string): {
33
+ content?: never;
33
34
  ok: false;
34
35
  message: string;
35
36
  plan?: never;
36
- content?: never;
37
37
  fingerprint?: never;
38
38
  installed?: never;
39
39
  } | import("./skills.js").InstallResult;
@@ -1,7 +1,7 @@
1
1
  import { ChatState, type ChatBinding, type ChatContent, type ChatEvent } from "./chat-delivery-state.js";
2
2
  import { uploadSlackBytes, type SlackApi, type SlackIdentity } from "./slack-api.js";
3
3
  import { type Store } from "./store.js";
4
- import type { SubscriptionMateRunner } from "./subscription-chat.js";
4
+ import type { SubscriptionLeadRunner } from "./subscription-chat.js";
5
5
  export type SlackChatOptions = {
6
6
  store: Store;
7
7
  identity: SlackIdentity;
@@ -11,7 +11,7 @@ export type SlackChatOptions = {
11
11
  evidenceRoot: string;
12
12
  current: () => boolean;
13
13
  origin: () => string | null;
14
- subscriptionRunner?: SubscriptionMateRunner;
14
+ subscriptionRunner?: SubscriptionLeadRunner;
15
15
  canNotify?: () => boolean;
16
16
  clock?: () => Date;
17
17
  upload?: typeof uploadSlackBytes;