toolroll 0.9.97 โ 0.9.99
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 +15 -0
- package/dist/approval-authority.d.ts +10 -1
- package/dist/approval-authority.js +18 -2
- package/dist/browser/workspace.js +1 -1
- package/dist/browser-workspace.d.ts +4 -3
- package/dist/chat-actions.d.ts +40 -5
- package/dist/chat-actions.js +124 -13
- package/dist/chat-card.js +4 -1
- package/dist/chat-channel.d.ts +3 -0
- package/dist/chat-channel.js +24 -7
- package/dist/chat-decide.js +4 -2
- package/dist/chat-task-actions.js +1 -1
- package/dist/chat-terms.d.ts +8 -0
- package/dist/chat-terms.js +14 -0
- package/dist/chat-voice.js +1 -1
- package/dist/checkout-cleanup.js +2 -1
- package/dist/contracts/chat-actions.d.ts +6 -0
- package/dist/contracts/chat-actions.js +2 -0
- package/dist/contracts/cli.d.ts +6 -0
- package/dist/contracts/cli.js +3 -0
- package/dist/contracts/lead-tools.d.ts +17 -0
- package/dist/contracts/lead-tools.js +4 -1
- package/dist/contracts/store-json.d.ts +1 -0
- package/dist/deploy-phase-data.json +21 -0
- package/dist/deploy-phase.d.ts +37 -0
- package/dist/deploy-phase.js +64 -0
- package/dist/deploy-rescue.d.ts +2 -1
- package/dist/deploy-rescue.js +6 -7
- package/dist/desktop-update.d.ts +2 -2
- package/dist/fast-start.d.ts +83 -6
- package/dist/fast-start.js +112 -15
- package/dist/home-groups.d.ts +39 -0
- package/dist/home-groups.js +33 -0
- package/dist/lead-answer-eval.d.ts +7 -0
- package/dist/lead-answer-eval.js +6 -2
- package/dist/lead-capabilities.js +1 -1
- package/dist/lead-context.js +2 -2
- package/dist/lead-contract.d.ts +1 -1
- package/dist/lead-contract.js +10 -7
- package/dist/lead-doors.d.ts +32 -0
- package/dist/lead-doors.js +80 -14
- package/dist/lead-status.d.ts +3 -0
- package/dist/lead-status.js +8 -3
- package/dist/lead-tools.d.ts +20 -0
- package/dist/lead-tools.js +66 -3
- package/dist/lead-voice.js +1 -1
- package/dist/lead.js +18 -2
- package/dist/local-owner.js +2 -0
- package/dist/one-step-chat-schema.d.ts +22 -0
- package/dist/one-step-chat-schema.js +37 -0
- package/dist/operate.d.ts +27 -1
- package/dist/operate.js +206 -32
- package/dist/project-delete.js +9 -0
- package/dist/project-knowledge.d.ts +7 -7
- package/dist/publish.d.ts +2 -0
- package/dist/publish.js +1 -1
- package/dist/pull-request-flow.d.ts +4 -0
- package/dist/pull-request-flow.js +16 -1
- package/dist/release-adapters.js +30 -14
- package/dist/release-gate.d.ts +3 -3
- package/dist/release-gate.js +7 -4
- package/dist/release-next.d.ts +3 -3
- package/dist/release-next.js +13 -16
- package/dist/reply-voice.d.ts +23 -0
- package/dist/reply-voice.js +37 -0
- package/dist/scope.d.ts +6 -1
- package/dist/scope.js +3 -2
- package/dist/server/chat.js +4 -4
- package/dist/server/chrome.js +2 -2
- package/dist/server/home.d.ts +4 -3
- package/dist/server/home.js +33 -28
- package/dist/server/render-chat.js +11 -6
- package/dist/server/render-pages.js +2 -2
- package/dist/setup-guide.d.ts +2 -2
- package/dist/staged-runtimes.js +2 -1
- package/dist/store.d.ts +4 -2
- package/dist/store.js +35 -12
- package/dist/telegram-lead.js +69 -5
- package/dist/telegram-status.js +9 -4
- package/dist/telegram.d.ts +10 -0
- package/dist/telegram.js +92 -8
- package/dist/toolroll-update.js +3 -1
- package/dist/tui-contract.d.ts +2 -0
- package/dist/tui-source.js +3 -0
- package/dist/tui.js +41 -41
- package/dist/typed-start.d.ts +51 -0
- package/dist/typed-start.js +98 -0
- package/dist/wake-hub.d.ts +72 -0
- package/dist/wake-hub.js +151 -0
- package/package.json +1 -1
package/dist/telegram.js
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* BotFather token plus `bridge telegram unpair` is the recovery, and both
|
|
19
19
|
* are one command.
|
|
20
20
|
*/
|
|
21
|
+
import { recordTypedStart, typedStartCard, typedYes } from "./typed-start.js";
|
|
21
22
|
import { telegramCardMessage } from "./card-render.js";
|
|
22
23
|
import { flowDoneCard } from "./flow-result-card.js";
|
|
23
24
|
import { applyTelegramQuestionReply, applyTelegramQuestionTap, openQuestionOf, telegramQuestionButtons } from "./subagent-question.js";
|
|
@@ -352,6 +353,8 @@ export const MAX_POLL_SECONDS = 25;
|
|
|
352
353
|
const FOLLOW_BACKOFF_MS = [1_000, 2_000, 4_000, 8_000, 16_000, 30_000];
|
|
353
354
|
/** A cycle that returned instantly with nothing is padded to this โ a scripted or broken server must not spin the loop hot. */
|
|
354
355
|
const FOLLOW_IDLE_FLOOR_MS = 1_000;
|
|
356
|
+
/** While a long poll is open, how often the follower looks for a message due to go out (live-wakeups.md). */
|
|
357
|
+
const DELIVERY_CHECK_MS = 1_000;
|
|
355
358
|
/** v98: how often the bridge checks Telegram still pushes to us; how long pushes may keep failing before it asks
|
|
356
359
|
* for updates itself; and how long it asks before trying pushes again. */
|
|
357
360
|
const PUSH_CHECK_MS = 60_000, PUSH_GIVE_UP_MS = 5 * 60_000, PUSH_RETRY_MS = 15 * 60_000, PUSH_IDLE_MS = 2_000;
|
|
@@ -532,6 +535,8 @@ export async function followBridge(store, options) {
|
|
|
532
535
|
const startedAt = Date.now();
|
|
533
536
|
const report = { sent: 0, answered: 0, paired: 0, ignored: 0, backlog: false, problems: [] };
|
|
534
537
|
await managePush(report);
|
|
538
|
+
// What this cycle's delivery below covers: anything newer may end the long poll early.
|
|
539
|
+
const deliveredThrough = newestNotificationIdOf(store);
|
|
535
540
|
if (options.deliver !== false) {
|
|
536
541
|
await deliverOutbox(store, botId, transport, owner, clock, report, options.readProjects, options.canDeliver, options.conversation?.phoneOrigin, options.conversation?.evidenceRoot);
|
|
537
542
|
await deliverTeam(store, botId, transport, clock, report, options.readProjects, options.canDeliver, options.conversation?.phoneOrigin);
|
|
@@ -547,7 +552,43 @@ export async function followBridge(store, options) {
|
|
|
547
552
|
report.pushed = pushed;
|
|
548
553
|
if (!pushing()) {
|
|
549
554
|
// While a turn is running, poll briefly so its reply is sent within a second or two of being written.
|
|
550
|
-
|
|
555
|
+
const seconds = inFlight === null ? pollSeconds : 1;
|
|
556
|
+
// A DELIVERY WAKE (live-wakeups.md): while the long poll is open, a message written for this bot since this
|
|
557
|
+
// cycle began delivering ends the poll, once per cycle, so it goes out now instead of after the poll. Same
|
|
558
|
+
// owner, same generation, one connection; the cursor moves only past updates already processed.
|
|
559
|
+
const poll = new AbortController();
|
|
560
|
+
const endPoll = () => poll.abort();
|
|
561
|
+
if (signal.aborted)
|
|
562
|
+
poll.abort();
|
|
563
|
+
else
|
|
564
|
+
signal.addEventListener("abort", endPoll, { once: true });
|
|
565
|
+
let woke = false;
|
|
566
|
+
const checker = options.deliver !== false && seconds > 1
|
|
567
|
+
? setInterval(() => {
|
|
568
|
+
if (woke || poll.signal.aborted)
|
|
569
|
+
return;
|
|
570
|
+
let due = false;
|
|
571
|
+
try {
|
|
572
|
+
due = telegramOutboxDue(store, botId, deliveredThrough, clock());
|
|
573
|
+
}
|
|
574
|
+
catch {
|
|
575
|
+
due = false;
|
|
576
|
+
}
|
|
577
|
+
if (!due)
|
|
578
|
+
return;
|
|
579
|
+
woke = true;
|
|
580
|
+
poll.abort();
|
|
581
|
+
}, options.deliveryCheckMs ?? DELIVERY_CHECK_MS)
|
|
582
|
+
: null;
|
|
583
|
+
checker?.unref?.();
|
|
584
|
+
try {
|
|
585
|
+
await drainUpdates(store, botId, transport, owner, lease.generation, lease.cursor, clock, report, seconds, poll.signal, options.readProjects, options.conversation, () => woke && !signal.aborted);
|
|
586
|
+
}
|
|
587
|
+
finally {
|
|
588
|
+
if (checker !== null)
|
|
589
|
+
clearInterval(checker);
|
|
590
|
+
signal.removeEventListener("abort", endPoll);
|
|
591
|
+
}
|
|
551
592
|
// A push address nobody here wants any more (the public address was removed): take it down, so asking works again.
|
|
552
593
|
if (report.webhookActive === true && push === null)
|
|
553
594
|
await transport("deleteWebhook", { drop_pending_updates: false }, signal);
|
|
@@ -587,7 +628,8 @@ export async function followBridge(store, options) {
|
|
|
587
628
|
const took = Date.now() - startedAt;
|
|
588
629
|
// While Telegram pushes (v98) there is no long poll to wait on: a pushed update waits at most this long.
|
|
589
630
|
const floor = pushing() ? PUSH_IDLE_MS : FOLLOW_IDLE_FLOOR_MS;
|
|
590
|
-
|
|
631
|
+
// A delivery wake goes straight back round to send: no padding.
|
|
632
|
+
if (!signal.aborted && report.deliveryWake !== true && report.sent === 0 && report.answered === 0 && (report.chatAnswered ?? 0) === 0 && took < floor) {
|
|
591
633
|
// While Telegram pushes, a push kept by this process's hook ends the idle at once.
|
|
592
634
|
await (pushing() ? idle(floor - took) : wait(floor - took));
|
|
593
635
|
}
|
|
@@ -1484,7 +1526,9 @@ function taskOf(store, decision) {
|
|
|
1484
1526
|
const run = store.getRun(decision.run);
|
|
1485
1527
|
return run === null ? "?" : store.externalIdFor(run.taskRef) ?? "?";
|
|
1486
1528
|
}
|
|
1487
|
-
async function drainUpdates(store, botId, transport, owner, generation, cursor, clock, report, pollSeconds = 0, signal, readProjects, conversation
|
|
1529
|
+
async function drainUpdates(store, botId, transport, owner, generation, cursor, clock, report, pollSeconds = 0, signal, readProjects, conversation,
|
|
1530
|
+
/** True when the poll was ended on purpose for a delivery: an answer that failed then is that abort, not the wire. */
|
|
1531
|
+
deliveryWake) {
|
|
1488
1532
|
const context = { store, botId, transport, clock, report, readProjects, conversation, projects: null };
|
|
1489
1533
|
let offset = cursor + 1;
|
|
1490
1534
|
for (let page = 0; page < PAGE_BUDGET; page++) {
|
|
@@ -1495,6 +1539,12 @@ async function drainUpdates(store, botId, transport, owner, generation, cursor,
|
|
|
1495
1539
|
allowed_updates: ["message", "callback_query"],
|
|
1496
1540
|
}, signal);
|
|
1497
1541
|
if (!answer.ok) {
|
|
1542
|
+
// Ended on purpose so a due message goes out now: no problem, no backoff; the next cycle asks again from the
|
|
1543
|
+
// same cursor, so nothing is lost.
|
|
1544
|
+
if (deliveryWake?.() === true) {
|
|
1545
|
+
report.deliveryWake = true;
|
|
1546
|
+
return;
|
|
1547
|
+
}
|
|
1498
1548
|
const said = answer.description ?? "failed";
|
|
1499
1549
|
// v98: another program asked Telegram for this bot's updates, and Telegram ended our
|
|
1500
1550
|
// request in its favour. Nothing is lost: an update is only gone once someone
|
|
@@ -1723,7 +1773,14 @@ function applyMessage(context, update, effects) {
|
|
|
1723
1773
|
return;
|
|
1724
1774
|
// Replies to decisions retain their existing note meaning, even if the
|
|
1725
1775
|
// note starts with a slash. New read commands are direct messages only.
|
|
1726
|
-
|
|
1776
|
+
// A bare "start" as the very next message after a Start card is that card's yes (docs/plans/one-step-chat.md C5),
|
|
1777
|
+
// not the help command. Only the routing is decided here, by the same ids the message path reads again when it
|
|
1778
|
+
// records the yes; "/start", and "start" anywhere else, stay help.
|
|
1779
|
+
const yesSeat = message.reply_to_message === undefined && chat.type === "private" && typeof message.text === "string" && typedYes(message.text)
|
|
1780
|
+
&& context.conversation !== undefined && context.readProjects !== undefined ? store.liveTelegramBindingFor(botId, String(from.id)) : null;
|
|
1781
|
+
const answersStartCard = yesSeat !== null && String(chat.id) === yesSeat.chatId
|
|
1782
|
+
&& typedStartCard(store, { binding: yesSeat.id, chatId: yesSeat.chatId, approver: yesSeat.approver }, { id: String(message.message_id), replyTo: null }) !== null;
|
|
1783
|
+
if (message.reply_to_message === undefined && !answersStartCard && applyPhoneRead(context, update, effects))
|
|
1727
1784
|
return;
|
|
1728
1785
|
// A reply to a decision message this bot sent is a note, exactly as
|
|
1729
1786
|
// before. Everything else that is ordinary text talks to the shared
|
|
@@ -1902,10 +1959,19 @@ function applyConversation(context, update, effects) {
|
|
|
1902
1959
|
report.ignored++;
|
|
1903
1960
|
return;
|
|
1904
1961
|
}
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1962
|
+
// A typed yes to a Start card (docs/plans/one-step-chat.md C5) is read here, before any model runs, and recorded with
|
|
1963
|
+
// the message in the same transaction: the fixed words only, tied to one exact card by message ids and Toolroll's own
|
|
1964
|
+
// delivery record. The worker acts on it (confirmTypedStart) and no lead turn runs for it. Anything else is queued for
|
|
1965
|
+
// the lead exactly as before.
|
|
1966
|
+
store.transact(() => {
|
|
1967
|
+
const typed = typedYes(text) ? typedStartCard(store, { binding: binding.id, chatId: binding.chatId, approver: binding.approver }, { id: String(message.message_id), replyTo }) : null;
|
|
1968
|
+
store.enqueueTelegramConversation({
|
|
1969
|
+
binding, updateId: update.update_id, messageId: String(message.message_id), replyTo,
|
|
1970
|
+
request: telegramRequestId(botId, binding.id, update.update_id), text, context: context_, taskId, sourceRun,
|
|
1971
|
+
}, clock());
|
|
1972
|
+
if (typed !== null)
|
|
1973
|
+
recordTypedStart(store, `m${update.update_id}`, typed, clock());
|
|
1974
|
+
});
|
|
1909
1975
|
report.chatQueued = (report.chatQueued ?? 0) + 1;
|
|
1910
1976
|
// Saved: ๐ on the owner's message right away (docs/plans/responsive-chat.md ยง3.1), once this update's transaction
|
|
1911
1977
|
// has committed. It comes off when the answer or the failure notice is delivered. Never awaited: a burst of messages
|
|
@@ -2653,3 +2719,21 @@ export function wakeTelegramBridge(botId) {
|
|
|
2653
2719
|
for (const wake of wakers)
|
|
2654
2720
|
wake();
|
|
2655
2721
|
}
|
|
2722
|
+
/** The newest notification id now: a follower cycle's mark for what its delivery covers. */
|
|
2723
|
+
function newestNotificationIdOf(store) {
|
|
2724
|
+
return Number(store.handle.prepare("SELECT COALESCE(MAX(id), 0) AS id FROM notification").get()?.["id"] ?? 0);
|
|
2725
|
+
}
|
|
2726
|
+
/**
|
|
2727
|
+
* Whether a message for this bot was written after `mark` and is still due: unresolved, addressed to everyone or to a
|
|
2728
|
+
* person paired with this bot, and the bot not held back by Telegram's rate limit. Read-only; delivery itself still
|
|
2729
|
+
* decides each row under its own fences.
|
|
2730
|
+
*/
|
|
2731
|
+
export function telegramOutboxDue(store, botId, mark, now) {
|
|
2732
|
+
const approvers = [...new Set(store.liveTelegramBindings(botId).map(binding => binding.approver))];
|
|
2733
|
+
if (approvers.length === 0)
|
|
2734
|
+
return false;
|
|
2735
|
+
if (store.telegramRetryAt(botId) > now.toISOString())
|
|
2736
|
+
return false;
|
|
2737
|
+
return store.handle.prepare(`SELECT 1 AS hit FROM notification WHERE id > ? AND resolved_at IS NULL
|
|
2738
|
+
AND (recipient IS NULL OR recipient IN (SELECT value FROM json_each(?))) LIMIT 1`).get(mark, JSON.stringify(approvers)) !== undefined;
|
|
2739
|
+
}
|
package/dist/toolroll-update.js
CHANGED
|
@@ -44,6 +44,7 @@ import { orphanKey, updateSafeSchema, V117_RENAMED_COLUMNS, V117_RENAMED_TABLES,
|
|
|
44
44
|
import { V152_ADDED_COLUMNS } from "./code-map-store.js";
|
|
45
45
|
import { V153_ADDED_COLUMNS } from "./plain-schema.js";
|
|
46
46
|
import { V158_ADDED_COLUMNS } from "./lead-latency-schema.js";
|
|
47
|
+
import { V159_ADDED_COLUMNS } from "./one-step-chat-schema.js";
|
|
47
48
|
import { isNewer, REGISTRY } from "./releases.js";
|
|
48
49
|
import { markNeverIndex } from "./never-index.js";
|
|
49
50
|
import { readRuntimeUpdateJournal, RUNTIME_PHASES, RUNTIME_UPDATE_STEPS, stagedStartedAt, updaterStartingOf } from "./contracts/update-journal.js";
|
|
@@ -432,7 +433,8 @@ const HISTORY_RULES = {
|
|
|
432
433
|
// v156 adds tables only (fallback chains): likewise.
|
|
433
434
|
// v158 (after v157's chat-app tables): a saved lead step reads no tool names and no failure detail, and a saved reply no route and no failure detail
|
|
434
435
|
// (NULL); the quick-answer switch is a new table.
|
|
435
|
-
|
|
436
|
+
// v159: a saved fast-start preview reads no lead turn (NULL); a typed yes's record is a new table.
|
|
437
|
+
...Object.fromEntries(Object.entries(addedColumns(V119_ADDED_COLUMNS, V120_ADDED_COLUMNS, V121_ADDED_COLUMNS, V122_ADDED_COLUMNS, V123_ADDED_COLUMNS, V125_ADDED_COLUMNS, V126_ADDED_COLUMNS, V128_ADDED_COLUMNS, V129_ADDED_COLUMNS, V134_ADDED_COLUMNS, V135_ADDED_COLUMNS, V138_ADDED_COLUMNS, V143_ADDED_COLUMNS, V147_ADDED_COLUMNS, V148_ADDED_COLUMNS, V150_ADDED_COLUMNS, V151_ADDED_COLUMNS, V152_ADDED_COLUMNS, V153_ADDED_COLUMNS, V158_ADDED_COLUMNS, V159_ADDED_COLUMNS))
|
|
436
438
|
.map(([table, rule]) => [table, { ...EARLIER_RULES[table], ...rule }])),
|
|
437
439
|
};
|
|
438
440
|
/** The tables a migration may remove whole (v115's removed features). */
|
package/dist/tui-contract.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ export type TuiRow = {
|
|
|
17
17
|
/** The attempt running now, if any: what Stop names. */
|
|
18
18
|
readonly liveRunId: number | null;
|
|
19
19
|
readonly repo: string | null;
|
|
20
|
+
/** Home's group (home-groups.ts homeGroupOf): waits on you, working, done, or none (cancelled, replaced). */
|
|
21
|
+
readonly group: "you" | "working" | "done" | null;
|
|
20
22
|
};
|
|
21
23
|
/** A parked question on a task, answered by choosing one of its options. */
|
|
22
24
|
export type TuiDecision = {
|
package/dist/tui-source.js
CHANGED
|
@@ -18,6 +18,7 @@ import { localModeOf, openStoreReadOnly } from "./store.js";
|
|
|
18
18
|
import { routeChangeOf } from "./route-change.js";
|
|
19
19
|
import { flakyCount } from "./flaky.js";
|
|
20
20
|
import { memoryWaiting } from "./lesson-notes.js";
|
|
21
|
+
import { homeGroupOf } from "./home-groups.js";
|
|
21
22
|
import { terminalText } from "./terminal-text.js";
|
|
22
23
|
import { welcomeHere, welcomeLines } from "./welcome-facts.js";
|
|
23
24
|
const record = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
@@ -74,6 +75,8 @@ export function createTuiSource(options) {
|
|
|
74
75
|
return {
|
|
75
76
|
id: words(row["id"], words(row["rootId"])), title: shown(row["title"]), status: shown(status["label"], words(row["state"])), detail: shown(status["detail"]),
|
|
76
77
|
needsYou: views.includes("needs-you"), liveRunId: typeof row["liveRunId"] === "number" ? row["liveRunId"] : null, repo: typeof row["repo"] === "string" ? row["repo"] : null,
|
|
78
|
+
// The same rule as Home's groups, over the same work index item.
|
|
79
|
+
group: homeGroupOf({ status: { views: views.filter((one) => typeof one === "string") } }),
|
|
77
80
|
};
|
|
78
81
|
});
|
|
79
82
|
const waiting = rows.filter(one => one.needsYou);
|