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.
- package/CHANGELOG.md +24 -0
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +22 -21
- package/dist/browser-workspace.d.ts +43 -1
- package/dist/chat-actions.d.ts +5 -0
- package/dist/chat-actions.js +57 -6
- package/dist/chat-delivery-state.d.ts +15 -0
- package/dist/chat-delivery-state.js +27 -2
- package/dist/chat-delivery.js +24 -4
- package/dist/chat-flow.d.ts +26 -3
- package/dist/chat-flow.js +217 -5
- package/dist/desktop-update.d.ts +1 -1
- package/dist/discord-chat.js +2 -2
- package/dist/evidence.d.ts +13 -1
- package/dist/evidence.js +10 -1
- package/dist/flow-engine.d.ts +7 -0
- package/dist/flow-engine.js +71 -27
- package/dist/flow-gallery-ui.d.ts +1 -1
- package/dist/flow-gallery-ui.js +8 -7
- package/dist/flow-gallery.d.ts +8 -0
- package/dist/flow-gallery.js +37 -3
- package/dist/flow-send.d.ts +80 -0
- package/dist/flow-send.js +249 -0
- package/dist/flow-share.js +7 -3
- package/dist/flow-steps.js +1 -1
- package/dist/flow-triggers.d.ts +7 -0
- package/dist/flow-triggers.js +17 -5
- package/dist/flows-cli.js +11 -3
- package/dist/flows-ui.js +22 -6
- package/dist/flows.d.ts +27 -1
- package/dist/flows.js +104 -18
- package/dist/invoke.d.ts +3 -0
- package/dist/invoke.js +1 -0
- package/dist/mate-tools.js +33 -10
- package/dist/operate.js +41 -0
- package/dist/plane-review.d.ts +15 -0
- package/dist/plane-review.js +62 -20
- package/dist/project-delete.js +4 -0
- package/dist/project-demo.d.ts +12 -0
- package/dist/project-demo.js +71 -0
- package/dist/provider.d.ts +14 -0
- package/dist/provider.js +8 -1
- package/dist/report-summary.d.ts +9 -0
- package/dist/report-summary.js +9 -0
- package/dist/result-shots.d.ts +2 -0
- package/dist/result-shots.js +24 -5
- package/dist/scout-net.d.ts +22 -0
- package/dist/scout-net.js +214 -0
- package/dist/scout-report.d.ts +160 -0
- package/dist/scout-report.js +171 -1
- package/dist/scout.d.ts +42 -0
- package/dist/scout.js +380 -69
- package/dist/serve.js +43 -5
- package/dist/slack-chat.js +3 -3
- package/dist/store.d.ts +53 -0
- package/dist/store.js +95 -4
- package/dist/teams-chat.js +1 -1
- package/dist/telegram-flow.d.ts +16 -2
- package/dist/telegram-flow.js +58 -0
- package/dist/telegram.js +46 -10
- 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
|
|
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
|
|
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
|
|
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
|
-
|
|
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 &&
|
|
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.
|
|
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"
|