toolroll 0.9.55 → 0.9.57

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 (223) hide show
  1. package/CHANGELOG.md +18 -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/assignment-adapters.d.ts +5 -0
  9. package/dist/assignment.d.ts +24 -3
  10. package/dist/assignment.js +36 -10
  11. package/dist/browser/workspace.js +5 -5
  12. package/dist/browser-workspace.d.ts +12 -12
  13. package/dist/browser-workspace.js +1 -1
  14. package/dist/budget-alerts.js +3 -3
  15. package/dist/chat-actions.d.ts +23 -18
  16. package/dist/chat-actions.js +110 -88
  17. package/dist/chat-ask.d.ts +2 -2
  18. package/dist/chat-ask.js +5 -5
  19. package/dist/chat-channel.d.ts +15 -15
  20. package/dist/chat-channel.js +20 -20
  21. package/dist/chat-controls.d.ts +0 -4
  22. package/dist/chat-controls.js +0 -1
  23. package/dist/chat-core.js +1 -1
  24. package/dist/chat-delivery-state.js +2 -2
  25. package/dist/chat-delivery.d.ts +2 -2
  26. package/dist/chat-delivery.js +30 -37
  27. package/dist/chat-flow.js +3 -3
  28. package/dist/chat-messages.d.ts +1 -1
  29. package/dist/chat-messages.js +4 -4
  30. package/dist/chat-migration.d.ts +1 -1
  31. package/dist/chat-migration.js +11 -11
  32. package/dist/chat-rooms.js +3 -3
  33. package/dist/chat-voice.js +1 -1
  34. package/dist/chat-warmth.d.ts +2 -2
  35. package/dist/check-levels.d.ts +3 -0
  36. package/dist/check-levels.js +5 -0
  37. package/dist/cli.d.ts +1 -1
  38. package/dist/cli.js +6 -7
  39. package/dist/contracts/chat-actions.d.ts +32 -27
  40. package/dist/contracts/chat-actions.js +29 -11
  41. package/dist/contracts/chat-content.d.ts +1 -1
  42. package/dist/contracts/chat-content.js +1 -1
  43. package/dist/contracts/chat-tables.js +4 -4
  44. package/dist/contracts/console-api.d.ts +6 -6
  45. package/dist/contracts/console-api.js +7 -7
  46. package/dist/contracts/flow-propose.d.ts +3 -3
  47. package/dist/contracts/flow.d.ts +132 -125
  48. package/dist/contracts/flow.js +27 -12
  49. package/dist/contracts/lead-context.d.ts +1 -1
  50. package/dist/contracts/lead-context.js +2 -2
  51. package/dist/contracts/lead-tools.d.ts +20 -18
  52. package/dist/contracts/lead-tools.js +15 -15
  53. package/dist/contracts/stage-output.d.ts +1 -1
  54. package/dist/contracts/stage-output.js +1 -1
  55. package/dist/contracts/store-json.d.ts +23 -22
  56. package/dist/contracts/store-json.js +17 -17
  57. package/dist/contracts/{teammate-turn.d.ts → subagent-turn.d.ts} +5 -5
  58. package/dist/contracts/{teammate-turn.js → subagent-turn.js} +7 -7
  59. package/dist/converse.d.ts +23 -23
  60. package/dist/converse.js +28 -28
  61. package/dist/coordinator-proposals.d.ts +2 -2
  62. package/dist/coordinator-proposals.js +1 -1
  63. package/dist/coordinator.js +1 -1
  64. package/dist/deprecations.d.ts +12 -0
  65. package/dist/deprecations.js +20 -0
  66. package/dist/desktop-update-gate.js +8 -4
  67. package/dist/desktop-update.js +6 -1
  68. package/dist/discord-chat.js +6 -6
  69. package/dist/dispatch.d.ts +1 -1
  70. package/dist/dispatch.js +1 -1
  71. package/dist/dispose.js +0 -5
  72. package/dist/envelope.js +0 -1
  73. package/dist/export-ui.js +1 -1
  74. package/dist/export.js +7 -7
  75. package/dist/flow-engine.d.ts +2 -2
  76. package/dist/flow-engine.js +21 -21
  77. package/dist/flow-insights.d.ts +1 -1
  78. package/dist/flow-share.js +13 -8
  79. package/dist/flow-steps.d.ts +5 -5
  80. package/dist/flow-steps.js +19 -19
  81. package/dist/flow-triggers.d.ts +1 -1
  82. package/dist/flow-triggers.js +1 -1
  83. package/dist/flows-cli.js +1 -1
  84. package/dist/flows-ui.js +20 -20
  85. package/dist/flows.js +19 -19
  86. package/dist/guides.js +4 -3
  87. package/dist/invoke.js +1 -1
  88. package/dist/kits-ui.js +5 -5
  89. package/dist/kits.d.ts +12 -12
  90. package/dist/kits.js +31 -31
  91. package/dist/{mate-cli.d.ts → lead-cli.d.ts} +11 -11
  92. package/dist/{mate-cli.js → lead-cli.js} +38 -38
  93. package/dist/lead-commitments.js +4 -4
  94. package/dist/lead-context.d.ts +2 -2
  95. package/dist/lead-context.js +9 -9
  96. package/dist/{mate-contract.d.ts → lead-contract.d.ts} +3 -3
  97. package/dist/{mate-contract.js → lead-contract.js} +7 -6
  98. package/dist/{mate-doors.d.ts → lead-doors.d.ts} +6 -6
  99. package/dist/{mate-doors.js → lead-doors.js} +24 -24
  100. package/dist/lead-follow.d.ts +5 -5
  101. package/dist/lead-follow.js +9 -9
  102. package/dist/lead-people.d.ts +5 -5
  103. package/dist/lead-people.js +14 -14
  104. package/dist/{mate-progress.d.ts → lead-progress.d.ts} +9 -9
  105. package/dist/{mate-progress.js → lead-progress.js} +3 -3
  106. package/dist/lead-status.d.ts +1 -1
  107. package/dist/lead-status.js +2 -2
  108. package/dist/{mate-tools.d.ts → lead-tools.d.ts} +19 -19
  109. package/dist/{mate-tools.js → lead-tools.js} +109 -101
  110. package/dist/{mate.d.ts → lead.d.ts} +27 -27
  111. package/dist/{mate.js → lead.js} +102 -102
  112. package/dist/limits-ui.d.ts +1 -1
  113. package/dist/limits-ui.js +1 -1
  114. package/dist/mcp-connect.d.ts +1 -1
  115. package/dist/mcp-connect.js +3 -3
  116. package/dist/mcp-core.js +1 -1
  117. package/dist/memory-pass.d.ts +2 -2
  118. package/dist/memory-pass.js +7 -7
  119. package/dist/notification-rows.d.ts +2 -2
  120. package/dist/operate.d.ts +3 -3
  121. package/dist/operate.js +52 -42
  122. package/dist/policy.d.ts +1 -1
  123. package/dist/principal.d.ts +1 -1
  124. package/dist/principal.js +2 -2
  125. package/dist/project-delete-ui.js +1 -1
  126. package/dist/project-delete.d.ts +1 -1
  127. package/dist/project-delete.js +26 -26
  128. package/dist/project-memory.js +1 -1
  129. package/dist/project-tools.d.ts +1 -1
  130. package/dist/project-tools.js +1 -1
  131. package/dist/proof.d.ts +9 -0
  132. package/dist/proof.js +12 -0
  133. package/dist/recipe-ui.js +1 -1
  134. package/dist/release-coverage.d.ts +16 -0
  135. package/dist/release-coverage.js +92 -0
  136. package/dist/retention.js +4 -4
  137. package/dist/scope.d.ts +2 -2
  138. package/dist/scout-report.d.ts +1 -1
  139. package/dist/scout-report.js +1 -1
  140. package/dist/serve.js +40 -45
  141. package/dist/server/chat.d.ts +4 -4
  142. package/dist/server/chat.js +105 -103
  143. package/dist/server/chrome.js +3 -3
  144. package/dist/server/flows.js +69 -62
  145. package/dist/server/guards.js +3 -3
  146. package/dist/server/http.d.ts +3 -3
  147. package/dist/server/http.js +1 -1
  148. package/dist/server/live.js +6 -6
  149. package/dist/server/people-tokens.js +1 -1
  150. package/dist/server/remote.js +12 -10
  151. package/dist/server/render-chat.d.ts +30 -30
  152. package/dist/server/render-chat.js +33 -33
  153. package/dist/server/render-pages.js +2 -2
  154. package/dist/server/render-results.d.ts +4 -1
  155. package/dist/server/render-results.js +23 -9
  156. package/dist/server/render-settings.d.ts +7 -0
  157. package/dist/server/render-settings.js +3 -0
  158. package/dist/server/route-table.js +9 -7
  159. package/dist/server/runtime.d.ts +13 -13
  160. package/dist/server/settings.js +14 -13
  161. package/dist/server/tasks.js +34 -30
  162. package/dist/setup-guide.d.ts +2 -2
  163. package/dist/slack-chat.d.ts +2 -2
  164. package/dist/slack-chat.js +6 -6
  165. package/dist/spend-ui.d.ts +3 -3
  166. package/dist/spend-ui.js +9 -9
  167. package/dist/spend.d.ts +14 -14
  168. package/dist/spend.js +31 -31
  169. package/dist/store-baseline.d.ts +19 -0
  170. package/dist/store-baseline.js +2873 -0
  171. package/dist/store.d.ts +200 -254
  172. package/dist/store.js +830 -3152
  173. package/dist/subagent-admin.d.ts +53 -0
  174. package/dist/{teammate-admin.js → subagent-admin.js} +37 -37
  175. package/dist/{teammate-desk.d.ts → subagent-desk.d.ts} +22 -23
  176. package/dist/{teammate-desk.js → subagent-desk.js} +26 -35
  177. package/dist/{teammate-memory.d.ts → subagent-memory.d.ts} +17 -17
  178. package/dist/{teammate-memory.js → subagent-memory.js} +41 -41
  179. package/dist/{teammate-question.d.ts → subagent-question.d.ts} +6 -6
  180. package/dist/{teammate-question.js → subagent-question.js} +17 -17
  181. package/dist/{teammate-tools.d.ts → subagent-tools.d.ts} +18 -18
  182. package/dist/{teammate-tools.js → subagent-tools.js} +36 -36
  183. package/dist/{teammate-week.d.ts → subagent-week.d.ts} +10 -10
  184. package/dist/{teammate-week.js → subagent-week.js} +23 -23
  185. package/dist/{teammate-work.d.ts → subagent-work.d.ts} +21 -21
  186. package/dist/{teammate-work.js → subagent-work.js} +50 -50
  187. package/dist/subagents-ui.d.ts +22 -0
  188. package/dist/{teammates-ui.js → subagents-ui.js} +45 -45
  189. package/dist/{teammates.d.ts → subagents.d.ts} +11 -11
  190. package/dist/{teammates.js → subagents.js} +21 -21
  191. package/dist/subscription-chat.d.ts +13 -13
  192. package/dist/subscription-chat.js +19 -19
  193. package/dist/surface.d.ts +19 -2
  194. package/dist/surface.js +7 -31
  195. package/dist/task-outcome-cli.js +3 -1
  196. package/dist/task-status.d.ts +59 -7
  197. package/dist/task-status.js +132 -16
  198. package/dist/team-cli.d.ts +1 -0
  199. package/dist/team-cli.js +14 -2
  200. package/dist/team-leads.js +13 -13
  201. package/dist/team-runtime.d.ts +2 -2
  202. package/dist/team-runtime.js +9 -9
  203. package/dist/teams-chat.js +6 -6
  204. package/dist/telegram-flow.d.ts +3 -3
  205. package/dist/telegram-flow.js +3 -3
  206. package/dist/{telegram-mate.d.ts → telegram-lead.d.ts} +5 -5
  207. package/dist/{telegram-mate.js → telegram-lead.js} +27 -27
  208. package/dist/telegram-status.d.ts +11 -4
  209. package/dist/telegram-status.js +18 -11
  210. package/dist/telegram-team.d.ts +1 -1
  211. package/dist/telegram-team.js +5 -5
  212. package/dist/telegram.d.ts +1 -1
  213. package/dist/telegram.js +13 -34
  214. package/dist/text-limits.d.ts +18 -18
  215. package/dist/text-limits.js +18 -18
  216. package/dist/toolroll-update.d.ts +8 -0
  217. package/dist/toolroll-update.js +45 -8
  218. package/dist/work-index.d.ts +6 -1
  219. package/dist/work-index.js +50 -8
  220. package/dist/workspace-ui.js +2 -1
  221. package/package.json +1 -2
  222. package/dist/teammate-admin.d.ts +0 -53
  223. package/dist/teammates-ui.d.ts +0 -21
@@ -2,7 +2,7 @@ import { type BrowserFirstRun, type BrowserHome, type BrowserPhoneCard } from '.
2
2
  import { type DemoResultView } from "../demo-chat.js";
3
3
  import type { DemoExchange } from "../demo.js";
4
4
  import { type FirstTaskSuggestion } from "../first-run.js";
5
- import type { MateProgress } from "../mate-progress.js";
5
+ import type { LeadProgress } from "../lead-progress.js";
6
6
  import type { DirectChatProviderId } from "../store.js";
7
7
  import type { ServerRuntime } from './runtime.js';
8
8
  import { type ChatEnablement } from "./render-chat.js";
@@ -14,10 +14,10 @@ export declare function createChatHandlers(runtime: ServerRuntime): {
14
14
  via: "cookie";
15
15
  }, now: Date) => BrowserPhoneCard | undefined;
16
16
  chatProjectOf: (values: string[]) => string | null | undefined;
17
- beginLiveTurn: (thread: number) => (event: MateProgress) => void;
17
+ beginLiveTurn: (thread: number) => (event: LeadProgress) => void;
18
18
  endLiveTurn: (thread: number, ok: boolean) => void;
19
- noteMate: (csrf: string, turn: number | null, message: string) => void;
20
- takeMateNote: (csrf: string, liveSession: number | null) => string | null;
19
+ noteLead: (csrf: string, turn: number | null, message: string) => void;
20
+ takeLeadNote: (csrf: string, liveSession: number | null) => string | null;
21
21
  demoResultView: (exchange: DemoExchange) => DemoResultView | null;
22
22
  storeChatKey: (provider: DirectChatProviderId, key: string) => {
23
23
  ok: true;
@@ -1,4 +1,5 @@
1
1
  import { handlersOf } from './handler-registry.js';
2
+ import { DEPRECATED_PAGE } from "../deprecations.js";
2
3
  /** chat handlers, moved without changing their route bodies. */
3
4
  import { randomBytes } from "node:crypto";
4
5
  import { chmodSync, rmSync as rmFileSync, writeFileSync as writeFsFileSync } from "node:fs";
@@ -18,8 +19,8 @@ import { leadBriefHtml } from '../lead-context.js';
18
19
  import { configureLeadFollow, leadFollowStatus } from '../lead-follow.js';
19
20
  import { leadActivity } from "../lead-voice.js";
20
21
  import { limitsView } from "../limits-ui.js";
21
- import { confirmCoordinatorProposal, confirmMateProposal, dismissCoordinatorProposal, dismissMateProposal } from "../mate-doors.js";
22
- import { MATE_MESSAGE_MAX_CHARS, runMateTurn } from "../mate.js";
22
+ import { confirmCoordinatorProposal, confirmLeadProposal, dismissCoordinatorProposal, dismissLeadProposal } from "../lead-doors.js";
23
+ import { LEAD_MESSAGE_MAX_CHARS, runLeadTurn } from "../lead.js";
23
24
  import { livePin, modelOptions } from "../model-catalog.js";
24
25
  import { verifyApproverByPassword, verifyApproverStanding } from "../principal.js";
25
26
  import { projectName } from "../project.js";
@@ -37,12 +38,12 @@ import { html, htmlString, postForm } from "../html.js";
37
38
  import { requestContext } from "./request-context.js";
38
39
  import { refuse, screen } from "./chrome.js";
39
40
  import { chatResultHref, chatReturnWithLatest, chatReturnWithSaid, projectChatHref, redirect, respond, safeChatReturn, safeReturn, taskChatHref, taskHref } from "./http.js";
40
- import { chatAckPage, chatPage, coordinatorProposalsSection, decisionsFor, mateAfterComposerHtml, mateChatVersion, mateMintCard, matePage, mateThreadHtml, PHONE_CARD_FACT, TASK_COMPOSER_MODES, taskChatLiveRegion, teamProposalCardParts } from "./render-chat.js";
41
+ import { chatAckPage, chatPage, coordinatorProposalsSection, decisionsFor, leadAfterComposerHtml, leadChatVersion, leadMintCard, leadPage, leadThreadHtml, PHONE_CARD_FACT, TASK_COMPOSER_MODES, taskChatLiveRegion, teamProposalCardParts } from "./render-chat.js";
41
42
  import {} from "./render-pages.js";
42
- import { completionForm, homePhaseWords, owedAcceptanceOf, resultPanelHtml } from "./render-results.js";
43
+ import { completionForm, hardBlockerOf, homePhaseWords, owedAcceptanceOf, resultPanelHtml } from "./render-results.js";
43
44
  import { browserCaller, oneLineUa } from "./session.js";
44
45
  export function createChatHandlers(runtime) {
45
- const { firstRunStepsNow, managedRepos, leadWords, store, options, phoneSetup, visible, liveTurns, mateSaid, evidenceRoot, clock, sessions, CHAT_CANDIDATE_TTL_MS, chatFetcher, CHAT_CANDIDATES_PER_APPROVER, chatCeilingDigest, workAccess, familyOf, firstTasks, matePrincipal, taskChatFocus, chatScopeOf, mateConversationRows, demoLeadHere, sendScreen, chromeFor, teamBrowserReply, team, teamChatProvider, runVisible, runIsLive, resultDetailOf, pullRequestTargetOf, chatEnablement, startMateConversation, projectFamilyPeek, needsYouBadge, liveRefreshSeconds, chatKeyFor, chatCatalog, providerHome, mintApprovalNonce, checkLocalAgents, agentSignInCommand, bustBadge, authenticateApprover, revisionDestination, armTaskResume, consumeApprovalNonce } = runtime;
46
+ const { firstRunStepsNow, managedRepos, leadWords, store, options, phoneSetup, visible, liveTurns, leadSaid, evidenceRoot, clock, sessions, CHAT_CANDIDATE_TTL_MS, chatFetcher, CHAT_CANDIDATES_PER_APPROVER, chatCeilingDigest, workAccess, familyOf, firstTasks, leadPrincipal, taskChatFocus, chatScopeOf, leadConversationRows, demoLeadHere, sendScreen, chromeFor, teamBrowserReply, team, teamChatProvider, runVisible, runIsLive, resultDetailOf, pullRequestTargetOf, chatEnablement, startLeadConversation, projectFamilyPeek, needsYouBadge, liveRefreshSeconds, chatKeyFor, chatCatalog, providerHome, mintApprovalNonce, checkLocalAgents, agentSignInCommand, bustBadge, authenticateApprover, revisionDestination, armTaskResume, consumeApprovalNonce } = runtime;
46
47
  // The read-only refresh (package 2): the same JSON status as before —
47
48
  // session binding, the send receipt, the live turn — plus a version
48
49
  // of the DISPLAYED facts and, only when the caller's version differs,
@@ -52,14 +53,14 @@ export function createChatHandlers(runtime) {
52
53
  async function mateStatus(ctx) {
53
54
  const { url, response, now } = ctx;
54
55
  const who = browserCaller(ctx.who);
55
- const session = store.activeMateSession(who.name);
56
- const principal = matePrincipal(who);
56
+ const session = store.activeLeadSession(who.name);
57
+ const principal = leadPrincipal(who);
57
58
  if (session === null || principal === null || session.ceilingDigest !== principal.ceilingDigest || session.approverGeneration !== principal.generation) {
58
59
  return respond(response, 200, "application/json", JSON.stringify({ session: null }));
59
60
  }
60
- store.sweepStaleMateTurns(now);
61
+ store.sweepStaleLeadTurns(now);
61
62
  const request = url.searchParams.get("request") ?? "";
62
- const receipt = /^[a-f0-9]{32}$/.test(request) ? store.mateRequestReceipt(session.id, request) : null;
63
+ const receipt = /^[a-f0-9]{32}$/.test(request) ? store.leadRequestReceipt(session.id, request) : null;
63
64
  const requestedTask = url.searchParams.get("task");
64
65
  const focusTask = requestedTask === null || requestedTask === "" ? null : taskChatFocus(requestedTask, now, who, { mintNonce: false });
65
66
  if (requestedTask !== null && requestedTask !== "" && focusTask === null) {
@@ -70,15 +71,15 @@ export function createChatHandlers(runtime) {
70
71
  const chatProject = focusTask !== null ? null : chatProjectOf(url.searchParams.getAll("project").filter(one => one !== ""));
71
72
  if (chatProject === undefined)
72
73
  return respond(response, 200, "application/json", JSON.stringify({ session: session.id, unavailable: true, received: receipt !== null }));
73
- const rows = mateConversationRows(who, principal, focusTask, now, chatProject);
74
- const version = mateChatVersion({ ...rows, focusTask });
74
+ const rows = leadConversationRows(who, principal, focusTask, now, chatProject);
75
+ const version = leadChatVersion({ ...rows, focusTask });
75
76
  const known = url.searchParams.get("version") ?? "";
76
77
  const csrf = who.session.csrf;
77
78
  const fragments = known === version
78
79
  ? null
79
80
  : {
80
- thread: htmlString(mateThreadHtml({ ...rows, focusTask, csrf, now, problem: takeMateNote(csrf, session.id), chatProject })),
81
- after: htmlString(mateAfterComposerHtml({ messages: rows.messages, pending: rows.pending, focusTask })),
81
+ thread: htmlString(leadThreadHtml({ ...rows, focusTask, csrf, now, problem: takeLeadNote(csrf, session.id), chatProject })),
82
+ after: htmlString(leadAfterComposerHtml({ messages: rows.messages, pending: rows.pending, focusTask })),
82
83
  live: focusTask === null ? null : htmlString(taskChatLiveRegion(focusTask, csrf, true, rows.pending !== null)),
83
84
  };
84
85
  return respond(response, 200, "application/json", JSON.stringify({
@@ -118,7 +119,8 @@ export function createChatHandlers(runtime) {
118
119
  // THIS session's memory; a bearer caller has nowhere to keep them.
119
120
  if (who.via !== "cookie")
120
121
  return refuse(response, who, 403, "chat is a browser surface — it keeps your drafts in the session");
121
- if (url.searchParams.get('private') !== '1' && !url.searchParams.has('task') && !url.searchParams.has('result') && !url.searchParams.has('project')) {
122
+ const teamAsked = url.searchParams.get('team') === '1' || url.searchParams.has('conversation') || url.searchParams.has('lead') || url.searchParams.has('proposal');
123
+ if (teamAsked && url.searchParams.get('private') !== '1' && !url.searchParams.has('task') && !url.searchParams.has('result') && !url.searchParams.has('project')) {
122
124
  const reply = teamBrowserReply(await team.execute({ name: who.name, generation: who.session.generation }, {
123
125
  operation: url.searchParams.has('conversation') ? 'show' : 'list',
124
126
  args: { ...(url.searchParams.has('conversation') ? { conversationId: url.searchParams.get('conversation') } : {}),
@@ -128,7 +130,7 @@ export function createChatHandlers(runtime) {
128
130
  return refuse(response, who, 403, reply.message, '/chat?private=1');
129
131
  if (url.searchParams.has('proposal')) {
130
132
  const id = Number(url.searchParams.get('proposal')), selected = reply.snapshot.selected;
131
- const proposal = Number.isSafeInteger(id) && id > 0 ? store.getMateProposal(id) : null;
133
+ const proposal = Number.isSafeInteger(id) && id > 0 ? store.getLeadProposal(id) : null;
132
134
  if (!selected || !proposal || proposal.thread !== selected.threadId)
133
135
  return refuse(response, who, 404, 'This proposal is unavailable.', '/chat');
134
136
  const back = '/chat?conversation=' + encodeURIComponent(selected.id);
@@ -136,15 +138,13 @@ export function createChatHandlers(runtime) {
136
138
  const card = teamProposalCardParts(store, { name: who.name, generation: who.session.generation }, proposal, reply.snapshot, who.session.csrf, decision, clock(), teamChatProvider).html;
137
139
  return sendScreen(response, 200, screen('Review action', html `<p><a href="${back}">Back to conversation</a></p>${card}`, { chrome: chromeFor(null, 'chat', undefined, 'all') }));
138
140
  }
139
- if (reply.snapshot.leads.length > 0 || url.searchParams.has('conversation') || url.searchParams.get('team') === '1') {
140
- const said = url.searchParams.get('said')?.slice(0, 1_000);
141
- return sendScreen(response, 200, screen('Chat', html `${said ? html `<p role="alert">${said}</p>` : ''}${teamWorkspaceHtml(reply.snapshot)}`, {
142
- chrome: chromeFor(null, 'chat', undefined, 'all'), workspace: { team: reply.snapshot, ...(said ? { notices: [said] } : {}) },
143
- }));
144
- }
141
+ const said = url.searchParams.get('said')?.slice(0, 1_000);
142
+ return sendScreen(response, 200, screen('Chat', html `<p class="problem" role="status" data-deprecated="team">${DEPRECATED_PAGE.team}</p>${said ? html `<p role="alert">${said}</p>` : ''}${teamWorkspaceHtml(reply.snapshot)}`, {
143
+ chrome: chromeFor(null, 'chat', undefined, 'all'), workspace: { team: reply.snapshot, notices: [DEPRECATED_PAGE.team, ...(said ? [said] : [])] },
144
+ }));
145
145
  }
146
146
  store.sweepStaleChatTurns(now);
147
- store.sweepStaleMateTurns(now);
147
+ store.sweepStaleLeadTurns(now);
148
148
  store.sweepCoordinatorProposals(now);
149
149
  sweepChatDrafts(Date.now());
150
150
  const requestedTask = url.searchParams.get("task");
@@ -185,7 +185,9 @@ export function createChatHandlers(runtime) {
185
185
  const roomId = url.searchParams.get('conversation');
186
186
  const resultLink = (href) => roomId ? href + '&conversation=' + encodeURIComponent(roomId) : href;
187
187
  // Accept and finish is here: the one act that accepts the person's own checks and finishes the task.
188
- const finishes = resultRun !== null && who.role === 'approver' && focusTask?.assignment?.state === 'ready-to-check' && focusTask.assignment.receipt?.runId === resultRun.id;
188
+ // Never offered where Complete is refused whatever is accepted (a failed check, a HIGH finding).
189
+ const finishes = resultRun !== null && who.role === 'approver' && focusTask?.assignment?.state === 'ready-to-check' && focusTask.assignment.receipt?.runId === resultRun.id &&
190
+ hardBlockerOf(focusTask.assignment) === null;
189
191
  const resultPanel = resultRun === null
190
192
  ? null
191
193
  : html `${resultPanelHtml(resultDetailOf(resultRun, who, now), {
@@ -233,8 +235,8 @@ export function createChatHandlers(runtime) {
233
235
  // The mate (mate arc §5): while a mate session is live, /chat IS the
234
236
  // thread — the same rows the CLI reads. Without one, fleet chat as
235
237
  // before, plus the card that mints a session.
236
- let mateSession = enabled.ok && who.role === "approver" ? store.activeMateSession(who.name) : null;
237
- const principal = enabled.ok && who.role === "approver" ? matePrincipal(who) : null;
238
+ let leadSession = enabled.ok && who.role === "approver" ? store.activeLeadSession(who.name) : null;
239
+ const principal = enabled.ok && who.role === "approver" ? leadPrincipal(who) : null;
238
240
  // Chat opens ready to talk (2026-09-23): an approver in good standing
239
241
  // needs no second password to start their own conversation. The one
240
242
  // exception to "a GET writes nothing": the strict same-site session
@@ -243,14 +245,14 @@ export function createChatHandlers(runtime) {
243
245
  // Membership chat only: it spends no dollars. Direct-API chat keeps a
244
246
  // one-tap Start with its visible spending limit (no password).
245
247
  const settingsView = url.searchParams.get("settings") === "1";
246
- if (!settingsView && enabled.ok && enabled.billing === "subscription" && principal !== null && (mateSession === null || mateSession.ceilingDigest !== principal.ceilingDigest) && !requestContext.getStore()?.workspaceRead) {
247
- startMateConversation(who, principal, enabled, 0, false, now);
248
- mateSession = store.activeMateSession(who.name);
248
+ if (!settingsView && enabled.ok && enabled.billing === "subscription" && principal !== null && (leadSession === null || leadSession.ceilingDigest !== principal.ceilingDigest) && !requestContext.getStore()?.workspaceRead) {
249
+ startLeadConversation(who, principal, enabled, 0, false, now);
250
+ leadSession = store.activeLeadSession(who.name);
249
251
  }
250
252
  // A session under another ceiling is not continuable from here; a GET
251
253
  // writes nothing (slice-2 review, finding 7) — the mint card below
252
254
  // starts a new conversation, and minting ends the old session.
253
- const ceilingStale = enabled.ok && mateSession !== null && principal !== null && mateSession.ceilingDigest !== principal.ceilingDigest;
255
+ const ceilingStale = enabled.ok && leadSession !== null && principal !== null && leadSession.ceilingDigest !== principal.ceilingDigest;
254
256
  const catchUp = leadBriefHtml(assignmentCatchUp(store, now, { principal: "operator", repos }, { limit: 6 }, evidenceRoot));
255
257
  const chatProjects = repos.map((repo, index) => {
256
258
  let peek = null;
@@ -267,7 +269,7 @@ export function createChatHandlers(runtime) {
267
269
  if (repos.length > 0) {
268
270
  try {
269
271
  fleetSnapshot = store.chatSnapshot(repos, now);
270
- const summaries = workIndexPage(store, now, { principal: 'operator', repos, includeUnplaced: false, viewer: who.name }, { limit: 100 }).items;
272
+ const summaries = workIndexPage(store, now, { principal: 'operator', repos, includeUnplaced: false, viewer: who.name }, { limit: 100, root: evidenceRoot }).items;
271
273
  fleetSnapshot.assignmentStates = Object.fromEntries(summaries.flatMap(value => [value.rootId, value.activeTaskId].map(id => [id, { state: value.assignmentState, label: value.status.label, detail: value.status.detail }])));
272
274
  fleetSnapshot.attentionCount = needsYouBadge(null);
273
275
  }
@@ -284,12 +286,12 @@ export function createChatHandlers(runtime) {
284
286
  const withFirstRun = (shown) => firstRun === undefined && phone === undefined && home === null ? shown
285
287
  : { ...shown, ...(home === null ? {} : { refreshSeconds: liveRefreshSeconds() }),
286
288
  workspace: { ...shown.workspace, ...(firstRun === undefined ? {} : { firstRun }), ...(phone === undefined ? {} : { phone }), ...(home === null ? {} : { home }) } };
287
- if (enabled.ok && mateSession !== null && principal !== null && !ceilingStale) {
289
+ if (enabled.ok && leadSession !== null && principal !== null && !ceilingStale) {
288
290
  {
289
- const said = takeMateNote(who.session.csrf, mateSession.id);
290
- return sendScreen(response, 200, withFirstRun(matePage(chromeFor(null, "chat", undefined, "all"), {
291
- session: mateSession,
292
- ...mateConversationRows(who, principal, focusTask, now, chatProject),
291
+ const said = takeLeadNote(who.session.csrf, leadSession.id);
292
+ return sendScreen(response, 200, withFirstRun(leadPage(chromeFor(null, "chat", undefined, "all"), {
293
+ session: leadSession,
294
+ ...leadConversationRows(who, principal, focusTask, now, chatProject),
293
295
  chatProject,
294
296
  latched,
295
297
  config: enabled.config,
@@ -338,9 +340,9 @@ export function createChatHandlers(runtime) {
338
340
  problem: url.searchParams.get("said") ??
339
341
  focusProblem ??
340
342
  (ceilingStale ? "Project access changed. Your tasks and results are saved. Start a conversation with the current projects to continue." : null) ??
341
- takeMateNote(who.session.csrf, null),
343
+ takeLeadNote(who.session.csrf, null),
342
344
  resultPanel,
343
- ...(enabled.ok && who.role === "approver" ? { mateMint: mateMintCard(enabled, focusTask === null ? "/chat" : taskChatHref(focusTask.id)) } : {}),
345
+ ...(enabled.ok && who.role === "approver" ? { leadMint: leadMintCard(enabled, focusTask === null ? "/chat" : taskChatHref(focusTask.id)) } : {}),
344
346
  ...(who.role === "approver" ? { coordinatorProposals: coordinatorProposalsSection(coordinatorRows, decisionsFor(store, coordinatorRows), now, true, focusTask === null ? null : taskChatHref(focusTask.id)) } : {}),
345
347
  })));
346
348
  }
@@ -361,7 +363,7 @@ export function createChatHandlers(runtime) {
361
363
  const { url, response } = ctx;
362
364
  const who = browserCaller(ctx.who);
363
365
  const proposalId = Number(url.pathname.split("/")[3]);
364
- const reviewProposal = store.getMateProposal(proposalId);
366
+ const reviewProposal = store.getLeadProposal(proposalId);
365
367
  const sharedConversation = reviewProposal ? store.handle.prepare('SELECT id FROM team_conversation WHERE thread=?').get(reviewProposal.thread) : null;
366
368
  let principal = null;
367
369
  if (sharedConversation) {
@@ -375,12 +377,12 @@ export function createChatHandlers(runtime) {
375
377
  }
376
378
  }
377
379
  else
378
- principal = matePrincipal(who);
380
+ principal = leadPrincipal(who);
379
381
  if (principal === null)
380
382
  return refuse(response, who, 403, 'Your access changed. Sign in again.', '/chat');
381
383
  try {
382
- const id = proposalId, saved = store.getMateProposal(id), savedAction = saved?.kind === 'action' ? sharedActionPayload(saved.payload) : null;
383
- if (saved && savedAction && saved.state !== 'pending' && (sharedConversation !== null || store.getMateThread(saved.thread)?.approver === principal.name) && principal.repos.includes(savedAction.repo) && store.accountCanAccess(principal.name, savedAction.repo)) {
384
+ const id = proposalId, saved = store.getLeadProposal(id), savedAction = saved?.kind === 'action' ? sharedActionPayload(saved.payload) : null;
385
+ if (saved && savedAction && saved.state !== 'pending' && (sharedConversation !== null || store.getLeadThread(saved.thread)?.approver === principal.name) && principal.repos.includes(savedAction.repo) && store.accountCanAccess(principal.name, savedAction.repo)) {
384
386
  const task = typeof saved.outcome?.['taskId'] === 'string' ? saved.outcome['taskId'] : typeof savedAction.request['task'] === 'string' ? savedAction.request['task'] : null;
385
387
  const destination = task ? taskHref(task) : `/settings/${savedAction.operation.startsWith('skill_') ? 'skills' : 'knowledge'}?repo=${encodeURIComponent(savedAction.repo)}`;
386
388
  const said = typeof saved.outcome?.['said'] === 'string' ? saved.outcome['said'] : saved.state === 'dismissed' ? 'Action dismissed.' : saved.state === 'expired' ? 'This proposal expired. Ask for a fresh proposal.' : 'This action has no completed outcome yet.';
@@ -459,7 +461,7 @@ export function createChatHandlers(runtime) {
459
461
  : verb === "change" ? lead.change(id, note, now)
460
462
  : verb === "revise" ? lead.requestChanges(id, note, now)
461
463
  : lead.complete(id, now, (taskId, runId) => {
462
- const principal = matePrincipal(who);
464
+ const principal = leadPrincipal(who);
463
465
  if (principal === null)
464
466
  return { ok: false, message: "Your access changed. Sign in again." };
465
467
  const current = assignmentOf(store, taskId, now, { principal: "operator", repos: principal.repos }, evidenceRoot);
@@ -613,11 +615,11 @@ export function createChatHandlers(runtime) {
613
615
  return redirect(response, back);
614
616
  }
615
617
  // ---- the mate (mate arc §5) ------------------------------------------
616
- async function mateMint(ctx) {
618
+ async function leadMint(ctx) {
617
619
  const { who, response, now, posted } = ctx;
618
- const body = readForm(posted, CONSOLE_FORMS.mateMint);
620
+ const body = readForm(posted, CONSOLE_FORMS.leadMint);
619
621
  if (who.via !== "cookie")
620
- return refuse(response, who, 403, "the mate is a browser surface");
622
+ return refuse(response, who, 403, "the lead is a browser surface");
621
623
  const back = safeChatReturn(body.get("return"));
622
624
  const enabled = chatEnablement();
623
625
  if (!enabled.ok)
@@ -636,60 +638,60 @@ export function createChatHandlers(runtime) {
636
638
  const verified = token === "" ? verifyApproverStanding(store, who.name, who.session.generation, managedRepos()) : verifyApproverByPassword(store, who.name, token, managedRepos());
637
639
  if (!verified.ok)
638
640
  return redirect(response, chatReturnWithSaid(back, token === "" ? "Your access changed. Sign in again to start chat." : "That password did not match."));
639
- startMateConversation(who, verified.who, enabled, ceilingUsd, body.get("follow") === "yes", now);
641
+ startLeadConversation(who, verified.who, enabled, ceilingUsd, body.get("follow") === "yes", now);
640
642
  return redirect(response, back);
641
643
  }
642
- async function mateFollow(ctx) {
644
+ async function leadFollow(ctx) {
643
645
  const { who, response, now, posted } = ctx;
644
- const body = readForm(posted, CONSOLE_FORMS.mateFollow);
646
+ const body = readForm(posted, CONSOLE_FORMS.leadFollow);
645
647
  if (who.via !== "cookie")
646
648
  return refuse(response, who, 403, "Open the conversation to change automatic updates.");
647
- const principal = matePrincipal(who), session = store.activeMateSession(who.name), thread = store.liveMateThreadFor(who.name);
649
+ const principal = leadPrincipal(who), session = store.activeLeadSession(who.name), thread = store.liveLeadThreadFor(who.name);
648
650
  if (!principal || !session || !thread || !configureLeadFollow(store, principal, session, thread, body.get("enabled") === "yes", now))
649
651
  return refuse(response, who, 409, "Conversation access changed. Start chat again.", "/chat");
650
652
  return redirect(response, safeChatReturn(body.get("return")));
651
653
  }
652
- async function mateEnd(ctx) {
654
+ async function leadEnd(ctx) {
653
655
  const { who, response, now, posted } = ctx;
654
- const body = readForm(posted, CONSOLE_FORMS.mateEnd);
656
+ const body = readForm(posted, CONSOLE_FORMS.leadEnd);
655
657
  if (who.via !== "cookie")
656
- return refuse(response, who, 403, "the mate is a browser surface");
658
+ return refuse(response, who, 403, "the lead is a browser surface");
657
659
  const back = safeChatReturn(body.get("return"));
658
660
  // Ending spend and forgetting the thread takes no password: any
659
661
  // approver may revoke (§1), and the thread is theirs to drop (ruling 11).
660
- store.failLiveMateTurnsFor(who.name, "ended", now);
661
- store.endMateSessionsFor(who.name, who.name, now);
662
- store.closeMateThreadsFor(who.name, now);
663
- mateSaid.delete(who.session.csrf);
662
+ store.failLiveLeadTurnsFor(who.name, "ended", now);
663
+ store.endLeadSessionsFor(who.name, who.name, now);
664
+ store.closeLeadThreadsFor(who.name, now);
665
+ leadSaid.delete(who.session.csrf);
664
666
  return redirect(response, back);
665
667
  }
666
- async function mateStop(ctx) {
668
+ async function leadStop(ctx) {
667
669
  const { who, response, now, posted } = ctx;
668
- const body = readForm(posted, CONSOLE_FORMS.mateStop);
670
+ const body = readForm(posted, CONSOLE_FORMS.leadStop);
669
671
  if (who.via !== "cookie")
670
- return refuse(response, who, 403, "the mate is a browser surface");
672
+ return refuse(response, who, 403, "the lead is a browser surface");
671
673
  const back = safeChatReturn(body.get("return"));
672
674
  const wanted = Number(body.get("turn") ?? "");
673
- const live = store.liveMateTurnFor(who.name);
675
+ const live = store.liveLeadTurnFor(who.name);
674
676
  if (!Number.isInteger(wanted) || live === null || live.id !== wanted) {
675
- noteMate(who.session.csrf, null, "that turn has already finished");
677
+ noteLead(who.session.csrf, null, "that turn has already finished");
676
678
  return redirect(response, chatReturnWithLatest(back));
677
679
  }
678
680
  // A stopped direct-API turn is conservatively charged its reserved
679
681
  // worst case: dispatch may already have happened. Membership turns
680
682
  // reserve zero. The conversation itself stays live.
681
- store.failLiveMateTurnsFor(who.name, "stopped", now);
682
- noteMate(who.session.csrf, live.id, "stopped — the conversation is still open");
683
+ store.failLiveLeadTurnsFor(who.name, "stopped", now);
684
+ noteLead(who.session.csrf, live.id, "stopped — the conversation is still open");
683
685
  return redirect(response, chatReturnWithLatest(back));
684
686
  }
685
687
  const chatProposalAct = (op) => async (ctx) => {
686
688
  const { url, who, request, response, now, posted } = ctx;
687
- const body = readForm(posted, CONSOLE_FORMS.mateProposal);
689
+ const body = readForm(posted, CONSOLE_FORMS.leadProposal);
688
690
  if (who.via !== "cookie")
689
- return refuse(response, who, 403, "the mate is a browser surface");
691
+ return refuse(response, who, 403, "the lead is a browser surface");
690
692
  const back = safeChatReturn(body.get("return"));
691
693
  const id = Number(url.pathname.split("/")[3]);
692
- const proposalRow = store.getMateProposal(id);
694
+ const proposalRow = store.getLeadProposal(id);
693
695
  const shared = proposalRow && store.handle.prepare('SELECT id FROM team_conversation WHERE thread=?').get(proposalRow.thread);
694
696
  const proposalTaskHref = (taskId) => taskChatHref(taskId) + (shared ? '&conversation=' + encodeURIComponent(String(shared['id'])) : '');
695
697
  let principal = null;
@@ -704,7 +706,7 @@ export function createChatHandlers(runtime) {
704
706
  }
705
707
  }
706
708
  else
707
- principal = matePrincipal(who);
709
+ principal = leadPrincipal(who);
708
710
  // Confirm in place (chat cards): the same door and checks, answered in
709
711
  // JSON so the conversation stays where it is; the card's refreshed
710
712
  // state carries the result. The secure review screen stays a form.
@@ -713,14 +715,14 @@ export function createChatHandlers(runtime) {
713
715
  if (principal === null)
714
716
  return cardJson ? cardAnswer(403, false, "Your approver standing changed. Sign in again.") : refuse(response, who, 403, "your approver standing changed — sign in again", back);
715
717
  if (op === "dismiss") {
716
- const dismissed = dismissMateProposal(store, principal, id, now);
718
+ const dismissed = dismissLeadProposal(store, principal, id, now);
717
719
  if (cardJson)
718
720
  return cardAnswer(dismissed ? 200 : 409, dismissed, dismissed ? "Dismissed." : "That card was already acted on.");
719
721
  if (!dismissed)
720
- noteMate(who.session.csrf, null, "that proposal was already acted on");
722
+ noteLead(who.session.csrf, null, "that proposal was already acted on");
721
723
  return redirect(response, chatReturnWithLatest(back));
722
724
  }
723
- const outcome = confirmMateProposal(store, principal, id, now, { chatProvider: teamChatProvider, confirm: body.get("confirm") === "yes", via: "web", evidenceRoot, ...(body.has("nonce") ? { actionReview: { nonce: body.get("nonce") ?? "", password: body.get("token") ?? "" } } : {}) });
725
+ const outcome = confirmLeadProposal(store, principal, id, now, { chatProvider: teamChatProvider, confirm: body.get("confirm") === "yes", via: "web", evidenceRoot, ...(body.has("nonce") ? { actionReview: { nonce: body.get("nonce") ?? "", password: body.get("token") ?? "" } } : {}) });
724
726
  if (cardJson) {
725
727
  if (!outcome.ok)
726
728
  return cardAnswer(outcome.reason === "standing" ? 403 : outcome.reason === "not-yours" ? 404 : 409, false, outcome.said);
@@ -737,7 +739,7 @@ export function createChatHandlers(runtime) {
737
739
  if (!outcome.ok && shared)
738
740
  return redirect(response, chatReturnWithSaid(back, outcome.said));
739
741
  if (!outcome.ok && outcome.reason === "needs-confirm")
740
- noteMate(who.session.csrf, null, outcome.said);
742
+ noteLead(who.session.csrf, null, outcome.said);
741
743
  // A confirmed task proposal leads to the task it actually created
742
744
  // (package 2): the lens over this same conversation, whose journey
743
745
  // and plan are the next step. The id is the door's recorded
@@ -748,7 +750,7 @@ export function createChatHandlers(runtime) {
748
750
  return redirect(response, `${proposalTaskHref(outcome.taskId)}#task-chat-live`);
749
751
  }
750
752
  if (outcome.ok && outcome.kind === "review" && outcome.taskId !== null) {
751
- const proposal = store.getMateProposal(id);
753
+ const proposal = store.getLeadProposal(id);
752
754
  // A revision confirmed here returns to its receipt: the newest
753
755
  // reply, whose confirmed card names the revision and links to the
754
756
  // plan. Landing at the top left that message under the phone's
@@ -758,7 +760,7 @@ export function createChatHandlers(runtime) {
758
760
  : `${proposalTaskHref(outcome.taskId)}&result=${Number(proposal.payload["run"])}#request-changes`);
759
761
  }
760
762
  if (outcome.ok && outcome.kind === "task_action" && outcome.taskId !== null) {
761
- const proposal = store.getMateProposal(id);
763
+ const proposal = store.getLeadProposal(id);
762
764
  if (proposal.payload["operation"] === "resume") {
763
765
  return armTaskResume(response, who, outcome.taskId, String(proposal.payload["run"]), "chat", now);
764
766
  }
@@ -773,7 +775,7 @@ export function createChatHandlers(runtime) {
773
775
  const body = readForm(posted, CONSOLE_FORMS.coordinatorProposal);
774
776
  if (who.via !== "cookie")
775
777
  return refuse(response, who, 403, "proposals are confirmed from the browser or the CLI");
776
- const principal = matePrincipal(who);
778
+ const principal = leadPrincipal(who);
777
779
  if (principal === null)
778
780
  return refuse(response, who, 403, "your approver standing changed — sign in again", "/chat");
779
781
  const id = Number(url.pathname.split("/")[2]);
@@ -835,34 +837,34 @@ export function createChatHandlers(runtime) {
835
837
  // A live mate session: the message is a mate turn — no password, the
836
838
  // session's ceremony already covered it (§1); the engine refuses on
837
839
  // its own terms and the thread shows why.
838
- const mateSession = who.role === "approver" ? store.activeMateSession(who.name) : null;
839
- if (mateSession !== null) {
840
+ const leadSession = who.role === "approver" ? store.activeLeadSession(who.name) : null;
841
+ if (leadSession !== null) {
840
842
  const requestId = body.get("request");
841
- if (requestId !== null && (body.getAll("request").length !== 1 || body.getAll("request-session").length !== 1 || body.get("request-session") !== String(mateSession.id))) {
843
+ if (requestId !== null && (body.getAll("request").length !== 1 || body.getAll("request-session").length !== 1 || body.get("request-session") !== String(leadSession.id))) {
842
844
  return said(409, "This conversation changed. Reload it before sending your message.");
843
845
  }
844
- const principal = matePrincipal(who);
846
+ const principal = leadPrincipal(who);
845
847
  if (principal === null)
846
848
  return wantsJson ? said(403, "your approver standing changed — sign in again") : refuse(response, who, 403, "your approver standing changed — sign in again", back);
847
849
  const message = (body.get("message") ?? "").trim();
848
- if (message === "" || message.length > MATE_MESSAGE_MAX_CHARS) {
849
- return said(400, `a message is 1 to ${MATE_MESSAGE_MAX_CHARS} characters`, mateSession.id);
850
+ if (message === "" || message.length > LEAD_MESSAGE_MAX_CHARS) {
851
+ return said(400, `a message is 1 to ${LEAD_MESSAGE_MAX_CHARS} characters`, leadSession.id);
850
852
  }
851
- const opened = store.openMateThread(who.name, principal.ceilingDigest, now, chatScopeOf(focusTask, chatProject));
853
+ const opened = store.openLeadThread(who.name, principal.ceilingDigest, now, chatScopeOf(focusTask, chatProject));
852
854
  const onProgress = beginLiveTurn(opened.thread.id);
853
- void runMateTurn({ store, who: principal, session: mateSession, thread: opened.thread, config: enabled.config, key: enabled.key, message, onProgress, channel: "console", ...(requestId === null ? {} : { requestId }), ...(focusTask === null && chatProject !== null ? { context: `Current project: ${projectName(chatProject)} (${chatProject}). Keep this conversation about that project unless the operator explicitly asks to broaden it; use it as the repo for project tools.` } : {}), ...(focusTask === null ? {} : { context: `Current task: ${focusTask.id}. Read it with get_task before answering or proposing changes. Read its currentExecution next and bind new actions to that exact execution. Never replace the target of a prior proposal with a newer revision. Keep this turn about that task unless the operator explicitly asks to broaden it.${resultContext}${modeContext}` }), fetcher: chatFetcher, ...(options.subscriptionChatRunner === undefined ? {} : { subscriptionRunner: options.subscriptionChatRunner }), clock, evidenceRoot })
855
+ void runLeadTurn({ store, who: principal, session: leadSession, thread: opened.thread, config: enabled.config, key: enabled.key, message, onProgress, channel: "console", ...(requestId === null ? {} : { requestId }), ...(focusTask === null && chatProject !== null ? { context: `Current project: ${projectName(chatProject)} (${chatProject}). Keep this conversation about that project unless the operator explicitly asks to broaden it; use it as the repo for project tools.` } : {}), ...(focusTask === null ? {} : { context: `Current task: ${focusTask.id}. Read it with get_task before answering or proposing changes. Read its currentExecution next and bind new actions to that exact execution. Never replace the target of a prior proposal with a newer revision. Keep this turn about that task unless the operator explicitly asks to broaden it.${resultContext}${modeContext}` }), fetcher: chatFetcher, ...(options.subscriptionChatRunner === undefined ? {} : { subscriptionRunner: options.subscriptionChatRunner }), clock, evidenceRoot })
854
856
  .then(outcome => {
855
857
  // A turn that saved its outcome in the thread (one stopped at its deadline) is said there, once.
856
858
  if (!outcome.ok && !("saved" in outcome && outcome.saved === true))
857
- noteMate(who.session.csrf, "turn" in outcome ? outcome.turn : null, outcome.message);
859
+ noteLead(who.session.csrf, "turn" in outcome ? outcome.turn : null, outcome.message);
858
860
  endLiveTurn(opened.thread.id, outcome.ok);
859
861
  })
860
- .catch(() => { noteMate(who.session.csrf, null, "the turn failed unexpectedly"); endLiveTurn(opened.thread.id, false); });
862
+ .catch(() => { noteLead(who.session.csrf, null, "the turn failed unexpectedly"); endLiveTurn(opened.thread.id, false); });
861
863
  // Accepted for the engine, not received: the receipt is written by
862
864
  // the turn's own admission, and the status poll is where the page
863
865
  // learns of it — the same road as after a native send.
864
866
  if (wantsJson)
865
- return respond(response, 202, "application/json", JSON.stringify({ ok: true, session: mateSession.id, task: focusTask?.id ?? "", ...(chatProject === null ? {} : { project: chatProject }), request: requestId }));
867
+ return respond(response, 202, "application/json", JSON.stringify({ ok: true, session: leadSession.id, task: focusTask?.id ?? "", ...(chatProject === null ? {} : { project: chatProject }), request: requestId }));
866
868
  return redirect(response, chatReturnWithLatest(back));
867
869
  }
868
870
  if (wantsJson)
@@ -1089,22 +1091,22 @@ export function createChatHandlers(runtime) {
1089
1091
  liveTurns.delete(thread); }, 60_000);
1090
1092
  live.expiry.unref();
1091
1093
  }
1092
- function noteMate(csrf, turn, message) {
1093
- if (mateSaid.size >= 500) {
1094
- const oldest = mateSaid.keys().next().value;
1094
+ function noteLead(csrf, turn, message) {
1095
+ if (leadSaid.size >= 500) {
1096
+ const oldest = leadSaid.keys().next().value;
1095
1097
  if (oldest !== undefined)
1096
- mateSaid.delete(oldest);
1098
+ leadSaid.delete(oldest);
1097
1099
  }
1098
- mateSaid.set(csrf, { turn, message });
1100
+ leadSaid.set(csrf, { turn, message });
1099
1101
  }
1100
- function takeMateNote(csrf, liveSession) {
1101
- const noted = mateSaid.get(csrf);
1102
+ function takeLeadNote(csrf, liveSession) {
1103
+ const noted = leadSaid.get(csrf);
1102
1104
  if (noted === undefined)
1103
1105
  return null;
1104
- mateSaid.delete(csrf);
1106
+ leadSaid.delete(csrf);
1105
1107
  if (noted.turn === null)
1106
1108
  return noted.message;
1107
- const turn = store.getMateTurn(noted.turn);
1109
+ const turn = store.getLeadTurn(noted.turn);
1108
1110
  return turn !== null && turn.session === liveSession ? noted.message : null;
1109
1111
  }
1110
1112
  /** A finished demo exchange's result, read back from the evidence its run actually stored. */
@@ -1291,9 +1293,9 @@ export function createChatHandlers(runtime) {
1291
1293
  agent: `${providerName(run.provider)} on ${run.runner}`, phase: homePhaseWords(run), project: run.repo === null ? null : projectName(run.repo), since: run.startedAt,
1292
1294
  activity: runActivityOf(store, run, now) };
1293
1295
  });
1294
- const all = workIndexPage(store, now, access, { view: "all", limit: WORK_INDEX_MAX_LIMIT });
1295
- const needs = workIndexPage(store, now, access, { view: "needs-you", limit: WORK_INDEX_MAX_LIMIT });
1296
- const done = workIndexPage(store, now, access, { view: "completed", limit: WORK_INDEX_MAX_LIMIT });
1296
+ const all = workIndexPage(store, now, access, { view: "all", limit: WORK_INDEX_MAX_LIMIT, root: evidenceRoot });
1297
+ const needs = workIndexPage(store, now, access, { view: "needs-you", limit: WORK_INDEX_MAX_LIMIT, root: evidenceRoot });
1298
+ const done = workIndexPage(store, now, access, { view: "completed", limit: WORK_INDEX_MAX_LIMIT, root: evidenceRoot });
1297
1299
  const weekAgo = new Date(now.getTime() - 7 * 86_400_000).toISOString();
1298
1300
  const counts = [
1299
1301
  { key: "working", label: "Working now", value: agents.length, href: "/work?view=running" },
@@ -1312,7 +1314,7 @@ export function createChatHandlers(runtime) {
1312
1314
  return href === null ? null : { label: one.primaryAction.label, href };
1313
1315
  })(),
1314
1316
  }));
1315
- const windows = who.via === "cookie" && store.isInstanceOperator(who.name) ? limitsView(store.providerLimits(), [], { project: projectName, teammate: id => `Teammate ${id}` }, now) : null;
1317
+ const windows = who.via === "cookie" && store.isInstanceOperator(who.name) ? limitsView(store.providerLimits(), [], { project: projectName, subagent: id => `Subagent ${id}` }, now) : null;
1316
1318
  // One line: what this person's lead is doing now and when it last acted.
1317
1319
  const activity = leadActivity(store, who.name);
1318
1320
  const leadTask = activity?.taskId == null ? null : store.lookupRef(activity.taskId);
@@ -1382,10 +1384,10 @@ export function createChatHandlers(runtime) {
1382
1384
  "push.remove": pushRemove,
1383
1385
  "onboarding.phone-dismiss": phoneDismiss,
1384
1386
  "chat.config": chatConfig,
1385
- "chat.mate-mint": mateMint,
1386
- "chat.mate-follow": mateFollow,
1387
- "chat.mate-end": mateEnd,
1388
- "chat.mate-stop": mateStop,
1387
+ "chat.mate-mint": leadMint,
1388
+ "chat.mate-follow": leadFollow,
1389
+ "chat.mate-end": leadEnd,
1390
+ "chat.mate-stop": leadStop,
1389
1391
  "chat.proposal.confirm": chatProposalAct("confirm"),
1390
1392
  "chat.proposal.dismiss": chatProposalAct("dismiss"),
1391
1393
  "coordinator.proposal.confirm": coordinatorProposalAct("confirm"),
@@ -1394,5 +1396,5 @@ export function createChatHandlers(runtime) {
1394
1396
  "chat.file": chatFile,
1395
1397
  "chat.ack-send": chatAckSend,
1396
1398
  });
1397
- return { registrations, chatFirstRun, phoneCard, chatProjectOf, beginLiveTurn, endLiveTurn, noteMate, takeMateNote, demoResultView, storeChatKey, forgetChatKey, sweepChatDrafts, runChatTurn, chatHomeOf, firstTasksFor, approverCandidateCount, evictOldestCandidate };
1399
+ return { registrations, chatFirstRun, phoneCard, chatProjectOf, beginLiveTurn, endLiveTurn, noteLead, takeLeadNote, demoResultView, storeChatKey, forgetChatKey, sweepChatDrafts, runChatTurn, chatHomeOf, firstTasksFor, approverCandidateCount, evictOldestCandidate };
1398
1400
  }
@@ -41,7 +41,7 @@ import {} from "../store.js";
41
41
  import { styleAsset } from "../style-asset.js";
42
42
  import { spendLine } from "../summary.js";
43
43
  import { TASK_STATUS_CSS } from "../task-status.js";
44
- import { TEAMMATE_CSS } from "../teammates-ui.js";
44
+ import { SUBAGENT_CSS } from "../subagents-ui.js";
45
45
  import { UPDATES_CSS } from "../toolroll-update-ui.js";
46
46
  import { TOOLS_CSS } from "../tools-ui.js";
47
47
  import { TRANSITIONS_CSS } from "../transitions-recipes.js";
@@ -144,7 +144,7 @@ export const INBOX_TABS_CSS = '.inbox-tabs{display:inline-flex;gap:2px;max-width
144
144
  '.inbox-ask{margin:18px 0 0}.inbox-ask>h2{display:flex;align-items:baseline;gap:8px;margin:0 0 4px;font-size:15px;font-weight:600}.inbox-ask>h2 .count{font:500 12px var(--so-mono,ui-monospace,monospace);font-variant-numeric:tabular-nums;color:var(--so-muted)}.inbox-ask h3{font-size:13px;font-weight:600;margin:12px 0 4px}' +
145
145
  '.inbox-unread{display:none;position:absolute;top:4px;right:3px;width:6px;height:6px;border-radius:50%;background:var(--so-signal)}' +
146
146
  '@media (max-width:760px){.inbox-tabs{display:flex;width:100%}.inbox-tabs a{flex:1;justify-content:center;min-height:44px;padding:0 6px}.inbox-unread{display:block}}';
147
- export const WORKSPACE_STYLE = styleAsset(STYLE + BRAND_MARK_CSS + INBOX_TABS_CSS + APPROVAL_RULES_CSS + SPEND_CSS + RETENTION_CSS + STORAGE_CSS + UPDATES_CSS + LIMITS_CSS + MONITORING_CSS + INTEGRATIONS_CSS + BACKUP_CSS + EXPORT_CSS + PROJECT_DELETE_CSS + POLICY_CSS + EVIDENCE_PACK_CSS + THEME_CONTROLS_CSS + CODING_CSS + CODING_SHIPPING_CSS + RECIPE_CSS + SKILLS_CSS + TOOLS_CSS + FLOWS_CSS + TEAMMATE_CSS + KITS_CSS + STARTERS_CSS + GALLERY_CSS + SSO_CSS + CREDENTIALS_CSS + REQUEST_LIMITS_CSS + PEOPLE_AUDIT_CSS + KNOWLEDGE_CSS + MODELS_CSS + CHAT_POLISH_CSS + TRANSITIONS_CSS + WORKSPACE_MOTION_CSS + ASSIGNMENT_CSS + TASK_STATUS_CSS + LEAD_CONTEXT_CSS + PULL_REQUEST_SETTINGS_CSS + CHECK_SETTINGS_CSS + DISCLOSURE_CSS + '.learning{min-width:0;overflow-wrap:anywhere}.learning .card{min-width:0}.learning code,.learning blockquote,.learning pre{white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word}.learning button,.learning summary,.learning .button-link{min-height:44px}.learning button{white-space:nowrap}.learning summary{padding:12px 0;cursor:pointer}.learning form{margin:12px 0}.learning select{max-width:100%}.learning blockquote{margin:8px 0}.learning ul{padding-left:20px}');
147
+ export const WORKSPACE_STYLE = styleAsset(STYLE + BRAND_MARK_CSS + INBOX_TABS_CSS + APPROVAL_RULES_CSS + SPEND_CSS + RETENTION_CSS + STORAGE_CSS + UPDATES_CSS + LIMITS_CSS + MONITORING_CSS + INTEGRATIONS_CSS + BACKUP_CSS + EXPORT_CSS + PROJECT_DELETE_CSS + POLICY_CSS + EVIDENCE_PACK_CSS + THEME_CONTROLS_CSS + CODING_CSS + CODING_SHIPPING_CSS + RECIPE_CSS + SKILLS_CSS + TOOLS_CSS + FLOWS_CSS + SUBAGENT_CSS + KITS_CSS + STARTERS_CSS + GALLERY_CSS + SSO_CSS + CREDENTIALS_CSS + REQUEST_LIMITS_CSS + PEOPLE_AUDIT_CSS + KNOWLEDGE_CSS + MODELS_CSS + CHAT_POLISH_CSS + TRANSITIONS_CSS + WORKSPACE_MOTION_CSS + ASSIGNMENT_CSS + TASK_STATUS_CSS + LEAD_CONTEXT_CSS + PULL_REQUEST_SETTINGS_CSS + CHECK_SETTINGS_CSS + DISCLOSURE_CSS + '.learning{min-width:0;overflow-wrap:anywhere}.learning .card{min-width:0}.learning code,.learning blockquote,.learning pre{white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word}.learning button,.learning summary,.learning .button-link{min-height:44px}.learning button{white-space:nowrap}.learning summary{padding:12px 0;cursor:pointer}.learning form{margin:12px 0}.learning select{max-width:100%}.learning blockquote{margin:8px 0}.learning ul{padding-left:20px}');
148
148
  /**
149
149
  * A server-rendered region that stays current: when the page's live stream
150
150
  * says something changed (GET /live, live-bus.ts), fetch this page's own
@@ -675,7 +675,7 @@ export const NAV_ICONS = {
675
675
  */
676
676
  export function workToolRows(scoped = false, _offersChat = false, offersCode = false) {
677
677
  const rows = [
678
- ...(offersCode ? [{ key: "code", href: "/code", label: "Coding sessions", hint: "Direct coding sessions and their saved results" }] : []),
678
+ // D5: coding sessions are deprecated; /code still opens this release, but nothing links to it.
679
679
  { key: "inbox", href: "/inbox", label: "Inbox", hint: "questions, approvals, and repairs to act on" },
680
680
  { key: "board", href: "/board", label: "Board", hint: "lanes by state, with the order view" },
681
681
  { key: "tasks", href: "/tasks", label: "Task list", hint: "everything, filterable by state" },