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
@@ -10,6 +10,7 @@ export type TeamCliOptions = {
10
10
  signal?: AbortSignal;
11
11
  sleep?: (milliseconds: number, signal: AbortSignal) => Promise<void>;
12
12
  };
13
+ /** D5: deprecated (deprecations.ts). Each still runs this release, hidden from help and the contract, with one warning on stderr. */
13
14
  export declare const TEAM_CLI_ACTIONS: readonly ['connect', 'lead list', 'lead create', 'lead update', 'lead member', 'lead transfer', 'conversation list', 'conversation create', 'conversation show', 'conversation member', 'conversation edit', 'conversation withdraw', 'conversation read', 'conversation follow', 'conversation stop'];
14
15
  export declare class UsageError extends Error {
15
16
  }
package/dist/team-cli.js CHANGED
@@ -9,6 +9,10 @@ import { sessionServiceOrigin } from './session-cli.js';
9
9
  import { databasePath } from './store.js';
10
10
  import { configBase, namedPath } from './names.js';
11
11
  import { parseApiToken } from './api-tokens.js';
12
+ import { deprecationWarning } from './deprecations.js';
13
+ /** The central service's own lead verbs; every other `lead …` (token, say, or none) is the local lead's. */
14
+ const CENTRAL_LEAD_ACTIONS = ['list', 'create', 'update', 'member', 'transfer'];
15
+ /** D5: deprecated (deprecations.ts). Each still runs this release, hidden from help and the contract, with one warning on stderr. */
12
16
  export const TEAM_CLI_ACTIONS = ['connect', 'lead list', 'lead create', 'lead update', 'lead member', 'lead transfer', 'conversation list', 'conversation create', 'conversation show', 'conversation member', 'conversation edit', 'conversation withdraw', 'conversation read', 'conversation follow', 'conversation stop'];
13
17
  const HELP = `toolroll connect <HTTPS-origin> --as <account> --token-stdin
14
18
  Pipe your API token (so_…, from Settings → API tokens) or use --token-file <private-file>;
@@ -335,8 +339,9 @@ function render(reply, listing) {
335
339
  /** null means preserve the existing local command path. Nothing here opens a database. */
336
340
  export async function maybeRunTeamCommand(argv, write, options = {}) {
337
341
  const command = argv[0];
338
- // `lead token` is local: the credential a lead agent acts with on this computer.
339
- if (command === 'lead' && (argv[1] === 'token' || argv[1] === 'say'))
342
+ // `lead token` and `lead say` are local: the credential a lead agent acts with on this computer, and its voice. A bare
343
+ // `lead` is the local lead's too ("lead" means only the lead); only the central service's verbs go on from here.
344
+ if (command === 'lead' && (argv[1] === 'token' || argv[1] === 'say' || !argv.some(arg => arg === '--profile' || CENTRAL_LEAD_ACTIONS.includes(arg))))
340
345
  return null;
341
346
  if (!['connect', 'lead', 'conversation', 'chat', 'brief'].includes(command ?? '') && !argv.includes('--profile'))
342
347
  return null;
@@ -357,6 +362,11 @@ export async function maybeRunTeamCommand(argv, write, options = {}) {
357
362
  return null;
358
363
  }
359
364
  const explicit = ['connect', 'lead', 'conversation'].includes(command) || argv.some(arg => ['--profile', '--lead', '--conversation', '--authorize', '--terms-digest', '--request-id'].includes(arg));
365
+ // D5: the central team service is deprecated. It still runs this release; each use says so once, on stderr.
366
+ const verb = command === 'lead' || command === 'conversation' ? `${command} ${argv.slice(1).find(arg => !arg.startsWith('--')) ?? ''}`.trim() : command === 'connect' ? command : `${command} --conversation`;
367
+ const warn = () => stderr(deprecationWarning(verb, 'team'));
368
+ if (explicit)
369
+ warn();
360
370
  if (explicit && (argv.includes('--help') || ['lead', 'conversation'].includes(command) && argv.length === 1)) {
361
371
  write(HELP);
362
372
  return 0;
@@ -364,6 +374,8 @@ export async function maybeRunTeamCommand(argv, write, options = {}) {
364
374
  const saved = profiles(options);
365
375
  if (!explicit && !saved)
366
376
  return null;
377
+ if (!explicit)
378
+ warn();
367
379
  const parsed = parse(argv), { flags, action } = parsed;
368
380
  if (flags.help || ((command === 'lead' || command === 'conversation') && !action)) {
369
381
  write(HELP);
@@ -185,7 +185,7 @@ export class TeamLeads {
185
185
  }
186
186
  const selected = conversationId ? this.access(actor, conversationId).conversation : conversations[0] ?? null;
187
187
  const access = selected ? this.access(actor, selected.id) : null;
188
- const messages = selected ? this.db.prepare(`SELECT * FROM (SELECT m.*,q.author,q.request_id,q.status,q.revision AS message_revision,q.error FROM mate_message m LEFT JOIN team_message q ON q.message=m.id WHERE m.thread=? ORDER BY m.id DESC LIMIT 201) ORDER BY id`).all(selected.threadId).map(r => ({ id: Number(r['id']), author: r['role'] === 'assistant' ? access.lead.name : String(r['author'] ?? selected.createdBy), role: String(r['role']), text: String(r['text']), status: (r['status'] ?? 'answered'), revision: Number(r['message_revision'] ?? 1), createdAt: String(r['created_at']), requestId: r['request_id'] == null ? null : String(r['request_id']), turnId: r['turn'] == null ? null : Number(r['turn']), error: r['error'] == null ? null : String(r['error']) })) : [];
188
+ const messages = selected ? this.db.prepare(`SELECT * FROM (SELECT m.*,q.author,q.request_id,q.status,q.revision AS message_revision,q.error FROM lead_message m LEFT JOIN team_message q ON q.message=m.id WHERE m.thread=? ORDER BY m.id DESC LIMIT 201) ORDER BY id`).all(selected.threadId).map(r => ({ id: Number(r['id']), author: r['role'] === 'assistant' ? access.lead.name : String(r['author'] ?? selected.createdBy), role: String(r['role']), text: String(r['text']), status: (r['status'] ?? 'answered'), revision: Number(r['message_revision'] ?? 1), createdAt: String(r['created_at']), requestId: r['request_id'] == null ? null : String(r['request_id']), turnId: r['turn'] == null ? null : Number(r['turn']), error: r['error'] == null ? null : String(r['error']) })) : [];
189
189
  const participants = selected ? this.db.prepare('SELECT account,role,active FROM team_participant WHERE conversation=? ORDER BY account').all(selected.id).map(r => ({ account: String(r['account']), role: String(r['role']), active: Number(r['active']) === 1 })) : [];
190
190
  const canManage = account.role === 'approver' && (access ? access.role === 'manager' : leadId ? this.leadAccess(actor, leadId).role === 'manager' : false);
191
191
  return { leads, conversations, selected, participants, messages: messages.slice(-200), canManage, canCreateLead: account.role === 'approver' && this.enrolled().some(p => projectAccessAllows(account.projects, p)), canSend: account.role === 'approver' && access !== null && rank(access.role) >= 1 && access.lead.status === 'active', cursor: this.cursor(actor, selected?.id) ?? 0, truncated: messages.length > 200, projects: this.enrolled().filter(p => projectAccessAllows(account.projects, p)), accounts: canManage ? this.store.accountFacts().filter(a => a.revokedAt === null && (selected?.projects ?? []).every(p => projectAccessAllows(a.projects, p))).map(a => a.name) : [] };
@@ -345,14 +345,14 @@ export class TeamLeads {
345
345
  return ok('Message already saved.', { messageId: Number(seen['message']), status: String(seen['status']) });
346
346
  }
347
347
  const revisions = this.memberRevisions(conversationId, actor.name);
348
- const messageId = this.store.appendMateMessage({ thread: conversation.threadId, turn: null, role: 'operator', text }, now);
348
+ const messageId = this.store.appendLeadMessage({ thread: conversation.threadId, turn: null, role: 'operator', text }, now);
349
349
  this.db.prepare("INSERT INTO team_message(message,conversation,author,author_generation,lead_member_revision,participant_revision,request_id,payload_hash,status) VALUES(?,?,?,?,?,?,?,?,'queued')").run(messageId, conversationId, actor.name, actor.generation, revisions.lead, revisions.participant, requestId, digest);
350
350
  this.event(lead.id, conversationId, 'message-queued', actor.name, now);
351
351
  return ok('Message saved.', { messageId, status: 'queued' });
352
352
  }
353
353
  if (request.operation === 'read') {
354
354
  const id = integer(a['messageId'], 'Message');
355
- if (id > 0 && !this.db.prepare('SELECT 1 FROM mate_message WHERE id=? AND thread=?').get(id, conversation.threadId))
355
+ if (id > 0 && !this.db.prepare('SELECT 1 FROM lead_message WHERE id=? AND thread=?').get(id, conversation.threadId))
356
356
  refuse('not-found', 'Message not available.');
357
357
  this.db.prepare('INSERT INTO team_read(conversation,account,message) VALUES(?,?,?) ON CONFLICT(conversation,account) DO UPDATE SET message=MAX(team_read.message,excluded.message)').run(conversationId, actor.name, id);
358
358
  return ok('Read position saved.');
@@ -374,9 +374,9 @@ export class TeamLeads {
374
374
  if (message['status'] === 'running') {
375
375
  this.db.prepare('UPDATE team_message SET stop_requested=1 WHERE message=?').run(id);
376
376
  if (message['turn_id'] !== null) {
377
- const turn = this.store.getMateTurn(Number(message['turn_id']));
377
+ const turn = this.store.getLeadTurn(Number(message['turn_id']));
378
378
  if (turn)
379
- this.store.finalizeMateTurn(turn.id, turn.generation, { state: 'failed', settledMicrousd: turn.reservedMicrousd, unknownSpend: true, tokensIn: 0, tokensOut: 0, failureReason: 'stopped' }, now);
379
+ this.store.finalizeLeadTurn(turn.id, turn.generation, { state: 'failed', settledMicrousd: turn.reservedMicrousd, unknownSpend: true, tokensIn: 0, tokensOut: 0, failureReason: 'stopped' }, now);
380
380
  }
381
381
  }
382
382
  else if (message['status'] === 'queued' || message['status'] === 'uncertain')
@@ -393,7 +393,7 @@ export class TeamLeads {
393
393
  refuse('conflict', 'This message changed or has started. Send a follow-up instead.');
394
394
  if (request.operation === 'edit') {
395
395
  const text = messageText(a['text']);
396
- this.db.prepare('UPDATE mate_message SET text=? WHERE id=?').run(text, id);
396
+ this.db.prepare('UPDATE lead_message SET text=? WHERE id=?').run(text, id);
397
397
  this.db.prepare('UPDATE team_message SET revision=revision+1 WHERE message=?').run(id);
398
398
  }
399
399
  else
@@ -404,9 +404,9 @@ export class TeamLeads {
404
404
  refuse('unsupported', 'This action is not available here.');
405
405
  }
406
406
  revokeMemberSessions(leadId, conversationId, account, now) {
407
- this.db.prepare(`UPDATE mate_session SET ended_at=?,ended_by='membership-changed' WHERE approver=? AND ended_at IS NULL
407
+ this.db.prepare(`UPDATE lead_session SET ended_at=?,ended_by='membership-changed' WHERE approver=? AND ended_at IS NULL
408
408
  AND EXISTS(SELECT 1 FROM team_mate_session ms JOIN team_conversation c ON c.thread=ms.thread
409
- WHERE ms.session=mate_session.id AND c.lead=? AND (? IS NULL OR c.id=?))`).run(now.toISOString(), account, leadId, conversationId, conversationId);
409
+ WHERE ms.session=lead_session.id AND c.lead=? AND (? IS NULL OR c.id=?))`).run(now.toISOString(), account, leadId, conversationId, conversationId);
410
410
  }
411
411
  memberRevisions(conversationId, account) {
412
412
  const row = this.db.prepare(`SELECT lm.revision AS lr,p.revision AS pr FROM team_conversation c JOIN team_lead_member lm ON lm.lead=c.lead AND lm.account=? AND lm.active=1 JOIN team_participant p ON p.conversation=c.id AND p.account=lm.account AND p.active=1 WHERE c.id=?`).get(account, conversationId);
@@ -427,7 +427,7 @@ export class TeamLeads {
427
427
  claimNext(runner, now = new Date(), eligible) {
428
428
  str(runner, 'Runner', 180);
429
429
  return this.store.transact(() => {
430
- const candidates = this.db.prepare(`SELECT q.*,m.text,c.lead,c.thread FROM team_message q JOIN mate_message m ON m.id=q.message JOIN team_conversation c ON c.id=q.conversation JOIN team_lead l ON l.id=c.lead AND l.status='active'
430
+ const candidates = this.db.prepare(`SELECT q.*,m.text,c.lead,c.thread FROM team_message q JOIN lead_message m ON m.id=q.message JOIN team_conversation c ON c.id=q.conversation JOIN team_lead l ON l.id=c.lead AND l.status='active'
431
431
  WHERE q.status='queued' AND NOT EXISTS(SELECT 1 FROM team_message live WHERE live.conversation=q.conversation AND live.status IN ('running','uncertain'))
432
432
  AND NOT EXISTS(SELECT 1 FROM team_message earlier WHERE earlier.conversation=q.conversation AND earlier.status='queued' AND earlier.message<q.message)
433
433
  AND (SELECT COUNT(*) FROM team_message running JOIN team_conversation other ON other.id=running.conversation WHERE running.status='running' AND other.lead=c.lead)<2
@@ -453,7 +453,7 @@ export class TeamLeads {
453
453
  current(claim) { const row = this.claimed(claim); return row !== undefined && Number(row['stop_requested']) === 0 && this.authorStillAllowed(row); }
454
454
  bindTurn(claim, turnId) {
455
455
  return this.store.transact(() => { if (!this.current(claim))
456
- return false; const turn = this.store.getMateTurn(turnId); if (!turn || turn.thread !== claim.threadId || turn.approver !== claim.actor.name)
456
+ return false; const turn = this.store.getLeadTurn(turnId); if (!turn || turn.thread !== claim.threadId || turn.approver !== claim.actor.name)
457
457
  return false; const changed = this.db.prepare('UPDATE team_message SET turn_id=? WHERE message=? AND turn_id IS NULL').run(turnId, claim.messageId); return Number(changed.changes) === 1; });
458
458
  }
459
459
  finish(claim, result, now = new Date()) {
@@ -467,7 +467,7 @@ export class TeamLeads {
467
467
  return false;
468
468
  this.db.prepare('UPDATE team_message SET status=?,error=?,turn_id=COALESCE(turn_id,?),revision=revision+1 WHERE message=?').run(status, result.error ?? (!allowed ? 'Access changed while this message was running.' : stopped ? 'Stopped.' : null), result.turnId ?? null, claim.messageId);
469
469
  if (allowed && !stopped && result.text)
470
- this.store.appendMateMessage({ thread: claim.threadId, turn: result.turnId ?? (row['turn_id'] === null ? null : Number(row['turn_id'])), role: 'assistant', text: result.text }, now);
470
+ this.store.appendLeadMessage({ thread: claim.threadId, turn: result.turnId ?? (row['turn_id'] === null ? null : Number(row['turn_id'])), role: 'assistant', text: result.text }, now);
471
471
  this.event(claim.leadId, claim.conversationId, 'message-finished', claim.actor.name, now);
472
472
  return true;
473
473
  });
@@ -478,7 +478,7 @@ export class TeamLeads {
478
478
  const row = this.claimed(claim);
479
479
  if (!row || row['turn_id'] !== null || !this.current(claim))
480
480
  return false;
481
- if (this.db.prepare('SELECT turn FROM mate_message WHERE id=?').get(claim.messageId)?.['turn'] !== null)
481
+ if (this.db.prepare('SELECT turn FROM lead_message WHERE id=?').get(claim.messageId)?.['turn'] !== null)
482
482
  return false;
483
483
  const changed = this.db.prepare("UPDATE team_message SET status='queued',generation=generation+1,revision=revision+1,runner=NULL,claimed_at=NULL,error=? WHERE message=? AND status='running' AND turn_id IS NULL AND generation=?").run(error, claim.messageId, claim.generation);
484
484
  if (Number(changed.changes) !== 1)
@@ -520,7 +520,7 @@ export class TeamLeads {
520
520
  return this.store.transact(() => {
521
521
  const rows = this.db.prepare("SELECT q.*,c.lead FROM team_message q JOIN team_conversation c ON c.id=q.conversation WHERE runner=? AND status='running'").all(runner);
522
522
  for (const row of rows) {
523
- const turn = row['turn_id'] === null ? null : this.store.getMateTurn(Number(row['turn_id']));
523
+ const turn = row['turn_id'] === null ? null : this.store.getLeadTurn(Number(row['turn_id']));
524
524
  const status = turn?.state === 'answered' ? 'answered' : turn?.state === 'failed' && !this.deliveryUncertain(turn.id) ? 'failed' : 'uncertain';
525
525
  const error = status === 'uncertain' ? 'Delivery unconfirmed after the previous worker stopped. Inspect activity before continuing.' : status === 'failed' ? savedFailure(turn?.failureReason) : null;
526
526
  this.db.prepare('UPDATE team_message SET status=?,generation=generation+1,revision=revision+1,error=? WHERE message=?').run(status, error, Number(row['message']));
@@ -2,14 +2,14 @@ import type { Store } from './store.js';
2
2
  import type { WorkspaceRevision } from './workspace-revision.js';
3
3
  import { TeamLeads } from './team-leads.js';
4
4
  import { type TeamActor, type TeamExecute } from './team-contract.js';
5
- import { type MateTurnInput } from './mate.js';
5
+ import { type LeadTurnInput } from './lead.js';
6
6
  import { type TeamChatProviderResolver } from './team-chat-authorization.js';
7
7
  export type TeamRuntimeOptions = {
8
8
  store: Store;
9
9
  repos: () => readonly string[];
10
10
  evidenceRoot: string;
11
11
  provider: TeamChatProviderResolver;
12
- subscriptionRunner?: MateTurnInput['subscriptionRunner'];
12
+ subscriptionRunner?: LeadTurnInput['subscriptionRunner'];
13
13
  fetcher?: typeof fetch;
14
14
  clock?: () => Date;
15
15
  capacity?: number;
@@ -6,8 +6,8 @@ import { workIndexPage } from './work-index.js';
6
6
  import { TeamLeads } from './team-leads.js';
7
7
  import { teamScopeAllows } from './team-contract.js';
8
8
  import { verifyApproverStanding } from './principal.js';
9
- import { credentialKeyOf, isDirectChatProvider, priceForConfig, subscriptionCredentialKey, mateWorstCaseForPrice } from './converse.js';
10
- import { runMateTurn } from './mate.js';
9
+ import { credentialKeyOf, isDirectChatProvider, priceForConfig, subscriptionCredentialKey, leadWorstCaseForPrice } from './converse.js';
10
+ import { runLeadTurn } from './lead.js';
11
11
  import { teamChatAuthorization } from './team-chat-authorization.js';
12
12
  import { updateAdmissionPaused } from './desktop-update-gate.js';
13
13
  const hash = (value) => createHash('sha256').update(JSON.stringify(value)).digest('hex');
@@ -34,7 +34,7 @@ export function createTeamRuntime(options) {
34
34
  const stamp = hash([grant.termsDigest, grant.enabled, session?.id, session?.spentMicrousd, used, weekly, Boolean(latched), legacyBusy]);
35
35
  let reason = grant.waitingReason ?? (!grant.enabled ? 'Enable chat using the current provider and limits.' : null);
36
36
  if (!reason && live && session) {
37
- const price = priceForConfig(live.config), minimum = isDirectChatProvider(live.config.provider) && price ? mateWorstCaseForPrice(price, 0, { steps: 1 }) : 0;
37
+ const price = priceForConfig(live.config), minimum = isDirectChatProvider(live.config.provider) && price ? leadWorstCaseForPrice(price, 0, { steps: 1 }) : 0;
38
38
  if (latched)
39
39
  reason = 'A previous chat cost is unconfirmed. Open Settings to inspect it.';
40
40
  else if (used >= live.config.dailyTurns)
@@ -58,7 +58,7 @@ export function createTeamRuntime(options) {
58
58
  const previous = pages.get(key);
59
59
  if (revision && previous?.revision === revision && previous.expires > now.getTime())
60
60
  return previous.page;
61
- const page = workIndexPage(store, now, { principal: 'operator', repos: selected.projects }, { leadId: selected.leadId, limit: 40 });
61
+ const page = workIndexPage(store, now, { principal: 'operator', repos: selected.projects }, { leadId: selected.leadId, limit: 40, root: options.evidenceRoot });
62
62
  if (revision && options.workspaceRevision) {
63
63
  if (pages.size >= 64)
64
64
  pages.delete(pages.keys().next().value);
@@ -72,7 +72,7 @@ export function createTeamRuntime(options) {
72
72
  const waitingReason = selected && view.canSend && grant.enabled ? admission(actor, selected.id).reason : null;
73
73
  return { ...view, chatAuthorization: { ...grant, ...(waitingReason ? { waitingReason } : {}) },
74
74
  ...(selected ? { tasks: tasksFor(selected),
75
- proposals: store.listMateProposals(selected.threadId).filter(p => p.state !== 'drafting').slice(-40).map(p => ({ id: p.id, turnId: p.turn, title: typeof p.payload['title'] === 'string' ? p.payload['title'].slice(0, 160) : `Review ${p.kind.replaceAll('_', ' ')}`, state: p.state, href: `/chat?conversation=${encodeURIComponent(selected.id)}&proposal=${p.id}` })) } : {}) };
75
+ proposals: store.listLeadProposals(selected.threadId).filter(p => p.state !== 'drafting').slice(-40).map(p => ({ id: p.id, turnId: p.turn, title: typeof p.payload['title'] === 'string' ? p.payload['title'].slice(0, 160) : `Review ${p.kind.replaceAll('_', ' ')}`, state: p.state, href: `/chat?conversation=${encodeURIComponent(selected.id)}&proposal=${p.id}` })) } : {}) };
76
76
  }
77
77
  function authorize(actor, args) {
78
78
  const id = typeof args['conversationId'] === 'string' ? args['conversationId'] : '';
@@ -113,7 +113,7 @@ export function createTeamRuntime(options) {
113
113
  domain.finish(claim, { status: 'cancelled', error: 'Automatic updates were paused.' }, clock());
114
114
  return;
115
115
  }
116
- const provider = options.provider(), session = store.teamMateSession(claim.actor.name, claim.threadId), thread = store.getMateThread(claim.threadId);
116
+ const provider = options.provider(), session = store.teamMateSession(claim.actor.name, claim.threadId), thread = store.getLeadThread(claim.threadId);
117
117
  const proof = verifyApproverStanding(store, claim.actor.name, claim.actor.generation, claim.projects);
118
118
  if (!provider || !session || !thread || !proof.ok || !grant?.enabled) {
119
119
  domain.finish(claim, { status: 'failed', error: grant?.waitingReason ?? 'Chat authorization changed. Enable chat before sending a new message.' }, clock());
@@ -122,14 +122,14 @@ export function createTeamRuntime(options) {
122
122
  const requestId = requestFor(claim);
123
123
  // A saved terminal provider receipt wins over delivery bookkeeping.
124
124
  // Neither failure nor an unconfirmed receipt causes another provider call.
125
- const receipt = store.mateRequestReceipt(session.id, requestId);
125
+ const receipt = store.leadRequestReceipt(session.id, requestId);
126
126
  if (receipt) {
127
- const turn = store.getMateTurn(receipt.turn);
127
+ const turn = store.getLeadTurn(receipt.turn);
128
128
  domain.finish(claim, { status: turn?.state === 'answered' ? 'answered' : turn?.state === 'failed' && !domain.deliveryUncertain(turn.id) ? 'failed' : 'uncertain',
129
129
  turnId: receipt.turn, ...(turn?.state === 'answered' ? {} : { error: 'Inspect the saved response; this message was not sent again.' }) }, clock());
130
130
  return;
131
131
  }
132
- const result = await runMateTurn({ store, who: proof.who, session, thread, ...provider, message: claim.text, requestId, channel: "console",
132
+ const result = await runLeadTurn({ store, who: proof.who, session, thread, ...provider, message: claim.text, requestId, channel: "console",
133
133
  ...(() => { const name = view.leads.find(lead => lead.id === claim.leadId)?.name; return name === undefined ? {} : { leadName: name }; })(),
134
134
  queuedMessageId: claim.messageId,
135
135
  onAdmitted: turn => { if (!domain.current(claim) || !domain.bindTurn(claim, turn))
@@ -1,13 +1,13 @@
1
1
  /** Microsoft Teams on the shared chat layer: activities in, Adaptive Cards
2
2
  * out, the same durable receipts, rooms and commands as every other channel. */
3
3
  import { resultShotsPruned } from "./result-shots.js";
4
- import { chatQuestionButtons } from "./teammate-question.js";
4
+ import { chatQuestionButtons } from "./subagent-question.js";
5
5
  import { chatAskButtons } from "./chat-ask.js";
6
6
  import { ChatDeliveryError, ChatState, chatHash, partContent } from "./chat-delivery-state.js";
7
7
  import { channelAccess, chatObject as object, planChatNotifications, planRoomMessages, processChatEvent, splitChatText } from "./chat-delivery.js";
8
8
  import { PLATFORM_LIMITS } from "./text-limits.js";
9
9
  import { roomCommand } from "./chat-rooms.js";
10
- import { MATE_MESSAGE_MAX_CHARS } from "./mate.js";
10
+ import { LEAD_MESSAGE_MAX_CHARS } from "./lead.js";
11
11
  import { renderReply } from "./reply-shape.js";
12
12
  import { armedCardText, armedYesLabel, proposalLink, proposalOutcomeText, proposalPreview } from "./chat-channel.js";
13
13
  import { chatFlowButtons } from "./chat-flow.js";
@@ -109,7 +109,7 @@ export function receiveTeams(state, identity, raw, serviceUrlClaim, now) {
109
109
  const text = isRoom ? withoutMentions(activity.text) : activity.text.trim();
110
110
  const pair = /^pair ([a-f0-9]{32})$/.exec(text);
111
111
  kind = pair ? "pair" : "message";
112
- payload = pair ? { hash: chatHash(pair[1]) } : { text: text.slice(0, MATE_MESSAGE_MAX_CHARS + 1), originalLength: text.length,
112
+ payload = pair ? { hash: chatHash(pair[1]) } : { text: text.slice(0, LEAD_MESSAGE_MAX_CHARS + 1), originalLength: text.length,
113
113
  ...(Array.isArray(activity.attachments) && activity.attachments.length > 0 ? { unsupported: "Incoming files are not supported yet. Describe the request in a message; saved result screenshots open from their links." } : {}) };
114
114
  eventId = chatHash(`${identity.installation}:message:${id}`);
115
115
  }
@@ -176,7 +176,7 @@ export async function deliverTeamsPart(options) {
176
176
  return true;
177
177
  }
178
178
  try {
179
- const session = event.session === null ? null : store.getMateSession(event.session);
179
+ const session = event.session === null ? null : store.getLeadSession(event.session);
180
180
  const repos = await channelAccess(shared, binding, session?.ceilingDigest);
181
181
  if (event.kind === "notice" && options.canNotify?.() === false)
182
182
  return false;
@@ -197,7 +197,7 @@ export async function deliverTeamsPart(options) {
197
197
  actions = openUrlAction(options.origin(), { label: "Open result", path: chatResultHref(content.image.taskId, content.image.run, "checks") });
198
198
  }
199
199
  if (content.proposal) {
200
- const proposal = store.getMateProposal(content.proposal);
200
+ const proposal = store.getLeadProposal(content.proposal);
201
201
  if (!proposal)
202
202
  text = "This proposal is unavailable.";
203
203
  else if (proposal.state !== "pending")
@@ -220,7 +220,7 @@ export async function deliverTeamsPart(options) {
220
220
  else if (!content.image) {
221
221
  // A flow decision (v88): Approve / Edit / Send back, then the link.
222
222
  const flow = content.flow || content.choose || content.note ? chatFlowButtons(state, row.id, now).map(one => ({ type: "Action.Submit", title: one.label, data: { so: one.token }, ...(one.action === "approve" ? { style: "positive" } : {}) })) : [];
223
- // A teammate's question (v93): its options, then "Answer in words".
223
+ // A subagent's question (v93): its options, then "Answer in words".
224
224
  const asked = content.question ? chatQuestionButtons(state, row.id, now).map(one => ({ type: "Action.Submit", title: one.label.slice(0, 80), data: { so: one.token } })) : [];
225
225
  // The lead's question to its owner: its options, then "Something else".
226
226
  const owner = content.ask ? chatAskButtons(state, row.id, now).map(one => ({ type: "Action.Submit", title: one.label.slice(0, 80), data: { so: one.token } })) : [];
@@ -1,6 +1,6 @@
1
1
  import { type FlowChoiceVisit, type FlowSendContent } from "./flow-send.js";
2
2
  import { type FlowStage } from "./flows.js";
3
- import { type InlineButton } from "./telegram-mate.js";
3
+ import { type InlineButton } from "./telegram-lead.js";
4
4
  import type { FlowCardRow, FlowRow, Store, TelegramBinding, TelegramFlowAction, TelegramFlowChoice, TelegramFlowPrompt } from "./store.js";
5
5
  /** A flow decision's notice: one per visit, and again (`:ask:<ms>`) when the owner asks the lead to approve it. */
6
6
  export declare const FLOW_DECIDE_KEY: RegExp;
@@ -14,8 +14,8 @@ type Waiting = {
14
14
  export declare function flowDecisionAt(store: Store, cardId: number, entry: number): Waiting | null;
15
15
  /**
16
16
  * A flow decision as Telegram shows it: the action and where each button takes the card first, then the draft (as
17
- * the card holds it now, so what is approved is what is read) and why a teammate handed it over, then the flow and
18
- * zone. `said` is the saved notice's body: the words before its instruction are the teammate's handoff.
17
+ * the card holds it now, so what is approved is what is read) and why a subagent handed it over, then the flow and
18
+ * zone. `said` is the saved notice's body: the words before its instruction are the subagent's handoff.
19
19
  */
20
20
  export declare function flowDecisionText(waiting: Waiting, said: string | null, head?: string): string;
21
21
  /** Buttons for one visit, minted before the send; `place` stamps the message they landed on. */
@@ -25,7 +25,7 @@ import { keptDraft } from "./flow-draft.js";
25
25
  import { decideFlowCard, draftFor, flowCardHref, flowDefinitionOf } from "./flow-engine.js";
26
26
  import { chooseFlowCard, flowChoiceAt, flowPersonOf, flowSendPaths, readFlowSend } from "./flow-send.js";
27
27
  import { deciderOf, FLOW_END, replyTarget } from "./flows.js";
28
- import { phoneLinkButton } from "./telegram-mate.js";
28
+ import { phoneLinkButton } from "./telegram-lead.js";
29
29
  import { telegramButton } from "./contracts/telegram-callback.js";
30
30
  /** A flow decision's notice: one per visit, and again (`:ask:<ms>`) when the owner asks the lead to approve it. */
31
31
  export const FLOW_DECIDE_KEY = /^flow-decide:([1-9][0-9]{0,14}):([1-9][0-9]{0,9})(?::ask:[0-9]{1,15})?$/;
@@ -49,8 +49,8 @@ function decisionTerms(waiting) {
49
49
  }
50
50
  /**
51
51
  * A flow decision as Telegram shows it: the action and where each button takes the card first, then the draft (as
52
- * the card holds it now, so what is approved is what is read) and why a teammate handed it over, then the flow and
53
- * zone. `said` is the saved notice's body: the words before its instruction are the teammate's handoff.
52
+ * the card holds it now, so what is approved is what is read) and why a subagent handed it over, then the flow and
53
+ * zone. `said` is the saved notice's body: the words before its instruction are the subagent's handoff.
54
54
  */
55
55
  export function flowDecisionText(waiting, said, head = `Approve “${waiting.card.title}”`) {
56
56
  const at = said === null ? -1 : said.indexOf(`${waiting.stage.title}: approve`);
@@ -1,7 +1,7 @@
1
1
  import { type PhoneLink } from "./chat-channel.js";
2
- export { channelRepos as telegramConversationRepos, resolveChannelMate as resolveTelegramMate, tooLongText, whichTaskText, replyContextFor, NO_PHONE_LINK, NO_TASK_LINK, proposalLink, confirmedLink, proposalPreview, proposalOutcomeText, handoffCardText, confirmedCardText, CHAT_ACTION_PARITY as TELEGRAM_ACTION_PARITY, parityGaps, type PhoneLink, type ResolvedMate, type ParitySupport } from "./chat-channel.js";
3
- import { type MateAsk, type Store, type TelegramBinding } from "./store.js";
4
- import type { SubscriptionMateRunner } from "./subscription-chat.js";
2
+ export { channelRepos as telegramConversationRepos, resolveChannelLead as resolveTelegramLead, tooLongText, whichTaskText, replyContextFor, NO_PHONE_LINK, NO_TASK_LINK, proposalLink, confirmedLink, proposalPreview, proposalOutcomeText, handoffCardText, confirmedCardText, CHAT_ACTION_PARITY as TELEGRAM_ACTION_PARITY, parityGaps, type PhoneLink, type ResolvedLead, type ParitySupport } from "./chat-channel.js";
3
+ import { type LeadAsk, type Store, type TelegramBinding } from "./store.js";
4
+ import type { SubscriptionLeadRunner } from "./subscription-chat.js";
5
5
  import type { TelegramTransport } from "./telegram.js";
6
6
  import { type TelegramCallbackButton, type TelegramUpdate } from "./contracts/telegram-callback.js";
7
7
  /** How long one claimed turn may go without a heartbeat before another poller may take it over. */
@@ -18,7 +18,7 @@ export type TelegramConversationOptions = {
18
18
  /** Where evidence lives — the same root the console and CLI read results from. */
19
19
  evidenceRoot: string;
20
20
  /** Injected by tests; production invokes the isolated local harness. */
21
- subscriptionRunner?: SubscriptionMateRunner;
21
+ subscriptionRunner?: SubscriptionLeadRunner;
22
22
  /**
23
23
  * The https origin a phone link may open, read again on EVERY call (a
24
24
  * card is linked immediately before it is sent or edited, never from a
@@ -71,7 +71,7 @@ export declare function phoneLinkButton(origin: string | null, link: PhoneLink |
71
71
  /** One inline button: an opaque callback token (a real in-chat act) or a url (navigation, never authority). */
72
72
  /** The buttons under the lead's question: options two to a row, then "Something else". Each names the turn and the
73
73
  * option's place, nothing more; a tap re-proves that the question is still open and is the tapper's own. */
74
- export declare function askKeyboard(ask: MateAsk): CallbackKeyboard;
74
+ export declare function askKeyboard(ask: LeadAsk): CallbackKeyboard;
75
75
  export type InlineButton = TelegramCallbackButton | {
76
76
  text: string;
77
77
  url: string;
@@ -1,11 +1,11 @@
1
1
  import { CHAT_ACTIONS, sharedActionAllowsChallenge, sharedActionPayload } from "./chat-actions.js";
2
- import { taskInCeiling, mirrorToTaskChat, couldNotAnswerText, channelRepos as telegramConversationRepos, resolveChannelMate as resolveTelegramMate, tooLongText, whichTaskText, replyContextFor, NO_PHONE_LINK, NO_TASK_LINK, proposalLink, confirmedLink, proposalPreview, proposalOutcomeText, handoffCardText, confirmedCardText } from "./chat-channel.js";
3
- export { channelRepos as telegramConversationRepos, resolveChannelMate as resolveTelegramMate, tooLongText, whichTaskText, replyContextFor, NO_PHONE_LINK, NO_TASK_LINK, proposalLink, confirmedLink, proposalPreview, proposalOutcomeText, handoffCardText, confirmedCardText, CHAT_ACTION_PARITY as TELEGRAM_ACTION_PARITY, parityGaps } from "./chat-channel.js";
2
+ import { taskInCeiling, mirrorToTaskChat, couldNotAnswerText, channelRepos as telegramConversationRepos, resolveChannelLead as resolveTelegramLead, tooLongText, whichTaskText, replyContextFor, NO_PHONE_LINK, NO_TASK_LINK, proposalLink, confirmedLink, proposalPreview, proposalOutcomeText, handoffCardText, confirmedCardText } from "./chat-channel.js";
3
+ export { channelRepos as telegramConversationRepos, resolveChannelLead as resolveTelegramLead, tooLongText, whichTaskText, replyContextFor, NO_PHONE_LINK, NO_TASK_LINK, proposalLink, confirmedLink, proposalPreview, proposalOutcomeText, handoffCardText, confirmedCardText, CHAT_ACTION_PARITY as TELEGRAM_ACTION_PARITY, parityGaps } from "./chat-channel.js";
4
4
  /**
5
5
  * The paired phone as one more way to use the same assistant.
6
6
  *
7
- * Transport only. An ordinary private Telegram message becomes a mate turn
8
- * through `runMateTurn` — the same saved thread, proposal rows, confirm
7
+ * Transport only. An ordinary private Telegram message becomes a lead turn
8
+ * through `runLeadTurn` — the same saved thread, proposal rows, confirm
9
9
  * doors and result actions the console and `toolroll chat` use —
10
10
  * and every act is still a card the operator confirms. This module owns
11
11
  * what is specific to the wire: proving the pairing (chat AND immutable
@@ -23,10 +23,10 @@ export { channelRepos as telegramConversationRepos, resolveChannelMate as resolv
23
23
  * the console and CLI state), and the door re-proves everything again.
24
24
  */
25
25
  import { createHash, randomBytes } from "node:crypto";
26
- import { MATE_MESSAGE_MAX_CHARS, mateFailureText, runMateTurn } from "./mate.js";
27
- import { confirmMateProposal, dismissMateProposal } from "./mate-doors.js";
26
+ import { LEAD_MESSAGE_MAX_CHARS, leadFailureText, runLeadTurn } from "./lead.js";
27
+ import { confirmLeadProposal, dismissLeadProposal } from "./lead-doors.js";
28
28
  import { ceilingDigestOf, verifyApproverStanding } from "./principal.js";
29
- import { MATE_ASK_OTHER } from "./store.js";
29
+ import { LEAD_ASK_OTHER } from "./store.js";
30
30
  import { phoneText } from "./telegram-status.js";
31
31
  import { shapeReplyParts, telegramReply } from "./reply-shape.js";
32
32
  import { WARM_EMOJI, warmTurn } from "./chat-warmth.js";
@@ -112,7 +112,7 @@ export function askKeyboard(ask) {
112
112
  const rows = [];
113
113
  for (let at = 0; at < ask.options.length; at += 2)
114
114
  rows.push(ask.options.slice(at, at + 2).map((label, offset) => telegramButton(label.slice(0, 60), `ask:${ask.turn}:${at + offset}`)));
115
- rows.push([telegramButton(MATE_ASK_OTHER, `ask:${ask.turn}:x`)]);
115
+ rows.push([telegramButton(LEAD_ASK_OTHER, `ask:${ask.turn}:x`)]);
116
116
  return rows;
117
117
  }
118
118
  /** Mint the card's tokens before the send, so a tap can never name a token that does not exist. */
@@ -151,7 +151,7 @@ export function applyProposalTap(store, binding, token, message, repos, options,
151
151
  ack("that button is stale — send /status to see what still waits");
152
152
  return { effects, confirmed: false, ignored: true };
153
153
  }
154
- const proposal = store.getMateProposal(action.proposal);
154
+ const proposal = store.getLeadProposal(action.proposal);
155
155
  if (proposal === null) {
156
156
  store.consumeTelegramProposalAction(token, now);
157
157
  ack("that card no longer exists");
@@ -209,10 +209,10 @@ export function applyProposalTap(store, binding, token, message, repos, options,
209
209
  ack("that button was already used");
210
210
  return { effects, confirmed: false, ignored: true };
211
211
  }
212
- const done = dismissMateProposal(store, who, proposal.id, now);
212
+ const done = dismissLeadProposal(store, who, proposal.id, now);
213
213
  store.consumeTelegramProposalActions(proposal.id, now);
214
214
  ack(done ? "dismissed" : "that proposal was already acted on");
215
- edit(done ? "Dismissed." : proposalOutcomeText(store.getMateProposal(proposal.id) ?? proposal));
215
+ edit(done ? "Dismissed." : proposalOutcomeText(store.getLeadProposal(proposal.id) ?? proposal));
216
216
  return { effects, confirmed: false, ignored: !done };
217
217
  }
218
218
  if (action.phase === "cancel") {
@@ -273,7 +273,7 @@ export function applyProposalTap(store, binding, token, message, repos, options,
273
273
  ack(action.phase === "yes" ? "that confirmation expired — start again from Confirm" : "that button was already used");
274
274
  return { effects, confirmed: false, ignored: true };
275
275
  }
276
- const outcome = confirmMateProposal(store, who, proposal.id, now, {
276
+ const outcome = confirmLeadProposal(store, who, proposal.id, now, {
277
277
  via: "telegram",
278
278
  evidenceRoot: options.evidenceRoot,
279
279
  confirm: action.phase === "yes",
@@ -405,7 +405,7 @@ async function runTelegramConversation(row, args) {
405
405
  const deliver = async (outcomeWord) => {
406
406
  // The row as it is NOW: the session was bound after this claim read it.
407
407
  const bound = store.getTelegramConversation(row.id)?.session ?? null;
408
- const session = bound === null ? null : store.getMateSession(bound);
408
+ const session = bound === null ? null : store.getLeadSession(bound);
409
409
  if (session === null) {
410
410
  await notify("This conversation was restarted before my reply went out, so it wasn't sent. Nothing was changed. Send your message again.");
411
411
  finish({ state: "failed", outcome: "unsent:no-session" });
@@ -421,7 +421,7 @@ async function runTelegramConversation(row, args) {
421
421
  continue;
422
422
  const now = clock();
423
423
  if (part.kind === "card") {
424
- const proposal = part.proposal === null ? null : store.getMateProposal(part.proposal);
424
+ const proposal = part.proposal === null ? null : store.getLeadProposal(part.proposal);
425
425
  if (proposal === null || proposal.state !== "pending") {
426
426
  // Confirmed or dismissed from the console or the terminal before the card went out: moot, not lost.
427
427
  if (!store.dropTelegramConversationPart(row.id, part.ordinal, owner, proposal === null ? "the proposal is gone" : `the proposal was already ${proposal.state}`, now))
@@ -523,7 +523,7 @@ async function runTelegramConversation(row, args) {
523
523
  // A card's link is minted NOW, from the persisted proposal and the
524
524
  // origin configured at this moment: a retry after the setting changed
525
525
  // or went away carries the current truth, never a stored URL.
526
- const proposal = part.kind === "card" && part.proposal !== null ? store.getMateProposal(part.proposal) : null;
526
+ const proposal = part.kind === "card" && part.proposal !== null ? store.getLeadProposal(part.proposal) : null;
527
527
  const origin = proposal === null ? null : options.phoneOrigin?.() ?? null;
528
528
  const wanted = proposal === null ? null : proposalLink(store, proposal, repos);
529
529
  const keyboard = keyboardWith(part.keyboard, phoneLinkButton(origin, wanted));
@@ -597,11 +597,11 @@ async function runTelegramConversation(row, args) {
597
597
  const now = clock();
598
598
  const parts = shapeReplyParts(reply, PART_CAP, { asked: row.text, appOrigin: options.phoneOrigin?.() ?? null }, shaped => telegramReply(shaped).text.length).map((text, index) => ({ kind: "reply", text, replyTo: index === 0 ? row.messageId : null }));
599
599
  // The lead's question to its owner: one tap per option, then "Something else".
600
- const ask = store.mateAsk(turn);
600
+ const ask = store.leadAsk(turn);
601
601
  if (ask !== null)
602
602
  parts.push({ kind: "reply", text: phoneText(ask.question, 1_000), keyboard: askKeyboard(ask) });
603
603
  // Only a turn that answered may have its selection sent: a failed or revoked turn's rows were deleted with its drafts, and the state is read again here.
604
- const selected = store.getMateTurn(turn)?.state === "answered" ? store.listMateTurnEvidence(turn) : [];
604
+ const selected = store.getLeadTurn(turn)?.state === "answered" ? store.listLeadTurnEvidence(turn) : [];
605
605
  for (const image of selected)
606
606
  parts.push({ kind: "image", text: image.caption, taskId: image.taskId, run: image.run, artifact: image.artifact, sha256: image.sha256 });
607
607
  for (const proposal of proposals) {
@@ -625,8 +625,8 @@ async function runTelegramConversation(row, args) {
625
625
  const recover = async (session, turnId, repos) => {
626
626
  // A turn that died past its deadline is swept to failed first, so a
627
627
  // restart reports the truth instead of waiting on a ghost.
628
- store.sweepStaleMateTurns(clock());
629
- const turn = store.getMateTurn(turnId);
628
+ store.sweepStaleLeadTurns(clock());
629
+ const turn = store.getLeadTurn(turnId);
630
630
  if (turn === null) {
631
631
  finish({ state: "failed", outcome: "replayed:missing" });
632
632
  report.refused++;
@@ -638,13 +638,13 @@ async function runTelegramConversation(row, args) {
638
638
  return;
639
639
  }
640
640
  if (turn.state !== "answered") {
641
- await notify(mateFailureText(turn.failureReason));
641
+ await notify(leadFailureText(turn.failureReason));
642
642
  finish({ state: "failed", outcome: `replayed:${turn.failureReason ?? "failed"}` });
643
643
  report.refused++;
644
644
  return;
645
645
  }
646
- const reply = store.listMateMessages(turn.thread, 200).find(one => one.turn === turn.id && one.role === "assistant")?.text ?? "(the reply text is no longer in the thread)";
647
- const proposals = store.listMateProposals(turn.thread, ["pending"]).filter(one => one.turn === turn.id);
646
+ const reply = store.listLeadMessages(turn.thread, 200).find(one => one.turn === turn.id && one.role === "assistant")?.text ?? "(the reply text is no longer in the thread)";
647
+ const proposals = store.listLeadProposals(turn.thread, ["pending"]).filter(one => one.turn === turn.id);
648
648
  if (!plan(session, turnId, reply, proposals, repos) && store.listTelegramConversationParts(row.id).length === 0)
649
649
  return;
650
650
  await sendOrHandOff("replayed");
@@ -676,14 +676,14 @@ async function runTelegramConversation(row, args) {
676
676
  // 4. A session bound before an earlier dispatch: the receipt lives there,
677
677
  // whatever session is live today.
678
678
  if (row.session !== null) {
679
- const receipt = store.mateRequestReceipt(row.session, row.request);
679
+ const receipt = store.leadRequestReceipt(row.session, row.request);
680
680
  if (receipt !== null) {
681
681
  await recover(row.session, receipt.turn, repos);
682
682
  return;
683
683
  }
684
684
  }
685
685
  // 5. The session and thread — shared with the console and the CLI.
686
- const resolved = resolveTelegramMate(store, binding, repos, clock());
686
+ const resolved = resolveTelegramLead(store, binding, repos, clock());
687
687
  if (!resolved.ok && resolved.reason === "busy") {
688
688
  await requeue("busy");
689
689
  return;
@@ -722,7 +722,7 @@ async function runTelegramConversation(row, args) {
722
722
  });
723
723
  let outcome;
724
724
  try {
725
- outcome = await runMateTurn({
725
+ outcome = await runLeadTurn({
726
726
  store, who, session, thread, config, key: null, message: row.text, requestId: row.request,
727
727
  ...(row.context === null ? {} : { context: row.context }),
728
728
  ...(options.subscriptionRunner === undefined ? {} : { subscriptionRunner: options.subscriptionRunner }),
@@ -733,7 +733,7 @@ async function runTelegramConversation(row, args) {
733
733
  clearInterval(heartbeat);
734
734
  warm.stop();
735
735
  }
736
- const receipt = store.mateRequestReceipt(session.id, row.request);
736
+ const receipt = store.leadRequestReceipt(session.id, row.request);
737
737
  if (receipt !== null)
738
738
  store.bindTelegramConversationTurn(row.id, owner, session.id, receipt.turn);
739
739
  if (!outcome.ok && "refused" in outcome) {
@@ -761,7 +761,7 @@ async function runTelegramConversation(row, args) {
761
761
  if (row.taskId !== null)
762
762
  mirrorToTaskChat(store, who, row.taskId, "Telegram", row.text, outcome.reply, clock());
763
763
  // 7. The reply and its cards: durable first, then sent.
764
- const proposals = store.listMateProposals(thread.id, ["pending"]).filter(one => one.turn === outcome.turn);
764
+ const proposals = store.listLeadProposals(thread.id, ["pending"]).filter(one => one.turn === outcome.turn);
765
765
  if (!plan(session.id, outcome.turn, outcome.reply, proposals, who.repos) && store.listTelegramConversationParts(row.id).length === 0)
766
766
  return;
767
767
  await sendOrHandOff("answered");
@@ -17,9 +17,16 @@ export declare const PHONE_HELP: string;
17
17
  /** The same display scrub for every phone-bound string a person or a model authored. */
18
18
  export declare const phoneText: typeof publicChatText;
19
19
  export declare function projectLabel(repo: string): string;
20
+ /** The phone's reading of saved results: `root` lets a finished result read its saved checks, requirements and
21
+ * review exactly as the console does. Without it, the dispatch diagnosis speaks. */
22
+ export type PhoneRead = {
23
+ root?: string | undefined;
24
+ now?: Date;
25
+ repos?: readonly string[];
26
+ };
20
27
  /** repos is the transport's explicit enrollment ceiling, never opened-project
21
28
  * history. This snapshot is intentionally bounded and advertises that bound. */
22
- export declare function phoneStatus(store: Store, repos: readonly string[], now: Date, focused?: string | null): string;
29
+ export declare function phoneStatus(store: Store, repos: readonly string[], now: Date, focused?: string | null, root?: string): string;
23
30
  /** One task a person can pick to talk about from a chat app. */
24
31
  export type PhoneTaskChoice = {
25
32
  id: string;
@@ -31,7 +38,7 @@ export type PhoneTaskChoice = {
31
38
  * then work in progress, waiting work and recent results; one entry per
32
39
  * task (its revisions share it), optionally narrowed by words in the id or
33
40
  * title. Bounded, within the chat's own project ceiling. */
34
- export declare function phoneTaskChoices(store: Store, repos: readonly string[], now: Date, query?: string | null, limit?: number): PhoneTaskChoice[];
41
+ export declare function phoneTaskChoices(store: Store, repos: readonly string[], now: Date, query?: string | null, limit?: number, root?: string): PhoneTaskChoice[];
35
42
  /** `id` keys the chat's choice (the task's first version, where its chat
36
43
  * lives); `view` is what to show: the exact version named, else the current one. */
37
44
  export type PhoneTaskPick = {
@@ -63,10 +70,10 @@ export type PhoneTaskLink = {
63
70
  };
64
71
  /** Where the rest lives when no button can say so: the closing line of an unlinked `/task`. */
65
72
  export declare const PHONE_CONSOLE_FOOTER = "Read-only status. Saved files and full actions are in the console.";
66
- export declare function phoneTask(store: Store, repos: readonly string[], id: string, now: Date): string;
73
+ export declare function phoneTask(store: Store, repos: readonly string[], id: string, now: Date, root?: string): string;
67
74
  /** One task's status for a chat app: the words, its one console link, and
68
75
  * the saved result it shows (a reply to it is about that result). */
69
- export declare function phoneTaskView(store: Store, repos: readonly string[], id: string, now: Date): {
76
+ export declare function phoneTaskView(store: Store, repos: readonly string[], id: string, now: Date, root?: string): {
70
77
  text: string;
71
78
  link: PhoneTaskLink | null;
72
79
  run: number | null;