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
@@ -18,7 +18,7 @@ import { readVerifiedReport, scanForSecrets } from "./evidence.js";
18
18
  import { cardFollowers, notifyPeople } from "./flow-people.js";
19
19
  import { DRAFT_CHARS, keptDraft } from "./flow-draft.js";
20
20
  import { chooseStep, flowPersonOf, sendStep } from "./flow-send.js";
21
- import { parseSoul, teammateLabel } from "./teammates.js";
21
+ import { parseSoul, subagentLabel } from "./subagents.js";
22
22
  import { withStageHandoff } from "./contracts/stage-output.js";
23
23
  /** The flow's drawing, or null when it can't be read (the card then says so and waits). */
24
24
  export function flowDefinitionOf(flow) {
@@ -42,15 +42,15 @@ function fill(template, card) {
42
42
  }
43
43
  const titleIn = (definition, id) => definition.stages.find(one => one.id === id)?.title ?? id;
44
44
  const flowDefinitionStage = (definition, id) => definition.stages.find(one => one.id === id);
45
- /** A teammate's name as its soul file gives it (its handle when the file can't be read). */
46
- function teammateName(mate) {
45
+ /** A subagent's name as its soul file gives it (its handle when the file can't be read). */
46
+ function subagentName(mate) {
47
47
  const read = parseSoul(mate.soul);
48
48
  return read.ok ? read.soul.name : mate.handle;
49
49
  }
50
- /** What a teammate said when it handed a card to its person, or why it couldn't decide. */
50
+ /** What a subagent said when it handed a card to its person, or why it couldn't decide. */
51
51
  function handoffWords(mate, turn) {
52
52
  const read = parseSoul(mate.soul);
53
- const label = read.ok ? teammateLabel(read.soul) : mate.handle;
53
+ const label = read.ok ? subagentLabel(read.soul) : mate.handle;
54
54
  let note = "";
55
55
  try {
56
56
  const decided = JSON.parse(turn.decisionJson ?? "{}");
@@ -187,20 +187,20 @@ function advanceCard(store, flow, definition, card, now, options, outcome) {
187
187
  case "approval": {
188
188
  const decider = deciderOf(stage, flow);
189
189
  const who = decider ?? "an approver";
190
- // v92: a zone an active teammate staffs is its to decide first (the step pass runs it). A person hears
191
- // about the card only when the teammate hands it over, can't decide, or is paused or gone.
192
- // v102: on a protected project an AI teammate never decides; the person does.
193
- const mate = stage.teammate === undefined || store.approvalRules(flow.repo).protectProject ? null : store.teammateByHandle(flow.repo, stage.teammate);
190
+ // v92: a zone an active subagent staffs is its to decide first (the step pass runs it). A person hears
191
+ // about the card only when the subagent hands it over, can't decide, or is paused or gone.
192
+ // v102: on a protected project a subagent never decides; the person does.
193
+ const mate = stage.subagent === undefined || store.approvalRules(flow.repo).protectProject ? null : store.subagentByHandle(flow.repo, stage.subagent);
194
194
  const turn = mate === null ? null : store.flowStepRun(card.id, card.entry);
195
- const handed = turn !== null && turn.kind === "teammate" && (turn.state === "failed" || turn.state === "passed");
195
+ const handed = turn !== null && turn.kind === "subagent" && (turn.state === "failed" || turn.state === "passed");
196
196
  if (mate !== null && mate.state === "active" && !handed) {
197
- const name = teammateName(mate);
197
+ const name = subagentName(mate);
198
198
  if (card.waiting !== `${name} is deciding`)
199
199
  store.updateFlowCard(card.id, { waiting: `${name} is deciding` }, now);
200
200
  return;
201
201
  }
202
202
  const handoff = handed && mate !== null ? handoffWords(mate, turn) : null;
203
- if (card.waiting === null || (mate !== null && card.waiting === `${teammateName(mate)} is deciding`)) {
203
+ if (card.waiting === null || (mate !== null && card.waiting === `${subagentName(mate)} is deciding`)) {
204
204
  // A named decider hears it alone; "anyone who approves" pages everyone who can.
205
205
  // With a draft in front of it, the decision carries the draft itself: it can be read (and answered) where it arrives.
206
206
  const draft = draftFor(definition, stage);
@@ -447,8 +447,8 @@ export function cancelFlowCard(store, card, actor, now) {
447
447
  /** The draft a decision is about: the draft zone that sends its cards to this one. */
448
448
  export function draftFor(definition, stage) {
449
449
  return definition.stages.find(one => one.kind === "draft" && one.next === stage.id)
450
- // A teammate's zone that leads here (by its next, or one of its answers) wrote the draft this decision checks (starter kits).
451
- ?? definition.stages.find(one => one.kind === "teammate" && (one.next === stage.id || (one.routes ?? []).some(route => route.to === stage.id))) ?? null;
450
+ // A subagent's zone that leads here (by its next, or one of its answers) wrote the draft this decision checks (starter kits).
451
+ ?? definition.stages.find(one => one.kind === "subagent" && (one.next === stage.id || (one.routes ?? []).some(route => route.to === stage.id))) ?? null;
452
452
  }
453
453
  /** Back to a build zone with a finished result: a revision of that same work, carrying the note; null when it isn't one. */
454
454
  export function revisionWithNote(store, card, target, note, actor, repos, evidenceRoot, now) {
@@ -478,21 +478,21 @@ export function decideFlowCard(store, input, now) {
478
478
  const stage = definition.stages.find(one => one.id === card.stage);
479
479
  if (stage === undefined || stage.kind !== "approval" || (input.entry !== undefined && input.entry !== card.entry))
480
480
  return { ok: false, message: "That card has moved on since; nothing was changed." };
481
- // A teammate decides only a zone it staffs; its person (and only them) can always decide instead.
481
+ // A subagent decides only a zone it staffs; its person (and only them) can always decide instead.
482
482
  const decider = deciderOf(stage, flow);
483
- if (input.teammate !== undefined ? stage.teammate !== input.teammate : decider !== null && decider !== input.actor)
483
+ if (input.subagent !== undefined ? stage.subagent !== input.subagent : decider !== null && decider !== input.actor)
484
484
  return { ok: false, message: `Only ${decider ?? "an approver"} decides here.` };
485
- // v102: a protected project's decisions are a person's, never an AI teammate's.
486
- if (input.teammate !== undefined && store.approvalRules(flow.repo).protectProject)
485
+ // v102: a protected project's decisions are a person's, never a subagent's.
486
+ if (input.subagent !== undefined && store.approvalRules(flow.repo).protectProject)
487
487
  return { ok: false, message: "This project is protected: a person decides here." };
488
488
  // Every decision is one ledger line, the same wherever it was made; only `via` says where.
489
489
  const ledger = (outcome) => store.recordAction({ at: now.toISOString(), actor: input.actor, repo: flow.repo, taskId: card.primaryTask, runId: null,
490
- action: "flow decision", outcome, source: "request", detail: `${flow.name} · card ${card.id} · ${stage.title}${input.teammate === undefined ? "" : ` · teammate ${input.teammate}`} · via ${input.where ?? "Toolroll"}` });
490
+ action: "flow decision", outcome, source: "request", detail: `${flow.name} · card ${card.id} · ${stage.title}${input.subagent === undefined ? "" : ` · subagent ${input.subagent}`} · via ${input.where ?? "Toolroll"}` });
491
491
  if (input.decision === "approve") {
492
492
  // An edited draft replaces the one Claude wrote, so the steps after this send what the person approved.
493
493
  const draft = draftFor(definition, stage);
494
- // A person's edit over the draft limit is refused with it, never cut. (A teammate's was asked once to shorten and is kept whole.)
495
- if (draft !== null && input.teammate === undefined && typeof input.draft === "string" && input.draft.trim().length > DRAFT_CHARS) {
494
+ // A person's edit over the draft limit is refused with it, never cut. (A subagent's was asked once to shorten and is kept whole.)
495
+ if (draft !== null && input.subagent === undefined && typeof input.draft === "string" && input.draft.trim().length > DRAFT_CHARS) {
496
496
  return { ok: false, message: `Keep the draft to ${DRAFT_CHARS.toLocaleString("en-US")} characters; this is ${input.draft.trim().length.toLocaleString("en-US")}.` };
497
497
  }
498
498
  if (draft !== null && typeof input.draft === "string" && input.draft.trim() !== "" && input.draft.trim() !== card.outputs[draft.id]?.trim()) {
@@ -58,7 +58,7 @@ export type StepRunInsight = {
58
58
  entry: number;
59
59
  zone: string;
60
60
  zoneTitle: string;
61
- kind: "check" | "update" | "sort" | "draft" | "request" | "email" | "tool" | "teammate";
61
+ kind: "check" | "update" | "sort" | "draft" | "request" | "email" | "tool" | "subagent";
62
62
  script: string | null;
63
63
  version: number | null;
64
64
  state: FlowStepRunRow["state"];
@@ -24,7 +24,7 @@ import { addFlowTriggerTo, describeTrigger, HOOK_PATH, readHooksBase, triggerCon
24
24
  import { durationMinutes, durationWords, FlowContractError, flowTerms, inStepWords, LANGUAGE_WORDS, validateFlowDefinition } from "./flows.js";
25
25
  import { stageReferenceProblems } from "./contracts/stage-output.js";
26
26
  import { readVersioned } from "./contracts/contract.js";
27
- import { FLOW_ALIASES, FLOW_FILE_FORMAT, FLOW_FILE_VERSION, flowFileSchema, PARAMETER_ID } from "./contracts/flow.js";
27
+ import { FLOW_ALIASES, FLOW_FILE_FORMAT, legacyFlowNeed, legacySubagentStep, FLOW_FILE_VERSION, flowFileSchema, PARAMETER_ID } from "./contracts/flow.js";
28
28
  import { parseSchedule } from "./flow-schedule.js";
29
29
  export { FLOW_FILE_FORMAT, FLOW_FILE_VERSION };
30
30
  /** The largest flow file Toolroll reads or fetches. */
@@ -38,7 +38,7 @@ function stepOf(stage, ask) {
38
38
  const step = { id: stage.id, title: stage.title, kind: stage.kind };
39
39
  const words = (minutes) => durationMinutes(durationWords(minutes)) === minutes ? durationWords(minutes) : minutes;
40
40
  // Only what the step's kind says: words a zone kept from a kind it was before stay behind.
41
- if (stage.instructions !== null && (stage.kind === "task" || stage.kind === "report" || stage.kind === "draft" || stage.kind === "teammate"))
41
+ if (stage.instructions !== null && (stage.kind === "task" || stage.kind === "report" || stage.kind === "draft" || stage.kind === "subagent"))
42
42
  step["instructions"] = stage.instructions;
43
43
  if (stage.kind === "task")
44
44
  step["planning"] = stage.planning;
@@ -79,8 +79,8 @@ function stepOf(stage, ask) {
79
79
  ...(stage.wait.for === "hours" ? { from: stage.wait.from, until: stage.wait.to, ...(stage.wait.timeZone === undefined ? {} : { timeZone: stage.wait.timeZone }) } : { wait: words(stage.wait.minutes) }) });
80
80
  if (stage.merge !== undefined)
81
81
  step["merge"] = stage.merge;
82
- if (stage.teammate !== undefined)
83
- step["teammate"] = stage.teammate;
82
+ if (stage.subagent !== undefined)
83
+ step["subagent"] = stage.subagent;
84
84
  if (stage.reply === true)
85
85
  step["reply"] = true;
86
86
  // A choice's buttons; "end" ignores the card. A build in another project names a path on this computer, so it stays here.
@@ -135,8 +135,8 @@ function needsOf(definition, triggers, scripts) {
135
135
  needs.add("email");
136
136
  if (stage.tool !== undefined)
137
137
  needs.add(`tool:${stage.tool.server}`);
138
- if (stage.teammate !== undefined)
139
- needs.add(`teammate:${stage.teammate}`);
138
+ if (stage.subagent !== undefined)
139
+ needs.add(`subagent:${stage.subagent}`);
140
140
  for (const name of stage.secrets ?? [])
141
141
  needs.add(`secret:${name}`);
142
142
  for (const value of Object.values(stage.request?.headers ?? {}))
@@ -296,7 +296,7 @@ function stageInputOf(step, index, find) {
296
296
  ...(kind === "email" ? { email: { to: step["to"], subject: step["subject"], body: step["body"] } } : {}),
297
297
  ...(kind === "tool" ? { tool: { server: step["server"], name: step["tool"], args: typeof step["args"] === "object" && step["args"] !== null ? JSON.stringify(step["args"]) : step["args"] } } : {}),
298
298
  ...(kind === "wait" ? { wait: step["waitFor"] === "hours" ? { for: "hours", from: step["from"], to: step["until"], timeZone: step["timeZone"] } : { for: step["waitFor"] ?? "reply", minutes: minutes(step["wait"] ?? "3 days", "wait") } } : {}),
299
- merge: step["merge"], teammate: step["teammate"], reply: step["reply"],
299
+ merge: step["merge"], subagent: step["subagent"], reply: step["reply"],
300
300
  ...(kind === "choose" ? { options: (list("options") ?? []).map((one, n) => ({ label: one["label"], to: one["goesTo"] === "end" ? "end" : find(one["goesTo"], `${at}.options[${n}].goesTo`) })) } : {}),
301
301
  ...(step["remindAfter"] === undefined ? {} : { limit: { minutes: minutes(step["remindAfter"], "remindAfter"), to: find(step["thenMoveTo"], `${at}.thenMoveTo`) } }),
302
302
  next: find(step["next"], `${at}.next`), onFail: find(fail, `${at}.${failKey}`),
@@ -371,6 +371,11 @@ export function parseFlowFile(text) {
371
371
  if (kind === "flow")
372
372
  refuse(`triggers[${index}].kind: a trigger from another flow can't come from a file; it names a flow on the installation it was made on`);
373
373
  });
374
+ // D5: a file written before subagents were named so (a "teammate" zone, a "teammate:" need) reads as one written since.
375
+ if (Array.isArray(input["zones"]))
376
+ input["zones"] = input["zones"].map(legacySubagentStep);
377
+ if (Array.isArray(input["needs"]))
378
+ input["needs"] = input["needs"].map(legacyFlowNeed);
374
379
  const read = readVersioned(flowFileSchema, input, {}, FLOW_ALIASES);
375
380
  if (!read.ok)
376
381
  return refuse(read.issues.map(one => one.line).join("\n"));
@@ -443,7 +448,7 @@ const NEED_WORDS = {
443
448
  };
444
449
  export function needWords(need) {
445
450
  const [kind, name] = need.includes(":") ? [need.slice(0, need.indexOf(":")), need.slice(need.indexOf(":") + 1)] : [need, ""];
446
- return NEED_WORDS[need] ?? (kind === "secret" ? `A saved secret called ${name}` : kind === "tool" ? `The ${name} tool (Settings → Tools)` : kind === "teammate" ? `An AI teammate called ${name}` : kind === "script" ? `The ${name} script` : need);
451
+ return NEED_WORDS[need] ?? (kind === "secret" ? `A saved secret called ${name}` : kind === "tool" ? `The ${name} tool (Settings → Tools)` : kind === "subagent" ? `A subagent called ${name}` : kind === "script" ? `The ${name} script` : need);
447
452
  }
448
453
  /** The import in plain words, checked against the project it lands in. Throws FlowFileError. */
449
454
  export function planFlowImport(store, repo, file, given, actor) {
@@ -2,8 +2,8 @@ import type { Runner } from "./backend.js";
2
2
  import { type DraftRunner } from "./flow-draft.js";
3
3
  import { type MailSender, type ToolCaller } from "./flow-actions.js";
4
4
  import type { Store } from "./store.js";
5
- import type { TurnRunner } from "./teammates.js";
6
- import type { ToolLister } from "./teammate-tools.js";
5
+ import type { TurnRunner } from "./subagents.js";
6
+ import type { ToolLister } from "./subagent-tools.js";
7
7
  export type StepIo = {
8
8
  /** `gh` for GitHub; git and the check's shell, both without a model. */
9
9
  gh: Runner;
@@ -27,9 +27,9 @@ export type StepIo = {
27
27
  callTool?: ToolCaller;
28
28
  /** Where the project tools' secrets live (default: this computer's home). */
29
29
  toolHome?: string;
30
- /** Takes a teammate's turn (default: Claude through this computer's sign-in, answering in its fixed shape). */
31
- teammate?: TurnRunner;
32
- /** Lists what a project tool offers a teammate (default: starts its MCP server and asks). */
30
+ /** Takes a subagent's turn (default: Claude through this computer's sign-in, answering in its fixed shape). */
31
+ subagent?: TurnRunner;
32
+ /** Lists what a project tool offers a subagent (default: starts its MCP server and asks). */
33
33
  listTools?: ToolLister;
34
34
  };
35
35
  export type StepPass = {
@@ -40,14 +40,14 @@ import { agentFence } from "./agent-fence.js";
40
40
  import { readProviderKey } from "./keys.js";
41
41
  import { replyInChannel } from "./chat-inbox.js";
42
42
  import { recordSent, threadOf } from "./flow-replies.js";
43
- import { ASKED, teammateReady, teammateTurn } from "./teammate-work.js";
43
+ import { ASKED, subagentReady, subagentTurn } from "./subagent-work.js";
44
44
  import { budgetHoldWords, claudeMachineBilling, monthOf } from "./spend.js";
45
45
  import { toolRefusal } from "./policy.js";
46
46
  import { pullRequestStep } from "./flow-pull-request.js";
47
47
  import { withStageHandoff } from "./contracts/stage-output.js";
48
48
  const RETRY_MS = [5 * 60_000, 15 * 60_000];
49
- /** A teammate's turn tries again sooner: a card waiting on a person's colleague shouldn't wait long for a hiccup. */
50
- const TEAMMATE_RETRY_MS = [60_000, 5 * 60_000];
49
+ /** A subagent's turn tries again sooner: a card waiting on a person's colleague shouldn't wait long for a hiccup. */
50
+ const SUBAGENT_RETRY_MS = [60_000, 5 * 60_000];
51
51
  const OUTPUT_CHARS = 3000;
52
52
  /** How much of a step's output its run keeps as the log. */
53
53
  const LOG_CHARS = 64_000;
@@ -71,9 +71,9 @@ export async function runFlowSteps(store, repo, now, io) {
71
71
  const stage = known.definition?.stages.find(one => one.id === card.stage);
72
72
  if (stage === undefined)
73
73
  continue;
74
- // v92: a teammate's turn — a zone it handles, or a decision it staffs (a person decides when it's paused or gone).
75
- if (stage.kind === "teammate" || (stage.kind === "approval" && stage.teammate !== undefined)) {
76
- await teammateStep(store, known.flow, known.definition, stage, card, now, io, pass);
74
+ // v92: a subagent's turn — a zone it handles, or a decision it staffs (a person decides when it's paused or gone).
75
+ if (stage.kind === "subagent" || (stage.kind === "approval" && stage.subagent !== undefined)) {
76
+ await subagentStep(store, known.flow, known.definition, stage, card, now, io, pass);
77
77
  continue;
78
78
  }
79
79
  // A pull request: opened for the card's result, then CI decides where it goes (flow-pull-request.ts).
@@ -108,7 +108,7 @@ export async function runFlowSteps(store, repo, now, io) {
108
108
  // (or everything) that holds key work holds them.
109
109
  const agents = stage.kind === "sort" ? [{ provider: "openrouter", billing: "api-key" }]
110
110
  : stage.kind === "draft" ? [{ provider: "claude", billing: claudeMachineBilling(store.handle) }] : null;
111
- const held = agents === null ? null : budgetGate({ project: repo, person: null, teammate: null, agents });
111
+ const held = agents === null ? null : budgetGate({ project: repo, person: null, subagent: null, agents });
112
112
  if (held !== null && held.over !== null) {
113
113
  const waiting = `${budgetHoldWords(held, monthOf(now).name)}. Raise it on Spend to go on.`;
114
114
  if (card.waiting !== waiting)
@@ -183,32 +183,32 @@ function claimStep(store, card, step, now) {
183
183
  return store.claimFlowStep({ card: card.id, entry: card.entry, ...step }, now);
184
184
  });
185
185
  }
186
- /** One teammate turn, when one is due for this card: claimed like any step, retried on failure, and waiting while its question is open. */
187
- async function teammateStep(store, flow, definition, stage, card, now, io, pass) {
188
- // v102: on a protected project an AI teammate never decides an approval; the engine asks the person.
186
+ /** One subagent turn, when one is due for this card: claimed like any step, retried on failure, and waiting while its question is open. */
187
+ async function subagentStep(store, flow, definition, stage, card, now, io, pass) {
188
+ // v102: on a protected project a subagent never decides an approval; the engine asks the person.
189
189
  if (stage.kind === "approval" && store.approvalRules(flow.repo).protectProject)
190
190
  return true;
191
- const mate = stage.teammate === undefined ? null : store.teammateByHandle(flow.repo, stage.teammate);
192
- const ready = teammateReady(store, mate, now);
191
+ const mate = stage.subagent === undefined ? null : store.subagentByHandle(flow.repo, stage.subagent);
192
+ const ready = subagentReady(store, mate, now);
193
193
  if (!ready.ok) {
194
194
  // A decision goes to its person (the engine asks them). A zone only it handles waits, saying why.
195
195
  if (stage.kind === "approval")
196
196
  return true;
197
- const waiting = ready.why === "gone" ? `There's no teammate called ${stage.teammate ?? "(none)"} in this project.` : `${mate.handle} is ${ready.why === "paused" ? "paused" : `waiting: ${ready.why}`}.`;
197
+ const waiting = ready.why === "gone" ? `There's no subagent called ${stage.subagent ?? "(none)"} in this project.` : `${mate.handle} is ${ready.why === "paused" ? "paused" : `waiting: ${ready.why}`}.`;
198
198
  if (card.waiting !== waiting)
199
199
  store.updateFlowCard(card.id, { waiting }, now);
200
200
  return true;
201
201
  }
202
202
  // Waiting on a person: its own question, or (v94) a tool call they approve first.
203
- if (store.openTeammateQuestionOn(card.id, card.entry) !== null)
203
+ if (store.openSubagentQuestionOn(card.id, card.entry) !== null)
204
204
  return true;
205
- if (!claimStep(store, card, { stage: stage.id, kind: "teammate", script: null, scriptVersion: null }, now))
205
+ if (!claimStep(store, card, { stage: stage.id, kind: "subagent", script: null, scriptVersion: null }, now))
206
206
  return true;
207
207
  pass.ran++;
208
208
  const started = Date.now();
209
209
  let outcome;
210
210
  try {
211
- outcome = await teammateTurn(store, flow, definition, stage, card, mate, now, { ...(io.teammate === undefined ? {} : { turn: io.teammate }), ...(io.evidenceRoot === undefined ? {} : { evidenceRoot: io.evidenceRoot }),
211
+ outcome = await subagentTurn(store, flow, definition, stage, card, mate, now, { ...(io.subagent === undefined ? {} : { turn: io.subagent }), ...(io.evidenceRoot === undefined ? {} : { evidenceRoot: io.evidenceRoot }),
212
212
  ...(io.callTool === undefined ? {} : { callTool: io.callTool }), ...(io.listTools === undefined ? {} : { listTools: io.listTools }), ...(io.toolHome === undefined ? {} : { toolHome: io.toolHome }) });
213
213
  }
214
214
  catch (error) {
@@ -218,15 +218,15 @@ async function teammateStep(store, flow, definition, stage, card, now, io, pass)
218
218
  const kept = { log: outcome.log === undefined ? null : keptLog(outcome.log), durationMs: Date.now() - started, ...(outcome.decisionJson === undefined ? {} : { decisionJson: outcome.decisionJson }) };
219
219
  if (outcome.state === "retry") {
220
220
  const attempts = run?.attempts ?? 1;
221
- if (attempts <= TEAMMATE_RETRY_MS.length) {
222
- store.finishFlowStep(card.id, card.entry, { state: "waiting", result: outcome.said, nextAt: new Date(now.getTime() + TEAMMATE_RETRY_MS[attempts - 1]).toISOString(), ...kept }, now);
221
+ if (attempts <= SUBAGENT_RETRY_MS.length) {
222
+ store.finishFlowStep(card.id, card.entry, { state: "waiting", result: outcome.said, nextAt: new Date(now.getTime() + SUBAGENT_RETRY_MS[attempts - 1]).toISOString(), ...kept }, now);
223
223
  if (stage.kind !== "approval")
224
224
  store.updateFlowCard(card.id, { waiting: `${outcome.said} Trying again in ${attempts === 1 ? "a minute" : "5 minutes"}.` }, now);
225
225
  pass.problems.push(`flow card ${card.id}: ${outcome.said}`);
226
226
  return true;
227
227
  }
228
228
  outcome = { state: "failed", said: `${outcome.said} It didn't work after three tries.` };
229
- store.addTeammateEvent({ teammate: mate.id, card: card.id, entry: card.entry, kind: "failed", said: `Couldn't take its turn on “${card.title}”: ${outcome.said}` }, now);
229
+ store.addSubagentEvent({ subagent: mate.id, card: card.id, entry: card.entry, kind: "failed", said: `Couldn't take its turn on “${card.title}”: ${outcome.said}` }, now);
230
230
  if (stage.kind !== "approval")
231
231
  store.updateFlowCard(card.id, { waiting: outcome.said }, now);
232
232
  }
@@ -155,7 +155,7 @@ export declare function planeStatusLink(config: TriggerConfig | null, status: st
155
155
  label: string;
156
156
  href: string;
157
157
  } | null;
158
- /** v96: a schedule (a teammate's routine) makes its card now, once per press, and keeps its own times. */
158
+ /** v96: a schedule (a subagent's routine) makes its card now, once per press, and keeps its own times. */
159
159
  export declare function runScheduleNow(store: Store, trigger: FlowTriggerRow, actor: string, now: Date): {
160
160
  ok: true;
161
161
  card: number;
@@ -731,7 +731,7 @@ export const NEEDS_YOU_HREF = "/work?view=needs-you";
731
731
  export function planeStatusLink(config, status) {
732
732
  return config?.kind === "plane-review" && status !== null && /waited over \d+ days for you\.$/.test(status) ? { label: "Needs you", href: NEEDS_YOU_HREF } : null;
733
733
  }
734
- /** v96: a schedule (a teammate's routine) makes its card now, once per press, and keeps its own times. */
734
+ /** v96: a schedule (a subagent's routine) makes its card now, once per press, and keeps its own times. */
735
735
  export function runScheduleNow(store, trigger, actor, now) {
736
736
  const config = triggerConfigOf(trigger);
737
737
  if (config?.kind !== "schedule" || config.script !== undefined)
package/dist/flows-cli.js CHANGED
@@ -516,7 +516,7 @@ function zoneOf(stage, flow) {
516
516
  ...(stage.kind === 'approval' ? { decider: deciderOf(stage, flow) } : {}),
517
517
  ...(stage.script === null ? {} : { script: stage.script }),
518
518
  ...(stage.merge === undefined ? {} : { merge: stage.merge }),
519
- ...(stage.teammate === undefined ? {} : { teammate: stage.teammate }),
519
+ ...(stage.subagent === undefined ? {} : { subagent: stage.subagent }),
520
520
  ...(stage.options === undefined ? {} : { options: stage.options.map(one => ({ label: one.label, to: one.to })) }),
521
521
  ...(stage.repo === undefined ? {} : { repo: stage.repo }),
522
522
  };
package/dist/flows-ui.js CHANGED
@@ -12,9 +12,9 @@ import { itemCaption, whyLines } from "./flow-items.js";
12
12
  import { FLOW_FILE_MAX_BYTES } from "./flow-share.js";
13
13
  import { googleConnected } from "./google-mail.js";
14
14
  import { mailboxReady } from "./mailbox.js";
15
- import { labelOf, nameOf } from "./teammate-admin.js";
16
- import { callWords, receiptWords } from "./teammate-tools.js";
17
- import { undoFor } from "./teammate-week.js";
15
+ import { labelOf, nameOf } from "./subagent-admin.js";
16
+ import { callWords, receiptWords } from "./subagent-tools.js";
17
+ import { undoFor } from "./subagent-week.js";
18
18
  import { flowCardTaskLine } from "./flow-card-task.js";
19
19
  import { readFlowSecrets } from "./flow-secrets.js";
20
20
  import { html, joinHtml, postForm } from "./html.js";
@@ -39,7 +39,7 @@ export function flowsListHtml(store, flows, projects, canCreate, problem) {
39
39
  const example = flows.length === 0 && canCreate && projects.length > 0
40
40
  ? postForm("/flows/example", html `<h2>See a flow work</h2><p class="meta">Claude drafts a reply to a sample customer question; you approve it here or in your chat app.</p>${projectField}<button>Try an example</button>`, { attrs: { class: "card flow-example" } })
41
41
  : null;
42
- const intro = html `<p class="meta">A flow is your process drawn as zones. Cards move through them: agents do the work, people approve, and the team hears about it.</p>${canCreate && projects.length > 0 && html `<p class="flow-chat">Describe how work should move and your lead drafts the flow for you to confirm, or start from a template. <a href="/chat?draft=${encodeURIComponent("Make a flow for ")}">Describe it in chat</a></p>`}<p class="flow-chat">AI teammates can decide and handle cards for you, within rules you write. <a href="/teammates">Teammates</a> · or set one up with its flow in one click: <a href="/kits">Starter kits</a></p>`;
42
+ const intro = html `<p class="meta">A flow is your process drawn as zones. Cards move through them: agents do the work, people approve, and the team hears about it.</p>${canCreate && projects.length > 0 && html `<p class="flow-chat">Describe how work should move and your lead drafts the flow for you to confirm, or start from a template. <a href="/chat?draft=${encodeURIComponent("Make a flow for ")}">Describe it in chat</a></p>`}<p class="flow-chat">subagents can decide and handle cards for you, within rules you write. <a href="/settings/lead/subagents">Subagents</a> · or set one up with its flow in one click: <a href="/kits">Starter kits</a></p>`;
43
43
  return html `<section class="flows">${problem !== null && html `<p class="problem" role="alert">${problem}</p>`}${intro}${create}${rows.length > 0 ? rows : example ?? html `<p class="meta">No flows yet.</p>`}</section>`;
44
44
  }
45
45
  /** Reads the chosen flow file into the form (the server reads it again, whole, before anything is made). */
@@ -75,7 +75,7 @@ export function flowView(store, flow, viewer, selectedCard, setup = { dir: null,
75
75
  const title = (id) => stages.find(one => one.id === id)?.title ?? id;
76
76
  const sortZones = new Set(stages.filter(one => one.kind === "sort").map(one => one.id));
77
77
  const decisions = sortZones.size === 0 ? new Map() : store.flowSortDecisions(flow.id);
78
- const mates = new Map(store.teammates([flow.repo]).map(one => [one.id, one]));
78
+ const mates = new Map(store.subagents([flow.repo]).map(one => [one.id, one]));
79
79
  const taskSecrets = new Map();
80
80
  const secretsFor = (repo) => {
81
81
  if (!taskSecrets.has(repo))
@@ -92,9 +92,9 @@ export function flowView(store, flow, viewer, selectedCard, setup = { dir: null,
92
92
  const discussion = store.flowComments(card.id);
93
93
  const watchers = store.flowCardWatchers(card.id);
94
94
  const decider = stage?.kind === "approval" ? deciderOf(stage, flow) : null;
95
- // While its teammate is deciding (v92), the card isn't anyone else's to decide yet.
96
- const teammateDeciding = stage?.teammate !== undefined && card.waiting !== null && card.waiting.endsWith(" is deciding");
97
- const canDecide = viewer.approver && card.state === "active" && stage?.kind === "approval" && (decider === null || decider === viewer.name) && !teammateDeciding;
95
+ // While its subagent is deciding (v92), the card isn't anyone else's to decide yet.
96
+ const subagentDeciding = stage?.subagent !== undefined && card.waiting !== null && card.waiting.endsWith(" is deciding");
97
+ const canDecide = viewer.approver && card.state === "active" && stage?.kind === "approval" && (decider === null || decider === viewer.name) && !subagentDeciding;
98
98
  // History reads moves and ownership together, newest first.
99
99
  const owned = discussion.filter(one => one.kind === "owner").map(one => ({ text: one.body === "" ? `${one.author} left it without an owner` : one.body === one.author ? `${one.author} took it on` : `${one.author} made ${one.body} the owner`, at: one.at }));
100
100
  const moves = store.flowEvents(card.id).map(event => ({ text: historyText(event, title, sortZones), at: event.at }));
@@ -109,15 +109,15 @@ export function flowView(store, flow, viewer, selectedCard, setup = { dir: null,
109
109
  label: stage.wait !== undefined && stage.wait.for !== "hours" ? (stage.wait.for === "reply" ? "No reply by" : "Moves on at") : stage.limit.to !== null ? `Moves to ${title(stage.limit.to)} at` : "Reminder at",
110
110
  };
111
111
  // Its open question: one of its own, or (v94) a tool call waiting for approval.
112
- const asked = card.state === "active" ? store.openTeammateQuestionOn(card.id, card.entry) : null;
113
- const pending = asked?.toolCall == null ? null : store.teammateCall(asked.toolCall);
114
- // A decision its teammate handed over carries what the teammate said (v92).
115
- const turn = card.state === "active" && stage?.kind === "approval" && stage.teammate !== undefined ? store.flowStepRun(card.id, card.entry) : null;
112
+ const asked = card.state === "active" ? store.openSubagentQuestionOn(card.id, card.entry) : null;
113
+ const pending = asked?.toolCall == null ? null : store.subagentCall(asked.toolCall);
114
+ // A decision its subagent handed over carries what the subagent said (v92).
115
+ const turn = card.state === "active" && stage?.kind === "approval" && stage.subagent !== undefined ? store.flowStepRun(card.id, card.entry) : null;
116
116
  let handoff = null;
117
- if (turn !== null && turn.kind === "teammate" && turn.state === "passed") {
117
+ if (turn !== null && turn.kind === "subagent" && turn.state === "passed") {
118
118
  try {
119
119
  const said = JSON.parse(turn.decisionJson ?? "{}");
120
- const mate = store.teammateByHandle(flow.repo, stage.teammate);
120
+ const mate = store.subagentByHandle(flow.repo, stage.subagent);
121
121
  if (said.action === "hand_off" && typeof said.note === "string" && said.note !== "" && mate !== null)
122
122
  handoff = { from: labelOf(mate), note: said.note };
123
123
  }
@@ -125,7 +125,7 @@ export function flowView(store, flow, viewer, selectedCard, setup = { dir: null,
125
125
  handoff = null;
126
126
  }
127
127
  }
128
- const asker = asked === null || asked.state !== "open" ? null : store.getTeammate(asked.teammate);
128
+ const asker = asked === null || asked.state !== "open" ? null : store.getSubagent(asked.subagent);
129
129
  // "Person chooses" (flow-send.ts): what was sent, and the options, for the person it waits on; the latest send otherwise.
130
130
  const choosing = card.state === "active" && stage?.kind === "choose" ? flowChoiceAt(store, card.id, card.entry) : null;
131
131
  const latest = store.flowSends(card.id)[0] ?? null;
@@ -162,11 +162,11 @@ export function flowView(store, flow, viewer, selectedCard, setup = { dir: null,
162
162
  question: asked === null || asked.state !== "open" || asker === null ? null
163
163
  : { id: asked.id, from: labelOf(asker), question: asked.question, options: asked.options, askedOf: asked.askedOf, mine: asked.askedOf === viewer.name,
164
164
  call: pending === null ? null : { why: pending.why, rule: pending.result ?? "" } },
165
- calls: store.teammateCallsOn(card.id).slice(-30).map(call => {
166
- const mate = mates.get(call.teammate) ?? null;
165
+ calls: store.subagentCallsOn(card.id).slice(-30).map(call => {
166
+ const mate = mates.get(call.subagent) ?? null;
167
167
  // A person's undo (v97) reads as theirs, and its outcome says what it undid.
168
- return { id: call.id, who: call.undoOf !== null ? call.decidedBy ?? "A person" : mate === null ? "A teammate" : nameOf(mate), words: callWords(call.tool, call.action, call.input, 400), state: call.state, outcome: receiptWords(store, call),
169
- why: call.undoOf !== null ? "" : call.why, result: call.state === "asked" ? null : call.result, at: call.createdAt, teammate: call.teammate, undo: viewer.approver ? undoFor(store, call) : null };
168
+ return { id: call.id, who: call.undoOf !== null ? call.decidedBy ?? "A person" : mate === null ? "A subagent" : nameOf(mate), words: callWords(call.tool, call.action, call.input, 400), state: call.state, outcome: receiptWords(store, call),
169
+ why: call.undoOf !== null ? "" : call.why, result: call.state === "asked" ? null : call.result, at: call.createdAt, subagent: call.subagent, undo: viewer.approver ? undoFor(store, call) : null };
170
170
  }),
171
171
  };
172
172
  });
@@ -197,7 +197,7 @@ export function flowView(store, flow, viewer, selectedCard, setup = { dir: null,
197
197
  startTrigger: setup.startTrigger ?? null,
198
198
  me: viewer.name,
199
199
  sortReady: setup.sortReady ?? false,
200
- teammates: store.teammates([flow.repo]).map(mate => ({ handle: mate.handle, label: labelOf(mate), name: nameOf(mate), working: mate.state === "active", href: `/teammates/${mate.id}` })),
200
+ subagents: store.subagents([flow.repo]).map(mate => ({ handle: mate.handle, label: labelOf(mate), name: nameOf(mate), working: mate.state === "active", href: `/settings/lead/subagents/${mate.id}` })),
201
201
  emailReady: sendingReady(setup.dir),
202
202
  requestSecrets: flowSecretNames(setup.dir, flow.repo),
203
203
  tools: projectToolsOf(store, flow.repo).map(tool => ({ name: tool.name, about: tool.spec.about, functions: tool.lastTest?.ok === true ? tool.lastTest.tools : [],
package/dist/flows.js CHANGED
@@ -52,7 +52,7 @@ import { createHash } from "node:crypto";
52
52
  import { projectName } from "./project.js";
53
53
  import { parseContract, readVersioned } from "./contracts/contract.js";
54
54
  import { stageReferenceProblems } from "./contracts/stage-output.js";
55
- import { CHOICES_MAX, CHOICES_MIN, FLOW_ALIASES, FLOW_COLORS, FLOW_DEFINITION_VERSION, FLOW_END, FLOW_MERGE_METHODS, FLOW_STAGE_KINDS, flowDefinitionSchema, flowStepsSchema, HEADERS_MAX, LONGEST_WAIT_MINUTES, savedFlowDefinitionSchema, SCRIPT_LANGUAGES, SCRIPT_NAME, sortKeyOf, SURE_AT_MAX, SURE_AT_MIN, ZONE_ID, } from "./contracts/flow.js";
55
+ import { CHOICES_MAX, CHOICES_MIN, FLOW_ALIASES, FLOW_COLORS, FLOW_DEFINITION_VERSION, FLOW_END, FLOW_MERGE_METHODS, FLOW_STAGE_KINDS, flowDefinitionSchema, flowStepsSchema, HEADERS_MAX, legacySubagentStep, LONGEST_WAIT_MINUTES, savedFlowDefinitionSchema, SCRIPT_LANGUAGES, SCRIPT_NAME, sortKeyOf, SURE_AT_MAX, SURE_AT_MIN, ZONE_ID, } from "./contracts/flow.js";
56
56
  export { CHOICES_MAX, CHOICES_MIN, FLOW_COLORS, FLOW_END, FLOW_MERGE_METHODS, FLOW_STAGE_KINDS, LONGEST_WAIT_MINUTES, SCRIPT_LANGUAGES, SCRIPT_NAME, sortKeyOf };
57
57
  /** What each kind is called and does, in the words the canvas uses. */
58
58
  export const FLOW_KIND_WORDS = {
@@ -70,7 +70,7 @@ export const FLOW_KIND_WORDS = {
70
70
  draft: { label: "Draft", about: "Claude writes a reply, summary or note from the card in seconds. Nothing is sent until a later step sends it." },
71
71
  sort: { label: "Sort", about: "Jev reads the card in under a second and sends it where its answer leads. Cards it isn't sure about take the not-sure path." },
72
72
  wait: { label: "Wait", about: "Waits for a reply to the card's email, or for a set time. A reply moves the card on; if none comes in time, it takes the no-reply path." },
73
- teammate: { label: "Teammate handles it", about: "An AI teammate reads the card, decides where it goes within its rules, and writes what the next zones send. When its rules say to ask, it asks you first." },
73
+ subagent: { label: "Subagent handles it", about: "A subagent reads the card, decides where it goes within its rules, and writes what the next zones send. When its rules say to ask, it asks you first." },
74
74
  send: { label: "Send to me", about: "Sends the card's owner what the step before produced: its summary, links and any screenshots, in each chat app they use and on the card. Then moves on." },
75
75
  choose: { label: "Person chooses", about: "Sends the card's owner what was done with 2 to 4 buttons you name, in their chat apps and here. Each leads to a zone or ignores the card; a reply instead goes where replies go, as the note." },
76
76
  done: { label: "Done", about: "The end of the flow." },
@@ -170,7 +170,7 @@ function canonicalSort(input) {
170
170
  sureAt, notes,
171
171
  };
172
172
  }
173
- /** A script's or teammate's answers as every release has kept them: trimmed, none left out. */
173
+ /** A script's or subagent's answers as every release has kept them: trimmed, none left out. */
174
174
  function canonicalRoutes(input) {
175
175
  if (!given(input))
176
176
  return undefined;
@@ -200,7 +200,7 @@ function canonicalWait(input) {
200
200
  function canonicalStage(input, index) {
201
201
  if (!isRecord(input))
202
202
  return input;
203
- const stage = input;
203
+ const stage = legacySubagentStep(input);
204
204
  const kind = stage["kind"];
205
205
  const zone = isRecord(stage["zone"]) ? stage["zone"] : {};
206
206
  const planning = stage["planning"] === "required" || stage["planning"] === "skip" || stage["planning"] === "auto" ? stage["planning"] : null;
@@ -231,9 +231,9 @@ function canonicalStage(input, index) {
231
231
  ...(kind === "wait" ? { wait: canonicalWait(stage["wait"]) } : {}),
232
232
  // true is a squash, the default; false or none is no merge.
233
233
  ...(kind === "pull-request" && given(stage["merge"]) && stage["merge"] !== false ? { merge: stage["merge"] === true ? "squash" : stage["merge"] } : {}),
234
- ...((kind === "approval" || kind === "teammate") && given(stage["teammate"]) && stage["teammate"] !== "" ? { teammate: stage["teammate"] } : {}),
235
- ...(kind === "teammate" && routes !== undefined ? { routes } : {}),
236
- ...(kind === "teammate" && stage["reply"] === true ? { reply: true } : {}),
234
+ ...((kind === "approval" || kind === "subagent") && given(stage["subagent"]) && stage["subagent"] !== "" ? { subagent: stage["subagent"] } : {}),
235
+ ...(kind === "subagent" && routes !== undefined ? { routes } : {}),
236
+ ...(kind === "subagent" && stage["reply"] === true ? { reply: true } : {}),
237
237
  ...(kind === "choose" ? { options: Array.isArray(stage["options"]) ? stage["options"].map(one => { const row = isRecord(one) ? one : {}; return { label: said(row["label"]), to: said(row["to"]) }; }) : given(stage["options"]) ? stage["options"] : [] } : {}),
238
238
  ...(kind === "task" && given(stage["repo"]) && stage["repo"] !== "" ? { repo: said(stage["repo"]) } : {}),
239
239
  ...(limit === undefined ? {} : { limit }),
@@ -565,7 +565,7 @@ function fillOne(key, stage, card) {
565
565
  return stage === undefined ? "" : card.outputs[stage] ?? "";
566
566
  }
567
567
  }
568
- const KIND_COLORS = { inbox: "slate", task: "blue", report: "violet", approval: "amber", check: "blue", "pull-request": "blue", update: "green", notify: "green", sort: "violet", draft: "violet", request: "blue", email: "green", tool: "blue", wait: "slate", teammate: "violet", send: "green", choose: "amber", done: "green" };
568
+ const KIND_COLORS = { inbox: "slate", task: "blue", report: "violet", approval: "amber", check: "blue", "pull-request": "blue", update: "green", notify: "green", sort: "violet", draft: "violet", request: "blue", email: "green", tool: "blue", wait: "slate", subagent: "violet", send: "green", choose: "amber", done: "green" };
569
569
  /** A step id as the lead may write it (sort_by_hand, Sort-By-Hand) in the one form zones use. */
570
570
  const idOf = (value) => value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 32);
571
571
  const slugOf = (title) => title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 28) || "zone";
@@ -632,7 +632,7 @@ export function withKeptSteps(steps, previous) {
632
632
  }
633
633
  /** A flow's steps, read by their one schema: `steps[0].routes[0]: unknown key 'to' (did you mean goesTo?)`. */
634
634
  export function readFlowSteps(input, previous = null) {
635
- const read = parseContract(flowStepsSchema, { steps: withKeptSteps(input, previous) }, FLOW_ALIASES);
635
+ const read = parseContract(flowStepsSchema, { steps: withKeptSteps(Array.isArray(input) ? input.map(legacySubagentStep) : input, previous) }, FLOW_ALIASES);
636
636
  return read.ok ? { ok: true, value: read.value.steps } : read;
637
637
  }
638
638
  /**
@@ -707,7 +707,7 @@ export function flowFromSteps(input, previous = null) {
707
707
  id, title, kind,
708
708
  zone: old?.zone ?? { x: 0, y: 0, w: 260, h: kind === "done" || kind === "notify" || kind === "send" ? 220 : 300, color: KIND_COLORS[kind] },
709
709
  // A kept step on our default instructions gets the default again, so it reads any research step added before it.
710
- instructions: kind === "teammate" ? step.instructions?.trim() || old?.instructions || "Read the card and decide what happens next."
710
+ instructions: kind === "subagent" ? step.instructions?.trim() || old?.instructions || "Read the card and decide what happens next."
711
711
  : kind === "draft" ? step.instructions?.trim() || old?.instructions || DRAFT_DEFAULT
712
712
  : kind === "task" || kind === "report"
713
713
  ? step.instructions?.trim() || (old?.instructions && old.instructions !== defaultInstructions(kind, previous.stages.slice(0, previous.stages.indexOf(old))) ? old.instructions : defaultInstructions(kind, earlier))
@@ -731,10 +731,10 @@ export function flowFromSteps(input, previous = null) {
731
731
  ...(kind === "tool" ? { tool: { server: step.server ?? old?.tool?.server ?? "", name: step.tool ?? old?.tool?.name ?? "", args: typeof step.args === "string" ? step.args : step.args !== undefined ? JSON.stringify(step.args) : old?.tool?.args ?? "{}" } } : {}),
732
732
  ...(kind === "wait" ? { wait: waitFromStep(step, old?.wait ?? null, at) } : {}),
733
733
  ...(kind === "pull-request" && (step.merge === undefined ? old?.merge !== undefined : step.merge !== false) ? { merge: step.merge === undefined || step.merge === true || step.merge === false ? old?.merge ?? "squash" : step.merge } : {}),
734
- // v92: "nobody" (or "none") takes a teammate off an approval step.
735
- ...((kind === "approval" || kind === "teammate") && (step.teammate === undefined ? old?.teammate !== undefined : !/^(nobody|none|no one)$/i.test(step.teammate.trim())) ? { teammate: idOf(step.teammate ?? old.teammate) } : {}),
736
- ...(kind === "teammate" ? step.routes !== undefined ? routesOf(step.routes) : old?.routes === undefined ? {} : { routes: old.routes } : {}),
737
- ...(kind === "teammate" && (step.reply ?? old?.reply) === true ? { reply: true } : {}),
734
+ // v92: "nobody" (or "none") takes a subagent off an approval step.
735
+ ...((kind === "approval" || kind === "subagent") && (step.subagent === undefined ? old?.subagent !== undefined : !/^(nobody|none|no one)$/i.test(step.subagent.trim())) ? { subagent: idOf(step.subagent ?? old.subagent) } : {}),
736
+ ...(kind === "subagent" ? step.routes !== undefined ? routesOf(step.routes) : old?.routes === undefined ? {} : { routes: old.routes } : {}),
737
+ ...(kind === "subagent" && (step.reply ?? old?.reply) === true ? { reply: true } : {}),
738
738
  ...(kind === "choose" ? { options: choicesFromStep(step, old?.options ?? null, at, find) } : {}),
739
739
  ...(kind === "task" && (step.repo ?? old?.repo) !== undefined && (step.repo ?? old?.repo).trim() !== "" ? { repo: (step.repo ?? old?.repo).trim() } : {}),
740
740
  ...(() => {
@@ -882,9 +882,9 @@ export function flowTerms(definition, previous) {
882
882
  else if (stage.kind === "tool" && stage.tool !== undefined)
883
883
  lines.push(`Uses ${stage.tool.server} → ${stage.tool.name} with ${plain(stage.tool.args).slice(0, 400)}`, `Then → ${to(stage.next)}${stage.onFail === null ? " If it fails → waits there." : ` If it fails → ${to(stage.onFail)}`}`);
884
884
  else if (stage.kind === "approval")
885
- lines.push(`Decides: ${stage.teammate !== undefined ? `the AI teammate ${stage.teammate}, within its rules, handing hard ones to ${stage.toOwner === true ? "the flow's owner" : stage.approver ?? "anyone who approves"}` : stage.toOwner === true ? "the flow's owner, in their chat app" : stage.approver ?? "anyone who approves on this project"}. Approve → ${to(stage.next)} Send back → ${stage.onFail === null ? "not possible." : to(stage.onFail)}`);
886
- else if (stage.kind === "teammate")
887
- lines.push(`The AI teammate ${stage.teammate ?? "(none)"} handles it${stage.instructions === null ? "" : `: ${plain(stage.instructions)}`}`, ...(stage.routes === undefined || stage.routes.length === 0 ? [`Then → ${to(stage.next)}`] : stage.routes.map(one => `${one.answer} → ${to(one.to)}`)), `It asks the flow's owner when its rules say to.${stage.reply === true ? " It sends what it writes back to whoever asked." : ""}${stage.onFail === null ? "" : ` If it can't → ${to(stage.onFail)}`}`);
885
+ lines.push(`Decides: ${stage.subagent !== undefined ? `the subagent ${stage.subagent}, within its rules, handing hard ones to ${stage.toOwner === true ? "the flow's owner" : stage.approver ?? "anyone who approves"}` : stage.toOwner === true ? "the flow's owner, in their chat app" : stage.approver ?? "anyone who approves on this project"}. Approve → ${to(stage.next)} Send back → ${stage.onFail === null ? "not possible." : to(stage.onFail)}`);
886
+ else if (stage.kind === "subagent")
887
+ lines.push(`The subagent ${stage.subagent ?? "(none)"} handles it${stage.instructions === null ? "" : `: ${plain(stage.instructions)}`}`, ...(stage.routes === undefined || stage.routes.length === 0 ? [`Then → ${to(stage.next)}`] : stage.routes.map(one => `${one.answer} → ${to(one.to)}`)), `It asks the flow's owner when its rules say to.${stage.reply === true ? " It sends what it writes back to whoever asked." : ""}${stage.onFail === null ? "" : ` If it can't → ${to(stage.onFail)}`}`);
888
888
  else if (stage.kind === "notify")
889
889
  lines.push(`Posts: ${plain(stage.message ?? "")}`, `Then → ${to(stage.next)}`);
890
890
  else if (stage.kind === "send")
@@ -945,8 +945,8 @@ export function flowTerms(definition, previous) {
945
945
  terms.push("Draft steps send each card's text to Claude through the lead chat's sign-in. Nothing a draft writes is sent until a later step sends it.");
946
946
  if (definition.stages.some(one => one.kind === "task" && one.repo !== undefined))
947
947
  terms.push("A build step in another project files its task there, only when the flow's owner may file work in that project.");
948
- if (definition.stages.some(one => one.teammate !== undefined))
949
- terms.push("AI teammates decide and act within their soul files' rules, reading each card through Claude on this computer's sign-in; they never approve code tasks or merges.");
948
+ if (definition.stages.some(one => one.subagent !== undefined))
949
+ terms.push("subagents decide and act within their soul files' rules, reading each card through Claude on this computer's sign-in; they never approve code tasks or merges.");
950
950
  return terms;
951
951
  }
952
952
  const zone = (x, y, color, h = 300) => ({ x, y, w: 260, h, color });
package/dist/guides.js CHANGED
@@ -318,7 +318,7 @@ ${AUTHORITY_LINE}
318
318
  transcripts following.
319
319
  - **projects** (\`/projects\`): enrol, open, or switch repositories; the
320
320
  project name in the header is the one-tap switcher on every screen.
321
- - **chat** (\`/chat\`): the mate — a conversation across every project
321
+ - **chat** (\`/chat\`): the lead — a conversation across every project
322
322
  that only PROPOSES (file, reorder, reserve, hold, rewrite a scope,
323
323
  cancel, answer a decision); each proposal is a card the person
324
324
  confirms. Coordinators over the MCP gateway propose the same way, and
@@ -340,7 +340,7 @@ ${AUTHORITY_LINE}
340
340
  - \`toolroll peek\`: one pane per live agent (digits focus, \`q\`
341
341
  leaves); \`peek <run>\` follows one; \`peek --tmux\` opens a tmux window
342
342
  per run.
343
- - \`toolroll chat\`: the mate in the terminal (\`--say "…"\` for one
343
+ - \`toolroll chat\`: the lead in the terminal (\`--say "…"\` for one
344
344
  turn); \`toolroll proposals\` lists and confirms coordinator
345
345
  proposals.
346
346
  - \`toolroll task add "<title>" --report\`: a SCOUT task — a
@@ -359,7 +359,8 @@ needs you · queued · waiting · building · done · failed; a *scope* is the
359
359
  contract a person approves; a *decision* is a typed question an agent
360
360
  parked; a *hold* pauses the next attempt; a *stale approval* means the
361
361
  approval no longer matches how builds are routed — approving again fixes
362
- it; a *scout* delivers a report; the *mate* proposes and never acts.
362
+ it; a *scout* delivers a report; the *lead* proposes and never acts, and
363
+ its *subagents* work within the rules you write for them.
363
364
  `,
364
365
  };
365
366
  const knowledge = {