toolroll 0.9.16 → 0.9.17
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 +17 -0
- package/dist/assignment-brief.js +11 -4
- package/dist/chat-actions.d.ts +7 -0
- package/dist/chat-actions.js +40 -4
- package/dist/chat-ask.js +29 -15
- package/dist/chat-channel.js +3 -1
- package/dist/chat-delivery-state.d.ts +2 -0
- package/dist/chat-delivery.d.ts +3 -0
- package/dist/chat-delivery.js +31 -16
- package/dist/chat-warmth.d.ts +21 -0
- package/dist/chat-warmth.js +36 -0
- package/dist/discord-api.d.ts +1 -1
- package/dist/discord-api.js +1 -1
- package/dist/discord-chat.d.ts +3 -2
- package/dist/discord-chat.js +18 -3
- package/dist/lead-about.d.ts +35 -0
- package/dist/lead-about.js +62 -0
- package/dist/lead-capabilities.d.ts +64 -0
- package/dist/lead-capabilities.js +199 -0
- package/dist/lead-commitments.js +13 -7
- package/dist/lead-context.d.ts +5 -3
- package/dist/lead-context.js +29 -7
- package/dist/lead-follow.d.ts +1 -1
- package/dist/lead-follow.js +3 -2
- package/dist/lead-identity.js +1 -1
- package/dist/lead-people.d.ts +55 -0
- package/dist/lead-people.js +128 -0
- package/dist/lead-voice.d.ts +3 -2
- package/dist/lead-voice.js +4 -3
- package/dist/mate-cli.d.ts +3 -0
- package/dist/mate-cli.js +15 -14
- package/dist/mate-contract.d.ts +1 -1
- package/dist/mate-contract.js +6 -4
- package/dist/mate-progress.js +2 -0
- package/dist/mate-tools.d.ts +8 -0
- package/dist/mate-tools.js +112 -6
- package/dist/mate.d.ts +26 -1
- package/dist/mate.js +89 -25
- package/dist/operate.js +2 -0
- package/dist/phase-routing.d.ts +1 -1
- package/dist/provider-auth.d.ts +4 -0
- package/dist/provider-auth.js +1 -1
- package/dist/reply-shape.d.ts +96 -0
- package/dist/reply-shape.js +452 -0
- package/dist/serve.js +53 -28
- package/dist/slack-chat.d.ts +2 -0
- package/dist/slack-chat.js +20 -3
- package/dist/store.d.ts +12 -3
- package/dist/store.js +44 -9
- package/dist/team-runtime.js +1 -1
- package/dist/teams-chat.js +12 -1
- package/dist/telegram-mate.d.ts +2 -0
- package/dist/telegram-mate.js +27 -11
- package/dist/telegram-team.js +6 -3
- package/dist/telegram.js +1 -1
- package/dist/test-temp.js +1 -1
- package/package.json +1 -1
package/dist/telegram-mate.js
CHANGED
|
@@ -23,11 +23,13 @@ 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, runMateTurn } from "./mate.js";
|
|
26
|
+
import { MATE_MESSAGE_MAX_CHARS, mateFailureText, runMateTurn } from "./mate.js";
|
|
27
27
|
import { confirmMateProposal, dismissMateProposal } from "./mate-doors.js";
|
|
28
28
|
import { ceilingDigestOf, verifyApproverStanding } from "./principal.js";
|
|
29
29
|
import { MATE_ASK_OTHER } from "./store.js";
|
|
30
30
|
import { phoneText } from "./telegram-status.js";
|
|
31
|
+
import { shapeReplyParts, telegramReply } from "./reply-shape.js";
|
|
32
|
+
import { WARM_EMOJI, warmTurn } from "./chat-warmth.js";
|
|
31
33
|
import { TeamLeads } from "./team-leads.js";
|
|
32
34
|
import { chatResultHref } from "./chat-controls.js";
|
|
33
35
|
import { resultImageFileName, safeResultImageCaption, verifyResultImage } from "./chat-evidence.js";
|
|
@@ -40,7 +42,7 @@ export const CONVERSATION_MAX_AGE_MS = 10 * 60_000;
|
|
|
40
42
|
export const CARD_TTL_MS = 24 * 3_600_000;
|
|
41
43
|
export const CHALLENGE_TTL_MS = 10 * 60_000;
|
|
42
44
|
/** Telegram's own message ceiling, with room for our part headers (the same bound telegram.ts splits at). */
|
|
43
|
-
const PART_CAP = 3_900;
|
|
45
|
+
export const PART_CAP = 3_900;
|
|
44
46
|
// ---- the ceiling and the principal --------------------------------------------
|
|
45
47
|
/**
|
|
46
48
|
* The phone's project ceiling: the enrolled registry, canonicalized and
|
|
@@ -405,7 +407,7 @@ async function runTelegramConversation(row, args) {
|
|
|
405
407
|
const bound = store.getTelegramConversation(row.id)?.session ?? null;
|
|
406
408
|
const session = bound === null ? null : store.getMateSession(bound);
|
|
407
409
|
if (session === null) {
|
|
408
|
-
await notify(
|
|
410
|
+
await notify("This conversation was restarted before my reply went out, so it wasn't sent. Nothing was changed. Send your message again.");
|
|
409
411
|
finish({ state: "failed", outcome: "unsent:no-session" });
|
|
410
412
|
report.refused++;
|
|
411
413
|
return;
|
|
@@ -525,11 +527,14 @@ async function runTelegramConversation(row, args) {
|
|
|
525
527
|
const origin = proposal === null ? null : options.phoneOrigin?.() ?? null;
|
|
526
528
|
const wanted = proposal === null ? null : proposalLink(store, proposal, repos);
|
|
527
529
|
const keyboard = keyboardWith(part.keyboard, phoneLinkButton(origin, wanted));
|
|
528
|
-
|
|
530
|
+
// The lead's own words carry their bold anchors and labelled links as entities, never as Markdown characters.
|
|
531
|
+
const voiced = part.kind === "reply" ? telegramReply(part.text) : null;
|
|
532
|
+
const text = proposal !== null && part.keyboard === null ? handoffCardText(part.text, linkNote(origin, wanted)) : voiced?.text ?? part.text;
|
|
529
533
|
method = "sendMessage";
|
|
530
534
|
send = () => transport("sendMessage", {
|
|
531
535
|
chat_id: chatId,
|
|
532
536
|
text,
|
|
537
|
+
...(voiced === null || voiced.entities.length === 0 ? {} : { entities: voiced.entities }),
|
|
533
538
|
link_preview_options: { is_disabled: true },
|
|
534
539
|
...(part.replyTo === null ? {} : { reply_parameters: { message_id: Number(part.replyTo) } }),
|
|
535
540
|
...(keyboard === undefined ? {} : { reply_markup: { inline_keyboard: keyboard } }),
|
|
@@ -590,7 +595,7 @@ async function runTelegramConversation(row, args) {
|
|
|
590
595
|
try {
|
|
591
596
|
return store.transact(() => {
|
|
592
597
|
const now = clock();
|
|
593
|
-
const parts =
|
|
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 }));
|
|
594
599
|
// The lead's question to its owner: one tap per option, then "Something else".
|
|
595
600
|
const ask = store.mateAsk(turn);
|
|
596
601
|
if (ask !== null)
|
|
@@ -633,7 +638,7 @@ async function runTelegramConversation(row, args) {
|
|
|
633
638
|
return;
|
|
634
639
|
}
|
|
635
640
|
if (turn.state !== "answered") {
|
|
636
|
-
await notify(
|
|
641
|
+
await notify(mateFailureText(turn.failureReason));
|
|
637
642
|
finish({ state: "failed", outcome: `replayed:${turn.failureReason ?? "failed"}` });
|
|
638
643
|
report.refused++;
|
|
639
644
|
return;
|
|
@@ -693,7 +698,13 @@ async function runTelegramConversation(row, args) {
|
|
|
693
698
|
const { who, session, thread, config } = resolved;
|
|
694
699
|
const revalidate = async () => {
|
|
695
700
|
const problem = await telegramChannelProblem(store, { botId, bindingId: binding.id, approverGeneration: binding.approverGeneration, repos: who.repos }, readProjects);
|
|
696
|
-
|
|
701
|
+
if (problem === null)
|
|
702
|
+
return { ok: true };
|
|
703
|
+
const reason = problem === UNREADABLE_REGISTRY ? "projects-unreadable"
|
|
704
|
+
: problem === "this chat is no longer paired" ? "unpaired"
|
|
705
|
+
: problem === "the paired account is no longer an approver" ? "not-approver"
|
|
706
|
+
: problem === "the connected projects changed" ? "projects-changed" : "access-changed";
|
|
707
|
+
return { ok: false, reason };
|
|
697
708
|
};
|
|
698
709
|
// Bound BEFORE the dispatch, under the claim: a crash from here on finds
|
|
699
710
|
// the receipt in this session, not in whichever session is live later.
|
|
@@ -704,17 +715,23 @@ async function runTelegramConversation(row, args) {
|
|
|
704
715
|
// crash gets the original turn back, never a second dispatch.
|
|
705
716
|
const heartbeat = setInterval(() => { held(); }, 30_000);
|
|
706
717
|
heartbeat.unref?.();
|
|
718
|
+
// Once the turn reaches a tool step: a 👍 on the owner's message, and "typing…" while the lead works.
|
|
719
|
+
const warm = warmTurn({
|
|
720
|
+
react: () => transport("setMessageReaction", { chat_id: chatId, message_id: Number(row.messageId), reaction: [{ type: "emoji", emoji: WARM_EMOJI }] }),
|
|
721
|
+
typing: () => transport("sendChatAction", { chat_id: chatId, action: "typing" }),
|
|
722
|
+
});
|
|
707
723
|
let outcome;
|
|
708
724
|
try {
|
|
709
725
|
outcome = await runMateTurn({
|
|
710
726
|
store, who, session, thread, config, key: null, message: row.text, requestId: row.request,
|
|
711
727
|
...(row.context === null ? {} : { context: row.context }),
|
|
712
728
|
...(options.subscriptionRunner === undefined ? {} : { subscriptionRunner: options.subscriptionRunner }),
|
|
713
|
-
clock, evidenceRoot: options.evidenceRoot, revalidate, mediaDelivery: "documents", channel: "telegram",
|
|
729
|
+
clock, evidenceRoot: options.evidenceRoot, revalidate, mediaDelivery: "documents", channel: "telegram", onProgress: warm.onProgress,
|
|
714
730
|
});
|
|
715
731
|
}
|
|
716
732
|
finally {
|
|
717
733
|
clearInterval(heartbeat);
|
|
734
|
+
warm.stop();
|
|
718
735
|
}
|
|
719
736
|
const receipt = store.mateRequestReceipt(session.id, row.request);
|
|
720
737
|
if (receipt !== null)
|
|
@@ -724,7 +741,7 @@ async function runTelegramConversation(row, args) {
|
|
|
724
741
|
await requeue("busy");
|
|
725
742
|
return;
|
|
726
743
|
}
|
|
727
|
-
await notify(phoneText(
|
|
744
|
+
await notify(phoneText(outcome.message, 1_000));
|
|
728
745
|
finish({ state: "failed", outcome: `refused:${outcome.refused}` });
|
|
729
746
|
report.refused++;
|
|
730
747
|
return;
|
|
@@ -732,8 +749,7 @@ async function runTelegramConversation(row, args) {
|
|
|
732
749
|
if (!outcome.ok) {
|
|
733
750
|
// Truthful: what failed, that nothing was kept, and that a new message
|
|
734
751
|
// is a new turn. A failed turn's drafts are already gone.
|
|
735
|
-
|
|
736
|
-
await notify(phoneText(words, 1_000));
|
|
752
|
+
await notify(phoneText(outcome.message, 1_000));
|
|
737
753
|
finish({ state: "failed", outcome: `failed:${outcome.failed}` });
|
|
738
754
|
report.refused++;
|
|
739
755
|
return;
|
package/dist/telegram-team.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { renderReply, shapeReplyParts } from "./reply-shape.js";
|
|
1
2
|
import { TeamLeads } from "./team-leads.js";
|
|
2
3
|
import { proposalPreview, tooLongText } from "./chat-channel.js";
|
|
3
|
-
import { mintCardTokens, phoneLinkButton, splitParts } from "./telegram-mate.js";
|
|
4
|
+
import { PART_CAP, mintCardTokens, phoneLinkButton, splitParts } from "./telegram-mate.js";
|
|
4
5
|
import { MATE_MESSAGE_MAX_CHARS } from "./mate.js";
|
|
5
6
|
import { phoneText } from "./telegram-status.js";
|
|
6
7
|
/** `/team` lists, `/team <n>` chooses, `/team off` (or `private`) leaves. Any other slash text is not ours. */
|
|
@@ -265,7 +266,9 @@ export async function deliverTeamChats(store, botId, transport, clock, report, p
|
|
|
265
266
|
const skip = role === "operator" && (ownMessage || status === "cancelled" || String(message["author"] ?? "").length === 0);
|
|
266
267
|
const prefix = `telegram-team:${chat.id}:`;
|
|
267
268
|
if (!skip) {
|
|
268
|
-
|
|
269
|
+
// The lead's reply is split as written, then each part shaped and measured as Telegram shows it, so no cut lands
|
|
270
|
+
// inside a link and no rendered part passes Telegram's ceiling.
|
|
271
|
+
const text = role === "assistant" ? shapeReplyParts(String(message["text"]), PART_CAP, { appOrigin: phoneOrigin?.() ?? null }, shaped => renderReply(shaped, "telegram").length).map(part => renderReply(part, "telegram")) : `${phoneText(String(message["author"]), 40)}: ${String(message["text"])}`;
|
|
269
272
|
const sent = await sendParts(store, transport, chat.chatId, text, `${prefix}message:${id}`, clock, access, report);
|
|
270
273
|
if (sent !== null) {
|
|
271
274
|
report.problems.push(`team chat ${chat.chatId}: ${sent}`);
|
|
@@ -329,7 +332,7 @@ function cardBindingFor(store, botId, chat, turn) {
|
|
|
329
332
|
return bindings.find(one => members.has(one.approver)) ?? null;
|
|
330
333
|
}
|
|
331
334
|
async function sendParts(store, transport, chatId, text, receipt, clock, access, report, keyboard, tokens = []) {
|
|
332
|
-
const parts = splitParts(text);
|
|
335
|
+
const parts = typeof text === "string" ? splitParts(text) : text;
|
|
333
336
|
const delivered = store.serviceCursor(receipt);
|
|
334
337
|
for (const [index, part] of parts.entries()) {
|
|
335
338
|
if (index < delivered)
|
package/dist/telegram.js
CHANGED
|
@@ -920,7 +920,7 @@ async function deliverOutboxTo(store, botId, binding, transport, owner, clock, r
|
|
|
920
920
|
};
|
|
921
921
|
/** The lead's words (lead-voice.ts): one message; a later say within two minutes edits it in place. */
|
|
922
922
|
const leadSayOutcome = async (fact) => {
|
|
923
|
-
const words = leadSayText(store, fact);
|
|
923
|
+
const words = leadSayText(store, fact, binding.approver);
|
|
924
924
|
const button = fact.link === null ? null : factButton(phoneOrigin, fact.link);
|
|
925
925
|
const keyboard = button === null ? [] : [button];
|
|
926
926
|
const earlier = leadSayEarlier(store, fact).map(id => store.telegramMessageOf(id, fact.destination)).find(one => one !== null) ?? null;
|
package/dist/test-temp.js
CHANGED
|
@@ -18,7 +18,7 @@ export const TEST_TEMP_PREFIXES = [
|
|
|
18
18
|
"flow-goal-", "flow-scratch", "flows-chat-", "invoke-attest-", "knowledge-test-", "learning-", "peek-", "phone-origin-",
|
|
19
19
|
"proposal-repo-", "race-", "refresh-migration-", "refresh-refusal-", "release-check-", "shared-chat-", "skills-test-",
|
|
20
20
|
"task-status-surfaces-", "template-repo-", "template-test-", "v14-migrate-", "v17-migrate-", "watch-anchor-",
|
|
21
|
-
"needs-you-", "deps-lock-", "deps-state-", "upgrade-fresh-", "upgrade-path-", "deploy-restore-", "result-review-", "one-result-page-", "deploy-before-swap-", "one-vocabulary-", "accept-finishes-", "lead-promises-",
|
|
21
|
+
"needs-you-", "deps-lock-", "deps-state-", "upgrade-fresh-", "upgrade-path-", "deploy-restore-", "result-review-", "one-result-page-", "deploy-before-swap-", "one-vocabulary-", "accept-finishes-", "lead-promises-", "lead-people-",
|
|
22
22
|
// Playwright's browser profiles and artifacts.
|
|
23
23
|
"playwright_chromiumdev_profile-", "playwright_firefoxdev_profile-", "playwright-artifacts-",
|
|
24
24
|
];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolroll",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.17",
|
|
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"
|