toolroll 0.9.19 → 0.9.21

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 (61) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/browser/workspace.css +1 -1
  3. package/dist/browser/workspace.js +22 -21
  4. package/dist/browser-workspace.d.ts +43 -1
  5. package/dist/chat-actions.d.ts +5 -0
  6. package/dist/chat-actions.js +57 -6
  7. package/dist/chat-delivery-state.d.ts +15 -0
  8. package/dist/chat-delivery-state.js +27 -2
  9. package/dist/chat-delivery.js +24 -4
  10. package/dist/chat-flow.d.ts +26 -3
  11. package/dist/chat-flow.js +217 -5
  12. package/dist/desktop-update.d.ts +1 -1
  13. package/dist/discord-chat.js +2 -2
  14. package/dist/evidence.d.ts +13 -1
  15. package/dist/evidence.js +10 -1
  16. package/dist/flow-engine.d.ts +7 -0
  17. package/dist/flow-engine.js +71 -27
  18. package/dist/flow-gallery-ui.d.ts +1 -1
  19. package/dist/flow-gallery-ui.js +8 -7
  20. package/dist/flow-gallery.d.ts +8 -0
  21. package/dist/flow-gallery.js +37 -3
  22. package/dist/flow-send.d.ts +80 -0
  23. package/dist/flow-send.js +249 -0
  24. package/dist/flow-share.js +7 -3
  25. package/dist/flow-steps.js +1 -1
  26. package/dist/flow-triggers.d.ts +7 -0
  27. package/dist/flow-triggers.js +17 -5
  28. package/dist/flows-cli.js +11 -3
  29. package/dist/flows-ui.js +22 -6
  30. package/dist/flows.d.ts +27 -1
  31. package/dist/flows.js +104 -18
  32. package/dist/invoke.d.ts +3 -0
  33. package/dist/invoke.js +1 -0
  34. package/dist/mate-tools.js +33 -10
  35. package/dist/operate.js +41 -0
  36. package/dist/plane-review.d.ts +15 -0
  37. package/dist/plane-review.js +62 -20
  38. package/dist/project-delete.js +4 -0
  39. package/dist/project-demo.d.ts +12 -0
  40. package/dist/project-demo.js +71 -0
  41. package/dist/provider.d.ts +14 -0
  42. package/dist/provider.js +8 -1
  43. package/dist/report-summary.d.ts +9 -0
  44. package/dist/report-summary.js +9 -0
  45. package/dist/result-shots.d.ts +2 -0
  46. package/dist/result-shots.js +24 -5
  47. package/dist/scout-net.d.ts +22 -0
  48. package/dist/scout-net.js +214 -0
  49. package/dist/scout-report.d.ts +160 -0
  50. package/dist/scout-report.js +171 -1
  51. package/dist/scout.d.ts +42 -0
  52. package/dist/scout.js +380 -69
  53. package/dist/serve.js +43 -5
  54. package/dist/slack-chat.js +3 -3
  55. package/dist/store.d.ts +53 -0
  56. package/dist/store.js +95 -4
  57. package/dist/teams-chat.js +1 -1
  58. package/dist/telegram-flow.d.ts +16 -2
  59. package/dist/telegram-flow.js +58 -0
  60. package/dist/telegram.js +46 -10
  61. package/package.json +1 -1
package/dist/telegram.js CHANGED
@@ -27,14 +27,15 @@ import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
27
27
  import { chmodSync, readFileSync, rmSync, writeFileSync } from "node:fs";
28
28
  import { join } from "node:path";
29
29
  import { validateNote } from "./decision.js";
30
- import { resultShotsFor } from "./result-shots.js";
30
+ import { isShotsKind, resultShotsFor } from "./result-shots.js";
31
31
  import { isLifecycleNotification, isTelegramProgressNotification, RESULT_SHOTS_KIND, TELEGRAM_HOLD_REASONS } from "./store.js";
32
32
  import { telegramProgressCard } from "./telegram-progress.js";
33
33
  import { enqueueEveningDigests, finishedView, isTaskFact, joinsBatch, needsPerson, quietCardView } from "./chat-quiet.js";
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 { applyFlowReply, applyFlowTap, FLOW_DECIDE_KEY, flowButtons, flowDecisionAt } from "./telegram-flow.js";
37
+ import { applyFlowChoiceTap, applyFlowReply, applyFlowTap, FLOW_DECIDE_KEY, flowButtons, flowChoiceButtons, flowDecisionAt, flowSendKeyboardRow, flowSentContent } from "./telegram-flow.js";
38
+ import { flowChoiceAt, FLOW_CHOOSE_KEY, FLOW_SEND_KEY } from "./flow-send.js";
38
39
  import { connectChannel, FLOW_WORDS, takeChannelMessage, watchedChannel } from "./chat-inbox.js";
39
40
  import { focusContextFor, taskInCeiling } from "./chat-channel.js";
40
41
  import { answersPrompt, applyDecideFeedback, applyDecideTap, decideFallbackLink, dropDecideTokens, hasLiveDecideTokens, linksFor, placeDecidePrompt, decideOffer, decideTargetOf, isDecideToken, mergedText, mintDecideButtons, offerFingerprint, openPromptFor, placeDecideTokens, recordChatMerge, retireDecideTokens } from "./chat-decide.js";
@@ -658,7 +659,7 @@ async function deliverOutbox(store, botId, transport, owner, clock, report, read
658
659
  // Routine progress facts are not a problem to fix: a first pairing
659
660
  // starts from now and settles them as history. Anything else pending
660
661
  // is named, once per pass, as before.
661
- if (store.listNotifications("pending").some(row => !isLifecycleNotification(row) && row.kind !== RESULT_SHOTS_KIND))
662
+ if (store.listNotifications("pending").some(row => !isLifecycleNotification(row) && !isShotsKind(row.kind)))
662
663
  report.problems.push("outbox rows are pending but no chat is paired — `toolroll bridge telegram pair`");
663
664
  return;
664
665
  }
@@ -1058,7 +1059,7 @@ async function deliverOutboxTo(store, botId, binding, transport, owner, clock, r
1058
1059
  return sent.ok ? { ok: true, receipt: receiptFor(botId, binding.chatId, sent.messageId) } : sent;
1059
1060
  };
1060
1061
  let outcome;
1061
- if (rows.length === 1 && row.kind === RESULT_SHOTS_KIND)
1062
+ if (rows.length === 1 && isShotsKind(row.kind))
1062
1063
  outcome = await shotsSender(row);
1063
1064
  else if (rows.length === 1 && row.kind === LEAD_SAY_KIND)
1064
1065
  outcome = await leadSayOutcome(row);
@@ -1107,7 +1108,7 @@ function isUrgent(notification) {
1107
1108
  /** A routine fact the digest may carry. Screenshots are never urgent (they follow their result message, which the
1108
1109
  * task's order fence holds them behind) and never a digest line: they go as their own photos once it has gone. */
1109
1110
  function joinsDigest(notification) {
1110
- return !isUrgent(notification) && notification.kind !== RESULT_SHOTS_KIND;
1111
+ return !isUrgent(notification) && !isShotsKind(notification.kind);
1111
1112
  }
1112
1113
  /** The digest text: a header with the count and the window, then one
1113
1114
  * fact per entry — its subject, then its body's first line, indented.
@@ -1193,7 +1194,7 @@ async function deliverOne(store, botId, binding, sender, notification, clock, ph
1193
1194
  const decided = offer === null ? null : mintDecideButtons(store, decideSeat(binding), target, offer, clock());
1194
1195
  const parts = split(offer?.text ?? `${notificationIdentity(notification, title !== undefined && mentions(words, title) ? undefined : title)}${words}`);
1195
1196
  const fallback = offer === null ? decideFallbackLink(target) : null;
1196
- const button = fallback !== null ? (() => { try {
1197
+ let button = fallback !== null ? (() => { try {
1197
1198
  return phoneLinkButton(phoneOrigin?.() ?? null, fallback);
1198
1199
  }
1199
1200
  catch {
@@ -1204,10 +1205,23 @@ async function deliverOne(store, botId, binding, sender, notification, clock, ph
1204
1205
  const visit = FLOW_DECIDE_KEY.exec(notification.dedupeKey);
1205
1206
  const waiting = visit === null ? null : flowDecisionAt(store, Number(visit[1]), Number(visit[2]));
1206
1207
  const flowKeys = waiting === null ? null : flowButtons(store, binding, waiting, clock());
1208
+ // A flow's "Send to me" or "Person chooses" (flow-send.ts): its links as buttons, and a choice's options above them.
1209
+ const sentVisit = FLOW_SEND_KEY.exec(notification.dedupeKey) ?? FLOW_CHOOSE_KEY.exec(notification.dedupeKey);
1210
+ const sent = sentVisit === null ? null : flowSentContent(store, Number(sentVisit[1]), Number(sentVisit[2]));
1211
+ const choosing = sent === null || !FLOW_CHOOSE_KEY.test(notification.dedupeKey) || notification.recipient !== binding.approver ? null : flowChoiceAt(store, Number(sentVisit[1]), Number(sentVisit[2]));
1212
+ const choiceKeys = choosing === null || sent === null ? null : flowChoiceButtons(store, binding, choosing, sent, clock());
1213
+ const sentRow = sent === null ? null : (() => { try {
1214
+ return flowSendKeyboardRow(phoneOrigin?.() ?? null, sent);
1215
+ }
1216
+ catch {
1217
+ return [];
1218
+ } })();
1219
+ if (sentRow !== null)
1220
+ button = sentRow.length === 0 ? null : sentRow;
1207
1221
  // A teammate's question (v93): its options and "Answer in words", for the person it asks.
1208
- const asked = flowKeys === null ? openQuestionOf(store, notification.dedupeKey) : null;
1222
+ const asked = flowKeys === null && choiceKeys === null ? openQuestionOf(store, notification.dedupeKey) : null;
1209
1223
  const questionKeys = asked === null ? null : telegramQuestionButtons(store, binding, asked, clock());
1210
- const keys = flowKeys ?? questionKeys;
1224
+ const keys = flowKeys ?? choiceKeys ?? questionKeys;
1211
1225
  let last = null;
1212
1226
  for (const [index, part] of parts.entries()) {
1213
1227
  const final = index === parts.length - 1;
@@ -1223,6 +1237,12 @@ async function deliverOne(store, botId, binding, sender, notification, clock, ph
1223
1237
  }
1224
1238
  if (flowKeys !== null && last !== null)
1225
1239
  store.placeTelegramFlowActions(flowKeys.tokens, last);
1240
+ if (choiceKeys !== null && last !== null) {
1241
+ store.placeTelegramFlowChoices(choiceKeys.tokens, last);
1242
+ // A reply to this message, instead of a tap, is the person's note (applyFlowReply).
1243
+ if (sent?.reply === true)
1244
+ store.recordTelegramFlowPrompt({ chatId: binding.chatId, messageId: last, binding: binding.id, card: choosing.card.id, entry: choosing.card.entry, mode: "send-back" }, clock(), 7 * 24);
1245
+ }
1226
1246
  if (questionKeys !== null && last !== null)
1227
1247
  store.placeTelegramQuestionActions(questionKeys.tokens, last);
1228
1248
  if (decided !== null && last !== null)
@@ -1472,7 +1492,7 @@ async function enrolledForMessage(context, update) {
1472
1492
  async function projectsForTap(context, update) {
1473
1493
  const callback = update.callback_query;
1474
1494
  // Flow decision buttons (v86) work with or without the lead's conversation on this phone.
1475
- const flowTap = callback !== undefined && context.store.getTelegramFlowAction(callback.data ?? "") !== null;
1495
+ const flowTap = callback !== undefined && (context.store.getTelegramFlowAction(callback.data ?? "") !== null || context.store.getTelegramFlowChoice(callback.data ?? "") !== null);
1476
1496
  // So do a result's, plan's or pull request's own buttons (chat-decide.ts).
1477
1497
  const decideTap = callback !== undefined && isDecideToken(context.store, callback.data ?? "");
1478
1498
  if (callback === undefined || context.readProjects === undefined || (context.conversation === undefined && !flowTap && !decideTap))
@@ -1481,7 +1501,7 @@ async function projectsForTap(context, update) {
1481
1501
  if (binding === null || callback.from === undefined ||
1482
1502
  callback.message?.chat === undefined ||
1483
1503
  // Proposal cards and task picks read the chat's project ceiling.
1484
- (context.store.getTelegramProposalAction(callback.data ?? "") === null && context.store.getTelegramFlowAction(callback.data ?? "") === null && !decideTap && !(callback.data ?? "").startsWith("pick:")))
1504
+ (context.store.getTelegramProposalAction(callback.data ?? "") === null && !flowTap && !decideTap && !(callback.data ?? "").startsWith("pick:")))
1485
1505
  return null;
1486
1506
  const chat = callback.message.chat;
1487
1507
  const chatId = String(chat.id);
@@ -2138,6 +2158,22 @@ function applyCallback(context, update, effects) {
2138
2158
  }
2139
2159
  return;
2140
2160
  }
2161
+ // A "Person chooses" option (flow-send.ts): the card goes where it leads, or is ignored.
2162
+ const choiceAction = store.getTelegramFlowChoice(token);
2163
+ if (choiceAction !== null) {
2164
+ if (choiceAction.binding !== binding.id || choiceAction.chatId !== tapChat || (choiceAction.messageId !== null && choiceAction.messageId !== String(message.message_id))) {
2165
+ report.ignored++;
2166
+ return;
2167
+ }
2168
+ const repos = context.projects === null ? null : telegramConversationRepos(store, binding.approver, context.projects);
2169
+ for (const effect of applyFlowChoiceTap(store, binding, choiceAction, { text: message.text ?? "" }, repos, clock())) {
2170
+ if (effect.kind === "ack")
2171
+ ack(effect.text);
2172
+ else if (effect.kind === "edit")
2173
+ editText(effect.text);
2174
+ }
2175
+ return;
2176
+ }
2141
2177
  // The lead's question to its owner: an option is sent as their next message; "Something else" asks them to type it.
2142
2178
  const askTap = /^ask:([1-9][0-9]{0,14}):([0-3]|x)$/.exec(token);
2143
2179
  if (askTap !== null) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolroll",
3
- "version": "0.9.19",
3
+ "version": "0.9.21",
4
4
  "description": "A control plane for unattended coding agents — queue tasks, walk away, come back to pull requests. Agents build in leased worktrees, never touch your default branch, and interrupt you only for decisions that need a human.",
5
5
  "directories": {
6
6
  "doc": "docs"