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 @@
2
2
  * Deleting a project: everything Toolroll holds for it goes, and
3
3
  * nothing else. Its tasks and their versions, runs and their evidence, the
4
4
  * checkouts and branches Toolroll made, its chats and threads, flows
5
- * and cards, teammates, budgets and settings. Never while any of its work is
5
+ * and cards, subagents, budgets and settings. Never while any of its work is
6
6
  * running. The project's own repository, its working copy and every branch
7
7
  * Toolroll didn't make stay exactly as they are. The action ledger
8
8
  * keeps every entry (its hash chain still verifies) and gains one: who
@@ -36,13 +36,13 @@ function doomed(db, repo) {
36
36
  const runs = ids(db, "SELECT id FROM run WHERE task_ref IN (SELECT value FROM json_each(?))", json(refs));
37
37
  const artifacts = ids(db, "SELECT id FROM artifact WHERE run IN (SELECT value FROM json_each(?))", json(runs));
38
38
  // A team conversation's thread is the team's, whatever it talked about.
39
- const threads = ids(db, `SELECT id FROM mate_thread WHERE ((scope_kind = 'project' AND scope_key = ?) OR (scope_kind = 'task' AND scope_key IN (SELECT value FROM json_each(?))))
39
+ const threads = ids(db, `SELECT id FROM lead_thread WHERE ((scope_kind = 'project' AND scope_key = ?) OR (scope_kind = 'task' AND scope_key IN (SELECT value FROM json_each(?))))
40
40
  AND id NOT IN (SELECT thread FROM team_conversation WHERE thread IS NOT NULL)`, repo, json(tasks));
41
41
  const turns = ids(db, "SELECT id FROM mate_turn WHERE thread IN (SELECT value FROM json_each(?))", json(threads));
42
42
  const flows = ids(db, "SELECT id FROM flow WHERE repo = ?", repo);
43
43
  const cards = ids(db, "SELECT id FROM flow_card WHERE flow IN (SELECT value FROM json_each(?))", json(flows));
44
- const teammates = ids(db, "SELECT id FROM teammate WHERE repo = ?", repo);
45
- return { refs, tasks, runs, artifacts, threads, turns, flows, cards, teammates };
44
+ const subagents = ids(db, "SELECT id FROM subagent WHERE repo = ?", repo);
45
+ return { refs, tasks, runs, artifacts, threads, turns, flows, cards, subagents };
46
46
  }
47
47
  /** A project's settings: one row per thing someone set for it. */
48
48
  const SETTINGS = [
@@ -61,8 +61,8 @@ export function projectHoldings(store, repo) {
61
61
  tasks: count("SELECT COUNT(DISTINCT external_id) AS n FROM task_ref WHERE repo = ?", repo) - versions, versions,
62
62
  runs: d.runs.length, evidence: d.artifacts.length,
63
63
  checkouts: count("SELECT COUNT(*) AS n FROM worktree WHERE repo = ?", repo),
64
- chats: d.threads.length, flows: d.flows.length, cards: d.cards.length, teammates: d.teammates.length,
65
- budgets: count(`SELECT COUNT(*) AS n FROM budget WHERE removed_at IS NULL AND ((scope_kind = 'project' AND scope_key = ?) OR (scope_kind = 'teammate' AND scope_key IN (SELECT CAST(value AS TEXT) FROM json_each(?))))`, repo, json(d.teammates)),
64
+ chats: d.threads.length, flows: d.flows.length, cards: d.cards.length, subagents: d.subagents.length,
65
+ budgets: count(`SELECT COUNT(*) AS n FROM budget WHERE removed_at IS NULL AND ((scope_kind = 'project' AND scope_key = ?) OR (scope_kind = 'subagent' AND scope_key IN (SELECT CAST(value AS TEXT) FROM json_each(?))))`, repo, json(d.subagents)),
66
66
  settings: SETTINGS.filter(([table]) => tableExists(db, table)).reduce((sum, [table, column]) => sum + count(`SELECT COUNT(*) AS n FROM ${table} WHERE ${column} = ?`, repo), 0),
67
67
  };
68
68
  }
@@ -72,7 +72,7 @@ export function holdingsWords(held) {
72
72
  plural(held.tasks, "task"), ...(held.versions > 0 ? [plural(held.versions, "version")] : []), plural(held.runs, "run"),
73
73
  ...(held.evidence > 0 ? [plural(held.evidence, "evidence file")] : []), ...(held.checkouts > 0 ? [plural(held.checkouts, "checkout")] : []),
74
74
  ...(held.branches ? [plural(held.branches, "branch", "branches")] : []), ...(held.chats > 0 ? [plural(held.chats, "chat")] : []),
75
- ...(held.flows > 0 ? [`${plural(held.flows, "flow")} with ${plural(held.cards, "card")}`] : []), ...(held.teammates > 0 ? [plural(held.teammates, "teammate")] : []),
75
+ ...(held.flows > 0 ? [`${plural(held.flows, "flow")} with ${plural(held.cards, "card")}`] : []), ...(held.subagents > 0 ? [plural(held.subagents, "subagent")] : []),
76
76
  ...(held.budgets > 0 ? [plural(held.budgets, "budget")] : []), ...(held.settings > 0 ? [plural(held.settings, "setting")] : []),
77
77
  ];
78
78
  return parts.join(", ");
@@ -87,7 +87,7 @@ export function projectRunning(store, repo, now) {
87
87
  const slots = count("SELECT COUNT(*) AS n FROM execution_slot WHERE state IN ('reserved','running') AND run IN (SELECT value FROM json_each(?))", json(d.runs));
88
88
  const chats = count("SELECT COUNT(*) AS n FROM mate_turn WHERE state IN ('queued','running') AND id IN (SELECT value FROM json_each(?))", json(d.turns));
89
89
  const steps = count("SELECT COUNT(*) AS n FROM flow_step_run WHERE state = 'running' AND card IN (SELECT value FROM json_each(?))", json(d.cards));
90
- const calls = count("SELECT COUNT(*) AS n FROM teammate_call WHERE state = 'running' AND teammate IN (SELECT value FROM json_each(?))", json(d.teammates));
90
+ const calls = count("SELECT COUNT(*) AS n FROM subagent_call WHERE state = 'running' AND subagent IN (SELECT value FROM json_each(?))", json(d.subagents));
91
91
  const merges = count(`SELECT COUNT(*) AS n FROM merge_intent WHERE state IN ('claimed','firing') AND publication IN (SELECT id FROM publication WHERE task_ref IN (SELECT value FROM json_each(?)))`, json(d.refs));
92
92
  const checkouts = count("SELECT COUNT(*) AS n FROM worktree WHERE repo = ? AND runner IS NOT NULL AND released_at IS NULL", repo);
93
93
  return [
@@ -95,7 +95,7 @@ export function projectRunning(store, repo, now) {
95
95
  ...(slots > 0 ? ["an agent is working"] : []),
96
96
  ...(chats > 0 ? ["a chat is answering"] : []),
97
97
  ...(steps > 0 ? ["a flow step is running"] : []),
98
- ...(calls > 0 ? ["a teammate is working"] : []),
98
+ ...(calls > 0 ? ["a subagent is working"] : []),
99
99
  ...(merges > 0 ? ["a merge is in progress"] : []),
100
100
  ...(checkouts > 0 && building === 0 ? [`${plural(checkouts, "checkout")} ${checkouts === 1 ? "is" : "are"} in use`] : []),
101
101
  ];
@@ -201,12 +201,12 @@ function deleteRows(store, repo, d, now) {
201
201
  rows += Number(db.prepare(`DELETE FROM ${table} WHERE ${where}`).run(...params).changes);
202
202
  };
203
203
  const IN = (column) => `${column} IN (SELECT value FROM json_each(?))`;
204
- const R = json(d.runs), T = json(d.refs), K = json(d.tasks), A = json(d.artifacts), H = json(d.threads), U = json(d.turns), F = json(d.flows), C = json(d.cards), M = json(d.teammates);
204
+ const R = json(d.runs), T = json(d.refs), K = json(d.tasks), A = json(d.artifacts), H = json(d.threads), U = json(d.turns), F = json(d.flows), C = json(d.cards), M = json(d.subagents);
205
205
  const decisions = json(ids(db, `SELECT id FROM decision WHERE ${IN("run")}`, R));
206
206
  const publications = json(ids(db, `SELECT id FROM publication WHERE ${IN("task_ref")} OR ${IN("run")}`, T, R));
207
207
  const notifications = json(ids(db, `SELECT id FROM notification WHERE project = ? OR ${IN("task_ref")} OR ${IN("source_run")}`, repo, T, R));
208
- const questions = json(ids(db, `SELECT id FROM teammate_question WHERE ${IN("teammate")} OR ${IN("card")}`, M, C));
209
- const proposals = json(ids(db, `SELECT id FROM mate_proposal WHERE ${IN("thread")}`, H));
208
+ const questions = json(ids(db, `SELECT id FROM subagent_question WHERE ${IN("subagent")} OR ${IN("card")}`, M, C));
209
+ const proposals = json(ids(db, `SELECT id FROM lead_proposal WHERE ${IN("thread")}`, H));
210
210
  // A chat message about one of the project's tasks (its turn named it), with its reply. Event ids are text ('m123'),
211
211
  // and the same id can be another app's: each is kept whole, as provider:id.
212
212
  const conversations = json(list(db, `SELECT provider || ':' || id FROM chat_event WHERE kind = 'message' AND json_extract(payload, '$.about.task') IN (SELECT value FROM json_each(?))`, K));
@@ -241,13 +241,13 @@ function deleteRows(store, repo, d, now) {
241
241
  del("chat_focus", IN("task"), K);
242
242
  del("team_task_owner", IN("task_ref"), T);
243
243
  del("team_mate_session", IN("thread"), H);
244
- del("mate_turn_evidence", `${IN("turn")} OR ${IN("run")} OR ${IN("task_ref")} OR ${IN("artifact")}`, U, R, T, A);
244
+ del("lead_turn_evidence", `${IN("turn")} OR ${IN("run")} OR ${IN("task_ref")} OR ${IN("artifact")}`, U, R, T, A);
245
245
  del("chat_turn", IN("mate_turn"), U);
246
- del("mate_ask", IN("turn"), U);
246
+ del("lead_ask", IN("turn"), U);
247
247
  del("mate_turn", IN("id"), U);
248
- del("mate_proposal", IN("id"), proposals);
249
- del("mate_message", IN("thread"), H);
250
- del("mate_thread", IN("id"), H);
248
+ del("lead_proposal", IN("id"), proposals);
249
+ del("lead_message", IN("thread"), H);
250
+ del("lead_thread", IN("id"), H);
251
251
  // Runs and their evidence.
252
252
  del("run_decision", `${IN("run")} OR ${IN("decision")}`, R, decisions);
253
253
  del("decision_artifact", `${IN("decision")} OR ${IN("artifact")}`, decisions, A);
@@ -288,19 +288,19 @@ function deleteRows(store, repo, d, now) {
288
288
  del("task_scope", IN("task_id"), K);
289
289
  del("task_edge", `${IN("blocked")} OR ${IN("blocker")}`, K, K);
290
290
  del("task", IN("id"), K);
291
- // Flows, cards and teammates.
291
+ // Flows, cards and subagents.
292
292
  del("flow_trigger_event", `${IN("card")} OR trigger IN (SELECT id FROM flow_trigger WHERE ${IN("flow")})`, C, F);
293
293
  del("flow_trigger", IN("flow"), F);
294
294
  for (const table of ["flow_card_watcher", "flow_comment", "flow_event", "flow_mail", "flow_step_run"])
295
295
  del(table, IN("card"), C);
296
- del("teammate_question", IN("id"), questions);
297
- del("teammate_call", `${IN("teammate")} OR ${IN("card")}`, M, C);
298
- for (const table of ["teammate_event", "teammate_memory", "teammate_turn"])
299
- del(table, `${IN("teammate")} OR ${IN("card")}`, M, C);
300
- for (const table of ["teammate_suggestion", "teammate_tool", "teammate_version"])
301
- del(table, IN("teammate"), M);
302
- del("budget", `(scope_kind = 'project' AND scope_key = ?) OR (scope_kind = 'teammate' AND scope_key IN (SELECT CAST(value AS TEXT) FROM json_each(?)))`, repo, M);
303
- del("teammate", IN("id"), M);
296
+ del("subagent_question", IN("id"), questions);
297
+ del("subagent_call", `${IN("subagent")} OR ${IN("card")}`, M, C);
298
+ for (const table of ["subagent_event", "subagent_memory", "subagent_turn"])
299
+ del(table, `${IN("subagent")} OR ${IN("card")}`, M, C);
300
+ for (const table of ["subagent_suggestion", "subagent_tool", "subagent_version"])
301
+ del(table, IN("subagent"), M);
302
+ del("budget", `(scope_kind = 'project' AND scope_key = ?) OR (scope_kind = 'subagent' AND scope_key IN (SELECT CAST(value AS TEXT) FROM json_each(?)))`, repo, M);
303
+ del("subagent", IN("id"), M);
304
304
  del("flow_card", IN("id"), C);
305
305
  del("flow", IN("id"), F);
306
306
  // Settings, knowledge and the project itself.
@@ -222,7 +222,7 @@ export function reindexProjectMemory(store, repo) {
222
222
  export function reindexConversations(store) {
223
223
  const key = 'memory-search:messages';
224
224
  const cursor = store.serviceCursor(key);
225
- const rows = store.handle.prepare('SELECT m.id,m.thread,m.role,m.text,t.approver FROM mate_message m JOIN mate_thread t ON t.id=m.thread WHERE m.id>? ORDER BY m.id LIMIT 500').all(cursor);
225
+ const rows = store.handle.prepare('SELECT m.id,m.thread,m.role,m.text,t.approver FROM lead_message m JOIN lead_thread t ON t.id=m.thread WHERE m.id>? ORDER BY m.id LIMIT 500').all(cursor);
226
226
  if (rows.length === 0)
227
227
  return;
228
228
  const insert = store.handle.prepare('INSERT INTO memory_search(kind,ref,repo,scope,title,body) VALUES (?,?,?,?,?,?)');
@@ -91,7 +91,7 @@ export declare function testTool(spec: ToolSpec, values: Record<string, string>,
91
91
  /**
92
92
  * Start the server once and read what each of its tools does (v94): the name,
93
93
  * its description, what input it takes, and whether it says it only reads.
94
- * A teammate is offered these, each under its own rule.
94
+ * A subagent is offered these, each under its own rule.
95
95
  */
96
96
  export declare function listProjectToolActions(spec: ToolSpec, values: Record<string, string>, options?: {
97
97
  timeoutMs?: number;
@@ -382,7 +382,7 @@ export async function testTool(spec, values, options = {}) {
382
382
  /**
383
383
  * Start the server once and read what each of its tools does (v94): the name,
384
384
  * its description, what input it takes, and whether it says it only reads.
385
- * A teammate is offered these, each under its own rule.
385
+ * A subagent is offered these, each under its own rule.
386
386
  */
387
387
  export async function listProjectToolActions(spec, values, options = {}) {
388
388
  const answer = await exchange(spec, values, { method: "tools/list", params: {}, doing: "listing its tools" }, options);
package/dist/proof.d.ts CHANGED
@@ -437,6 +437,15 @@ export declare function matrixWords(matrix: readonly CriterionMatrixRow[]): stri
437
437
  /** One criterion's context standing in words — shared by the CLI matrix
438
438
  * and the console badge title so the two never drift. */
439
439
  export declare function coverageStateWords(coverage: CriterionCoverage): string;
440
+ /** The verdict a result shows today, read from the stored one and the facts recorded since, never written back: a
441
+ * failed check or an unresolved HIGH review finding refutes; a pending review, or any acceptance criterion that has
442
+ * not passed, keeps it short. It only ever lowers the stored verdict. */
443
+ export declare function shownVerdictOf(stored: ProofVerdict | null, facts: {
444
+ checkFailed: boolean;
445
+ highFindings: number;
446
+ reviewPending: boolean;
447
+ criteriaPassed: boolean;
448
+ }): ProofVerdict | null;
440
449
  /** Plain words for a verdict, shared by every surface (the `summary.ts`
441
450
  * pattern) so the task page, run page, board, CLI, and chat cannot say
442
451
  * three different things about the same run. */
package/dist/proof.js CHANGED
@@ -976,6 +976,18 @@ export function coverageStateWords(coverage) {
976
976
  return `GAP — inherited context is missing: ${coverage.gaps.join("; ")}${coverage.items.length > 0 ? ` (partial: ${coverage.items.join(", ")})` : ""}${prior}`;
977
977
  }
978
978
  }
979
+ /** The verdict a result shows today, read from the stored one and the facts recorded since, never written back: a
980
+ * failed check or an unresolved HIGH review finding refutes; a pending review, or any acceptance criterion that has
981
+ * not passed, keeps it short. It only ever lowers the stored verdict. */
982
+ export function shownVerdictOf(stored, facts) {
983
+ if (facts.checkFailed || facts.highFindings > 0 || stored === "refuted")
984
+ return "refuted";
985
+ if (stored === null)
986
+ return null;
987
+ if (facts.reviewPending || !facts.criteriaPassed)
988
+ return "short";
989
+ return stored;
990
+ }
979
991
  /** Plain words for a verdict, shared by every surface (the `summary.ts`
980
992
  * pattern) so the task page, run page, board, CLI, and chat cannot say
981
993
  * three different things about the same run. */
package/dist/recipe-ui.js CHANGED
@@ -55,7 +55,7 @@ export function recipeFromForm(body) {
55
55
  }
56
56
  export function recipeLibraryHtml(starters, saved, project, revision, recent) {
57
57
  const cards = (recipes, reusable = false) => html `<div class="recipe-grid"${reusable && html ` data-saved-recipes`}>${recipes.map(recipe => html `<article class="recipe-card" data-recipe-card data-recipe-search="${recipe.document.name + " " + recipe.document.description}"><p class="recipe-kicker">${recipe.document.deliverable === "report" ? "Report" : recipe.document.schedule === null ? "One-time work" : "Recurring care"}${recipe.document.version === 2 ? ` · ${recipe.document.inputs.length} question${recipe.document.inputs.length === 1 ? "" : "s"}` : ""}</p><h3>${recipe.document.name}</h3><p>${recipe.document.description}</p><a class="recipe-card-link" href="/recipes/${reusable ? "run" : "start"}?recipe=${recipe.id}&revision=${recipe.revision}">${reusable ? "Use recipe" : "Customize recipe"} <span aria-hidden="true">→</span></a>${reusable && html `<a class="meta" href="/recipes/start?recipe=${recipe.id}&revision=${recipe.revision}">Edit a copy</a>`}</article>`)}</div>`;
58
- const savedSection = html `<h2>Your project’s recipes</h2>${saved.length >= 100 && html `<p class="meta">Showing 100 recipes, with recently used recipes first.</p>`}${saved.length ? html `<p class="hint">Your usual work, ready to go. Recently used recipes come first.</p><label class="recipe-search">Find a recipe<input type="search" data-recipe-search-input placeholder="Search your recipes…"></label>${cards(saved, true)}<p data-recipe-empty hidden>No recipes match. Try another search.</p>` : html `<p class="hint">Save work you do often. Teammates with project access can use the same recipe.</p>`}`;
58
+ const savedSection = html `<h2>Your project’s recipes</h2>${saved.length >= 100 && html `<p class="meta">Showing 100 recipes, with recently used recipes first.</p>`}${saved.length ? html `<p class="hint">Your usual work, ready to go. Recently used recipes come first.</p><label class="recipe-search">Find a recipe<input type="search" data-recipe-search-input placeholder="Search your recipes…"></label>${cards(saved, true)}<p data-recipe-empty hidden>No recipes match. Try another search.</p>` : html `<p class="hint">Save work you do often. Subagents with project access can use the same recipe.</p>`}`;
59
59
  return html `<div class="recipe-heading"><p class="recipe-kicker">Workflows</p><h1>Good work, on repeat.</h1><p class="hint">Save the work you do often. Answer what changes this time. Kick it off.</p><p><a href="/recipes/new" class="new-task">Create a recipe</a> <a href="/flows">Manage scheduled work →</a></p></div>${project === null && html `<div class="card"><strong>Choose a project to get started.</strong><p>Browse recipes now; <a href="/projects">open a project</a> to customize, save, or run one.</p></div>`}${saved.length > 0 && savedSection}<h2>Start here</h2>${cards(starters)}${saved.length === 0 && savedSection}${recent.length > 0 && html `<h2>Recent workflows</h2><div class="card recipe-history">${recent.map(one => html `<a href="${one.href}"><strong>${one.name}</strong><span>${one.state} →</span></a>`)}</div>`}${project !== null && html `<details class="card"><summary>Import a shared recipe</summary><p class="hint">Paste exported recipe JSON. You’ll review it in this project before creating any work.</p>${postForm("/recipes/import", html `<label>Recipe JSON<textarea name="document" rows="7" required maxlength="32768" spellcheck="false"></textarea></label><button type="submit">Review imported recipe</button>`, { projectRevision: String(revision), hidden: { repo: project } })}</details>`}`;
60
60
  }
61
61
  function criterionHtml(index, criterion, values) {
@@ -0,0 +1,16 @@
1
+ import type { Store } from "./store.js";
2
+ export type ReleaseCoverage = {
3
+ run: number;
4
+ head: string;
5
+ };
6
+ /** How many of the newest passing release checks one read considers. */
7
+ export declare const RELEASE_SCAN_LIMIT = 20;
8
+ export type IsAncestor = (repo: string, ancestor: string, descendant: string) => boolean | null;
9
+ /** `git merge-base --is-ancestor`: true, false, or null when Git couldn't say. */
10
+ export declare const gitIsAncestor: IsAncestor;
11
+ export type ReleaseMemo = Map<string, ReleaseCoverage[]>;
12
+ /** The release check covering `head` in `repo`, or null. `memo` keeps one read's receipts. */
13
+ export declare function releaseCoverageOf(store: Store, root: string | undefined, repo: string | null, head: string | null, options?: {
14
+ memo?: ReleaseMemo;
15
+ isAncestor?: IsAncestor;
16
+ }): ReleaseCoverage | null;
@@ -0,0 +1,92 @@
1
+ /** Whether a release check already covers a result whose checks were Off: a
2
+ * passing FULL release check (run_check.release) on the same project whose
3
+ * checked commit contains the result's commit. Read at read time from the
4
+ * existing records and Git; nothing is written back, so no proof, receipt,
5
+ * scope digest or schema changes. Fails closed: a receipt that can't be read,
6
+ * a missing commit or any Git error leaves the result uncovered. */
7
+ import { execFileSync } from "node:child_process";
8
+ import { verificationEvidence } from "./verification-evidence.js";
9
+ import { runCheckLevel } from "./check-levels.js";
10
+ /** How many of the newest passing release checks one read considers. */
11
+ export const RELEASE_SCAN_LIMIT = 20;
12
+ const SHA = /^[a-f0-9]{40}$/;
13
+ /** Ancestry between two fixed commits never changes, so a settled answer is kept; errors are not. */
14
+ const ancestry = new Map();
15
+ const ANCESTRY_LIMIT = 4096;
16
+ /** `git merge-base --is-ancestor`: true, false, or null when Git couldn't say. */
17
+ export const gitIsAncestor = (repo, ancestor, descendant) => {
18
+ if (!SHA.test(ancestor) || !SHA.test(descendant))
19
+ return null;
20
+ if (ancestor === descendant)
21
+ return true;
22
+ const key = `${repo}\0${ancestor}\0${descendant}`;
23
+ const known = ancestry.get(key);
24
+ if (known !== undefined)
25
+ return known;
26
+ let answer;
27
+ try {
28
+ execFileSync("git", ["-C", repo, "merge-base", "--is-ancestor", ancestor, descendant], { stdio: "ignore", timeout: 5_000 });
29
+ answer = true;
30
+ }
31
+ catch (error) {
32
+ // Exit 1 is Git's "no"; anything else (a missing object, no repository, a timeout) is no answer.
33
+ answer = error.status === 1 ? false : null;
34
+ }
35
+ if (answer !== null) {
36
+ if (ancestry.size >= ANCESTRY_LIMIT)
37
+ ancestry.clear();
38
+ ancestry.set(key, answer);
39
+ }
40
+ return answer;
41
+ };
42
+ /** The newest passing full release checks of one project, each with its sealed, verified commit. Read once per
43
+ * caller-held memo, so a list of results reads the receipts once. */
44
+ function passingReleases(store, root, repo) {
45
+ let rows;
46
+ try {
47
+ rows = store.handle.prepare(`SELECT rc.run FROM run_check AS rc INDEXED BY run_check_release
48
+ JOIN run ON run.id = rc.run JOIN task_ref AS ref ON ref.id = run.task_ref
49
+ WHERE rc.release = 1 AND rc.status = 'passed' AND rc.exit_code = 0 AND ref.repo = ?
50
+ ORDER BY rc.run DESC LIMIT ${RELEASE_SCAN_LIMIT}`).all(repo);
51
+ }
52
+ catch {
53
+ return [];
54
+ }
55
+ return rows.flatMap(row => {
56
+ const run = Number(row["run"]);
57
+ try {
58
+ // Full only: a quick or Off release check covers nothing.
59
+ const level = runCheckLevel(store, run);
60
+ if (level !== null && level !== "full")
61
+ return [];
62
+ const gate = verificationEvidence(store, root, run);
63
+ if (!gate.ok || gate.bytes === null)
64
+ return [];
65
+ const receipt = JSON.parse(gate.bytes);
66
+ const head = store.getRun(run)?.headRevision ?? null;
67
+ if (receipt.run !== run || receipt.result?.ran !== true || receipt.result.exitCode !== 0)
68
+ return [];
69
+ if (typeof receipt.command?.repo === "string" && receipt.command.repo.startsWith("quick:"))
70
+ return [];
71
+ if (typeof receipt.head !== "string" || !SHA.test(receipt.head) || receipt.head !== head)
72
+ return [];
73
+ return [{ run, head: receipt.head }];
74
+ }
75
+ catch {
76
+ return [];
77
+ }
78
+ });
79
+ }
80
+ /** The release check covering `head` in `repo`, or null. `memo` keeps one read's receipts. */
81
+ export function releaseCoverageOf(store, root, repo, head, options = {}) {
82
+ if (root === undefined || repo === null || head === null || !SHA.test(head))
83
+ return null;
84
+ const memo = options.memo ?? new Map();
85
+ let releases = memo.get(repo);
86
+ if (releases === undefined) {
87
+ releases = passingReleases(store, root, repo);
88
+ memo.set(repo, releases);
89
+ }
90
+ const isAncestor = options.isAncestor ?? gitIsAncestor;
91
+ return releases.find(one => isAncestor(repo, head, one.head) === true) ?? null;
92
+ }
package/dist/retention.js CHANGED
@@ -174,7 +174,7 @@ export function retentionPlan(store, evidenceRoot, now, periods = store.retentio
174
174
  }
175
175
  }
176
176
  if (periods.chat !== null) {
177
- const rows = db.prepare(`SELECT m.id AS id, length(m.text) + COALESCE(length(m.activity), 0) + 64 AS bytes FROM mate_message m JOIN mate_thread th ON th.id = m.thread
177
+ const rows = db.prepare(`SELECT m.id AS id, length(m.text) + COALESCE(length(m.activity), 0) + 64 AS bytes FROM lead_message m JOIN lead_thread th ON th.id = m.thread
178
178
  WHERE m.created_at < ?
179
179
  AND NOT EXISTS (SELECT 1 FROM team_message tm WHERE tm.message = m.id AND tm.status IN ('queued', 'running', 'uncertain'))
180
180
  AND NOT EXISTS (SELECT 1 FROM mate_turn turn WHERE turn.id = m.turn AND turn.state IN ('queued', 'running'))
@@ -263,10 +263,10 @@ export function sweepRetention(store, evidenceRoot, now, actor = "worker") {
263
263
  }
264
264
  if (periods.chat !== null) {
265
265
  const team = db.prepare("DELETE FROM team_message WHERE message = ? AND status NOT IN ('queued', 'running', 'uncertain')");
266
- const message = db.prepare("DELETE FROM mate_message WHERE id = ? AND NOT EXISTS (SELECT 1 FROM team_message WHERE message = ?)");
267
- const asked = db.prepare("SELECT turn FROM mate_message WHERE id = ? AND role = 'assistant' AND turn IS NOT NULL");
266
+ const message = db.prepare("DELETE FROM lead_message WHERE id = ? AND NOT EXISTS (SELECT 1 FROM team_message WHERE message = ?)");
267
+ const asked = db.prepare("SELECT turn FROM lead_message WHERE id = ? AND role = 'assistant' AND turn IS NOT NULL");
268
268
  // The lead's question goes with the reply that asked it; its buttons then say it expired.
269
- const ask = db.prepare("DELETE FROM mate_ask WHERE turn = ? AND NOT EXISTS (SELECT 1 FROM mate_message WHERE turn = ? AND role = 'assistant')");
269
+ const ask = db.prepare("DELETE FROM lead_ask WHERE turn = ? AND NOT EXISTS (SELECT 1 FROM lead_message WHERE turn = ? AND role = 'assistant')");
270
270
  for (const one of plan.items.chat) {
271
271
  team.run(one.id);
272
272
  const turn = asked.get(one.id)?.["turn"];
package/dist/scope.d.ts CHANGED
@@ -304,10 +304,10 @@ export type Scope = SavedScopeTerms & {
304
304
  * non-null verdict, so filtering, defaulting, or coercion can never
305
305
  * turn a corrupt row into authority. Undefined on a hand-built scope. */
306
306
  termsProblem?: string | null;
307
- /** Who wrote the text (mate arc, ruling 2): a confirmed mate proposal,
307
+ /** Who wrote the text (mate arc, ruling 2): a confirmed lead proposal,
308
308
  * the coordinator, a scout, or a person (null). Read raw and proved by
309
309
  * `termsProblem` — a word outside these is a stated problem, never a
310
- * value that slips past the mate quarantine at a mode seal. */
310
+ * value that slips past the lead quarantine at a mode seal. */
311
311
  proposedVia?: "mate" | "coordinator" | "scout" | null;
312
312
  };
313
313
  export type Approval = {
@@ -2,7 +2,7 @@
2
2
  * The scout's terminal handoff (mate arc §10), read against the report contract (src/contracts/scout-report.ts) with
3
3
  * the 422 rule: fail closed, every problem reported at once, each naming its path (`items[0].url: must be an http or
4
4
  * https address`), stable reasons, caps and control-character rejection on every string. A report reaches the task
5
- * page, the ledger, the terminal, and — through mateView — the mate; and each follow-up becomes a filing's title and
5
+ * page, the ledger, the terminal, and — through leadView — the lead; and each follow-up becomes a filing's title and
6
6
  * goal at one tap, so it gets the park discipline exactly as the plan does.
7
7
  */
8
8
  import { type ContractProblem } from "./contracts/contract.js";
@@ -2,7 +2,7 @@
2
2
  * The scout's terminal handoff (mate arc §10), read against the report contract (src/contracts/scout-report.ts) with
3
3
  * the 422 rule: fail closed, every problem reported at once, each naming its path (`items[0].url: must be an http or
4
4
  * https address`), stable reasons, caps and control-character rejection on every string. A report reaches the task
5
- * page, the ledger, the terminal, and — through mateView — the mate; and each follow-up becomes a filing's title and
5
+ * page, the ledger, the terminal, and — through leadView — the lead; and each follow-up becomes a filing's title and
6
6
  * goal at one tap, so it gets the park discipline exactly as the plan does.
7
7
  */
8
8
  import { contractProblemOf } from "./contracts/contract.js";
package/dist/serve.js CHANGED
@@ -87,7 +87,7 @@ import { firstRunSteps, signInCommandFor } from "./first-run.js";
87
87
  import { installMethod } from "./install-method.js";
88
88
  import { createIntegrationMonitor } from "./integrations.js";
89
89
  import { logEvent } from "./log.js";
90
- import { MATE_MESSAGE_MAX_CHARS } from "./mate.js";
90
+ import { LEAD_MESSAGE_MAX_CHARS } from "./lead.js";
91
91
  import {} from "./model-catalog.js";
92
92
  import { modeTermsFromJson } from "./modes.js";
93
93
  import { envValue } from "./names.js";
@@ -120,7 +120,7 @@ import { createSettingsHandlers } from './server/settings.js';
120
120
  import { requestContext } from "./server/request-context.js";
121
121
  import { chromeScript, DEMO_BANNER, DEMO_BANNER_SHORT, focusDocument, KBD_HELP, pinnedTheme, QUEUE_VIEW, refuse, screen, SENSITIVE_INPUT, shell, sidebarScript } from "./server/chrome.js";
122
122
  import { BODY_CAP, form, loginHref, matchTaskPath, page, projectChatHref, redactedPath, redirect, respond, safeReturn, SHUTDOWN_WAIT_MS, taskChatHref } from "./server/http.js";
123
- import { decisionsFor, LEAD_BY_DEFAULT_FACT, mateBrowserMessages, mateChatVersion, teamProposalCardParts } from "./server/render-chat.js";
123
+ import { decisionsFor, LEAD_BY_DEFAULT_FACT, leadBrowserMessages, leadChatVersion, teamProposalCardParts } from "./server/render-chat.js";
124
124
  import {} from "./server/render-pages.js";
125
125
  import { ssoStepUps, wrongHostPage } from "./server/render-people.js";
126
126
  import { NO_PROJECT, NO_TOUCH_FRAGMENTS, NONCE_CAP, NONCE_TTL_MS, PersistentSessions, SESSION_ABSOLUTE_MS, SESSION_IDLE_MS, SIGN_IN_LINK_MS, SIGN_IN_LINK_PATH } from "./server/session.js";
@@ -453,7 +453,7 @@ export function createDecisionServer(options) {
453
453
  if (!snapshot?.selected || !snapshot.proposals)
454
454
  return reply;
455
455
  return { ...reply, snapshot: { ...snapshot, proposals: snapshot.proposals.map(summary => {
456
- const proposal = store.getMateProposal(summary.id);
456
+ const proposal = store.getLeadProposal(summary.id);
457
457
  if (!proposal || proposal.thread !== snapshot.selected.threadId)
458
458
  return summary;
459
459
  const decision = proposal.kind === 'answer' && typeof proposal.payload['decision'] === 'number' ? store.getDecision(proposal.payload['decision']) : null;
@@ -780,10 +780,10 @@ export function createDecisionServer(options) {
780
780
  }
781
781
  const shared = /^\/chat\/(?:action\/([0-9]{1,15})|proposal\/([0-9]{1,15})\/(?:confirm|dismiss))$/.exec(url.pathname);
782
782
  if (shared) {
783
- const proposal = store.getMateProposal(Number(shared[1] ?? shared[2]));
783
+ const proposal = store.getLeadProposal(Number(shared[1] ?? shared[2]));
784
784
  const action = proposal?.kind === 'action' ? sharedActionPayload(proposal.payload) : null;
785
785
  const room = proposal ? store.handle.prepare('SELECT id FROM team_conversation WHERE thread=?').get(proposal.thread) : null;
786
- let admitted = proposal && store.getMateThread(proposal.thread)?.approver === who.name;
786
+ let admitted = proposal && store.getLeadThread(proposal.thread)?.approver === who.name;
787
787
  if (room && who.via === 'cookie') {
788
788
  try {
789
789
  team.domain.access({ name: who.name, generation: who.session.generation }, String(room['id']), 'contributor');
@@ -981,36 +981,36 @@ export function createDecisionServer(options) {
981
981
  // ---- fleet chat (v13) ----------------------------------------------------
982
982
  const chatFetcher = options.chatFetcher ?? fetch;
983
983
  const chatEnv = options.chatEnv ?? process.env;
984
- /** The mate's last non-answer per BROWSER session (keyed by its csrf —
984
+ /** The lead's last non-answer per BROWSER session (keyed by its csrf —
985
985
  * never by name, so two browsers on one account do not read each other's
986
986
  * notes), bound to the mate turn it came from so a note from a session
987
987
  * that has since ended never shows; bounded; read once. */
988
- const mateSaid = new Map();
988
+ const leadSaid = new Map();
989
989
  /** The unified conversation's rows for one reader (package 2): the SAME
990
990
  * rows the page renders and the status poll's fragments re-render, so a
991
991
  * live update can never show a card the page would not. A task lens
992
- * keeps only that task's coordinator cards; the mate's own thread is one
992
+ * keeps only that task's coordinator cards; the lead's own thread is one
993
993
  * thread regardless of lens. */
994
- function mateConversationRows(who, principal, focusTask, now, chatProject = null) {
994
+ function leadConversationRows(who, principal, focusTask, now, chatProject = null) {
995
995
  const allCoordinatorRows = store.listCoordinatorProposals({ repos: managedRepos(), states: ["pending", "confirmed", "refused"], limit: 30 });
996
996
  const coordinatorProposals = focusTask !== null ? allCoordinatorRows.filter(one => focusTask.family.versions.some(version => version.id === one.payload["task"]))
997
997
  : chatProject !== null ? allCoordinatorRows.filter(one => one.repo === chatProject) : allCoordinatorRows;
998
- const opened = store.openMateThread(who.name, principal.ceilingDigest, now, chatScopeOf(focusTask, chatProject));
999
- const proposals = store.listMateProposals(opened.thread.id);
998
+ const opened = store.openLeadThread(who.name, principal.ceilingDigest, now, chatScopeOf(focusTask, chatProject));
999
+ const proposals = store.listLeadProposals(opened.thread.id);
1000
1000
  // One reply runs at a time per person; this thread shows it only when
1001
1001
  // the reply is its own.
1002
- const live = store.liveMateTurnFor(who.name);
1003
- const messages = store.listMateMessages(opened.thread.id, 40);
1002
+ const live = store.liveLeadTurnFor(who.name);
1003
+ const messages = store.listLeadMessages(opened.thread.id, 40);
1004
1004
  // The lead's question with buttons, while its reply is the last word in the thread.
1005
1005
  const last = messages.at(-1);
1006
- const ask = last?.role === "assistant" && last.turn !== null && store.getMateTurn(last.turn)?.approver === who.name ? store.mateAskOpen(last.turn, now) : null;
1006
+ const ask = last?.role === "assistant" && last.turn !== null && store.getLeadTurn(last.turn)?.approver === who.name ? store.leadAskOpen(last.turn, now) : null;
1007
1007
  // Answered questions stay readable above the answer; only their buttons go.
1008
- const asks = new Map(messages.flatMap(one => { const asked = one.role === "assistant" && one.turn !== null ? store.mateAsk(one.turn) : null; return asked === null ? [] : [[asked.turn, asked]]; }));
1008
+ const asks = new Map(messages.flatMap(one => { const asked = one.role === "assistant" && one.turn !== null ? store.leadAsk(one.turn) : null; return asked === null ? [] : [[asked.turn, asked]]; }));
1009
1009
  // The thread a ceiling change replaced (ruling 9): read after the open, display only.
1010
1010
  const replaced = store.replacedLeadThread(opened.thread);
1011
- const previousProposals = replaced === null ? [] : store.listMateProposals(replaced.id);
1011
+ const previousProposals = replaced === null ? [] : store.listLeadProposals(replaced.id);
1012
1012
  return {
1013
- previous: replaced === null ? null : { messages: store.listMateMessages(replaced.id, 40), proposals: previousProposals, decisions: decisionsFor(store, previousProposals) },
1013
+ previous: replaced === null ? null : { messages: store.listLeadMessages(replaced.id, 40), proposals: previousProposals, decisions: decisionsFor(store, previousProposals) },
1014
1014
  messages,
1015
1015
  ask,
1016
1016
  asks,
@@ -1018,7 +1018,7 @@ export function createDecisionServer(options) {
1018
1018
  decisions: decisionsFor(store, [...proposals, ...coordinatorProposals]),
1019
1019
  coordinatorProposals,
1020
1020
  pending: live !== null && live.thread === opened.thread.id ? live : null,
1021
- recent: store.recentMateTurns(who.name, 5),
1021
+ recent: store.recentLeadTurns(who.name, 5),
1022
1022
  };
1023
1023
  }
1024
1024
  /** The thread a chat surface speaks in (v77): the task's own thread, the
@@ -1035,21 +1035,21 @@ export function createDecisionServer(options) {
1035
1035
  if (who.via !== "cookie" || who.role !== "approver" || (focusTask === null && chatProject === null))
1036
1036
  return null;
1037
1037
  const enabled = chatEnablement();
1038
- const principal = enabled.ok ? matePrincipal(who) : null;
1038
+ const principal = enabled.ok ? leadPrincipal(who) : null;
1039
1039
  if (!enabled.ok || principal === null)
1040
1040
  return null;
1041
- let session = store.activeMateSession(who.name);
1041
+ let session = store.activeLeadSession(who.name);
1042
1042
  if ((session === null || session.ceilingDigest !== principal.ceilingDigest) && enabled.billing === "subscription" && !requestContext.getStore()?.workspaceRead) {
1043
- startMateConversation(who, principal, enabled, 0, false, now);
1044
- session = store.activeMateSession(who.name);
1043
+ startLeadConversation(who, principal, enabled, 0, false, now);
1044
+ session = store.activeLeadSession(who.name);
1045
1045
  }
1046
1046
  if (session === null || session.ceilingDigest !== principal.ceilingDigest || session.approverGeneration !== principal.generation)
1047
1047
  return null;
1048
- const rows = mateConversationRows(who, principal, focusTask, now, chatProject);
1048
+ const rows = leadConversationRows(who, principal, focusTask, now, chatProject);
1049
1049
  return {
1050
- sessionId: session.id, user: session.approver, version: mateChatVersion({ ...rows, focusTask }),
1051
- messages: mateBrowserMessages(rows, back, { task: focusTask?.id ?? null, project: chatProject }),
1052
- pendingTurnId: rows.pending?.id ?? null, requestId: randomBytes(16).toString("hex"), maxChars: MATE_MESSAGE_MAX_CHARS,
1050
+ sessionId: session.id, user: session.approver, version: leadChatVersion({ ...rows, focusTask }),
1051
+ messages: leadBrowserMessages(rows, back, { task: focusTask?.id ?? null, project: chatProject }),
1052
+ pendingTurnId: rows.pending?.id ?? null, requestId: randomBytes(16).toString("hex"), maxChars: LEAD_MESSAGE_MAX_CHARS,
1053
1053
  taskId: focusTask?.id ?? null, resultRunId, project: focusTask === null ? chatProject : null,
1054
1054
  };
1055
1055
  }
@@ -1058,17 +1058,17 @@ export function createDecisionServer(options) {
1058
1058
  /** Start the person's lead conversation: the session every later turn
1059
1059
  * debits (a membership spends nothing; direct API gets a per-conversation
1060
1060
  * ceiling) and its thread. Starting ends any older session. */
1061
- function startMateConversation(who, principal, enabled, ceilingUsd, follow, now) {
1061
+ function startLeadConversation(who, principal, enabled, ceilingUsd, follow, now) {
1062
1062
  const ceilingMicrousd = enabled.billing === "subscription" ? 0 : Math.round(ceilingUsd * 1_000_000);
1063
1063
  const termsDigest = createHash("sha256").update(`${ceilingMicrousd}\n${principal.ceilingDigest}`).digest("hex");
1064
- const sessionId = store.mintMateSession({ approver: who.name, approverGeneration: principal.generation, credentialKey: enabled.credentialKey, ceilingMicrousd, ceilingDigest: principal.ceilingDigest, termsDigest }, now);
1065
- const thread = store.openMateThread(who.name, principal.ceilingDigest, now).thread;
1064
+ const sessionId = store.mintLeadSession({ approver: who.name, approverGeneration: principal.generation, credentialKey: enabled.credentialKey, ceilingMicrousd, ceilingDigest: principal.ceilingDigest, termsDigest }, now);
1065
+ const thread = store.openLeadThread(who.name, principal.ceilingDigest, now).thread;
1066
1066
  if (follow)
1067
- configureLeadFollow(store, principal, store.getMateSession(sessionId), thread, true, now);
1068
- mateSaid.delete(who.session.csrf);
1067
+ configureLeadFollow(store, principal, store.getLeadSession(sessionId), thread, true, now);
1068
+ leadSaid.delete(who.session.csrf);
1069
1069
  return sessionId;
1070
1070
  }
1071
- function matePrincipal(who) {
1071
+ function leadPrincipal(who) {
1072
1072
  const verified = verifyApproverStanding(store, who.name, who.session.generation, managedRepos());
1073
1073
  return verified.ok ? verified.who : null;
1074
1074
  }
@@ -1237,12 +1237,7 @@ export function createDecisionServer(options) {
1237
1237
  if (s.workspace?.view?.kind === "flow")
1238
1238
  s = { ...s, workspace: { ...s.workspace, view: { ...s.workspace.view, stepUp: { label: ssoFacts.label, fresh: ssoFacts.fresh, confirmHref: `/login/sso?reauth=1&return=${encodeURIComponent(back)}` } } } };
1239
1239
  }
1240
- const teamEntry = requestContext.getStore()?.returnTo;
1241
- if (teamEntry?.startsWith('/chat') && !s.workspace?.team && !teamEntry.includes('task=') && !teamEntry.includes('proposal=') && demoLeadHere() === null) {
1242
- s.body = html `<p class="team-entry"><a href="/chat?team=1">Open team chat</a></p>${s.body}`;
1243
- if (s.workspace?.conversation)
1244
- s.workspace.controlsHtml = html `<p><a href="/chat?team=1">Open team chat</a></p>${s.workspace.controlsHtml}`;
1245
- }
1240
+ // D5: team chat (the central team service) is deprecated; /chat?team=1 still opens this release, but nothing links to it.
1246
1241
  const sensitive = s.forceSensitive === true ||
1247
1242
  SENSITIVE_INPUT.test(htmlString(s.body)) ||
1248
1243
  (s.chrome?.listPane !== undefined && SENSITIVE_INPUT.test(htmlString(s.chrome.listPane)));
@@ -1296,7 +1291,7 @@ export function createDecisionServer(options) {
1296
1291
  try {
1297
1292
  const actor = requestFacts.actor ?? '';
1298
1293
  if (actor !== '') {
1299
- chats = store.listMateThreads(actor, 16).flatMap((thread) => {
1294
+ chats = store.listLeadThreads(actor, 16).flatMap((thread) => {
1300
1295
  if (thread.scope.kind === 'project') {
1301
1296
  const repo = thread.scope.key;
1302
1297
  return visible(repo) && managedRepos().includes(repo) ? [{ kind: 'project', title: projectName(repo), href: projectChatHref(repo), at: thread.lastMessageAt }] : [];
@@ -1319,7 +1314,7 @@ export function createDecisionServer(options) {
1319
1314
  ...(requestFacts.actor ? { leadName: leadNameOf(store, requestFacts.actor) } : {}),
1320
1315
  refreshUrl: path.pathname + path.search,
1321
1316
  receipt: conversation !== null && request && REQUEST_TOKEN.test(request)
1322
- ? { request, received: store.mateRequestReceipt(conversation.sessionId, request) !== null } : null,
1317
+ ? { request, received: store.leadRequestReceipt(conversation.sessionId, request) !== null } : null,
1323
1318
  projects: browserProjectsOf(s.chrome.projects ?? []), ...crew,
1324
1319
  conversation, ...(extras.team ? { team: extras.team } : {}), focus: extras.focus ?? null, result: extras.result ?? null,
1325
1320
  catchUpHtml: extras.catchUpHtml === undefined ? '' : htmlString(extras.catchUpHtml), controlsHtml: extras.controlsHtml === undefined ? '' : htmlString(extras.controlsHtml), notices, view: extras.view ?? null,
@@ -1564,7 +1559,7 @@ export function createDecisionServer(options) {
1564
1559
  });
1565
1560
  });
1566
1561
  // Keep native process custody until shutdown has verified agent/tool exit.
1567
- // Coding custody does not depend on teammates or the lead's follow pass, so
1562
+ // Coding custody does not depend on subagents or the lead's follow pass, so
1568
1563
  // it closes at once and never waits behind a model turn: a stop must release
1569
1564
  // the owner record well inside the service's exit window (Oct 2: deploys
1570
1565
  // over 0.9.11 found it still held by a killed process).
@@ -1645,18 +1640,18 @@ export function createDecisionServer(options) {
1645
1640
  get flowRooms() { return flowRooms; },
1646
1641
  get providerHome() { return providerHome; },
1647
1642
  get toolHome() { return toolHome; },
1648
- get matePrincipal() { return matePrincipal; },
1643
+ get leadPrincipal() { return leadPrincipal; },
1649
1644
  get chatScopeOf() { return chatScopeOf; },
1650
1645
  get liveTurns() { return liveTurns; },
1651
1646
  get liveTurnStarted() { return liveTurnStarted; },
1652
- get mateConversationRows() { return mateConversationRows; },
1647
+ get leadConversationRows() { return leadConversationRows; },
1653
1648
  get demoLeadHere() { return demoLeadHere; },
1654
1649
  get teamBrowserReply() { return teamBrowserReply; },
1655
1650
  get team() { return team; },
1656
1651
  get teamChatProvider() { return teamChatProvider; },
1657
1652
  get pullRequestTargetOf() { return pullRequestTargetOf; },
1658
1653
  get chatEnablement() { return chatEnablement; },
1659
- get startMateConversation() { return startMateConversation; },
1654
+ get startLeadConversation() { return startLeadConversation; },
1660
1655
  get projectFamilyPeek() { return projectFamilyPeek; },
1661
1656
  get needsYouBadge() { return needsYouBadge; },
1662
1657
  get liveRefreshSeconds() { return liveRefreshSeconds; },
@@ -1689,7 +1684,7 @@ export function createDecisionServer(options) {
1689
1684
  get deletedRepos() { return deletedRepos; },
1690
1685
  get lookupSession() { return lookupSession; },
1691
1686
  get authorizeMutation() { return authorizeMutation; },
1692
- get mateSaid() { return mateSaid; },
1687
+ get leadSaid() { return leadSaid; },
1693
1688
  get armTaskResume() { return armTaskResume; },
1694
1689
  get chatFetcher() { return chatFetcher; },
1695
1690
  get chatCeilingDigest() { return chatCeilingDigest; },