toolroll 0.9.36 → 0.9.38

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 (175) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/dist/assignment-adapters.d.ts +12 -48
  3. package/dist/assignment-adapters.js +33 -43
  4. package/dist/browser/THIRD_PARTY_NOTICES.txt +3 -3
  5. package/dist/browser/workspace.css +1 -1
  6. package/dist/browser/workspace.js +26 -26
  7. package/dist/browser-crew.js +5 -0
  8. package/dist/browser-workspace.d.ts +2 -0
  9. package/dist/build-review.d.ts +1 -1
  10. package/dist/build-review.js +2 -1
  11. package/dist/chat-acceptance.d.ts +1 -1
  12. package/dist/chat-actions.d.ts +18 -12
  13. package/dist/chat-actions.js +15 -65
  14. package/dist/chat-ask.js +5 -5
  15. package/dist/chat-channel.js +20 -9
  16. package/dist/chat-decide.d.ts +18 -4
  17. package/dist/chat-decide.js +31 -11
  18. package/dist/chat-delivery-state.d.ts +12 -70
  19. package/dist/chat-delivery-state.js +17 -135
  20. package/dist/chat-delivery.js +58 -30
  21. package/dist/chat-flow.js +8 -6
  22. package/dist/chat-inbox.js +1 -1
  23. package/dist/coding-context.d.ts +2 -39
  24. package/dist/coding-context.js +2 -1
  25. package/dist/coding-handoff.d.ts +6 -25
  26. package/dist/coding-handoff.js +11 -5
  27. package/dist/coding-types.d.ts +2 -26
  28. package/dist/coding-workspace.d.ts +2 -0
  29. package/dist/coding-workspace.js +50 -14
  30. package/dist/contracts/acceptance-terms.d.ts +14 -0
  31. package/dist/contracts/acceptance-terms.js +16 -0
  32. package/dist/contracts/chat-actions.d.ts +328 -0
  33. package/dist/contracts/chat-actions.js +133 -0
  34. package/dist/contracts/chat-callback-rows.d.ts +59 -0
  35. package/dist/contracts/chat-callback-rows.js +46 -0
  36. package/dist/contracts/chat-content.d.ts +276 -0
  37. package/dist/contracts/chat-content.js +127 -0
  38. package/dist/contracts/chat-tables.d.ts +6 -0
  39. package/dist/contracts/chat-tables.js +136 -0
  40. package/dist/contracts/coding-context.d.ts +97 -0
  41. package/dist/contracts/coding-context.js +60 -0
  42. package/dist/contracts/coding-handoff.d.ts +44 -0
  43. package/dist/contracts/coding-handoff.js +47 -0
  44. package/dist/contracts/coding-workspace.d.ts +165 -0
  45. package/dist/contracts/coding-workspace.js +71 -0
  46. package/dist/contracts/contract.d.ts +7 -2
  47. package/dist/contracts/contract.js +21 -12
  48. package/dist/contracts/decision.d.ts +45 -0
  49. package/dist/contracts/decision.js +45 -0
  50. package/dist/contracts/discord-callback.d.ts +45 -0
  51. package/dist/contracts/discord-callback.js +34 -0
  52. package/dist/contracts/flow-card.d.ts +50 -0
  53. package/dist/contracts/flow-card.js +46 -0
  54. package/dist/contracts/flow-send.d.ts +119 -0
  55. package/dist/contracts/flow-send.js +112 -0
  56. package/dist/contracts/flow.d.ts +5 -0
  57. package/dist/contracts/flow.js +5 -0
  58. package/dist/contracts/gateway-tools.d.ts +374 -0
  59. package/dist/contracts/gateway-tools.js +94 -0
  60. package/dist/contracts/lead-context.d.ts +165 -0
  61. package/dist/contracts/lead-context.js +73 -0
  62. package/dist/contracts/lead-tools.d.ts +1905 -0
  63. package/dist/contracts/lead-tools.js +265 -0
  64. package/dist/contracts/memory-pass.d.ts +158 -0
  65. package/dist/contracts/memory-pass.js +102 -0
  66. package/dist/contracts/project-knowledge.d.ts +126 -0
  67. package/dist/contracts/project-knowledge.js +93 -0
  68. package/dist/contracts/project-memory.d.ts +117 -0
  69. package/dist/contracts/project-memory.js +51 -0
  70. package/dist/contracts/project-skills.d.ts +121 -0
  71. package/dist/contracts/project-skills.js +76 -0
  72. package/dist/contracts/review-findings.d.ts +46 -0
  73. package/dist/contracts/review-findings.js +48 -0
  74. package/dist/contracts/route.d.ts +484 -0
  75. package/dist/contracts/route.js +128 -0
  76. package/dist/contracts/scope.d.ts +105 -0
  77. package/dist/contracts/scope.js +114 -0
  78. package/dist/contracts/scout-report.d.ts +234 -0
  79. package/dist/contracts/scout-report.js +141 -0
  80. package/dist/contracts/slack-callback.d.ts +43 -0
  81. package/dist/contracts/slack-callback.js +35 -0
  82. package/dist/contracts/sort-answer.d.ts +83 -0
  83. package/dist/contracts/sort-answer.js +77 -0
  84. package/dist/contracts/stage-output.d.ts +127 -0
  85. package/dist/contracts/stage-output.js +166 -0
  86. package/dist/contracts/task-sizing.d.ts +24 -0
  87. package/dist/contracts/task-sizing.js +28 -0
  88. package/dist/contracts/teammate-turn.d.ts +43 -0
  89. package/dist/contracts/teammate-turn.js +51 -0
  90. package/dist/contracts/teams-callback.d.ts +14 -0
  91. package/dist/contracts/teams-callback.js +14 -0
  92. package/dist/contracts/telegram-callback.d.ts +120 -0
  93. package/dist/contracts/telegram-callback.js +97 -0
  94. package/dist/decision.d.ts +11 -20
  95. package/dist/decision.js +86 -167
  96. package/dist/desktop-update.d.ts +1 -1
  97. package/dist/discord-chat.js +31 -20
  98. package/dist/flow-engine.js +14 -17
  99. package/dist/flow-items.d.ts +4 -13
  100. package/dist/flow-items.js +1 -15
  101. package/dist/flow-send.d.ts +6 -37
  102. package/dist/flow-send.js +18 -14
  103. package/dist/flow-share.js +7 -1
  104. package/dist/flow-sort.d.ts +7 -26
  105. package/dist/flow-sort.js +25 -20
  106. package/dist/flow-steps.js +3 -2
  107. package/dist/flows.d.ts +2 -4
  108. package/dist/flows.js +9 -5
  109. package/dist/invoke.js +4 -4
  110. package/dist/lead-context.d.ts +4 -2
  111. package/dist/lead-context.js +24 -17
  112. package/dist/mate-progress.d.ts +26 -0
  113. package/dist/mate-progress.js +24 -0
  114. package/dist/mate-tools.d.ts +4 -0
  115. package/dist/mate-tools.js +108 -275
  116. package/dist/mate.js +11 -2
  117. package/dist/mcp-connect.d.ts +24 -4
  118. package/dist/mcp-connect.js +78 -15
  119. package/dist/mcp.js +84 -216
  120. package/dist/memory-pass.d.ts +17 -42
  121. package/dist/memory-pass.js +54 -26
  122. package/dist/phase-routing.d.ts +19 -72
  123. package/dist/phase-routing.js +34 -164
  124. package/dist/project-knowledge.d.ts +16 -35
  125. package/dist/project-knowledge.js +42 -21
  126. package/dist/project-memory.d.ts +2 -28
  127. package/dist/project-memory.js +28 -13
  128. package/dist/project-skills.d.ts +16 -38
  129. package/dist/project-skills.js +41 -32
  130. package/dist/project-tools.d.ts +1 -0
  131. package/dist/project-tools.js +4 -4
  132. package/dist/provider.d.ts +2 -0
  133. package/dist/provider.js +4 -16
  134. package/dist/result-shots.d.ts +1 -1
  135. package/dist/result-shots.js +2 -1
  136. package/dist/review-switch.d.ts +3 -8
  137. package/dist/reviewer.d.ts +8 -12
  138. package/dist/reviewer.js +11 -35
  139. package/dist/scope.d.ts +21 -43
  140. package/dist/scope.js +42 -128
  141. package/dist/scout-report.d.ts +9 -194
  142. package/dist/scout-report.js +113 -264
  143. package/dist/scout.js +15 -11
  144. package/dist/serve.js +23 -5
  145. package/dist/setup-guide.d.ts +2 -2
  146. package/dist/slack-chat.js +26 -20
  147. package/dist/slack-state.js +2 -1
  148. package/dist/store.d.ts +9 -32
  149. package/dist/store.js +18 -15
  150. package/dist/structured-output.js +2 -1
  151. package/dist/subscription-chat.d.ts +4 -0
  152. package/dist/subscription-chat.js +4 -1
  153. package/dist/task-review-brief.d.ts +1 -7
  154. package/dist/task-review-brief.js +6 -6
  155. package/dist/task-sizing.d.ts +3 -27
  156. package/dist/task-sizing.js +11 -26
  157. package/dist/task-text.d.ts +0 -5
  158. package/dist/task-text.js +1 -3
  159. package/dist/teammate-question.js +7 -5
  160. package/dist/teammate-work.js +9 -5
  161. package/dist/teammates.d.ts +13 -64
  162. package/dist/teammates.js +32 -33
  163. package/dist/teams-chat.js +20 -11
  164. package/dist/telegram-flow.d.ts +9 -0
  165. package/dist/telegram-flow.js +44 -8
  166. package/dist/telegram-mate.d.ts +4 -14
  167. package/dist/telegram-mate.js +9 -8
  168. package/dist/telegram-team.d.ts +2 -19
  169. package/dist/telegram.d.ts +1 -1
  170. package/dist/telegram.js +94 -68
  171. package/dist/text-limits.d.ts +142 -1
  172. package/dist/text-limits.js +144 -1
  173. package/dist/tools-ui.d.ts +2 -1
  174. package/dist/tools-ui.js +7 -4
  175. package/package.json +17 -12
@@ -4,6 +4,7 @@ import { type DoorOptions } from "./mate-doors.js";
4
4
  import { type MateAsk, type Store, type TelegramBinding } from "./store.js";
5
5
  import type { SubscriptionMateRunner } from "./subscription-chat.js";
6
6
  import type { TelegramTransport } from "./telegram.js";
7
+ import { type TelegramCallbackButton, type TelegramUpdate } from "./contracts/telegram-callback.js";
7
8
  /** How long one claimed turn may go without a heartbeat before another poller may take it over. */
8
9
  export declare const CONVERSATION_CLAIM_MS: number;
9
10
  /** A busy engine (one turn at a time) defers a queued message this long, up to the age bound below. */
@@ -74,19 +75,13 @@ export declare function phoneLinkButton(origin: string | null, link: PhoneLink |
74
75
  /** The buttons under the lead's question: options two to a row, then "Something else". Each names the turn and the
75
76
  * option's place, nothing more; a tap re-proves that the question is still open and is the tapper's own. */
76
77
  export declare function askKeyboard(ask: MateAsk): CallbackKeyboard;
77
- export type InlineButton = {
78
- text: string;
79
- callback_data: string;
80
- } | {
78
+ export type InlineButton = TelegramCallbackButton | {
81
79
  text: string;
82
80
  url: string;
83
81
  };
84
82
  type Keyboard = InlineButton[][];
85
83
  /** What a part persists: callback tokens only. A url is minted at send time, never stored. */
86
- type CallbackKeyboard = {
87
- text: string;
88
- callback_data: string;
89
- }[][];
84
+ type CallbackKeyboard = TelegramCallbackButton[][];
90
85
  /** Mint the card's tokens before the send, so a tap can never name a token that does not exist. */
91
86
  export declare function mintCardTokens(store: Store, binding: TelegramBinding, proposal: number, now: Date, messageId?: string, chatId?: string): {
92
87
  keyboard: CallbackKeyboard;
@@ -111,12 +106,7 @@ export type CardEffect = {
111
106
  * ceiling; the door re-proves standing, ceiling, session and the
112
107
  * proposal's own terms again inside its transaction.
113
108
  */
114
- export declare function applyProposalTap(store: Store, binding: TelegramBinding, token: string, message: {
115
- message_id: number;
116
- chat?: {
117
- id: number;
118
- };
119
- }, repos: readonly string[] | null, options: TelegramConversationOptions, now: Date): {
109
+ export declare function applyProposalTap(store: Store, binding: TelegramBinding, token: string, message: NonNullable<NonNullable<TelegramUpdate["callback_query"]>["message"]>, repos: readonly string[] | null, options: TelegramConversationOptions, now: Date): {
120
110
  effects: CardEffect[];
121
111
  confirmed: boolean;
122
112
  ignored: boolean;
@@ -33,6 +33,7 @@ import { WARM_EMOJI, warmTurn } from "./chat-warmth.js";
33
33
  import { TeamLeads } from "./team-leads.js";
34
34
  import { chatResultHref } from "./chat-controls.js";
35
35
  import { resultImageFileName, safeResultImageCaption, verifyResultImage } from "./chat-evidence.js";
36
+ import { telegramButton } from "./contracts/telegram-callback.js";
36
37
  /** How long one claimed turn may go without a heartbeat before another poller may take it over. */
37
38
  export const CONVERSATION_CLAIM_MS = 2 * 60_000;
38
39
  /** A busy engine (one turn at a time) defers a queued message this long, up to the age bound below. */
@@ -110,8 +111,8 @@ function keyboardWith(callbacks, link) {
110
111
  export function askKeyboard(ask) {
111
112
  const rows = [];
112
113
  for (let at = 0; at < ask.options.length; at += 2)
113
- rows.push(ask.options.slice(at, at + 2).map((label, offset) => ({ text: label.slice(0, 60), callback_data: `ask:${ask.turn}:${at + offset}` })));
114
- rows.push([{ text: MATE_ASK_OTHER, callback_data: `ask:${ask.turn}:x` }]);
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
116
  return rows;
116
117
  }
117
118
  /** Mint the card's tokens before the send, so a tap can never name a token that does not exist. */
@@ -121,7 +122,7 @@ export function mintCardTokens(store, binding, proposal, now, messageId, chatId
121
122
  for (const [token, phase] of [[confirm, "confirm"], [dismiss, "dismiss"]]) {
122
123
  store.createTelegramProposalAction({ token, binding: binding.id, proposal, phase, chatId, ttlMs: CARD_TTL_MS, ...(messageId === undefined ? {} : { messageId }) }, now);
123
124
  }
124
- return { keyboard: [[{ text: "Confirm", callback_data: confirm }, { text: "Dismiss", callback_data: dismiss }]], tokens: [confirm, dismiss] };
125
+ return { keyboard: [[telegramButton("Confirm", confirm), telegramButton("Dismiss", dismiss)]], tokens: [confirm, dismiss] };
125
126
  }
126
127
  /**
127
128
  * One tap on a proposal card, applied inside the update's own transaction
@@ -249,15 +250,15 @@ export function applyProposalTap(store, binding, token, message, repos, options,
249
250
  if (challenged) {
250
251
  ack("confirm it");
251
252
  edit(`${body}\n\nThis records that you handled this exact result. Confirm?`, [
252
- [{ text: `✓ Yes, ${CHAT_ACTIONS[sharedAction.operation].label.toLowerCase()}`, callback_data: yes }],
253
- [{ text: "Cancel", callback_data: cancel }],
253
+ [telegramButton(`✓ Yes, ${CHAT_ACTIONS[sharedAction.operation].label.toLowerCase()}`, yes)],
254
+ [telegramButton("Cancel", cancel)],
254
255
  ]);
255
256
  return;
256
257
  }
257
258
  ack("irreversible — confirm it");
258
- edit(`⚠ This answer is IRREVERSIBLE.\n\n${body}\n\nConfirm?`, [
259
- [{ text: "⚠ Yes, answer it", callback_data: yes }],
260
- [{ text: "Cancel", callback_data: cancel }],
259
+ edit(`${body}\n\n⚠ Last step: this answer can't be undone. Confirm?`, [
260
+ [telegramButton("⚠ Yes, answer it", yes)],
261
+ [telegramButton("Cancel", cancel)],
261
262
  ]);
262
263
  };
263
264
  if (action.phase === "confirm" && (irreversible || challenged)) {
@@ -9,6 +9,7 @@
9
9
  import type { Store } from "./store.js";
10
10
  import { type InlineButton } from "./telegram-mate.js";
11
11
  import type { TelegramTransport } from "./telegram.js";
12
+ import type { TelegramMessage } from "./contracts/telegram-callback.js";
12
13
  export type TeamCommand = {
13
14
  kind: "list";
14
15
  } | {
@@ -36,25 +37,7 @@ export type TeamInbound = {
36
37
  projects: readonly string[] | null;
37
38
  phoneOrigin: (() => string | null) | undefined;
38
39
  updateId: number;
39
- message: {
40
- message_id: number;
41
- text?: string;
42
- chat?: {
43
- id: number;
44
- type?: string;
45
- };
46
- from?: {
47
- id: number;
48
- };
49
- reply_to_message?: {
50
- message_id: number;
51
- };
52
- forward_origin?: unknown;
53
- forward_date?: unknown;
54
- via_bot?: unknown;
55
- sender_chat?: unknown;
56
- caption?: string;
57
- };
40
+ message: TelegramMessage;
58
41
  /** Queue one reply for after the update's transaction commits. */
59
42
  say: (chatId: string, text: string, keyboard?: InlineButton[][]) => void;
60
43
  };
@@ -37,7 +37,7 @@ export declare function telegramHookSecret(dir: string, make?: boolean): string
37
37
  export declare function telegramPushUrl(hooksBase: string | null): string | null;
38
38
  /** A push is Telegram's when its secret header matches ours (compared in constant time). */
39
39
  export declare function pushedByTelegram(header: unknown, secret: string | null): boolean;
40
- /** Keep one pushed update for the bridge: a JSON object with a positive integer update_id. */
40
+ /** Keep one pushed update for the bridge. A schema-invalid JSON update is logged and acknowledged, never retried. */
41
41
  export declare function keepPushedUpdate(store: Store, botId: string, body: Buffer, now: Date): {
42
42
  ok: true;
43
43
  kept: boolean;
package/dist/telegram.js CHANGED
@@ -34,7 +34,7 @@ import { enqueueEveningDigests, finishedView, isTaskFact, joinsBatch, needsPerso
34
34
  import { BATCH_MS, chatText, chatTitle, factLinkLabel, mentions, nameTelegramBot } from "./chat-voice.js";
35
35
  import { LEAD_SAY_KIND, enqueueLeadLapses, leadSayEarlier, leadSayText, leadSubjectOf } from "./lead-voice.js";
36
36
  import { applyTeamInbound, deliverTeamChats, teamCommand } from "./telegram-team.js";
37
- import { applyFlowChoiceTap, applyFlowReply, applyFlowTap, FLOW_DECIDE_KEY, flowButtons, flowChoiceButtons, flowDecisionAt, flowSendKeyboardRow, flowSentContent } from "./telegram-flow.js";
37
+ import { applyFlowChoiceTap, applyFlowReply, applyFlowTap, FLOW_DECIDE_KEY, flowButtons, flowChoiceButtons, flowChoiceHead, flowDecisionAt, flowDecisionText, flowSendKeyboardRow, flowSentContent } from "./telegram-flow.js";
38
38
  import { flowChoiceAt, flowSendTail, FLOW_CHOOSE_KEY, FLOW_SEND_KEY } from "./flow-send.js";
39
39
  import { cleanFlowMessage, fitFlowMessage } from "./flow-items.js";
40
40
  import { telegramReply } from "./reply-shape.js";
@@ -46,6 +46,7 @@ import { phoneCommand, phoneStatus, phoneTaskView, PHONE_CONSOLE_FOOTER, PHONE_H
46
46
  import { MATE_MESSAGE_MAX_CHARS } from "./mate.js";
47
47
  import { envValue } from "./names.js";
48
48
  import { applyProposalTap, processTelegramConversations, phoneLinkButton, replyContextFor, telegramConversationRepos, telegramRequestId, tooLongText, whichTaskText, } from "./telegram-mate.js";
49
+ import { askOf, parseTelegramUpdate, pickOf, readTelegramButtonData, readTelegramUpdate, telegramButton } from "./contracts/telegram-callback.js";
49
50
  /** The environment name — and therefore the name the builder strips from agents (both names; see names.ts). */
50
51
  export { TELEGRAM_TOKEN_ENV as TOKEN_ENV, TELEGRAM_TOKEN_ENVS as TOKEN_ENVS } from "./names.js";
51
52
  /** BotFather's shape: numeric bot id, colon, secret. */
@@ -99,19 +100,21 @@ export function pushedByTelegram(header, secret) {
99
100
  const given = Buffer.from(header), wanted = Buffer.from(secret);
100
101
  return given.length === wanted.length && timingSafeEqual(given, wanted);
101
102
  }
102
- /** Keep one pushed update for the bridge: a JSON object with a positive integer update_id. */
103
+ /** Keep one pushed update for the bridge. A schema-invalid JSON update is logged and acknowledged, never retried. */
103
104
  export function keepPushedUpdate(store, botId, body, now) {
104
- let update;
105
+ let raw;
105
106
  try {
106
- update = JSON.parse(body.toString("utf8"));
107
+ raw = JSON.parse(body.toString("utf8"));
107
108
  }
108
109
  catch {
109
110
  return { ok: false };
110
111
  }
111
- const id = update?.update_id;
112
- if (update === null || typeof update !== "object" || typeof id !== "number" || !Number.isSafeInteger(id) || id <= 0)
113
- return { ok: false };
114
- return { ok: true, kept: store.queueTelegramUpdate(botId, id, JSON.stringify(update), now) };
112
+ const update = readTelegramUpdate(raw);
113
+ if (!update.ok) {
114
+ console.warn(`Ignoring a pushed Telegram update: ${update.issues.map(issue => issue.line).join("; ")}`);
115
+ return { ok: true, kept: false };
116
+ }
117
+ return { ok: true, kept: store.queueTelegramUpdate(botId, update.value.update_id, JSON.stringify(raw), now) };
115
118
  }
116
119
  /**
117
120
  * Environment wins, the credential file beside the database otherwise.
@@ -623,7 +626,7 @@ function decideKeyboard(phoneOrigin, rows) {
623
626
  catch {
624
627
  origin = null;
625
628
  }
626
- return rows.map(row => row.flatMap((one) => "token" in one ? [{ text: one.label, callback_data: one.token }]
629
+ return rows.map(row => row.flatMap((one) => "token" in one ? [telegramButton(one.label, one.token)]
627
630
  : phoneLinkButton(origin, one.link) ?? [])).filter(row => row.length > 0);
628
631
  }
629
632
  /** What this card may act on in the chat now, for this person under their current ceiling; null keeps its link. */
@@ -631,7 +634,7 @@ function decideOfferFor(store, binding, target, projects, now, root) {
631
634
  if (target === null || target === undefined)
632
635
  return null;
633
636
  const principal = verifyApproverStanding(store, binding.approver, binding.approverGeneration, telegramConversationRepos(store, binding.approver, projects));
634
- return principal.ok ? decideOffer(store, target, principal.who, now, root) : null;
637
+ return principal.ok ? decideOffer(store, target, principal.who, now, root, "telegram") : null;
635
638
  }
636
639
  /** Team-conversation traffic to the chats that follow one — after the outbox, under the same delivery switch, never a problem to raise when nothing follows anything. */
637
640
  async function deliverTeam(store, botId, transport, clock, report, readProjects, canDeliver, phoneOrigin) {
@@ -1222,13 +1225,23 @@ async function deliverOne(store, botId, binding, sender, notification, clock, ph
1222
1225
  } })();
1223
1226
  if (sentRow !== null)
1224
1227
  button = sentRow.length === 0 ? null : sentRow;
1228
+ // A flow decision or choice for this person leads with what to do and where each button takes the card
1229
+ // (telegram-flow.ts); the flow and zone close it. The saved notice keeps its own words for every other place.
1230
+ const pull = sent === null ? [] : sent.links.flatMap(one => "url" in one ? [`${one.label}: ${one.url}`] : []).slice(0, 1);
1231
+ const choiceHead = choosing === null || sent === null ? null : flowChoiceHead(choosing, sent);
1232
+ const choiceTail = choosing === null ? [] : [...pull, `${choosing.flow.name}${sent?.from === undefined ? "" : ` · after ${sent.from}`}`];
1233
+ if (offer === null && flowKeys !== null && waiting !== null)
1234
+ parts = split(`${notificationIdentity(notification)}${chatText(flowDecisionText(waiting, notification.body), task)}`);
1235
+ else if (offer === null && choiceHead !== null && sent !== null)
1236
+ parts = split(`${notificationIdentity(notification)}${chatText([...choiceHead, "", sent.summary, "", ...choiceTail.flatMap((line, index) => index === 0 ? [line] : ["", line])].join("\n"), task)}`);
1225
1237
  // After research (flow-items.ts): each item numbered under the summary, its words cleaned as the rest of the message's
1226
1238
  // are, within Telegram's limit with every link kept — one message, unless head, links and tail alone overflow it.
1227
1239
  let entities = [];
1228
1240
  if (sent?.items !== undefined && sent.items.length > 0 && offer === null) {
1229
1241
  const subject = chatText(leadSubjectOf(store, notification, binding.approver), task);
1230
- const head = `${notificationIdentity(notification, title !== undefined && mentions(subject, title) ? undefined : title)}${subject}`;
1231
- const message = cleanFlowMessage({ head, summary: sent.summary, items: sent.items, tail: flowSendTail(sent) }, words => chatText(words, task));
1242
+ const head = choiceHead !== null ? `${notificationIdentity(notification)}${choiceHead.join("\n")}`
1243
+ : `${notificationIdentity(notification, title !== undefined && mentions(subject, title) ? undefined : title)}${subject}`;
1244
+ const message = cleanFlowMessage({ head, summary: sent.summary, items: sent.items, tail: choiceHead !== null ? choiceTail : flowSendTail(sent) }, words => chatText(words, task));
1232
1245
  const voiced = fitFlowMessage(message, TELEGRAM_TEXT_MAX, shaped => telegramReply(shaped).text.length).map(telegramReply);
1233
1246
  parts = voiced.map(one => one.text);
1234
1247
  entities = voiced.map(one => one.entities);
@@ -1265,24 +1278,11 @@ async function deliverOne(store, botId, binding, sender, notification, clock, ph
1265
1278
  return { ok: true, receipt: receiptFor(botId, binding.chatId, last) };
1266
1279
  }
1267
1280
  // A decision. Every safety-bearing word goes out before anything tappable
1268
- // exists: recap, question, and every option's consequence, split across as
1269
- // many plain messages as they need — a button whose warning was truncated
1270
- // away is a trap, so the keyboard rides the LAST part only, and only if
1271
- // every earlier part arrived.
1272
- const lines = [
1273
- `${notificationIdentity(notification)}Decision needed`,
1274
- "",
1275
- decision.recap,
1276
- "",
1277
- `Q: ${decision.question}`,
1278
- "",
1279
- ...decision.options.flatMap(option => [
1280
- `[${option.id}] ${option.label}${option.id === decision.recommendation ? " (recommended)" : ""}${option.reversible ? "" : " — IRREVERSIBLE"}`,
1281
- ` ${option.consequence}`,
1282
- ]),
1283
- ...(decision.deadline === null ? [] : ["", `deadline: ${decision.deadline}`]),
1284
- ];
1285
- const parts = split(lines.join("\n"));
1281
+ // exists: the question and every option's consequence first, then the
1282
+ // recap, split across as many plain messages as they need — a button whose
1283
+ // warning was truncated away is a trap, so the keyboard rides the LAST part
1284
+ // only, and only if every earlier part arrived.
1285
+ const parts = split([`${notificationIdentity(notification)}${decisionAsk(decision)}`, "", `Background: ${decision.recap}`].join("\n"));
1286
1286
  for (const part of parts.slice(0, -1)) {
1287
1287
  const sent = await sender(part);
1288
1288
  if (!sent.ok)
@@ -1311,10 +1311,7 @@ async function deliverOne(store, botId, binding, sender, notification, clock, ph
1311
1311
  }, clock());
1312
1312
  }
1313
1313
  const keyboard = tokens.map(({ option, token }) => [
1314
- {
1315
- text: `${option.label}${option.id === decision.recommendation ? " ✓" : ""}${option.reversible ? "" : " ⚠"}`,
1316
- callback_data: token,
1317
- },
1314
+ telegramButton(`${option.label}${option.id === decision.recommendation ? " ✓" : ""}${option.reversible ? "" : " ⚠"}`, token),
1318
1315
  ]);
1319
1316
  const last = parts[parts.length - 1];
1320
1317
  const sent = await sender(last, keyboard);
@@ -1433,12 +1430,22 @@ async function drainUpdates(store, botId, transport, owner, generation, cursor,
1433
1430
  report.problems.push(`getUpdates: ${said}`);
1434
1431
  return;
1435
1432
  }
1436
- const updates = answer.result ?? [];
1433
+ const updates = Array.isArray(answer.result) ? answer.result : [];
1437
1434
  if (updates.length === 0)
1438
1435
  return;
1439
- for (const update of updates) {
1440
- await processUpdate(context, update, owner, generation);
1441
- offset = update.update_id + 1;
1436
+ for (const raw of updates) {
1437
+ const update = readTelegramUpdate(raw);
1438
+ if (!update.ok) {
1439
+ // Telegram's own update in a shape this bridge doesn't read: said, and passed over so the next one is read.
1440
+ const id = raw?.update_id;
1441
+ report.problems.push(`an update Telegram sent couldn't be read: ${update.issues.map(issue => issue.line).join("; ")}`);
1442
+ if (typeof id !== "number" || !Number.isSafeInteger(id))
1443
+ return;
1444
+ offset = id + 1;
1445
+ continue;
1446
+ }
1447
+ await processUpdate(context, update.value, owner, generation);
1448
+ offset = update.value.update_id + 1;
1442
1449
  }
1443
1450
  }
1444
1451
  // The budget ran out with Telegram still holding pages: said, not hidden.
@@ -1473,15 +1480,11 @@ async function processUpdate(context, update, owner, generation) {
1473
1480
  async function drainInbox(context, owner, generation) {
1474
1481
  let applied = 0;
1475
1482
  for (const queued of context.store.telegramInbox(context.botId, PAGE_BUDGET * 100)) {
1476
- let update = null;
1477
- try {
1478
- update = JSON.parse(queued.payload);
1479
- }
1480
- catch {
1481
- update = null;
1482
- }
1483
- if (update !== null && update.update_id === queued.updateId)
1484
- await processUpdate(context, update, owner, generation);
1483
+ const update = parseTelegramUpdate(queued.payload);
1484
+ if (update.ok && update.value.update_id === queued.updateId)
1485
+ await processUpdate(context, update.value, owner, generation);
1486
+ else if (!update.ok)
1487
+ context.report.problems.push(`a pushed update couldn't be read: ${update.issues.map(issue => issue.line).join("; ")}`);
1485
1488
  context.store.dropTelegramInbox(queued.updateId);
1486
1489
  applied++;
1487
1490
  }
@@ -1843,10 +1846,10 @@ function pickKeyboard(store, choices, focused) {
1843
1846
  for (const one of choices) {
1844
1847
  const ref = store.lookupRef(one.id);
1845
1848
  if (ref !== null)
1846
- rows.push([{ text: `${one.title} · ${one.label}`.slice(0, 60), callback_data: `pick:${ref.id}` }]);
1849
+ rows.push([telegramButton(`${one.title} · ${one.label}`.slice(0, 60), `pick:${ref.id}`)]);
1847
1850
  }
1848
1851
  if (focused)
1849
- rows.push([{ text: "Back to the lead", callback_data: "pick:lead" }]);
1852
+ rows.push([telegramButton("Back to the lead", "pick:lead")]);
1850
1853
  return rows;
1851
1854
  }
1852
1855
  /** The title of the task this Telegram chat chose to talk about, if any. */
@@ -2106,23 +2109,30 @@ function applyCallback(context, update, effects) {
2106
2109
  report.ignored++;
2107
2110
  return;
2108
2111
  }
2112
+ // The paired person, in their chat: a button whose data isn't one Toolroll makes is answered with why, and does nothing.
2113
+ const data = readTelegramButtonData(callback.data);
2114
+ if (!data.ok) {
2115
+ report.ignored++;
2116
+ ack(`That button couldn't be read (${data.issues.map(issue => issue.line).join("; ")}). Nothing was done.`.slice(0, 190));
2117
+ return;
2118
+ }
2109
2119
  // A task picked from /tasks: this private chat now talks about it (or,
2110
2120
  // "Back to the lead", about everything again). The task is re-proved
2111
2121
  // against the chat's ceiling at the tap.
2112
- if (token.startsWith("pick:")) {
2122
+ const pick = pickOf(token);
2123
+ if (pick !== null) {
2113
2124
  if (tapChat !== binding.chatId) {
2114
2125
  report.ignored++;
2115
2126
  return;
2116
2127
  }
2117
- if (token === "pick:lead") {
2128
+ if (pick.ref === null) {
2118
2129
  store.setChatFocus("telegram", binding.id, null, clock());
2119
2130
  store.recordTelegramTaskMessage(binding, String(message.message_id), null, null, clock());
2120
2131
  ack("Back to the lead");
2121
2132
  editText(PHONE_BACK_TO_LEAD);
2122
2133
  return;
2123
2134
  }
2124
- const refId = /^pick:([1-9][0-9]{0,14})$/.exec(token)?.[1];
2125
- const taskId = refId === undefined ? null : store.externalIdFor(Number(refId));
2135
+ const taskId = store.externalIdFor(pick.ref);
2126
2136
  const repos = context.projects === null ? null : telegramConversationRepos(store, binding.approver, context.projects);
2127
2137
  if (taskId === null || repos === null || !taskInCeiling(store, taskId, repos)) {
2128
2138
  ack("That task isn't available here now.");
@@ -2136,7 +2146,7 @@ function applyCallback(context, update, effects) {
2136
2146
  const current = store.taskFamilyOf(taskId, repos, false)?.current.id ?? id;
2137
2147
  const view = phoneTaskView(store, repos, current, clock());
2138
2148
  store.recordTelegramTaskMessage(binding, String(message.message_id), current, view.run, clock());
2139
- editText(phoneFocusText(view.text), [[{ text: "Back to the lead", callback_data: "pick:lead" }]]);
2149
+ editText(phoneFocusText(view.text), [[telegramButton("Back to the lead", "pick:lead")]]);
2140
2150
  return;
2141
2151
  }
2142
2152
  // A teammate's question (v93): an option answers it; "Answer in words" asks for a reply.
@@ -2206,13 +2216,13 @@ function applyCallback(context, update, effects) {
2206
2216
  return;
2207
2217
  }
2208
2218
  // The lead's question to its owner: an option is sent as their next message; "Something else" asks them to type it.
2209
- const askTap = /^ask:([1-9][0-9]{0,14}):([0-3]|x)$/.exec(token);
2219
+ const askTap = askOf(token);
2210
2220
  if (askTap !== null) {
2211
2221
  if (tapChat !== binding.chatId || context.conversation === undefined) {
2212
2222
  report.ignored++;
2213
2223
  return;
2214
2224
  }
2215
- const turn = Number(askTap[1]);
2225
+ const turn = askTap.turn;
2216
2226
  const found = store.getMateTurn(turn)?.approver === binding.approver ? store.mateAskState(turn, clock()) : { state: "expired" };
2217
2227
  if (found.state !== "open") {
2218
2228
  const line = found.state === "answered" ? "That question was already answered." : "That question expired. Send your answer as a message.";
@@ -2225,11 +2235,11 @@ function applyCallback(context, update, effects) {
2225
2235
  ack("Your answer is on its way.");
2226
2236
  return;
2227
2237
  }
2228
- if (askTap[2] === "x") {
2238
+ if (askTap.option === "x") {
2229
2239
  ack("Type your answer as a message.");
2230
2240
  return;
2231
2241
  }
2232
- const option = ask.options[Number(askTap[2])];
2242
+ const option = ask.options[askTap.option];
2233
2243
  if (option === undefined) {
2234
2244
  ack("That option is no longer there.");
2235
2245
  return;
@@ -2369,9 +2379,9 @@ function applyCallback(context, update, effects) {
2369
2379
  store.createTelegramAction({ token: confirm, binding: binding.id, decision: decision.id, optionId: option.id, phase: "confirm", chatId: binding.chatId, messageId: placedOn, ttlMs: CONFIRM_TTL_MS, ...(digest === undefined ? {} : { noteDigest: digest }) }, clock());
2370
2380
  store.createTelegramAction({ token: cancel, binding: binding.id, decision: decision.id, optionId: option.id, phase: "cancel", chatId: binding.chatId, messageId: placedOn, ttlMs: CONFIRM_TTL_MS }, clock());
2371
2381
  ack("irreversible — confirm it");
2372
- editText(`⚠ ${option.label} is IRREVERSIBLE.\n${option.consequence}\n${draft === null ? "" : `\nWith your note:\n${draft.note.split("\n").map(line => `| ${line}`).join("\n")}\n`}\nConfirm?`, [
2373
- [{ text: `⚠ Yes, ${option.label}`, callback_data: confirm }],
2374
- [{ text: "Cancel", callback_data: cancel }],
2382
+ editText(armedDecisionText(option, draft?.note ?? null), [
2383
+ [telegramButton(`⚠ Yes, ${option.label}`, confirm)],
2384
+ [telegramButton("Cancel", cancel)],
2375
2385
  ]);
2376
2386
  return;
2377
2387
  }
@@ -2417,11 +2427,10 @@ function applyCallback(context, update, effects) {
2417
2427
  store.createTelegramAction({ token: choose, binding: binding.id, decision: decision.id, optionId: option.id, phase: "choose", chatId: binding.chatId, messageId: String(message.message_id) }, clock());
2418
2428
  }
2419
2429
  ack("cancelled");
2420
- editText(`Q: ${decision.question}`, fresh.map(({ option, token: choose }) => [
2421
- {
2422
- text: `${option.label}${option.id === decision.recommendation ? " ✓" : ""}${option.reversible ? "" : " ⚠"}`,
2423
- callback_data: choose,
2424
- },
2430
+ // The card again, within one message; a decision too long for one keeps the consequences its earlier parts show.
2431
+ const ask = decisionAsk(decision);
2432
+ editText(ask.length <= PART_CAP ? ask : `Decide: ${decision.question}`, fresh.map(({ option, token: choose }) => [
2433
+ telegramButton(`${option.label}${option.id === decision.recommendation ? " ✓" : ""}${option.reversible ? "" : " ⚠"}`, choose),
2425
2434
  ]));
2426
2435
  }
2427
2436
  function answerNow(context, decision, choice, binding, ack, editText) {
@@ -2450,10 +2459,27 @@ function answerNow(context, decision, choice, binding, ack, editText) {
2450
2459
  }
2451
2460
  ack(`could not answer: ${answered.reason}`);
2452
2461
  }
2462
+ /** A decision's card, action first: the question, then each option with what it does, then its deadline. */
2463
+ function decisionAsk(decision) {
2464
+ return [
2465
+ `Decide: ${decision.question}`,
2466
+ ...decision.options.map(option => `• ${option.label}${option.id === decision.recommendation ? " (recommended)" : ""}${option.reversible ? "" : " ⚠ can't be undone"}: ${option.consequence}`),
2467
+ ...(decision.deadline === null ? [] : [`Decide by ${decision.deadline.slice(0, 16).replace("T", " ")} UTC`]),
2468
+ ].join("\n");
2469
+ }
2470
+ /** The armed confirm of an option that can't be undone: what it is and what it does, then the note that travels. */
2471
+ function armedDecisionText(option, note) {
2472
+ return [
2473
+ `${option.label}? ⚠ This can't be undone.`,
2474
+ option.consequence,
2475
+ ...(note === null ? [] : ["", "With your note:", ...note.split("\n").map(line => `| ${line}`)]),
2476
+ ].join("\n");
2477
+ }
2453
2478
  function answeredText(store, decision) {
2479
+ const chosen = decision.options.find(one => one.id === decision.choice)?.label ?? decision.choice ?? "?";
2454
2480
  return [
2455
- `✓ ${taskOf(store, decision)} — answered: ${decision.choice ?? "?"}`,
2456
- `by ${decision.answeredBy ?? "?"} via ${decision.answeredVia ?? "?"}`,
2481
+ `✓ Answered: ${chosen}`,
2482
+ `${taskOf(store, decision)} · by ${decision.answeredBy ?? "?"} via ${decision.answeredVia ?? "?"}`,
2457
2483
  // Line-prefixed, never inline: a multiline note must not be able to
2458
2484
  // draw fake status lines (Codex free-text review, finding 7).
2459
2485
  ...(decision.note === null ? [] : ["with note:", ...decision.note.split("\n").map(line => `| ${line}`)]),
@@ -79,6 +79,18 @@ export declare const TEXT_LIMITS: {
79
79
  readonly flowScriptInline: 1600;
80
80
  /** A research report's summary, in UTF-8 bytes. */
81
81
  readonly reportSummary: 2500;
82
+ /** A scout's whole report file (src/contracts/scout-report.ts), in UTF-8 bytes. */
83
+ readonly reportPayloadBytes: number;
84
+ /** A report's document (markdown), in UTF-8 bytes. */
85
+ readonly reportDocumentBytes: number;
86
+ /** A report's title, and a follow-up's or an item's title, in UTF-8 bytes. */
87
+ readonly reportTitleBytes: 200;
88
+ /** Why an item fits, in UTF-8 bytes. */
89
+ readonly reportWhyBytes: 1000;
90
+ /** A web address a report cites, in UTF-8 bytes. */
91
+ readonly reportUrlBytes: 2000;
92
+ /** A screenshot's caption and its file name, in UTF-8 bytes. */
93
+ readonly reportCaptionBytes: 300;
82
94
  /** What one flow step passes on to the steps after it. */
83
95
  readonly stageOutput: 12000;
84
96
  /** The planner's whole handoff file, in UTF-8 bytes. */
@@ -91,6 +103,10 @@ export declare const TEXT_LIMITS: {
91
103
  readonly planItem: 600;
92
104
  /** One path a plan expects to touch. */
93
105
  readonly planTouch: 200;
106
+ /** How many paths a task's scope may say it touches (a plan says at most 32 of them). A count, not a length. */
107
+ readonly scopeTouches: 50;
108
+ /** Why a task was sized as it was, as its sealed route keeps it. */
109
+ readonly sizeReason: 300;
94
110
  /** A planner's amendment note: why the filed contract must change. */
95
111
  readonly planAmendment: 1000;
96
112
  /** An acceptance criterion's id, statement and advisory how, in UTF-8 bytes. */
@@ -106,6 +122,131 @@ export declare const TEXT_LIMITS: {
106
122
  /** One proof line — a criterion statement, evidence ref, check command or summary, changed path, caveat, or
107
123
  * screenshot path or caption — in UTF-8 bytes. */
108
124
  readonly proofLineBytes: 300;
125
+ /** The task sizing classifier's reason (src/contracts/task-sizing.ts). */
126
+ readonly sizingReason: 160;
127
+ /** A review comment's or build-review finding's path, and its note or failure scenario (src/contracts/review-findings.ts). */
128
+ readonly reviewPath: 300;
129
+ readonly reviewNote: 500;
130
+ /** A parked decision (src/contracts/decision.ts): its recap and question, an option's id, label and consequence, and who it is for. */
131
+ readonly decisionRecap: 2000;
132
+ readonly decisionQuestion: 2000;
133
+ readonly decisionOptionId: 40;
134
+ /** A decision's recommendation: an option's id, optionally followed by ": " and why. */
135
+ readonly decisionRecommendation: 500;
136
+ readonly decisionLabel: 120;
137
+ readonly decisionConsequence: 500;
138
+ readonly decisionAssignee: 120;
139
+ /** A teammate's turn (src/contracts/teammate-turn.ts): the answer it picks (and each option it offers), its question, its reason and what it remembers. */
140
+ readonly teammateAnswer: 60;
141
+ readonly teammateQuestion: 600;
142
+ readonly teammateReason: 400;
143
+ readonly teammateRemember: 300;
144
+ /** The lead's tools and the MCP gateway (src/contracts/lead-tools.ts, gateway-tools.ts): a task's id as a tool names
145
+ * it, a task's title and one path it touches, and a project's path as the gateway names it (an assignment brief or
146
+ * project context, and the rest of the gateway). */
147
+ readonly taskRef: 64;
148
+ readonly taskTitle: 200;
149
+ readonly taskTouch: 200;
150
+ readonly projectPath: 4096;
151
+ readonly gatewayRepo: 800;
152
+ /** A project-context query, a check-log search, a memory search, a task search and a changed file's path. */
153
+ readonly contextQuery: 1000;
154
+ readonly logSearch: 120;
155
+ readonly memorySearch: 300;
156
+ readonly taskSearch: 200;
157
+ readonly diffPath: 300;
158
+ /** A hold's or cancel's reason, an answer's rationale and option, a worker's name, an agent change's reason, a
159
+ * provider and model name, how a task's checks were asked for, and an ISO time a recap counts from. */
160
+ readonly proposalReason: 200;
161
+ readonly answerRationale: 400;
162
+ readonly answerOption: 64;
163
+ readonly workerName: 60;
164
+ readonly agentWhy: 400;
165
+ readonly agentProvider: 20;
166
+ readonly agentModel: 120;
167
+ readonly checksWords: 80;
168
+ readonly recapSince: 30;
169
+ /** A shared action's fields as the lead proposes them (propose_action). */
170
+ readonly actionSample: 800;
171
+ readonly actionContent: 12000;
172
+ readonly actionTitle: 120;
173
+ readonly actionName: 40;
174
+ readonly actionCommand: 400;
175
+ readonly actionUrl: 500;
176
+ readonly actionSecret: 64;
177
+ readonly actionAbout: 240;
178
+ /** A teammate change (propose_teammate): a soul section's title, a routine's schedule, a tool, an action, a limit's
179
+ * field, a name, a soul file, a note, a choice and text. */
180
+ readonly teammateSection: 60;
181
+ readonly teammateSchedule: 80;
182
+ readonly teammateTool: 40;
183
+ readonly teammateAction: 64;
184
+ readonly teammateName: 40;
185
+ readonly teammateSoul: 12000;
186
+ readonly teammateNote: 300;
187
+ readonly teammateChoice: 60;
188
+ readonly teammateText: 2000;
189
+ /** A project knowledge reference id, a promise and why it is released, what the lead remembers (its text, a
190
+ * decision's reason and source), a person's name, and a question to the owner with its options. */
191
+ readonly knowledgeReference: 20;
192
+ readonly promise: 240;
193
+ readonly rememberText: 240;
194
+ readonly rememberWhy: 2000;
195
+ readonly rememberSource: 200;
196
+ readonly personName: 80;
197
+ readonly ownerQuestion: 200;
198
+ readonly ownerOption: 40;
199
+ /** A gateway filing's idempotency key. */
200
+ readonly idempotencyKey: 64;
201
+ /** The lead's catch-up bundle (src/contracts/lead-context.ts), in UTF-8 bytes, and the lines it clips: an open promise,
202
+ * a confirmed correction, the end of changed instructions a correction shows, and what an open proposal is about. */
203
+ readonly leadContextBytes: 8000;
204
+ readonly leadPromise: 160;
205
+ readonly leadCorrection: 300;
206
+ readonly leadCorrectionInstructions: 240;
207
+ readonly leadProposalAbout: 120;
208
+ /** Project knowledge (src/contracts/project-knowledge.ts), in UTF-8 bytes: the instructions (also the memory pass's
209
+ * budget for them), a reference's title, path and text, the context a run gets, the decision lines in it, and the
210
+ * repository excerpts added when at least the minimum is left. */
211
+ readonly knowledgeInstructionsBytes: 4000;
212
+ readonly knowledgeTitleBytes: 120;
213
+ readonly knowledgePathBytes: 300;
214
+ readonly knowledgeReferenceBytes: 12000;
215
+ readonly knowledgeContextBytes: 24000;
216
+ readonly knowledgeDecisionsBytes: 1500;
217
+ readonly knowledgeRepositoryBytes: 6000;
218
+ readonly knowledgeRepositoryMinBytes: 3000;
219
+ /** A project decision (src/contracts/project-memory.ts), in UTF-8 bytes: the choice, why, who decided, where it came
220
+ * from, and why it was retired. */
221
+ readonly decisionClaimBytes: 240;
222
+ readonly decisionWhyBytes: 2000;
223
+ readonly decisionByBytes: 80;
224
+ readonly decisionSourceBytes: 200;
225
+ readonly decisionRetireBytes: 500;
226
+ /** A skill package (src/contracts/project-skills.ts): its name, description, requirements and source in UTF-8 bytes,
227
+ * a file's path, SKILL.md, one file, the package and the enabled selection in bytes; a test's sample request and
228
+ * feedback in characters and bytes. */
229
+ readonly skillNameBytes: 64;
230
+ readonly skillDescriptionBytes: 1024;
231
+ readonly skillRequirementsBytes: 500;
232
+ readonly skillSourceBytes: 500;
233
+ readonly skillPath: 240;
234
+ readonly skillBodyBytes: number;
235
+ readonly skillFileBytes: number;
236
+ readonly skillPackageBytes: number;
237
+ readonly skillSelectionBytes: number;
238
+ readonly skillSample: 800;
239
+ readonly skillSampleBytes: 4000;
240
+ readonly skillFeedback: 500;
241
+ readonly skillFeedbackBytes: 2000;
242
+ /** The memory pass (src/contracts/memory-pass.ts): one session's trace, and in its verdict an effect, a mistake and
243
+ * the instruction it proposes. */
244
+ readonly memoryTrace: 40000;
245
+ readonly memoryEffect: 300;
246
+ readonly memoryMistake: 300;
247
+ readonly memoryInstruction: 240;
248
+ /** A Telegram button's callback_data, in UTF-8 bytes: Telegram refuses a button with more. */
249
+ readonly telegramCallbackDataBytes: 64;
109
250
  };
110
251
  export type TextLimitKey = keyof typeof TEXT_LIMITS;
111
252
  /** One chat message on each platform. Longer text is split across messages, never cut. Teams: the text one Adaptive Card
@@ -118,7 +259,7 @@ export declare const PLATFORM_LIMITS: {
118
259
  };
119
260
  export type Platform = keyof typeof PLATFORM_LIMITS;
120
261
  /** The line a model reads before it writes a field with a limit. */
121
- export declare function limitRule(what: string, limit: number, unit?: "characters" | "bytes"): string;
262
+ export declare function limitRule(what: string, limit: number, unit?: "characters" | "bytes", shorten?: boolean): string;
122
263
  /** One field over its limit: what it is, its limit, and how long it came back. */
123
264
  export type Overrun = {
124
265
  field: string;