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
@@ -1,20 +1,20 @@
1
- import { labelOf, nameOf, summaryOf, TEAMMATE_MODELS, zonesOf } from "./teammate-admin.js";
2
- import { TEAMMATE_TEMPLATES } from "./teammates.js";
1
+ import { labelOf, nameOf, summaryOf, SUBAGENT_MODELS, zonesOf } from "./subagent-admin.js";
2
+ import { SUBAGENT_TEMPLATES } from "./subagents.js";
3
3
  import { projectToolsOf } from "./project-tools.js";
4
- import { callWords, defaultRule, numberFields, receiptWords } from "./teammate-tools.js";
5
- import { MEMORY_CHARS, searchMemories } from "./teammate-memory.js";
6
- import { deskOf, localZone, routinesOf } from "./teammate-desk.js";
7
- import { weekOf, weekWords } from "./teammate-week.js";
4
+ import { callWords, defaultRule, numberFields, receiptWords } from "./subagent-tools.js";
5
+ import { MEMORY_CHARS, searchMemories } from "./subagent-memory.js";
6
+ import { deskOf, localZone, routinesOf } from "./subagent-desk.js";
7
+ import { weekOf, weekWords } from "./subagent-week.js";
8
8
  import { describeSchedule, parseSchedule } from "./flow-schedule.js";
9
9
  import { html, joinHtml, postForm } from "./html.js";
10
10
  /** v94: the Tools section's rule rows; a limit's fields show only while "Up to a limit" is chosen. */
11
- export const TEAMMATE_CSS = `.teammate-tools .tool-grant{border-top:1px solid var(--so-line);padding-top:12px;margin-top:12px}.teammate-tools .tool-grant:first-of-type{border-top:0;margin-top:0;padding-top:0}.teammate-tools h3{font-size:1rem;margin:0 0 4px}.teammate-tools .tool-rule{display:grid;gap:6px;padding:10px 0;border-bottom:1px solid var(--so-line)}.teammate-tools .tool-rule:last-of-type{border-bottom:0}.teammate-tools .tool-rule .meta{display:block}.teammate-tools select,.teammate-tools input{max-width:100%;box-sizing:border-box}.teammate-tools .tool-limit{display:none;flex-wrap:wrap;align-items:center;gap:6px}.teammate-tools .tool-rule:has(option[value="limit"]:checked) .tool-limit{display:flex}.teammate-tools .tool-rule>select{width:auto;min-width:16em}.teammate-tools .tool-limit select{width:auto}.teammate-tools .tool-limit input{width:7em}.teammate-tools .tool-buttons{display:flex;flex-wrap:wrap;gap:8px;margin-top:8px}.teammate-tools button{min-height:44px}.teammate-activity li{overflow-wrap:anywhere}.teammate-memory .memory-tell,.teammate-memory .memory-search{display:flex;flex-wrap:wrap;gap:8px;align-items:flex-end;margin:0 0 12px}.teammate-memory .memory-tell textarea{flex:1 1 280px}.teammate-memory .memory-search input{flex:1 1 220px}.teammate-memory button{min-height:44px}.teammate-memories{list-style:none;padding:0;margin:0}.teammate-memories li{padding:10px 0;border-top:1px solid var(--so-line);overflow-wrap:anywhere}.teammate-memories li:first-child{border-top:0}.teammate-memories .meta{display:block;font-size:.85rem}.teammate-memories details.memory-edit{border:0;padding:0;margin:2px 0 0;background:transparent;box-shadow:none}.memory-edit summary{cursor:pointer;min-height:32px;display:inline-flex;align-items:center;font-size:.85rem;font-weight:400;color:var(--so-muted)}.tool-undo{border:0;padding:0;margin:8px 0;background:transparent;box-shadow:none}.tool-undo summary{cursor:pointer;min-height:36px;display:inline-flex;align-items:center;font-size:.9rem}.tool-undo-row{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin:6px 0}.tool-undo-row select{width:auto}.teammate-overrides,.teammate-undo{padding-left:18px}.teammate-undo li{margin:6px 0}.teammate-undo form{display:inline}.teammate-week h3{font-size:1rem;margin:14px 0 4px}.teammate-week button{min-height:44px}.teammate-routines{list-style:none;padding:0;margin:8px 0}.teammate-routines li{display:grid;gap:4px;padding:10px 0;border-top:1px solid var(--so-line)}.teammate-routines li:first-child{border-top:0}.routine-buttons{display:flex;flex-wrap:wrap;gap:8px}.routine-buttons button,.routine-add button{min-height:44px}.routine-add{display:flex;flex-wrap:wrap;gap:8px;align-items:flex-end;margin:8px 0}.routine-add label{display:grid;gap:4px;flex:1 1 200px}.routine-add label:first-of-type{flex:0 1 180px}.teammate-actions,.question-options{display:flex;flex-wrap:wrap;gap:8px;margin:8px 0}.teammate-actions form,.question-options form{margin:0}.memory-edit form{display:grid;gap:8px;margin-top:6px}.memory-buttons{display:flex;flex-wrap:wrap;gap:8px}@media(max-width:600px){.teammate-tools select,.teammate-tools input{font-size:16px}}`;
11
+ export const SUBAGENT_CSS = `.subagent-tools .tool-grant{border-top:1px solid var(--so-line);padding-top:12px;margin-top:12px}.subagent-tools .tool-grant:first-of-type{border-top:0;margin-top:0;padding-top:0}.subagent-tools h3{font-size:1rem;margin:0 0 4px}.subagent-tools .tool-rule{display:grid;gap:6px;padding:10px 0;border-bottom:1px solid var(--so-line)}.subagent-tools .tool-rule:last-of-type{border-bottom:0}.subagent-tools .tool-rule .meta{display:block}.subagent-tools select,.subagent-tools input{max-width:100%;box-sizing:border-box}.subagent-tools .tool-limit{display:none;flex-wrap:wrap;align-items:center;gap:6px}.subagent-tools .tool-rule:has(option[value="limit"]:checked) .tool-limit{display:flex}.subagent-tools .tool-rule>select{width:auto;min-width:16em}.subagent-tools .tool-limit select{width:auto}.subagent-tools .tool-limit input{width:7em}.subagent-tools .tool-buttons{display:flex;flex-wrap:wrap;gap:8px;margin-top:8px}.subagent-tools button{min-height:44px}.subagent-activity li{overflow-wrap:anywhere}.subagent-memory .memory-tell,.subagent-memory .memory-search{display:flex;flex-wrap:wrap;gap:8px;align-items:flex-end;margin:0 0 12px}.subagent-memory .memory-tell textarea{flex:1 1 280px}.subagent-memory .memory-search input{flex:1 1 220px}.subagent-memory button{min-height:44px}.subagent-memories{list-style:none;padding:0;margin:0}.subagent-memories li{padding:10px 0;border-top:1px solid var(--so-line);overflow-wrap:anywhere}.subagent-memories li:first-child{border-top:0}.subagent-memories .meta{display:block;font-size:.85rem}.subagent-memories details.memory-edit{border:0;padding:0;margin:2px 0 0;background:transparent;box-shadow:none}.memory-edit summary{cursor:pointer;min-height:32px;display:inline-flex;align-items:center;font-size:.85rem;font-weight:400;color:var(--so-muted)}.tool-undo{border:0;padding:0;margin:8px 0;background:transparent;box-shadow:none}.tool-undo summary{cursor:pointer;min-height:36px;display:inline-flex;align-items:center;font-size:.9rem}.tool-undo-row{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin:6px 0}.tool-undo-row select{width:auto}.subagent-overrides,.subagent-undo{padding-left:18px}.subagent-undo li{margin:6px 0}.subagent-undo form{display:inline}.subagent-week h3{font-size:1rem;margin:14px 0 4px}.subagent-week button{min-height:44px}.subagent-routines{list-style:none;padding:0;margin:8px 0}.subagent-routines li{display:grid;gap:4px;padding:10px 0;border-top:1px solid var(--so-line)}.subagent-routines li:first-child{border-top:0}.routine-buttons{display:flex;flex-wrap:wrap;gap:8px}.routine-buttons button,.routine-add button{min-height:44px}.routine-add{display:flex;flex-wrap:wrap;gap:8px;align-items:flex-end;margin:8px 0}.routine-add label{display:grid;gap:4px;flex:1 1 200px}.routine-add label:first-of-type{flex:0 1 180px}.subagent-actions,.question-options{display:flex;flex-wrap:wrap;gap:8px;margin:8px 0}.subagent-actions form,.question-options form{margin:0}.memory-edit form{display:grid;gap:8px;margin-top:6px}.memory-buttons{display:flex;flex-wrap:wrap;gap:8px}@media(max-width:600px){.subagent-tools select,.subagent-tools input{font-size:16px}}`;
12
12
  const USES = [["free", "Do it"], ["limit", "Do it, up to a limit"], ["ask", "Ask first"], ["never", "Never"]];
13
13
  /** v94: which project tools it may use, and its rule for each action. */
14
14
  function toolsSection(store, mate, canManage) {
15
15
  const name = nameOf(mate);
16
16
  const tools = projectToolsOf(store, mate.repo);
17
- const grants = store.teammateGrants(mate.id);
17
+ const grants = store.subagentGrants(mate.id);
18
18
  const toolsPage = `/settings/tools?repo=${encodeURIComponent(mate.repo)}`;
19
19
  const granted = grants.map(grant => {
20
20
  const gone = !tools.some(one => one.name === grant.tool);
@@ -38,41 +38,41 @@ function toolsSection(store, mate, canManage) {
38
38
  });
39
39
  const undoBox = undoing.length > 0 && html `<details class="tool-undo"${grant.actions.some(one => grant.rules[one.name]?.undo !== undefined) && html ` open`}><summary>Undo</summary><p class="meta">When an action has an opposite, a person can press Undo on its receipts: the opposite is called with the same input.</p>${undoing}</details>`;
40
40
  return canManage
41
- ? html `<div class="tool-grant" data-tool-grant="${grant.tool}">${head}${postForm(`/teammates/${mate.id}/tools`, html `${rows}${undoBox}<div class="tool-buttons"><button name="op" value="rules">Save rules</button><button name="op" value="revoke" class="secondary">Stop using ${grant.tool}</button></div>`, { hidden: { tool: grant.tool } })}</div>`
41
+ ? html `<div class="tool-grant" data-tool-grant="${grant.tool}">${head}${postForm(`/settings/lead/subagents/${mate.id}/tools`, html `${rows}${undoBox}<div class="tool-buttons"><button name="op" value="rules">Save rules</button><button name="op" value="revoke" class="secondary">Stop using ${grant.tool}</button></div>`, { hidden: { tool: grant.tool } })}</div>`
42
42
  : html `<div class="tool-grant" data-tool-grant="${grant.tool}">${head}<ul>${rows}</ul></div>`;
43
43
  });
44
44
  const open = tools.filter(one => !grants.some(grant => grant.tool === one.name));
45
45
  const add = !canManage ? null : tools.length === 0 ? html `<p class="meta">This project has no tools yet. <a href="${toolsPage}">Add one</a>, then let ${name} use it here.</p>`
46
- : open.length === 0 ? null : postForm(`/teammates/${mate.id}/tools`, html `<label>Let ${name} use<select name="tool">${open.map(one => html `<option value="${one.name}">${one.name}${one.spec.about === "" ? "" : ` — ${one.spec.about.slice(0, 80)}`}</option>`)}</select></label><button>Add tool</button>`, { attrs: { class: "tool-add" }, hidden: { op: "grant" } });
46
+ : open.length === 0 ? null : postForm(`/settings/lead/subagents/${mate.id}/tools`, html `<label>Let ${name} use<select name="tool">${open.map(one => html `<option value="${one.name}">${one.name}${one.spec.about === "" ? "" : ` — ${one.spec.about.slice(0, 80)}`}</option>`)}</select></label><button>Add tool</button>`, { attrs: { class: "tool-add" }, hidden: { op: "grant" } });
47
47
  if (grants.length === 0 && add === null)
48
48
  return "";
49
- return html `<section class="card teammate-tools" id="tools"><h2>Tools</h2>${grants.length === 0 && html `<p class="meta">${name} doesn't use any tools yet. Reading starts as “Do it”; everything else asks you first.</p>`}${granted}${add}</section>`;
49
+ return html `<section class="card subagent-tools" id="tools"><h2>Tools</h2>${grants.length === 0 && html `<p class="meta">${name} doesn't use any tools yet. Reading starts as “Do it”; everything else asks you first.</p>`}${granted}${add}</section>`;
50
50
  }
51
51
  const when = (at) => new Date(at).toLocaleString("en", { month: "short", day: "numeric", hour: "numeric", minute: "2-digit" });
52
52
  const startOfDay = () => { const day = new Date(); day.setHours(0, 0, 0, 0); return day.toISOString(); };
53
53
  /** Text whose line breaks show as breaks. */
54
54
  const lines = (text) => joinHtml(text.split("\n"), html `<br>`);
55
- /** A blank soul file: the sections a teammate reads, to fill in. */
55
+ /** A blank soul file: the sections a subagent reads, to fill in. */
56
56
  export const BLANK_SOUL = `---\nname: \nrole: \n---\n\n## Who you are\n\n\n## How you write\n\n\n## What you know\n\n\n## Decide on your own\n- \n\n## Ask first\n- \n\n## Never\n- \n`;
57
- export function teammatesListHtml(store, mates, projects, projectName, canCreate, notice) {
57
+ export function subagentsListHtml(store, mates, projects, projectName, canCreate, notice) {
58
58
  const rows = mates.map(mate => {
59
- const today = store.teammateTurnsSince(mate.id, startOfDay());
60
- const open = store.openTeammateQuestions([mate.id]).length;
61
- return html `<article class="card" data-teammate="${mate.handle}"><div class="flow-row"><h2><a href="/teammates/${mate.id}">${labelOf(mate)}</a></h2><span class="flow-counts">${projectName(mate.repo)} · ${mate.state === "active" ? "Working" : "Paused"} · ${today} today${open > 0 ? ` · ${open} question${open === 1 ? "" : "s"} for you` : ""}</span></div></article>`;
59
+ const today = store.subagentTurnsSince(mate.id, startOfDay());
60
+ const open = store.openSubagentQuestions([mate.id]).length;
61
+ return html `<article class="card" data-subagent="${mate.handle}"><div class="flow-row"><h2><a href="/settings/lead/subagents/${mate.id}">${labelOf(mate)}</a></h2><span class="flow-counts">${projectName(mate.repo)} · ${mate.state === "active" ? "Working" : "Paused"} · ${today} today${open > 0 ? ` · ${open} question${open === 1 ? "" : "s"} for you` : ""}</span></div></article>`;
62
62
  });
63
63
  const create = canCreate && projects.length > 0
64
- ? html `<details class="card"${mates.length === 0 && html ` open`}><summary>New teammate</summary>${postForm("/teammates/new", html `<label>Start from<select name="template">${TEAMMATE_TEMPLATES.map(one => html `<option value="${one.id}">${one.label}: ${one.about}</option>`)}<option value="blank">Blank: write its soul file yourself</option></select></label><label>Name<input name="name" maxlength="40" placeholder="keep the template's name"></label>${projects.length === 1 ? html `<input type="hidden" name="repo" value="${projects[0]}">` : html `<label>Project<select name="repo">${projects.map(repo => html `<option value="${repo}">${projectName(repo)}</option>`)}</select></label>`}<button>Add teammate</button>`, { attrs: { "data-new-teammate": true } })}</details>`
64
+ ? html `<details class="card" id="add"${(mates.length === 0 || notice.adding === true) && html ` open`}><summary>Add subagent</summary>${postForm("/settings/lead/subagents/new", html `<label>Start from<select name="template">${SUBAGENT_TEMPLATES.map(one => html `<option value="${one.id}">${one.label}: ${one.about}</option>`)}<option value="blank">Blank: write its soul file yourself</option></select></label><label>Name<input name="name" maxlength="40" placeholder="keep the template's name"></label>${projects.length === 1 ? html `<input type="hidden" name="repo" value="${projects[0]}">` : html `<label>Project<select name="repo">${projects.map(repo => html `<option value="${repo}">${projectName(repo)}</option>`)}</select></label>`}<button>Add subagent</button>`, { attrs: { "data-new-subagent": true } })}</details>`
65
65
  : "";
66
- const intro = html `<p class="meta">AI teammates work your flows' cards within rules you write: they decide, reply and move cards on, and ask you when their rules say to. The quickest start is a <a href="/kits">starter kit</a>: a teammate and its flow, set up in one click.</p>`;
67
- return html `<section class="teammates">${notice.problem && html `<p class="problem" role="alert">${notice.problem}</p>`}${notice.said && html `<p class="said" role="status">${notice.said}</p>`}${intro}${rows.length > 0 ? rows : html `<p class="meta">No teammates yet.</p>`}${create}</section>`;
66
+ const intro = html `<p class="meta">Your lead's helpers. Each works within rules you write and asks you when they say to. The quickest start is a <a href="/kits">starter kit</a>.</p>`;
67
+ return html `<section class="subagents">${notice.problem && html `<p class="problem" role="alert">${notice.problem}</p>`}${notice.said && html `<p class="said" role="status">${notice.said}</p>`}${intro}${rows.length > 0 ? rows : html `<p class="meta">No subagents yet.</p>`}${create}</section>`;
68
68
  }
69
69
  /** v97: its week: what it did and cost, what people overrode, and calls that can be undone. */
70
70
  function weekSection(store, mate, canManage) {
71
71
  const week = weekOf(store, mate, new Date());
72
- const overrides = week.overrides.length > 0 && html `<h3>What you overrode</h3><ul class="teammate-overrides">${week.overrides.map(one => html `<li>${one.said}${one.href !== null && html ` · <a href="${one.href}">open</a>`} <span class="meta">${when(one.at)}</span></li>`)}</ul>`;
73
- const undo = week.undoable.length > 0 && html `<h3>Undo</h3><ul class="teammate-undo">${week.undoable.map(one => html `<li data-undo="${one.call}"><span>${one.words}</span> <span class="meta">${when(one.at)}${one.href !== null && html ` · <a href="${one.href}">open</a>`}</span>${canManage && postForm(`/teammates/${mate.id}/week`, html `<button name="op" value="undo" class="secondary">Undo with ${one.undo}</button>`, { hidden: { id: one.call } })}</li>`)}</ul>`;
74
- const send = canManage && postForm(`/teammates/${mate.id}/week`, html `<button name="op" value="send" class="secondary">Send the week's report</button>`);
75
- return html `<details class="card teammate-week" id="week" open><summary>This week</summary><p class="teammate-week-said">${lines(weekWords(mate, week))}</p>${overrides}${undo}${send}</details>`;
72
+ const overrides = week.overrides.length > 0 && html `<h3>What you overrode</h3><ul class="subagent-overrides">${week.overrides.map(one => html `<li>${one.said}${one.href !== null && html ` · <a href="${one.href}">open</a>`} <span class="meta">${when(one.at)}</span></li>`)}</ul>`;
73
+ const undo = week.undoable.length > 0 && html `<h3>Undo</h3><ul class="subagent-undo">${week.undoable.map(one => html `<li data-undo="${one.call}"><span>${one.words}</span> <span class="meta">${when(one.at)}${one.href !== null && html ` · <a href="${one.href}">open</a>`}</span>${canManage && postForm(`/settings/lead/subagents/${mate.id}/week`, html `<button name="op" value="undo" class="secondary">Undo with ${one.undo}</button>`, { hidden: { id: one.call } })}</li>`)}</ul>`;
74
+ const send = canManage && postForm(`/settings/lead/subagents/${mate.id}/week`, html `<button name="op" value="send" class="secondary">Send the week's report</button>`);
75
+ return html `<details class="card subagent-week" id="week" open><summary>This week</summary><p class="subagent-week-said">${lines(weekWords(mate, week))}</p>${overrides}${undo}${send}</details>`;
76
76
  }
77
77
  /** v96: its desk — where messages to it by name and its routines land — and its routines. */
78
78
  function deskSection(store, mate, canManage) {
@@ -80,58 +80,58 @@ function deskSection(store, mate, canManage) {
80
80
  const desk = deskOf(store, mate);
81
81
  const routines = routinesOf(store, mate);
82
82
  const intro = html `<p class="meta">Message ${name} by name in your chat app, like “@${mate.handle} where's order 1042?”. It lands on ${desk === null ? "its desk" : html `<a href="/flows/${desk.id}">${name}'s desk</a>`}, and the answer comes back to you.</p>`;
83
- const list = routines.length > 0 && html `<ul class="teammate-routines">${routines.map(one => html `<li data-routine="${one.id}"><span><strong>${scheduleWords(one.schedule)}</strong> · ${one.text}</span><span class="meta">${one.state === "paused" ? "Paused" : one.nextAt === null ? "" : `Next ${when(one.nextAt)}`}${one.lastAt !== null && ` · last ${when(one.lastAt)}${one.lastOutcome === null ? "" : `: ${one.lastOutcome}`}`}</span>${canManage && postForm(`/teammates/${mate.id}/routines`, html `<button name="op" value="run" class="secondary">Run now</button><button name="op" value="remove" class="secondary">Remove</button>`, { attrs: { class: "routine-buttons" }, hidden: { id: one.id } })}</li>`)}</ul>`;
84
- const add = canManage && postForm(`/teammates/${mate.id}/routines`, html `<label>When<input type="text" name="schedule" maxlength="80" placeholder="weekdays 09:00" required></label><label>What to do<input type="text" name="text" maxlength="200" placeholder="Look up yesterday's refunds and tell me the total." required></label><button>Add routine</button>`, { attrs: { class: "routine-add" }, hidden: { op: "add" } });
85
- return html `<section class="card teammate-desk" id="desk"><h2>Desk and routines</h2>${intro}${list}${add}${canManage && html `<p class="meta">A routine puts a card on its desk on that schedule (“weekdays 09:00”, “daily 17:00”, “monday 09:00”, “every 2 hours”; times are this computer's, ${localZone()}, unless you name one); the answer goes to ${mate.manager}.</p>`}</section>`;
83
+ const list = routines.length > 0 && html `<ul class="subagent-routines">${routines.map(one => html `<li data-routine="${one.id}"><span><strong>${scheduleWords(one.schedule)}</strong> · ${one.text}</span><span class="meta">${one.state === "paused" ? "Paused" : one.nextAt === null ? "" : `Next ${when(one.nextAt)}`}${one.lastAt !== null && ` · last ${when(one.lastAt)}${one.lastOutcome === null ? "" : `: ${one.lastOutcome}`}`}</span>${canManage && postForm(`/settings/lead/subagents/${mate.id}/routines`, html `<button name="op" value="run" class="secondary">Run now</button><button name="op" value="remove" class="secondary">Remove</button>`, { attrs: { class: "routine-buttons" }, hidden: { id: one.id } })}</li>`)}</ul>`;
84
+ const add = canManage && postForm(`/settings/lead/subagents/${mate.id}/routines`, html `<label>When<input type="text" name="schedule" maxlength="80" placeholder="weekdays 09:00" required></label><label>What to do<input type="text" name="text" maxlength="200" placeholder="Look up yesterday's refunds and tell me the total." required></label><button>Add routine</button>`, { attrs: { class: "routine-add" }, hidden: { op: "add" } });
85
+ return html `<section class="card subagent-desk" id="desk"><h2>Desk and routines</h2>${intro}${list}${add}${canManage && html `<p class="meta">A routine puts a card on its desk on that schedule (“weekdays 09:00”, “daily 17:00”, “monday 09:00”, “every 2 hours”; times are this computer's, ${localZone()}, unless you name one); the answer goes to ${mate.manager}.</p>`}</section>`;
86
86
  }
87
87
  /** A schedule's stored form ("weekdays:09:00@Europe/London") in words. */
88
88
  const scheduleWords = (text) => { const parsed = parseSchedule(text); return parsed === null ? text : describeSchedule(parsed); };
89
89
  /** v95: what it remembers — what its people told it, and what it kept from cards — to search, edit and forget. */
90
90
  function memorySection(store, mate, canManage, query) {
91
91
  const name = nameOf(mate);
92
- const all = store.teammateMemories(mate.id, { limit: 500 });
92
+ const all = store.subagentMemories(mate.id, { limit: 500 });
93
93
  const shown = query === null || query.trim() === "" ? all.slice(0, 30) : searchMemories(store, mate, query);
94
- const tell = canManage && postForm(`/teammates/${mate.id}/note`, html `<label class="sr-only" for="teammate-note">Tell ${name} something</label><textarea id="teammate-note" name="note" rows="2" maxlength="${MEMORY_CHARS}" placeholder="For example: this week, offer free shipping instead of a refund when you can."></textarea><button>Tell ${name}</button>`, { attrs: { class: "memory-tell" } });
95
- const search = (all.length > 8 || (query ?? "") !== "") && html `<form method="get" action="/teammates/${mate.id}#memory" class="memory-search" role="search"><label class="sr-only" for="memory-q">Search what ${name} remembers</label><input id="memory-q" type="search" name="q" value="${query ?? ""}" placeholder="Search what ${name} remembers"><button class="secondary">Search</button></form>`;
94
+ const tell = canManage && postForm(`/settings/lead/subagents/${mate.id}/note`, html `<label class="sr-only" for="subagent-note">Tell ${name} something</label><textarea id="subagent-note" name="note" rows="2" maxlength="${MEMORY_CHARS}" placeholder="For example: this week, offer free shipping instead of a refund when you can."></textarea><button>Tell ${name}</button>`, { attrs: { class: "memory-tell" } });
95
+ const search = (all.length > 8 || (query ?? "") !== "") && html `<form method="get" action="/settings/lead/subagents/${mate.id}#memory" class="memory-search" role="search"><label class="sr-only" for="memory-q">Search what ${name} remembers</label><input id="memory-q" type="search" name="q" value="${query ?? ""}" placeholder="Search what ${name} remembers"><button class="secondary">Search</button></form>`;
96
96
  const items = shown.map(one => {
97
97
  const card = one.card === null ? null : store.getFlowCard(one.card);
98
98
  const whence = one.source === "person" ? `${one.createdBy} told it` : card === null ? "it kept this" : html `it kept this from <a href="/flows/${card.flow}?card=${card.id}">${card.title.slice(0, 60)}</a>`;
99
- const edit = canManage && html `<details class="memory-edit"><summary>Edit</summary>${postForm(`/teammates/${mate.id}/memory`, html `<label class="sr-only" for="memory-${one.id}">Memory</label><textarea id="memory-${one.id}" name="text" rows="2" maxlength="${MEMORY_CHARS}">${one.text}</textarea><div class="memory-buttons"><button name="op" value="edit">Save</button><button name="op" value="forget" class="secondary">Forget</button></div>`, { hidden: { id: one.id } })}</details>`;
99
+ const edit = canManage && html `<details class="memory-edit"><summary>Edit</summary>${postForm(`/settings/lead/subagents/${mate.id}/memory`, html `<label class="sr-only" for="memory-${one.id}">Memory</label><textarea id="memory-${one.id}" name="text" rows="2" maxlength="${MEMORY_CHARS}">${one.text}</textarea><div class="memory-buttons"><button name="op" value="edit">Save</button><button name="op" value="forget" class="secondary">Forget</button></div>`, { hidden: { id: one.id } })}</details>`;
100
100
  return html `<li data-memory="${one.id}" data-source="${one.source}"><span>${one.text}</span> <span class="meta">${whence} · ${when(one.updatedAt)}</span>${edit}</li>`;
101
101
  });
102
102
  const empty = (query ?? "") !== "" ? html `<p class="meta">Nothing ${name} remembers matches that.</p>` : html `<p class="meta">Nothing yet. What you tell ${name} is kept here, and it keeps short facts from the cards it works.</p>`;
103
- return html `<section class="card teammate-memory" id="memory"><h2>Memory</h2>${tell}${search}${shown.length === 0 ? empty : html `<ul class="teammate-memories">${items}</ul>`}${query === null && all.length > shown.length && html `<p class="meta">${all.length - shown.length} older ${all.length - shown.length === 1 ? "memory" : "memories"}: search to find them.</p>`}</section>`;
103
+ return html `<section class="card subagent-memory" id="memory"><h2>Memory</h2>${tell}${search}${shown.length === 0 ? empty : html `<ul class="subagent-memories">${items}</ul>`}${query === null && all.length > shown.length && html `<p class="meta">${all.length - shown.length} older ${all.length - shown.length === 1 ? "memory" : "memories"}: search to find them.</p>`}</section>`;
104
104
  }
105
- export function teammatePageHtml(store, mate, viewer, projectName, canManage, approvers, notice) {
105
+ export function subagentPageHtml(store, mate, viewer, projectName, canManage, approvers, notice) {
106
106
  const name = nameOf(mate);
107
- const questions = store.openTeammateQuestions([mate.id]);
107
+ const questions = store.openSubagentQuestions([mate.id]);
108
108
  const asked = questions.map(question => {
109
109
  const card = store.getFlowCard(question.card);
110
110
  const flow = card === null ? null : store.getFlow(card.flow);
111
111
  const mine = question.askedOf === viewer;
112
112
  const cardLink = card === null || flow === null ? "a card" : html `<a href="/flows/${flow.id}?card=${card.id}">${card.title}</a>`;
113
- const answer = mine && html `<div class="question-options">${question.options.map(one => postForm(`/teammates/questions/${question.id}/answer`, html `<button>${one.label}</button>`, { hidden: { choice: one.id } }))}</div>${postForm(`/teammates/questions/${question.id}/answer`, html `<label>Or answer in your words<textarea name="text" rows="2" maxlength="2000"></textarea></label><button>Answer</button>`, { attrs: { class: "question-reply" } })}`;
114
- return html `<article class="card teammate-question" data-question="${question.id}"${question.suggestion !== null && html ` data-suggestion`}><p class="meta">${question.suggestion === null ? html `About ${cardLink}` : "Suggests a rule change"} · ${when(question.createdAt)}${!mine && ` · for ${question.askedOf}`}</p><p><strong>${question.question}</strong></p>${answer}</article>`;
113
+ const answer = mine && html `<div class="question-options">${question.options.map(one => postForm(`/settings/lead/subagents/questions/${question.id}/answer`, html `<button>${one.label}</button>`, { hidden: { choice: one.id } }))}</div>${postForm(`/settings/lead/subagents/questions/${question.id}/answer`, html `<label>Or answer in your words<textarea name="text" rows="2" maxlength="2000"></textarea></label><button>Answer</button>`, { attrs: { class: "question-reply" } })}`;
114
+ return html `<article class="card subagent-question" data-question="${question.id}"${question.suggestion !== null && html ` data-suggestion`}><p class="meta">${question.suggestion === null ? html `About ${cardLink}` : "Suggests a rule change"} · ${when(question.createdAt)}${!mine && ` · for ${question.askedOf}`}</p><p><strong>${question.question}</strong></p>${answer}</article>`;
115
115
  });
116
116
  const zones = zonesOf(store, mate);
117
117
  const where = zones.length === 0
118
- ? html `<p class="meta">Not on any zone yet. In a flow, choose ${name} under “Who decides” on a “Person decides” zone, or add a “Teammate handles it” zone.</p>`
118
+ ? html `<p class="meta">Not on any zone yet. In a flow, choose ${name} under “Who decides” on a “Person decides” zone, or add a “Subagent handles it” zone.</p>`
119
119
  : html `<ul>${zones.map(one => html `<li><a href="/flows/${one.flow}">${one.flowName}</a> · ${one.kind === "decides" ? "decides" : "handles"} ${one.title}</li>`)}</ul>`;
120
120
  // What it did and why, with every tool call it made or asked to make (v94), newest first.
121
121
  const events = [
122
- ...store.teammateEvents(mate.id, 40).filter(one => one.kind !== "note").map(one => ({ kind: one.kind, said: one.said, card: one.card, at: one.at })),
123
- ...store.teammateCallsOf(mate.id, 40).map(one => ({ kind: "call", said: `Used ${callWords(one.tool, one.action, one.input, 200)} · ${receiptWords(store, one)}`, card: one.card, at: one.createdAt })),
122
+ ...store.subagentEvents(mate.id, 40).filter(one => one.kind !== "note").map(one => ({ kind: one.kind, said: one.said, card: one.card, at: one.at })),
123
+ ...store.subagentCallsOf(mate.id, 40).map(one => ({ kind: "call", said: `Used ${callWords(one.tool, one.action, one.input, 200)} · ${receiptWords(store, one)}`, card: one.card, at: one.createdAt })),
124
124
  ].sort((a, b) => b.at.localeCompare(a.at)).slice(0, 40);
125
- const activity = events.length === 0 ? html `<p class="meta">Nothing yet.</p>` : html `<ul class="teammate-activity">${events.map(one => {
125
+ const activity = events.length === 0 ? html `<p class="meta">Nothing yet.</p>` : html `<ul class="subagent-activity">${events.map(one => {
126
126
  const card = one.card === null ? null : store.getFlowCard(one.card);
127
127
  return html `<li data-event="${one.kind}">${one.said}${card !== null && html ` · <a href="/flows/${card.flow}?card=${card.id}">open</a>`} <span class="meta">${when(one.at)}</span></li>`;
128
128
  })}</ul>`;
129
- const versions = store.teammateVersions(mate.id);
129
+ const versions = store.subagentVersions(mate.id);
130
130
  const latest = versions[0];
131
- const manage = canManage && html `<div class="teammate-actions">${postForm(`/teammates/${mate.id}/state`, html `<button${mate.state === "active" && html ` class="secondary"`}>${mate.state === "active" ? `Pause ${name}` : `Resume ${name}`}</button>`, { hidden: { state: mate.state === "active" ? "paused" : "active" } })}${postForm(`/teammates/${mate.id}/summary`, html `<button class="secondary">Send today's summary</button>`)}</div>`;
132
- const soul = html `<details class="card"${canManage && html ` open`}><summary>Soul file</summary><p class="meta">Who ${name} is and its rules, read every turn. Version ${mate.version}${latest !== undefined && ` · saved by ${latest.savedBy}, ${when(latest.savedAt)}`} · <a href="/teammates/${mate.id}/soul.md" download>Download</a></p>${canManage
133
- ? postForm(`/teammates/${mate.id}/soul`, html `<label class="sr-only" for="teammate-soul">Soul file</label><textarea id="teammate-soul" name="soul" rows="24" class="mono" maxlength="12000" spellcheck="false">${notice.soulDraft ?? mate.soul}</textarea><button>Save</button>`, { attrs: { "data-soul-form": true } })
131
+ const manage = canManage && html `<div class="subagent-actions">${postForm(`/settings/lead/subagents/${mate.id}/state`, html `<button${mate.state === "active" && html ` class="secondary"`}>${mate.state === "active" ? `Pause ${name}` : `Resume ${name}`}</button>`, { hidden: { state: mate.state === "active" ? "paused" : "active" } })}${postForm(`/settings/lead/subagents/${mate.id}/summary`, html `<button class="secondary">Send today's summary</button>`)}</div>`;
132
+ const soul = html `<details class="card"${canManage && html ` open`}><summary>Soul file</summary><p class="meta">Who ${name} is and its rules, read every turn. Version ${mate.version}${latest !== undefined && ` · saved by ${latest.savedBy}, ${when(latest.savedAt)}`} · <a href="/settings/lead/subagents/${mate.id}/soul.md" download>Download</a></p>${canManage
133
+ ? postForm(`/settings/lead/subagents/${mate.id}/soul`, html `<label class="sr-only" for="subagent-soul">Soul file</label><textarea id="subagent-soul" name="soul" rows="24" class="mono" maxlength="12000" spellcheck="false">${notice.soulDraft ?? mate.soul}</textarea><button>Save</button>`, { attrs: { "data-soul-form": true } })
134
134
  : html `<pre class="mono">${mate.soul}</pre>`}</details>`;
135
- const settings = canManage && html `<details class="card"><summary>Settings</summary>${postForm(`/teammates/${mate.id}/settings`, html `<label>Model<select name="model">${TEAMMATE_MODELS.map(one => html `<option value="${one}"${(mate.model ?? "default") === one && html ` selected`}>${one === "default" ? "The lead chat's model" : one[0].toUpperCase() + one.slice(1)}</option>`)}</select></label><label>Turns a day, at most<input type="number" name="dailyTurns" min="1" max="2000" value="${mate.dailyTurns}"></label><label>Reports to<select name="manager">${[...new Set([mate.manager, ...approvers])].map(one => html `<option value="${one}"${one === mate.manager && html ` selected`}>${one}</option>`)}</select></label><button>Save settings</button>`)}${postForm(`/teammates/${mate.id}/state`, html `<button class="danger">Remove ${name} from the team</button>`, { attrs: { class: "danger-zone" }, hidden: { state: "removed" } })}</details>`;
136
- return html `${notice.problem && html `<p class="problem" role="alert">${notice.problem}</p>`}${notice.said && html `<p class="said" role="status">${notice.said}</p>`}<p class="meta">${projectName(mate.repo)} · ${mate.state === "active" ? "Working" : "Paused: its decisions go to people"} · reports to ${mate.manager}</p>${manage}${questions.length > 0 && html `<section><h2>Questions</h2>${asked}</section>`}<section class="card"><h2>Today</h2><p class="teammate-summary">${lines(summaryOf(store, mate, startOfDay()).said)}</p></section>${weekSection(store, mate, canManage)}<section class="card"><h2>Works on</h2>${where}</section>${toolsSection(store, mate, canManage)}${deskSection(store, mate, canManage)}${memorySection(store, mate, canManage, notice.query ?? null)}${soul}<details class="card"><summary>What ${name} did</summary>${activity}</details>${settings}`;
135
+ const settings = canManage && html `<details class="card"><summary>Settings</summary>${postForm(`/settings/lead/subagents/${mate.id}/settings`, html `<label>Model<select name="model">${SUBAGENT_MODELS.map(one => html `<option value="${one}"${(mate.model ?? "default") === one && html ` selected`}>${one === "default" ? "The lead chat's model" : one[0].toUpperCase() + one.slice(1)}</option>`)}</select></label><label>Turns a day, at most<input type="number" name="dailyTurns" min="1" max="2000" value="${mate.dailyTurns}"></label><label>Reports to<select name="manager">${[...new Set([mate.manager, ...approvers])].map(one => html `<option value="${one}"${one === mate.manager && html ` selected`}>${one}</option>`)}</select></label><button>Save settings</button>`)}${postForm(`/settings/lead/subagents/${mate.id}/state`, html `<button class="danger">Remove ${name} from the team</button>`, { attrs: { class: "danger-zone" }, hidden: { state: "removed" } })}</details>`;
136
+ return html `${notice.problem && html `<p class="problem" role="alert">${notice.problem}</p>`}${notice.said && html `<p class="said" role="status">${notice.said}</p>`}<p class="meta">${projectName(mate.repo)} · ${mate.state === "active" ? "Working" : "Paused: its decisions go to people"} · reports to ${mate.manager}</p>${manage}${questions.length > 0 && html `<section><h2>Questions</h2>${asked}</section>`}<section class="card"><h2>Today</h2><p class="subagent-summary">${lines(summaryOf(store, mate, startOfDay()).said)}</p></section>${weekSection(store, mate, canManage)}<section class="card"><h2>Works on</h2>${where}</section>${toolsSection(store, mate, canManage)}${deskSection(store, mate, canManage)}${memorySection(store, mate, canManage, notice.query ?? null)}${soul}<details class="card"><summary>What ${name} did</summary>${activity}</details>${settings}`;
137
137
  }
@@ -1,6 +1,6 @@
1
1
  import { run, type ExecResult } from "./exec.js";
2
2
  import type { ContractResult } from "./contracts/contract.js";
3
- import { type TurnAnswer } from "./contracts/teammate-turn.js";
3
+ import { type TurnAnswer } from "./contracts/subagent-turn.js";
4
4
  import { type Overrun } from "./text-limits.js";
5
5
  export declare const SOUL_CHARS = 12000;
6
6
  export type Soul = {
@@ -24,17 +24,17 @@ export declare function parseSoul(text: string): {
24
24
  };
25
25
  /** A soul file with one section replaced (by title, any case), or added at the end when it has no such section. */
26
26
  export declare function withSection(soul: string, title: string, text: string): string;
27
- /** Starters to copy and make your own. Every rule is plain words the teammate reads each turn. */
28
- export declare const TEAMMATE_TEMPLATES: readonly {
27
+ /** Starters to copy and make your own. Every rule is plain words the subagent reads each turn. */
28
+ export declare const SUBAGENT_TEMPLATES: readonly {
29
29
  id: string;
30
30
  label: string;
31
31
  about: string;
32
32
  soul: string;
33
33
  }[];
34
- export type { TurnAction, TurnAnswer } from "./contracts/teammate-turn.js";
34
+ export type { TurnAction, TurnAnswer } from "./contracts/subagent-turn.js";
35
35
  /** Each field's limit, in characters: stated in the prompt before it answers (turnPrompt), and an answer over one is
36
- * asked once to shorten (teammateTurn); one still over is kept whole, never cut. "text" is what the next zones read;
37
- * "note" is a decision or send-back note. Not in the turn's schema (src/contracts/teammate-turn.ts): the CLI would
36
+ * asked once to shorten (subagentTurn); one still over is kept whole, never cut. "text" is what the next zones read;
37
+ * "note" is a decision or send-back note. Not in the turn's schema (src/contracts/subagent-turn.ts): the CLI would
38
38
  * refuse an answer a few characters over one whole. */
39
39
  export declare const TURN_LIMITS: {
40
40
  readonly answer: 60;
@@ -53,7 +53,7 @@ export type TurnContext = {
53
53
  role: string;
54
54
  flow: string;
55
55
  zone: string;
56
- /** decide: a "Person decides" zone; handle: a "Teammate handles it" zone. */
56
+ /** decide: a "Person decides" zone; handle: a "Subagent handles it" zone. */
57
57
  kind: "decide" | "handle";
58
58
  instructions: string | null;
59
59
  /** decide: the draft being decided on (null: none), whether it can be sent back, and to whom hard ones go. */
@@ -111,7 +111,7 @@ export type TurnContext = {
111
111
  };
112
112
  export declare function turnPrompt(context: TurnContext): string;
113
113
  /**
114
- * A turn's answer, read through its contract (src/contracts/teammate-turn.ts) and then checked against what this zone
114
+ * A turn's answer, read through its contract (src/contracts/subagent-turn.ts) and then checked against what this zone
115
115
  * allows, each refusal naming its path (`action: "route" isn't one this zone allows`). Text is trimmed and kept whole
116
116
  * (a field over its limit is asked to shorten, turnOverruns); blank options are dropped, and an ask offers at most 4,
117
117
  * each cut to an answer's length, and a tool name is cut to 140, as they always were.
@@ -142,7 +142,7 @@ export declare const TURN_TIMEOUT_MS = 180000;
142
142
  type CommandRunner = (file: string, args: readonly string[], options: Parameters<typeof run>[2]) => Promise<ExecResult>;
143
143
  /** The production runner: Claude through this computer's sign-in, answering in the turn schema (TURN_MODEL_SCHEMA), with no tools, no MCP servers and no repository. */
144
144
  export declare function claudeTurnRunner(runner?: CommandRunner): TurnRunner;
145
- /** How a teammate is named to people: "Maya · Support". */
146
- export declare const teammateLabel: (soul: Pick<Soul, "name" | "role">) => string;
145
+ /** How a subagent is named to people: "Maya · Support". */
146
+ export declare const subagentLabel: (soul: Pick<Soul, "name" | "role">) => string;
147
147
  /** How its acts read in a card's history: "Maya (AI)". */
148
- export declare const teammateActor: (soul: Pick<Soul, "name">) => string;
148
+ export declare const subagentActor: (soul: Pick<Soul, "name">) => string;
@@ -1,19 +1,19 @@
1
1
  /**
2
- * AI teammates (v92): agents that work a flow's cards the way an employee
2
+ * subagents (v92): agents that work a flow's cards the way an employee
3
3
  * would, within rules a person wrote for them.
4
4
  *
5
- * A teammate is a soul file: who it is, how it writes, what it knows, what it
5
+ * A subagent is a soul file: who it is, how it writes, what it knows, what it
6
6
  * decides on its own, what it asks about first, and what it never does. It is
7
7
  * staffed on zones: a "Person decides" zone it decides (approve, send back,
8
- * or hand it to a person), or a "Teammate handles it" zone where it picks
8
+ * or hand it to a person), or a "Subagent handles it" zone where it picks
9
9
  * where the card goes and writes what the next zones send. It never acts
10
10
  * itself: each turn answers with one decision as JSON, through Claude with no
11
11
  * tools, no repository and no MCP servers, and Toolroll carries it out
12
12
  * within the zone's choices. The card is data, never instructions. (v94: it
13
13
  * may ask for a project tool call on its turn; Toolroll makes it, or
14
- * asks a person first, by the rules in teammate-tools.ts.)
14
+ * asks a person first, by the rules in subagent-tools.ts.)
15
15
  *
16
- * Teammates do flow work only: they never approve a code task, a merge or
16
+ * Subagents do flow work only: they never approve a code task, a merge or
17
17
  * spending — those stay with people (or a signed hands-off mode).
18
18
  */
19
19
  import { Buffer } from "node:buffer";
@@ -24,13 +24,13 @@ import { strictJsonParse } from "./converse.js";
24
24
  import { scanForSecrets } from "./evidence.js";
25
25
  import { run } from "./exec.js";
26
26
  import { ALL_CREDENTIAL_ENV } from "./provider.js";
27
- import { readTurnAnswer, TURN_MODEL_SCHEMA } from "./contracts/teammate-turn.js";
27
+ import { readTurnAnswer, TURN_MODEL_SCHEMA } from "./contracts/subagent-turn.js";
28
28
  import { LIMITS } from "./decision.js";
29
29
  import { overruns, TEXT_LIMITS } from "./text-limits.js";
30
30
  export const SOUL_CHARS = 12_000;
31
31
  const HANDLE = /^[a-z0-9][a-z0-9-]{0,31}$/;
32
32
  /** A handle from a name: "Maya" → "maya". */
33
- export const handleOf = (name) => name.toLowerCase().normalize("NFKD").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 32) || "teammate";
33
+ export const handleOf = (name) => name.toLowerCase().normalize("NFKD").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 32) || "subagent";
34
34
  export const isHandle = (value) => typeof value === "string" && HANDLE.test(value);
35
35
  /** A soul file read: its name and role (front matter) and its sections. */
36
36
  export function parseSoul(text) {
@@ -47,9 +47,9 @@ export function parseSoul(text) {
47
47
  const field = (key) => new RegExp(`^${key}:\\s*(.+)$`, "im").exec(front[1])?.[1]?.trim() ?? "";
48
48
  const name = field("name"), role = field("role");
49
49
  if (!/^[\p{L}][\p{L}\p{N} .'-]{0,39}$/u.test(name))
50
- return { ok: false, problem: "Give the teammate a name of up to 40 letters, like “name: Maya”." };
50
+ return { ok: false, problem: "Give the subagent a name of up to 40 letters, like “name: Maya”." };
51
51
  if (role === "" || role.length > 40)
52
- return { ok: false, problem: "Say what the teammate does in up to 40 characters, like “role: Support”." };
52
+ return { ok: false, problem: "Say what the subagent does in up to 40 characters, like “role: Support”." };
53
53
  const body = normal.slice(front[0].length);
54
54
  const sections = [];
55
55
  for (const part of body.split(/^## /m).slice(1)) {
@@ -57,7 +57,7 @@ export function parseSoul(text) {
57
57
  sections.push({ title: title.trim(), body: rest.join("\n").trim() });
58
58
  }
59
59
  if (sections.length === 0 || sections.every(one => one.body === ""))
60
- return { ok: false, problem: "Write at least one section, like “## Who you are”, saying who the teammate is." };
60
+ return { ok: false, problem: "Write at least one section, like “## Who you are”, saying who the subagent is." };
61
61
  return { ok: true, soul: { name, role, sections } };
62
62
  }
63
63
  /** A soul file with one section replaced (by title, any case), or added at the end when it has no such section. */
@@ -73,8 +73,8 @@ export function withSection(soul, title, text) {
73
73
  return `${parts.join("").trimEnd()}\n`;
74
74
  }
75
75
  const soul = (name, role, parts) => `---\nname: ${name}\nrole: ${role}\n---\n\n${Object.entries(parts).map(([title, lines]) => `## ${title}\n${lines.join("\n")}`).join("\n\n")}\n`;
76
- /** Starters to copy and make your own. Every rule is plain words the teammate reads each turn. */
77
- export const TEAMMATE_TEMPLATES = [
76
+ /** Starters to copy and make your own. Every rule is plain words the subagent reads each turn. */
77
+ export const SUBAGENT_TEMPLATES = [
78
78
  {
79
79
  id: "support", label: "Support rep", about: "Answers customers, handles refunds and replacements within limits, and hands angry or costly cases to you.",
80
80
  soul: soul("Maya", "Support", {
@@ -121,10 +121,10 @@ export const TEAMMATE_TEMPLATES = [
121
121
  },
122
122
  ];
123
123
  /** Each field's limit, in characters: stated in the prompt before it answers (turnPrompt), and an answer over one is
124
- * asked once to shorten (teammateTurn); one still over is kept whole, never cut. "text" is what the next zones read;
125
- * "note" is a decision or send-back note. Not in the turn's schema (src/contracts/teammate-turn.ts): the CLI would
124
+ * asked once to shorten (subagentTurn); one still over is kept whole, never cut. "text" is what the next zones read;
125
+ * "note" is a decision or send-back note. Not in the turn's schema (src/contracts/subagent-turn.ts): the CLI would
126
126
  * refuse an answer a few characters over one whole. */
127
- export const TURN_LIMITS = { answer: TEXT_LIMITS.teammateAnswer, text: TEXT_LIMITS.stageOutput, note: LIMITS.note, question: TEXT_LIMITS.teammateQuestion, reason: TEXT_LIMITS.teammateReason, remember: TEXT_LIMITS.teammateRemember };
127
+ export const TURN_LIMITS = { answer: TEXT_LIMITS.subagentAnswer, text: TEXT_LIMITS.stageOutput, note: LIMITS.note, question: TEXT_LIMITS.subagentQuestion, reason: TEXT_LIMITS.subagentReason, remember: TEXT_LIMITS.subagentRemember };
128
128
  /** The fields of an answer over their limits. */
129
129
  export function turnOverruns(answer) {
130
130
  return overruns({ answer: answer.answer, text: answer.text, note: answer.note, question: answer.question, reason: answer.reason, remember: answer.remember }, TURN_LIMITS);
@@ -186,7 +186,7 @@ export function turnPrompt(context) {
186
186
  /** How many options an ask offers, and the longest tool name and input a tool call may give. */
187
187
  const ASK_OPTIONS = 4, TOOL_NAME_CHARS = 140, TOOL_INPUT_CHARS = 8000;
188
188
  /**
189
- * A turn's answer, read through its contract (src/contracts/teammate-turn.ts) and then checked against what this zone
189
+ * A turn's answer, read through its contract (src/contracts/subagent-turn.ts) and then checked against what this zone
190
190
  * allows, each refusal naming its path (`action: "route" isn't one this zone allows`). Text is trimmed and kept whole
191
191
  * (a field over its limit is asked to shorten, turnOverruns); blank options are dropped, and an ask offers at most 4,
192
192
  * each cut to an answer's length, and a tool name is cut to 140, as they always were.
@@ -204,7 +204,7 @@ export function readTurn(value, context) {
204
204
  const refuse = (path, what) => ({ ok: false, issues: [{ path, kind: "bad-value", line: `${path}: ${what}` }] });
205
205
  if (!allowed.includes(answer.action))
206
206
  return refuse("action", `"${answer.action}" isn't one this zone allows (${allowed.map(one => `"${one}"`).join(", ")})`);
207
- // A tool call is checked against the teammate's rules where it's carried out; here only that it names one and fits.
207
+ // A tool call is checked against the subagent's rules where it's carried out; here only that it names one and fits.
208
208
  if (answer.action === "use_tool" && answer.tool === "")
209
209
  return refuse("tool", "required for use_tool");
210
210
  if (answer.action === "use_tool" && answer.input.length > TOOL_INPUT_CHARS)
@@ -221,7 +221,7 @@ export const TURN_TIMEOUT_MS = 180_000;
221
221
  /** The production runner: Claude through this computer's sign-in, answering in the turn schema (TURN_MODEL_SCHEMA), with no tools, no MCP servers and no repository. */
222
222
  export function claudeTurnRunner(runner = run) {
223
223
  return async (request) => {
224
- const dir = mkdtempSync(join(tmpdir(), "standing-orders-teammate-"));
224
+ const dir = mkdtempSync(join(tmpdir(), "standing-orders-subagent-"));
225
225
  const started = Date.now();
226
226
  try {
227
227
  const result = await runner("claude", [
@@ -259,7 +259,7 @@ function costOf(body) {
259
259
  const tokensIn = count("input_tokens") + count("cache_creation_input_tokens") + count("cache_read_input_tokens"), tokensOut = count("output_tokens");
260
260
  return { ...(cost === undefined ? {} : { costUsd: cost }), ...(tokensIn > 0 ? { tokensIn } : {}), ...(tokensOut > 0 ? { tokensOut } : {}) };
261
261
  }
262
- /** How a teammate is named to people: "Maya · Support". */
263
- export const teammateLabel = (soul) => `${soul.name} · ${soul.role}`;
262
+ /** How a subagent is named to people: "Maya · Support". */
263
+ export const subagentLabel = (soul) => `${soul.name} · ${soul.role}`;
264
264
  /** How its acts read in a card's history: "Maya (AI)". */
265
- export const teammateActor = (soul) => `${soul.name} (AI)`;
265
+ export const subagentActor = (soul) => `${soul.name} (AI)`;
@@ -1,16 +1,16 @@
1
- import { type MateHistoryMessage, type MateProviderAnswer, type MateToolSchema } from "./converse.js";
1
+ import { type LeadHistoryMessage, type LeadProviderAnswer, type LeadToolSchema } from "./converse.js";
2
2
  import { run, type ExecResult } from "./exec.js";
3
3
  import type { SubscriptionChatProviderId } from "./store.js";
4
- export type SubscriptionMateRequest = {
4
+ export type SubscriptionLeadRequest = {
5
5
  provider: SubscriptionChatProviderId;
6
6
  /** `default` lets the authenticated harness choose its current default. */
7
7
  model: string;
8
8
  system: string;
9
9
  dataDocument: string;
10
- history: readonly MateHistoryMessage[];
11
- tools: readonly MateToolSchema[];
10
+ history: readonly LeadHistoryMessage[];
11
+ tools: readonly LeadToolSchema[];
12
12
  /** A structured answer of another shape (the memory pass's verdict): this JSON Schema is the harness's
13
- * `--json-schema` (Codex's `--output-schema`) in place of the mate envelope, and its JSON text comes back as the
13
+ * `--json-schema` (Codex's `--output-schema`) in place of the lead envelope, and its JSON text comes back as the
14
14
  * answer's text, unread, for the caller's own schema to parse. */
15
15
  outputSchema?: Record<string, unknown>;
16
16
  timeoutMs: number;
@@ -20,31 +20,31 @@ export type SubscriptionMateRequest = {
20
20
  * (Claude). Display only: the finished answer is still parsed whole. */
21
21
  onText?: (text: string) => void;
22
22
  };
23
- export type SubscriptionMateRunner = (request: SubscriptionMateRequest) => Promise<{
23
+ export type SubscriptionLeadRunner = (request: SubscriptionLeadRequest) => Promise<{
24
24
  ok: true;
25
- answer: MateProviderAnswer;
25
+ answer: LeadProviderAnswer;
26
26
  } | {
27
27
  ok: false;
28
28
  problem: string;
29
29
  }>;
30
30
  type CommandRunner = (file: string, args: readonly string[], options: Parameters<typeof run>[2]) => Promise<ExecResult>;
31
- /** The model-visible request. Tool results are already redacted by mateView. */
32
- export declare function composeSubscriptionMatePrompt(request: Omit<SubscriptionMateRequest, "provider" | "model" | "timeoutMs" | "signal">): string;
31
+ /** The model-visible request. Tool results are already redacted by leadView. */
32
+ export declare function composeSubscriptionLeadPrompt(request: Omit<SubscriptionLeadRequest, "provider" | "model" | "timeoutMs" | "signal">): string;
33
33
  /** Strictly turn the harness's structured output into the provider-neutral answer. */
34
- export declare function parseSubscriptionMateAnswer(text: string, usage?: {
34
+ export declare function parseSubscriptionLeadAnswer(text: string, usage?: {
35
35
  tokensIn?: unknown;
36
36
  tokensOut?: unknown;
37
37
  }): {
38
38
  ok: true;
39
- answer: MateProviderAnswer;
39
+ answer: LeadProviderAnswer;
40
40
  } | {
41
41
  ok: false;
42
42
  problem: string;
43
43
  };
44
44
  /** Production runner; injectable so tests never consume a subscription turn. */
45
- export declare function performSubscriptionMateRequest(request: SubscriptionMateRequest, runner?: CommandRunner): Promise<{
45
+ export declare function performSubscriptionLeadRequest(request: SubscriptionLeadRequest, runner?: CommandRunner): Promise<{
46
46
  ok: true;
47
- answer: MateProviderAnswer;
47
+ answer: LeadProviderAnswer;
48
48
  } | {
49
49
  ok: false;
50
50
  problem: string;