toolroll 0.9.55 → 0.9.56

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +24 -8
  3. package/dist/action-ledger.d.ts +2 -4
  4. package/dist/action-ledger.js +2 -10
  5. package/dist/approval-policy.d.ts +5 -5
  6. package/dist/approval-policy.js +4 -4
  7. package/dist/approval-rules-ui.js +2 -2
  8. package/dist/browser/workspace.js +5 -5
  9. package/dist/browser-workspace.d.ts +12 -12
  10. package/dist/budget-alerts.js +3 -3
  11. package/dist/chat-actions.d.ts +23 -18
  12. package/dist/chat-actions.js +110 -88
  13. package/dist/chat-ask.d.ts +2 -2
  14. package/dist/chat-ask.js +5 -5
  15. package/dist/chat-channel.d.ts +15 -15
  16. package/dist/chat-channel.js +20 -20
  17. package/dist/chat-controls.d.ts +0 -4
  18. package/dist/chat-controls.js +0 -1
  19. package/dist/chat-core.js +1 -1
  20. package/dist/chat-delivery-state.js +2 -2
  21. package/dist/chat-delivery.d.ts +2 -2
  22. package/dist/chat-delivery.js +27 -34
  23. package/dist/chat-flow.js +3 -3
  24. package/dist/chat-messages.d.ts +1 -1
  25. package/dist/chat-messages.js +4 -4
  26. package/dist/chat-migration.d.ts +1 -1
  27. package/dist/chat-migration.js +11 -11
  28. package/dist/chat-rooms.js +3 -3
  29. package/dist/chat-warmth.d.ts +2 -2
  30. package/dist/cli.d.ts +1 -1
  31. package/dist/cli.js +6 -7
  32. package/dist/contracts/chat-actions.d.ts +32 -27
  33. package/dist/contracts/chat-actions.js +29 -11
  34. package/dist/contracts/chat-content.d.ts +1 -1
  35. package/dist/contracts/chat-content.js +1 -1
  36. package/dist/contracts/chat-tables.js +4 -4
  37. package/dist/contracts/console-api.d.ts +6 -6
  38. package/dist/contracts/console-api.js +7 -7
  39. package/dist/contracts/flow-propose.d.ts +3 -3
  40. package/dist/contracts/flow.d.ts +132 -125
  41. package/dist/contracts/flow.js +27 -12
  42. package/dist/contracts/lead-context.d.ts +1 -1
  43. package/dist/contracts/lead-context.js +2 -2
  44. package/dist/contracts/lead-tools.d.ts +20 -18
  45. package/dist/contracts/lead-tools.js +15 -15
  46. package/dist/contracts/stage-output.d.ts +1 -1
  47. package/dist/contracts/stage-output.js +1 -1
  48. package/dist/contracts/store-json.d.ts +23 -22
  49. package/dist/contracts/store-json.js +17 -17
  50. package/dist/contracts/{teammate-turn.d.ts → subagent-turn.d.ts} +5 -5
  51. package/dist/contracts/{teammate-turn.js → subagent-turn.js} +7 -7
  52. package/dist/converse.d.ts +23 -23
  53. package/dist/converse.js +28 -28
  54. package/dist/coordinator-proposals.d.ts +2 -2
  55. package/dist/coordinator-proposals.js +1 -1
  56. package/dist/coordinator.js +1 -1
  57. package/dist/deprecations.d.ts +12 -0
  58. package/dist/deprecations.js +20 -0
  59. package/dist/desktop-update-gate.js +8 -4
  60. package/dist/desktop-update.js +6 -1
  61. package/dist/discord-chat.js +6 -6
  62. package/dist/dispatch.d.ts +1 -1
  63. package/dist/dispatch.js +1 -1
  64. package/dist/envelope.js +0 -1
  65. package/dist/export-ui.js +1 -1
  66. package/dist/export.js +7 -7
  67. package/dist/flow-engine.d.ts +2 -2
  68. package/dist/flow-engine.js +21 -21
  69. package/dist/flow-insights.d.ts +1 -1
  70. package/dist/flow-share.js +13 -8
  71. package/dist/flow-steps.d.ts +5 -5
  72. package/dist/flow-steps.js +19 -19
  73. package/dist/flow-triggers.d.ts +1 -1
  74. package/dist/flow-triggers.js +1 -1
  75. package/dist/flows-cli.js +1 -1
  76. package/dist/flows-ui.js +20 -20
  77. package/dist/flows.js +19 -19
  78. package/dist/guides.js +4 -3
  79. package/dist/invoke.js +1 -1
  80. package/dist/kits-ui.js +5 -5
  81. package/dist/kits.d.ts +12 -12
  82. package/dist/kits.js +31 -31
  83. package/dist/{mate-cli.d.ts → lead-cli.d.ts} +11 -11
  84. package/dist/{mate-cli.js → lead-cli.js} +38 -38
  85. package/dist/lead-commitments.js +4 -4
  86. package/dist/lead-context.d.ts +2 -2
  87. package/dist/lead-context.js +9 -9
  88. package/dist/{mate-contract.d.ts → lead-contract.d.ts} +3 -3
  89. package/dist/{mate-contract.js → lead-contract.js} +7 -6
  90. package/dist/{mate-doors.d.ts → lead-doors.d.ts} +6 -6
  91. package/dist/{mate-doors.js → lead-doors.js} +24 -24
  92. package/dist/lead-follow.d.ts +5 -5
  93. package/dist/lead-follow.js +9 -9
  94. package/dist/lead-people.d.ts +5 -5
  95. package/dist/lead-people.js +14 -14
  96. package/dist/{mate-progress.d.ts → lead-progress.d.ts} +9 -9
  97. package/dist/{mate-progress.js → lead-progress.js} +3 -3
  98. package/dist/{mate-tools.d.ts → lead-tools.d.ts} +19 -19
  99. package/dist/{mate-tools.js → lead-tools.js} +109 -101
  100. package/dist/{mate.d.ts → lead.d.ts} +27 -27
  101. package/dist/{mate.js → lead.js} +102 -102
  102. package/dist/limits-ui.d.ts +1 -1
  103. package/dist/limits-ui.js +1 -1
  104. package/dist/mcp-connect.d.ts +1 -1
  105. package/dist/mcp-connect.js +3 -3
  106. package/dist/mcp-core.js +1 -1
  107. package/dist/memory-pass.d.ts +2 -2
  108. package/dist/memory-pass.js +7 -7
  109. package/dist/notification-rows.d.ts +2 -2
  110. package/dist/operate.d.ts +3 -3
  111. package/dist/operate.js +35 -34
  112. package/dist/policy.d.ts +1 -1
  113. package/dist/principal.d.ts +1 -1
  114. package/dist/principal.js +2 -2
  115. package/dist/project-delete-ui.js +1 -1
  116. package/dist/project-delete.d.ts +1 -1
  117. package/dist/project-delete.js +26 -26
  118. package/dist/project-memory.js +1 -1
  119. package/dist/project-tools.d.ts +1 -1
  120. package/dist/project-tools.js +1 -1
  121. package/dist/recipe-ui.js +1 -1
  122. package/dist/retention.js +4 -4
  123. package/dist/scope.d.ts +2 -2
  124. package/dist/scout-report.d.ts +1 -1
  125. package/dist/scout-report.js +1 -1
  126. package/dist/serve.js +40 -45
  127. package/dist/server/chat.d.ts +4 -4
  128. package/dist/server/chat.js +97 -97
  129. package/dist/server/chrome.js +3 -3
  130. package/dist/server/flows.js +69 -62
  131. package/dist/server/guards.js +3 -3
  132. package/dist/server/http.d.ts +3 -3
  133. package/dist/server/http.js +1 -1
  134. package/dist/server/live.js +6 -6
  135. package/dist/server/people-tokens.js +1 -1
  136. package/dist/server/remote.js +12 -10
  137. package/dist/server/render-chat.d.ts +30 -30
  138. package/dist/server/render-chat.js +33 -33
  139. package/dist/server/render-pages.js +2 -2
  140. package/dist/server/render-settings.d.ts +7 -0
  141. package/dist/server/render-settings.js +3 -0
  142. package/dist/server/route-table.js +9 -7
  143. package/dist/server/runtime.d.ts +13 -13
  144. package/dist/server/settings.js +14 -13
  145. package/dist/server/tasks.js +6 -5
  146. package/dist/setup-guide.d.ts +2 -2
  147. package/dist/slack-chat.d.ts +2 -2
  148. package/dist/slack-chat.js +6 -6
  149. package/dist/spend-ui.d.ts +3 -3
  150. package/dist/spend-ui.js +9 -9
  151. package/dist/spend.d.ts +14 -14
  152. package/dist/spend.js +31 -31
  153. package/dist/store-baseline.d.ts +19 -0
  154. package/dist/store-baseline.js +2873 -0
  155. package/dist/store.d.ts +197 -249
  156. package/dist/store.js +747 -3084
  157. package/dist/subagent-admin.d.ts +53 -0
  158. package/dist/{teammate-admin.js → subagent-admin.js} +37 -37
  159. package/dist/{teammate-desk.d.ts → subagent-desk.d.ts} +22 -23
  160. package/dist/{teammate-desk.js → subagent-desk.js} +26 -35
  161. package/dist/{teammate-memory.d.ts → subagent-memory.d.ts} +17 -17
  162. package/dist/{teammate-memory.js → subagent-memory.js} +41 -41
  163. package/dist/{teammate-question.d.ts → subagent-question.d.ts} +6 -6
  164. package/dist/{teammate-question.js → subagent-question.js} +17 -17
  165. package/dist/{teammate-tools.d.ts → subagent-tools.d.ts} +18 -18
  166. package/dist/{teammate-tools.js → subagent-tools.js} +36 -36
  167. package/dist/{teammate-week.d.ts → subagent-week.d.ts} +10 -10
  168. package/dist/{teammate-week.js → subagent-week.js} +23 -23
  169. package/dist/{teammate-work.d.ts → subagent-work.d.ts} +21 -21
  170. package/dist/{teammate-work.js → subagent-work.js} +50 -50
  171. package/dist/subagents-ui.d.ts +22 -0
  172. package/dist/{teammates-ui.js → subagents-ui.js} +45 -45
  173. package/dist/{teammates.d.ts → subagents.d.ts} +11 -11
  174. package/dist/{teammates.js → subagents.js} +21 -21
  175. package/dist/subscription-chat.d.ts +13 -13
  176. package/dist/subscription-chat.js +19 -19
  177. package/dist/surface.d.ts +19 -2
  178. package/dist/surface.js +7 -31
  179. package/dist/team-cli.d.ts +1 -0
  180. package/dist/team-cli.js +14 -2
  181. package/dist/team-leads.js +13 -13
  182. package/dist/team-runtime.d.ts +2 -2
  183. package/dist/team-runtime.js +8 -8
  184. package/dist/teams-chat.js +6 -6
  185. package/dist/telegram-flow.d.ts +3 -3
  186. package/dist/telegram-flow.js +3 -3
  187. package/dist/{telegram-mate.d.ts → telegram-lead.d.ts} +5 -5
  188. package/dist/{telegram-mate.js → telegram-lead.js} +27 -27
  189. package/dist/telegram-status.js +1 -1
  190. package/dist/telegram-team.d.ts +1 -1
  191. package/dist/telegram-team.js +5 -5
  192. package/dist/telegram.d.ts +1 -1
  193. package/dist/telegram.js +9 -30
  194. package/dist/text-limits.d.ts +18 -18
  195. package/dist/text-limits.js +18 -18
  196. package/dist/toolroll-update.d.ts +8 -0
  197. package/dist/toolroll-update.js +45 -8
  198. package/package.json +1 -2
  199. package/dist/teammate-admin.d.ts +0 -53
  200. package/dist/teammates-ui.d.ts +0 -21
@@ -1,13 +1,13 @@
1
1
  import { chatFlowButtons } from "./chat-flow.js";
2
2
  import { refuseResultShots, resultShotsPruned, shotWaitsUntil } from "./result-shots.js";
3
- import { chatQuestionButtons } from "./teammate-question.js";
3
+ import { chatQuestionButtons } from "./subagent-question.js";
4
4
  import { chatAskButtons } from "./chat-ask.js";
5
5
  import { channelInbox } from "./chat-inbox.js";
6
6
  import { roomCommand } from "./chat-rooms.js";
7
7
  import { processChatEvent, applyChatAction, planChatNotifications, channelAccess, planRoomMessages, } from "./chat-delivery.js";
8
8
  /** Slack is a transport for the same saved assistant and confirmation doors. */
9
9
  import { proposalPreview, proposalLink, proposalOutcomeText, armedCardText, armedYesLabel, } from "./chat-channel.js";
10
- import { MATE_MESSAGE_MAX_CHARS } from "./mate.js";
10
+ import { LEAD_MESSAGE_MAX_CHARS } from "./lead.js";
11
11
  import { renderReply } from "./reply-shape.js";
12
12
  import { ChatDeliveryError, ChatState, chatHash, partContent } from "./chat-delivery-state.js";
13
13
  import { readChatPart } from "./contracts/chat-content.js";
@@ -65,7 +65,7 @@ export function receiveSlack(state, identity, type, raw, now) {
65
65
  payload = match
66
66
  ? { hash: chatHash(match[1]) }
67
67
  : {
68
- text: event.text.slice(0, MATE_MESSAGE_MAX_CHARS + 1),
68
+ text: event.text.slice(0, LEAD_MESSAGE_MAX_CHARS + 1),
69
69
  originalLength: event.text.length,
70
70
  };
71
71
  id = chatHash(`${identity.installation}:event:${body.event_id}`);
@@ -217,7 +217,7 @@ export async function deliverSlackPart(options) {
217
217
  return true;
218
218
  }
219
219
  try {
220
- const session = event.session === null ? null : store.getMateSession(event.session);
220
+ const session = event.session === null ? null : store.getLeadSession(event.session);
221
221
  const repos = await access(options, binding, session?.ceilingDigest);
222
222
  if (event.kind === "notice" && options.canNotify?.() === false)
223
223
  return false;
@@ -300,7 +300,7 @@ export async function deliverSlackPart(options) {
300
300
  }
301
301
  }
302
302
  if (content.proposal) {
303
- const proposal = store.getMateProposal(content.proposal);
303
+ const proposal = store.getLeadProposal(content.proposal);
304
304
  if (!proposal)
305
305
  text = "This proposal is unavailable.";
306
306
  else if (proposal.state !== "pending")
@@ -363,7 +363,7 @@ export async function deliverSlackPart(options) {
363
363
  ...(one.action === "approve" ? { style: "primary" } : {}),
364
364
  }))
365
365
  : []),
366
- // A teammate's question (v93): its options, then "Answer in words".
366
+ // A subagent's question (v93): its options, then "Answer in words".
367
367
  ...(content.question
368
368
  ? chatQuestionButtons(state, row.id, now).map((one) => ({
369
369
  type: "button",
@@ -16,9 +16,9 @@ export type SpendView = {
16
16
  targets: {
17
17
  value: string;
18
18
  label: string;
19
- group: "Everything" | "Projects" | "People" | "Teammates";
19
+ group: "Everything" | "Projects" | "People" | "Subagents";
20
20
  }[];
21
- teammateNames: Map<number, string>;
21
+ subagentNames: Map<number, string>;
22
22
  csrf: string;
23
23
  };
24
24
  export declare function spendHtml(view: SpendView, notice: {
@@ -26,4 +26,4 @@ export declare function spendHtml(view: SpendView, notice: {
26
26
  problem?: string | null;
27
27
  }): Html;
28
28
  /** One row per piece of spend, for a spreadsheet. */
29
- export declare function spendCsv(items: readonly SpendItem[], teammateNames: Map<number, string>): string;
29
+ export declare function spendCsv(items: readonly SpendItem[], subagentNames: Map<number, string>): string;
package/dist/spend-ui.js CHANGED
@@ -41,32 +41,32 @@ export function spendHtml(view, notice) {
41
41
  const note = notice.problem ? html `<p class="problem" role="alert">${notice.problem}</p>` : notice.said ? html `<p role="status">${notice.said}</p>` : "";
42
42
  const total = view.items.reduce((sum, item) => sum + (item.microusd ?? 0), 0);
43
43
  const unpriced = view.items.filter(item => item.microusd === null && (item.tokensIn !== null || item.kind !== "run")).length;
44
- const counts = { run: view.items.filter(item => item.kind === "run").length, teammate: view.items.filter(item => item.kind === "teammate").length, chat: view.items.filter(item => item.kind === "chat").length, sort: view.items.filter(item => item.kind === "sort").length };
45
- const parts = [`${counts.run} ${counts.run === 1 ? "run" : "runs"}`, ...(counts.teammate > 0 ? [`${counts.teammate} teammate turns`] : []), ...(counts.chat > 0 ? [`${counts.chat} chat turns`] : []), ...(counts.sort > 0 ? [`${counts.sort} ${counts.sort === 1 ? "sort" : "sorts"}`] : [])];
46
- const nameOfTeammate = (key) => view.teammateNames.get(Number(key)) ?? `Teammate ${key}`;
44
+ const counts = { run: view.items.filter(item => item.kind === "run").length, subagent: view.items.filter(item => item.kind === "subagent").length, chat: view.items.filter(item => item.kind === "chat").length, sort: view.items.filter(item => item.kind === "sort").length };
45
+ const parts = [`${counts.run} ${counts.run === 1 ? "run" : "runs"}`, ...(counts.subagent > 0 ? [`${counts.subagent} subagent turns`] : []), ...(counts.chat > 0 ? [`${counts.chat} chat turns`] : []), ...(counts.sort > 0 ? [`${counts.sort} ${counts.sort === 1 ? "sort" : "sorts"}`] : [])];
46
+ const nameOfSubagent = (key) => view.subagentNames.get(Number(key)) ?? `Subagent ${key}`;
47
47
  const budgets = view.budgets.map(budget => {
48
48
  const width = Math.min(100, Math.max(0, budget.percent));
49
49
  const over = budget.spentMicrousd >= budget.limitMicrousd;
50
- const label = budgetLabel(budget, budget.scope === "teammate" ? nameOfTeammate(budget.key) : undefined).replace(/'s$/, "");
50
+ const label = budgetLabel(budget, budget.scope === "subagent" ? nameOfSubagent(budget.key) : undefined).replace(/'s$/, "");
51
51
  return html `<div class="budget${over ? " over" : ""}" data-budget="${budget.id}"><span class="name">${label}</span><span class="figures">${usd(budget.spentMicrousd)} of ${usd(budget.limitMicrousd)} · ${budget.percent}%</span><span class="bar" aria-hidden="true"><span style="width:${width}%"></span></span><span class="meta">${over && budget.hardStop ? "Used up: new API work waits until next month or a higher budget." : budget.hardStop ? "Stops API work at 100%." : "Alerts only."}${budget.unpriced > 0 ? ` ${budget.unpriced} unpriced` : ""}</span><details><summary>Change</summary>${postForm("/spend/budget", html `<label>Monthly limit (US dollars)<input type="number" name="usd" min="1" step="1" value="${Math.round(budget.limitMicrousd / 1_000_000)}" required></label><label class="choice"><input type="checkbox" name="stop" value="1"${budget.hardStop ? html ` checked` : ""}> Stop new work at 100%</label><label>Your Toolroll password<input type="password" name="password" autocomplete="current-password"></label><button type="submit" name="action" value="save">Save</button> <button type="submit" name="action" value="remove">Remove</button>`, { attrs: { class: "budget-form" }, hidden: { target: `${budget.scope}:${budget.key}` } })}</details></div>`;
52
52
  });
53
- const groups = ["Everything", "Projects", "People", "Teammates"];
53
+ const groups = ["Everything", "Projects", "People", "Subagents"];
54
54
  const options = groups.map(group => {
55
55
  const members = view.targets.filter(one => one.group === group);
56
56
  return members.length === 0 ? "" : html `<optgroup label="${group}">${members.map(one => html `<option value="${one.value}">${one.label}</option>`)}</optgroup>`;
57
57
  });
58
58
  const add = html `<details${view.budgets.length === 0 ? html ` open` : ""}><summary>Add a budget</summary>${postForm("/spend/budget", html `<label>For<select name="target" required>${options}</select></label><label>Monthly limit (US dollars)<input type="number" name="usd" min="1" step="1" required></label><label class="choice"><input type="checkbox" name="stop" value="1" checked> Stop new work at 100%</label><label>Your Toolroll password<input type="password" name="password" autocomplete="current-password"></label><button type="submit" name="action" value="save">Add budget</button>`, { attrs: { class: "budget-form" } })}</details>`;
59
- return html `<article class="spend"><div class="spend-head"><h1>Spend</h1><p class="spend-month"><a href="/spend?month=${view.previous}">← ${view.previous}</a><strong>${view.month}</strong>${view.next === null ? "" : html `<a href="/spend?month=${view.next}">${view.next} →</a>`}<a href="/spend?month=${view.month}&amp;format=csv" download>CSV</a></p></div>${note}<p class="spend-total" data-spend-total="${total}">${usd(total)}</p><p class="meta">${parts.join(" · ")}${unpriced > 0 ? ` · ${unpriced} unpriced (no reported cost or catalogue price)` : ""} · UTC month</p><section><h2>Budgets</h2>${budgets.length === 0 ? html `<p class="meta">No budgets yet.</p>` : budgets}${add}</section><div class="spend-grid">${table("By project", breakdown(view.items, item => item.project), projectName)}${table("By person", breakdown(view.items, item => item.person), key => key)}${table("By teammate", breakdown(view.items, item => item.teammate === null ? null : String(item.teammate)), nameOfTeammate)}${table("By model", breakdown(view.items, item => `${item.provider}${item.model === null ? "" : ` · ${item.model}`}`), key => key)}</div><p class="meta">Subscription work is $0; its limits are on Tasks. API work is what the provider reported, or its tokens at the prices in Settings → Models.</p></article>`;
59
+ return html `<article class="spend"><div class="spend-head"><h1>Spend</h1><p class="spend-month"><a href="/spend?month=${view.previous}">← ${view.previous}</a><strong>${view.month}</strong>${view.next === null ? "" : html `<a href="/spend?month=${view.next}">${view.next} →</a>`}<a href="/spend?month=${view.month}&amp;format=csv" download>CSV</a></p></div>${note}<p class="spend-total" data-spend-total="${total}">${usd(total)}</p><p class="meta">${parts.join(" · ")}${unpriced > 0 ? ` · ${unpriced} unpriced (no reported cost or catalogue price)` : ""} · UTC month</p><section><h2>Budgets</h2>${budgets.length === 0 ? html `<p class="meta">No budgets yet.</p>` : budgets}${add}</section><div class="spend-grid">${table("By project", breakdown(view.items, item => item.project), projectName)}${table("By person", breakdown(view.items, item => item.person), key => key)}${table("By subagent", breakdown(view.items, item => item.subagent === null ? null : String(item.subagent)), nameOfSubagent)}${table("By model", breakdown(view.items, item => `${item.provider}${item.model === null ? "" : ` · ${item.model}`}`), key => key)}</div><p class="meta">Subscription work is $0; its limits are on Tasks. API work is what the provider reported, or its tokens at the prices in Settings → Models.</p></article>`;
60
60
  }
61
61
  /** One row per piece of spend, for a spreadsheet. */
62
- export function spendCsv(items, teammateNames) {
62
+ export function spendCsv(items, subagentNames) {
63
63
  const cell = (value) => {
64
64
  const text = value === null || value === undefined ? "" : String(value);
65
65
  const safe = /^[=+\-@\t\r]/.test(text) ? `'${text}` : text;
66
66
  return /[",\n\r]/.test(safe) ? `"${safe.replace(/"/g, '""')}"` : safe;
67
67
  };
68
- const head = ["time_utc", "kind", "project", "person", "teammate", "task", "run", "provider", "model", "tokens_in", "tokens_out", "cost_usd", "priced_by", "billing"];
69
- const rows = items.map(item => [item.at, item.kind, item.project, item.person, item.teammate === null ? null : teammateNames.get(item.teammate) ?? item.teammate,
68
+ const head = ["time_utc", "kind", "project", "person", "subagent", "task", "run", "provider", "model", "tokens_in", "tokens_out", "cost_usd", "priced_by", "billing"];
69
+ const rows = items.map(item => [item.at, item.kind, item.project, item.person, item.subagent === null ? null : subagentNames.get(item.subagent) ?? item.subagent,
70
70
  item.taskId, item.runId, item.provider, item.model, item.tokensIn, item.tokensOut, item.microusd === null ? null : (item.microusd / 1_000_000).toFixed(6), item.source, item.authMode].map(cell).join(","));
71
71
  return `${head.join(",")}\r\n${rows.map(row => `${row}\r\n`).join("")}`;
72
72
  }
package/dist/spend.d.ts CHANGED
@@ -7,8 +7,8 @@
7
7
  * Models), or at the provider's highest listed price when the model isn't
8
8
  * listed; work with no tokens and no cost is "unpriced" and said so.
9
9
  *
10
- * Spend counts toward a project (a run's task, a teammate's, a project chat),
11
- * a person (who filed the task, or had the chat), a teammate (its turns, and
10
+ * Spend counts toward a project (a run's task, a subagent's, a project chat),
11
+ * a person (who filed the task, or had the chat), a subagent (its turns, and
12
12
  * tasks it filed), and the whole installation. Budgets are per calendar
13
13
  * month (UTC).
14
14
  */
@@ -30,7 +30,7 @@ export declare function claudeBillingFrom(seen: {
30
30
  model: string | null;
31
31
  planWindows: boolean;
32
32
  }): Billing | null;
33
- /** How Claude bills on this computer when Toolroll gives it no key (teammate turns, drafts): as its keyless runs
33
+ /** How Claude bills on this computer when Toolroll gives it no key (subagent turns, drafts): as its keyless runs
34
34
  * were last seen, and its plan until one has been. */
35
35
  export declare function claudeMachineBilling(db: Database): Billing;
36
36
  /** Whether API work on this provider can be priced: it reports its cost, or the catalogue has its prices. */
@@ -60,8 +60,8 @@ export declare function priceWork(db: Database, work: {
60
60
  source: PriceSource;
61
61
  price: Price | null;
62
62
  };
63
- export declare const SPEND_SCHEMA = "\nCREATE TABLE IF NOT EXISTS run_spend (\n run INTEGER PRIMARY KEY,\n microusd INTEGER,\n source TEXT NOT NULL CHECK (source IN ('reported', 'estimated', 'unpriced', 'subscription')),\n billing TEXT NOT NULL CHECK (billing IN ('subscription', 'api-key')),\n -- 1 once the run's own evidence set it (a key it was given, what its CLI said); a later turn can only make it a key.\n billing_fixed INTEGER NOT NULL DEFAULT 0,\n price_model TEXT,\n input_usd REAL,\n output_usd REAL,\n priced_at TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS budget (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n scope_kind TEXT NOT NULL CHECK (scope_kind IN ('installation', 'project', 'person', 'teammate')),\n scope_key TEXT NOT NULL,\n limit_microusd INTEGER NOT NULL CHECK (limit_microusd > 0),\n hard_stop INTEGER NOT NULL DEFAULT 1,\n created_by TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_by TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n removed_by TEXT,\n removed_at TEXT\n);\nCREATE UNIQUE INDEX IF NOT EXISTS budget_live ON budget (scope_kind, scope_key) WHERE removed_at IS NULL;\nCREATE TABLE IF NOT EXISTS provider_limit (\n provider TEXT NOT NULL,\n window TEXT NOT NULL,\n used_percent REAL NOT NULL,\n window_minutes INTEGER,\n resets_at TEXT,\n reached INTEGER NOT NULL DEFAULT 0,\n plan TEXT,\n observed_at TEXT NOT NULL,\n PRIMARY KEY (provider, window)\n);\n-- Spend that isn't a run, a teammate turn or a chat: a flow's Claude draft.\nCREATE TABLE IF NOT EXISTS side_spend (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n kind TEXT NOT NULL CHECK (kind IN ('draft')),\n repo TEXT,\n provider TEXT NOT NULL,\n model TEXT,\n cost_usd REAL,\n billing TEXT NOT NULL CHECK (billing IN ('subscription', 'api-key')),\n at TEXT NOT NULL\n);\nCREATE INDEX IF NOT EXISTS side_spend_at ON side_spend (at);\nCREATE TABLE IF NOT EXISTS provider_account (\n provider TEXT PRIMARY KEY,\n billing TEXT NOT NULL CHECK (billing IN ('subscription', 'api-key')),\n observed_at TEXT NOT NULL\n);\nCREATE INDEX IF NOT EXISTS chat_turn_created ON chat_turn (created_at);\nCREATE INDEX IF NOT EXISTS mate_turn_created ON mate_turn (created_at);\nCREATE INDEX IF NOT EXISTS teammate_turn_at ON teammate_turn (at);\n";
64
- export type BudgetScope = "installation" | "project" | "person" | "teammate";
63
+ export declare const SPEND_SCHEMA = "\nCREATE TABLE IF NOT EXISTS run_spend (\n run INTEGER PRIMARY KEY,\n microusd INTEGER,\n source TEXT NOT NULL CHECK (source IN ('reported', 'estimated', 'unpriced', 'subscription')),\n billing TEXT NOT NULL CHECK (billing IN ('subscription', 'api-key')),\n -- 1 once the run's own evidence set it (a key it was given, what its CLI said); a later turn can only make it a key.\n billing_fixed INTEGER NOT NULL DEFAULT 0,\n price_model TEXT,\n input_usd REAL,\n output_usd REAL,\n priced_at TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS budget (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n scope_kind TEXT NOT NULL CHECK (scope_kind IN ('installation', 'project', 'person', 'subagent')),\n scope_key TEXT NOT NULL,\n limit_microusd INTEGER NOT NULL CHECK (limit_microusd > 0),\n hard_stop INTEGER NOT NULL DEFAULT 1,\n created_by TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_by TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n removed_by TEXT,\n removed_at TEXT\n);\nCREATE UNIQUE INDEX IF NOT EXISTS budget_live ON budget (scope_kind, scope_key) WHERE removed_at IS NULL;\nCREATE TABLE IF NOT EXISTS provider_limit (\n provider TEXT NOT NULL,\n window TEXT NOT NULL,\n used_percent REAL NOT NULL,\n window_minutes INTEGER,\n resets_at TEXT,\n reached INTEGER NOT NULL DEFAULT 0,\n plan TEXT,\n observed_at TEXT NOT NULL,\n PRIMARY KEY (provider, window)\n);\n-- Spend that isn't a run, a subagent turn or a chat: a flow's Claude draft.\nCREATE TABLE IF NOT EXISTS side_spend (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n kind TEXT NOT NULL CHECK (kind IN ('draft')),\n repo TEXT,\n provider TEXT NOT NULL,\n model TEXT,\n cost_usd REAL,\n billing TEXT NOT NULL CHECK (billing IN ('subscription', 'api-key')),\n at TEXT NOT NULL\n);\nCREATE INDEX IF NOT EXISTS side_spend_at ON side_spend (at);\nCREATE TABLE IF NOT EXISTS provider_account (\n provider TEXT PRIMARY KEY,\n billing TEXT NOT NULL CHECK (billing IN ('subscription', 'api-key')),\n observed_at TEXT NOT NULL\n);\nCREATE INDEX IF NOT EXISTS chat_turn_created ON chat_turn (created_at);\nCREATE INDEX IF NOT EXISTS mate_turn_created ON mate_turn (created_at);\nCREATE INDEX IF NOT EXISTS subagent_turn_at ON subagent_turn (at);\n";
64
+ export type BudgetScope = "installation" | "project" | "person" | "subagent";
65
65
  export type Budget = {
66
66
  id: number;
67
67
  scope: BudgetScope;
@@ -74,12 +74,12 @@ export type Budget = {
74
74
  /** One piece of spend, whatever made it, with what it counts toward. */
75
75
  export type SpendItem = {
76
76
  at: string;
77
- kind: "run" | "teammate" | "chat" | "sort" | "draft";
77
+ kind: "run" | "subagent" | "chat" | "sort" | "draft";
78
78
  microusd: number | null;
79
79
  source: PriceSource;
80
80
  project: string | null;
81
81
  person: string | null;
82
- teammate: number | null;
82
+ subagent: number | null;
83
83
  provider: string;
84
84
  model: string | null;
85
85
  tokensIn: number | null;
@@ -100,9 +100,9 @@ export declare function monthNamed(name: string | null): {
100
100
  to: string;
101
101
  name: string;
102
102
  } | null;
103
- /** Every teammate's name by id (its handle when its soul file names none). */
104
- export declare function teammateNames(db: Database): Map<number, string>;
105
- export declare function teammateFilers(db: Database): (repo: string | null, filedBy: string | null, kind: string | null) => number | null;
103
+ /** Every subagent's name by id (its handle when its soul file names none). */
104
+ export declare function subagentNames(db: Database): Map<number, string>;
105
+ export declare function subagentFilers(db: Database): (repo: string | null, filedBy: string | null, kind: string | null) => number | null;
106
106
  /** Who a task's work counts toward: its own filer, or, for a revision filed by automation (an automatic repair), the
107
107
  * filer of the task it revises, up its ancestry (bounded). */
108
108
  export declare function filersOf(db: Database): (taskRef: number) => {
@@ -110,7 +110,7 @@ export declare function filersOf(db: Database): (taskRef: number) => {
110
110
  filedBy: string | null;
111
111
  kind: string | null;
112
112
  };
113
- /** Every piece of spend in [from, to): runs (by when they started), teammate turns and chat turns. `modeOf` says how
113
+ /** Every piece of spend in [from, to): runs (by when they started), subagent turns and chat turns. `modeOf` says how
114
114
  * a provider bills when a record doesn't (older rows): its current login or key. */
115
115
  export declare function spendItems(db: Database, from: string, to: string, modeOf?: (provider: string) => Billing): SpendItem[];
116
116
  /** Whether a piece of spend counts toward a budget. */
@@ -134,9 +134,9 @@ export type BudgetHold = {
134
134
  remainingMicrousd: number | null;
135
135
  };
136
136
  /** Why work waits on a budget, in words. */
137
- export declare function budgetHoldWords(hold: BudgetHold, month: string, teammateName?: string): string;
137
+ export declare function budgetHoldWords(hold: BudgetHold, month: string, subagentName?: string): string;
138
138
  /** Each budget's month so far: what counted toward it, and how many pieces of work had no price. */
139
139
  export declare function budgetStates(budgets: readonly Budget[], items: readonly SpendItem[]): BudgetState[];
140
- /** A budget in words: "The shop project's", "alex's", "Maya's (teammate)", "The whole installation's". */
141
- export declare function budgetLabel(budget: Pick<Budget, "scope" | "key">, teammateName?: string): string;
140
+ /** A budget in words: "The shop project's", "alex's", "Maya's (subagent)", "The whole installation's". */
141
+ export declare function budgetLabel(budget: Pick<Budget, "scope" | "key">, subagentName?: string): string;
142
142
  export declare const usd: (microusd: number | null) => string;
package/dist/spend.js CHANGED
@@ -30,7 +30,7 @@ export function claudeBillingFrom(seen) {
30
30
  return "subscription";
31
31
  return seen.keySource === "none" ? "api-key" : null;
32
32
  }
33
- /** How Claude bills on this computer when Toolroll gives it no key (teammate turns, drafts): as its keyless runs
33
+ /** How Claude bills on this computer when Toolroll gives it no key (subagent turns, drafts): as its keyless runs
34
34
  * were last seen, and its plan until one has been. */
35
35
  export function claudeMachineBilling(db) {
36
36
  return seenBilling(db, "claude") ?? "subscription";
@@ -100,7 +100,7 @@ CREATE TABLE IF NOT EXISTS run_spend (
100
100
  );
101
101
  CREATE TABLE IF NOT EXISTS budget (
102
102
  id INTEGER PRIMARY KEY AUTOINCREMENT,
103
- scope_kind TEXT NOT NULL CHECK (scope_kind IN ('installation', 'project', 'person', 'teammate')),
103
+ scope_kind TEXT NOT NULL CHECK (scope_kind IN ('installation', 'project', 'person', 'subagent')),
104
104
  scope_key TEXT NOT NULL,
105
105
  limit_microusd INTEGER NOT NULL CHECK (limit_microusd > 0),
106
106
  hard_stop INTEGER NOT NULL DEFAULT 1,
@@ -123,7 +123,7 @@ CREATE TABLE IF NOT EXISTS provider_limit (
123
123
  observed_at TEXT NOT NULL,
124
124
  PRIMARY KEY (provider, window)
125
125
  );
126
- -- Spend that isn't a run, a teammate turn or a chat: a flow's Claude draft.
126
+ -- Spend that isn't a run, a subagent turn or a chat: a flow's Claude draft.
127
127
  CREATE TABLE IF NOT EXISTS side_spend (
128
128
  id INTEGER PRIMARY KEY AUTOINCREMENT,
129
129
  kind TEXT NOT NULL CHECK (kind IN ('draft')),
@@ -142,7 +142,7 @@ CREATE TABLE IF NOT EXISTS provider_account (
142
142
  );
143
143
  CREATE INDEX IF NOT EXISTS chat_turn_created ON chat_turn (created_at);
144
144
  CREATE INDEX IF NOT EXISTS mate_turn_created ON mate_turn (created_at);
145
- CREATE INDEX IF NOT EXISTS teammate_turn_at ON teammate_turn (at);
145
+ CREATE INDEX IF NOT EXISTS subagent_turn_at ON subagent_turn (at);
146
146
  `;
147
147
  /** The calendar month (UTC) `at` falls in: [start, end) as ISO strings, and its name (YYYY-MM). */
148
148
  export function monthOf(at) {
@@ -156,25 +156,25 @@ export function monthNamed(name) {
156
156
  return null;
157
157
  return monthOf(new Date(`${name}-01T00:00:00.000Z`));
158
158
  }
159
- /** The teammate behind a filing: tasks a teammate files are filed as its name ("Maya (AI)", from its soul file), in
160
- * its project. A later teammate with the same name and project wins, as it would read on the card. */
161
- /** A teammate's name as its soul file gives it ("Maya"), or null. */
159
+ /** The subagent behind a filing: tasks a subagent files are filed as its name ("Maya (AI)", from its soul file), in
160
+ * its project. A later subagent with the same name and project wins, as it would read on the card. */
161
+ /** A subagent's name as its soul file gives it ("Maya"), or null. */
162
162
  const soulName = (soul) => {
163
163
  const front = /^---\s*\n([\s\S]*?)\n---/.exec(String(soul ?? ""));
164
164
  return front === null ? null : /^name:\s*(.+?)\s*$/m.exec(front[1])?.[1] ?? null;
165
165
  };
166
- /** Every teammate's name by id (its handle when its soul file names none). */
167
- export function teammateNames(db) {
168
- return new Map(db.prepare("SELECT id, handle, soul FROM teammate").all().map(row => [Number(row["id"]), soulName(row["soul"]) ?? String(row["handle"])]));
166
+ /** Every subagent's name by id (its handle when its soul file names none). */
167
+ export function subagentNames(db) {
168
+ return new Map(db.prepare("SELECT id, handle, soul FROM subagent").all().map(row => [Number(row["id"]), soulName(row["soul"]) ?? String(row["handle"])]));
169
169
  }
170
- export function teammateFilers(db) {
170
+ export function subagentFilers(db) {
171
171
  const byName = new Map();
172
- for (const row of db.prepare("SELECT id, repo, handle, soul FROM teammate ORDER BY id").all()) {
172
+ for (const row of db.prepare("SELECT id, repo, handle, soul FROM subagent ORDER BY id").all()) {
173
173
  const name = soulName(row["soul"]);
174
174
  for (const one of [String(row["handle"]), ...(name === null ? [] : [name])])
175
175
  byName.set(`${String(row["repo"])}\n${one} (AI)`, Number(row["id"]));
176
176
  }
177
- return (repo, filedBy, kind) => kind !== "teammate" || repo === null || filedBy === null ? null : byName.get(`${repo}\n${filedBy}`) ?? null;
177
+ return (repo, filedBy, kind) => kind !== "subagent" || repo === null || filedBy === null ? null : byName.get(`${repo}\n${filedBy}`) ?? null;
178
178
  }
179
179
  /** Who a task's work counts toward: its own filer, or, for a revision filed by automation (an automatic repair), the
180
180
  * filer of the task it revises, up its ancestry (bounded). */
@@ -198,10 +198,10 @@ export function filersOf(db) {
198
198
  return found;
199
199
  };
200
200
  }
201
- /** Every piece of spend in [from, to): runs (by when they started), teammate turns and chat turns. `modeOf` says how
201
+ /** Every piece of spend in [from, to): runs (by when they started), subagent turns and chat turns. `modeOf` says how
202
202
  * a provider bills when a record doesn't (older rows): its current login or key. */
203
203
  export function spendItems(db, from, to, modeOf = provider => billingOf(provider, db)) {
204
- const teammateOf = teammateFilers(db);
204
+ const subagentOf = subagentFilers(db);
205
205
  const filerOf = filersOf(db);
206
206
  const text = (value) => value == null ? null : String(value);
207
207
  const count = (value) => value == null ? null : Number(value);
@@ -224,19 +224,19 @@ export function spendItems(db, from, to, modeOf = provider => billingOf(provider
224
224
  source: settled ? String(row["source"]) : priced.source,
225
225
  project: text(row["repo"]),
226
226
  person: kind === "person" || kind === "coordinator" ? filer.filedBy : null,
227
- teammate: teammateOf(filer.repo, filer.filedBy, kind), provider, model: text(row["model"]),
227
+ subagent: subagentOf(filer.repo, filer.filedBy, kind), provider, model: text(row["model"]),
228
228
  tokensIn: count(row["tokens_in"]), tokensOut: count(row["tokens_out"]),
229
229
  taskId: String(row["task"]), runId: Number(row["id"]), authMode: billing,
230
230
  };
231
231
  });
232
- // A teammate's own turns get no key from Toolroll (teammates.ts claudeTurnRunner): they bill as this computer's
232
+ // A subagent's own turns get no key from Toolroll (subagents.ts claudeTurnRunner): they bill as this computer's
233
233
  // Claude sign-in was seen billing when the turn ran (a plan, or a Console login, key helper or gateway).
234
- for (const row of db.prepare(`SELECT t.at, t.teammate, t.model, t.cost_usd, t.tokens_in, t.tokens_out, t.billing, m.repo FROM teammate_turn t JOIN teammate m ON m.id = t.teammate
234
+ for (const row of db.prepare(`SELECT t.at, t.subagent, t.model, t.cost_usd, t.tokens_in, t.tokens_out, t.billing, m.repo FROM subagent_turn t JOIN subagent m ON m.id = t.subagent
235
235
  WHERE t.at >= ? AND t.at < ? ORDER BY t.id`).all(from, to)) {
236
236
  const billing = row["billing"] === "api-key" ? "api-key" : "subscription";
237
237
  const priced = priceWork(db, { provider: "claude", model: text(row["model"]), costUsd: count(row["cost_usd"]), tokensIn: count(row["tokens_in"]), tokensOut: count(row["tokens_out"]), billing });
238
- items.push({ at: String(row["at"]), kind: "teammate", microusd: priced.microusd, source: priced.source, project: String(row["repo"]), person: null,
239
- teammate: Number(row["teammate"]), provider: "claude", model: text(row["model"]),
238
+ items.push({ at: String(row["at"]), kind: "subagent", microusd: priced.microusd, source: priced.source, project: String(row["repo"]), person: null,
239
+ subagent: Number(row["subagent"]), provider: "claude", model: text(row["model"]),
240
240
  tokensIn: count(row["tokens_in"]), tokensOut: count(row["tokens_out"]), taskId: null, runId: null, authMode: billing });
241
241
  }
242
242
  // A chat turn that mirrors a project chat's turn is that turn: counted once, as the project chat's. A task's chat
@@ -246,19 +246,19 @@ export function spendItems(db, from, to, modeOf = provider => billingOf(provider
246
246
  UNION ALL SELECT t.created_at, t.approver, COALESCE((SELECT c.provider FROM chat_turn c WHERE c.mate_turn = t.id ORDER BY c.id LIMIT 1), 'lead'),
247
247
  (SELECT c.model FROM chat_turn c WHERE c.mate_turn = t.id ORDER BY c.id LIMIT 1), t.settled_microusd, t.tokens_in, t.tokens_out,
248
248
  CASE th.scope_kind WHEN 'project' THEN th.scope_key WHEN 'task' THEN (SELECT r.repo FROM task_ref r WHERE r.external_id = th.scope_key ORDER BY r.id DESC LIMIT 1) END
249
- FROM mate_turn t LEFT JOIN mate_thread th ON th.id = t.thread WHERE t.created_at >= ? AND t.created_at < ?`).all(from, to, from, to)) {
249
+ FROM mate_turn t LEFT JOIN lead_thread th ON th.id = t.thread WHERE t.created_at >= ? AND t.created_at < ?`).all(from, to, from, to)) {
250
250
  const provider = String(row["provider"]);
251
251
  const subscription = provider.endsWith("-subscription") && modeOf(provider) === "subscription";
252
252
  const settled = count(row["settled_microusd"]);
253
253
  items.push({ at: String(row["at"]), kind: "chat", microusd: subscription ? 0 : settled, source: subscription ? "subscription" : settled === null ? "unpriced" : "reported",
254
- project: text(row["project"]), person: text(row["approver"]), teammate: null, provider, model: text(row["model"]),
254
+ project: text(row["project"]), person: text(row["approver"]), subagent: null, provider, model: text(row["model"]),
255
255
  tokensIn: count(row["tokens_in"]), tokensOut: count(row["tokens_out"]), taskId: null, runId: null, authMode: subscription ? "subscription" : "api-key" });
256
256
  }
257
- // A flow's Claude drafts: like a teammate's turns, billed as the sign-in was.
257
+ // A flow's Claude drafts: like a subagent's turns, billed as the sign-in was.
258
258
  for (const row of db.prepare("SELECT at, kind, repo, provider, model, cost_usd, billing FROM side_spend WHERE at >= ? AND at < ? ORDER BY id").all(from, to)) {
259
259
  const billing = row["billing"] === "api-key" ? "api-key" : "subscription";
260
260
  const priced = priceWork(db, { provider: String(row["provider"]), model: text(row["model"]), costUsd: count(row["cost_usd"]), tokensIn: null, tokensOut: null, billing });
261
- items.push({ at: String(row["at"]), kind: "draft", microusd: priced.microusd, source: priced.source, project: text(row["repo"]), person: null, teammate: null,
261
+ items.push({ at: String(row["at"]), kind: "draft", microusd: priced.microusd, source: priced.source, project: text(row["repo"]), person: null, subagent: null,
262
262
  provider: String(row["provider"]), model: text(row["model"]), tokensIn: null, tokensOut: null, taskId: null, runId: null, authMode: billing });
263
263
  }
264
264
  // A flow's Sort zone asks Jev on OpenRouter (the key's credit): what it cost is kept with its answer.
@@ -267,7 +267,7 @@ export function spendItems(db, from, to, modeOf = provider => billingOf(provider
267
267
  WHERE fs.kind = 'sort' AND fs.decision_json IS NOT NULL AND fs.started_at >= ? AND fs.started_at < ? ORDER BY fs.started_at`).all(from, to)) {
268
268
  const cost = typeof row["cost"] === "number" && Number.isFinite(row["cost"]) && row["cost"] >= 0 ? row["cost"] : null;
269
269
  items.push({ at: String(row["at"]), kind: "sort", microusd: cost === null ? null : Math.round(cost * 1_000_000), source: cost === null ? "unpriced" : "reported",
270
- project: text(row["repo"]), person: null, teammate: null, provider: "openrouter", model: text(row["model"]), tokensIn: null, tokensOut: null, taskId: null, runId: null, authMode: "api-key" });
270
+ project: text(row["repo"]), person: null, subagent: null, provider: "openrouter", model: text(row["model"]), tokensIn: null, tokensOut: null, taskId: null, runId: null, authMode: "api-key" });
271
271
  }
272
272
  return items;
273
273
  }
@@ -279,14 +279,14 @@ export function countsToward(item, budget) {
279
279
  return item.project === budget.key;
280
280
  if (budget.scope === "person")
281
281
  return item.person === budget.key;
282
- return item.teammate !== null && String(item.teammate) === budget.key;
282
+ return item.subagent !== null && String(item.subagent) === budget.key;
283
283
  }
284
284
  const PROVIDER_WORDS = { claude: "Claude", codex: "Codex", gemini: "Gemini", openrouter: "OpenRouter" };
285
285
  /** Why work waits on a budget, in words. */
286
- export function budgetHoldWords(hold, month, teammateName) {
286
+ export function budgetHoldWords(hold, month, subagentName) {
287
287
  if (hold.over === null)
288
288
  return "";
289
- const label = budgetLabel(hold.over, teammateName);
289
+ const label = budgetLabel(hold.over, subagentName);
290
290
  return hold.why === "unpriced"
291
291
  ? `${label} budget can't price ${PROVIDER_WORDS[hold.unpricedProvider ?? ""] ?? hold.unpricedProvider} work on a key yet. Open Settings → Models to load prices`
292
292
  : `${label} budget is used up for ${month}`;
@@ -300,14 +300,14 @@ export function budgetStates(budgets, items) {
300
300
  percent: Math.floor((spent / budget.limitMicrousd) * 100) };
301
301
  });
302
302
  }
303
- /** A budget in words: "The shop project's", "alex's", "Maya's (teammate)", "The whole installation's". */
304
- export function budgetLabel(budget, teammateName) {
303
+ /** A budget in words: "The shop project's", "alex's", "Maya's (subagent)", "The whole installation's". */
304
+ export function budgetLabel(budget, subagentName) {
305
305
  if (budget.scope === "installation")
306
306
  return "The whole installation's";
307
307
  if (budget.scope === "project")
308
308
  return `The ${budget.key.split("/").filter(Boolean).pop() ?? budget.key} project's`;
309
309
  if (budget.scope === "person")
310
310
  return `${budget.key}'s`;
311
- return `${teammateName ?? `Teammate ${budget.key}`}'s`;
311
+ return `${subagentName ?? `Subagent ${budget.key}`}'s`;
312
312
  }
313
313
  export const usd = (microusd) => microusd === null ? "unpriced" : `$${(microusd / 1_000_000).toFixed(microusd !== 0 && Math.abs(microusd) < 10_000_000 ? 2 : 0)}`;