toolroll 0.9.62 → 0.9.65
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 +80 -0
- package/README.md +66 -8
- package/dist/acp-agents.d.ts +43 -0
- package/dist/acp-agents.js +62 -0
- package/dist/acp-attest.d.ts +44 -0
- package/dist/acp-attest.js +269 -0
- package/dist/acp-check.d.ts +28 -0
- package/dist/acp-check.js +410 -0
- package/dist/acp-client.d.ts +163 -0
- package/dist/acp-client.js +738 -0
- package/dist/acp-inspect.d.ts +23 -0
- package/dist/acp-inspect.js +109 -0
- package/dist/agent-board.d.ts +19 -6
- package/dist/agent-board.js +30 -9
- package/dist/agent-fence.d.ts +203 -10
- package/dist/agent-fence.js +623 -41
- package/dist/agent-pulse.js +9 -2
- package/dist/agent-state-words.d.ts +19 -0
- package/dist/agent-state-words.js +20 -0
- package/dist/agent-state.d.ts +65 -0
- package/dist/agent-state.js +229 -0
- package/dist/agentconfig.js +36 -13
- package/dist/agents-board.d.ts +34 -2
- package/dist/agents-board.js +110 -1
- package/dist/approval-rules-ui.js +2 -2
- package/dist/assignment-delivery.d.ts +14 -0
- package/dist/assignment-delivery.js +13 -0
- package/dist/assignment.js +13 -0
- package/dist/attest.js +7 -0
- package/dist/backup-ui.js +2 -2
- package/dist/browser/THIRD_PARTY_NOTICES.txt +48 -0
- package/dist/browser/terminal.css +37 -0
- package/dist/browser/terminal.js +117 -0
- package/dist/browser/workspace.css +2 -2
- package/dist/browser/workspace.js +42 -38
- package/dist/browser-shell.js +6 -3
- package/dist/browser-workspace.d.ts +263 -1
- package/dist/browser-workspace.js +60 -0
- package/dist/builder.d.ts +6 -1
- package/dist/builder.js +34 -13
- package/dist/chat-actions.d.ts +13 -1
- package/dist/chat-actions.js +199 -17
- package/dist/chat-ask.d.ts +4 -0
- package/dist/chat-ask.js +14 -0
- package/dist/chat-channel.js +20 -0
- package/dist/chat-continuity.js +5 -4
- package/dist/chat-decide.d.ts +0 -14
- package/dist/chat-decide.js +4 -1
- package/dist/chat-delivery.d.ts +9 -0
- package/dist/chat-delivery.js +45 -6
- package/dist/chat-messages.d.ts +8 -0
- package/dist/chat-messages.js +36 -4
- package/dist/check-levels-ui.js +2 -2
- package/dist/check-levels.js +3 -0
- package/dist/checkout-proof.d.ts +28 -0
- package/dist/checkout-proof.js +62 -0
- package/dist/claim.d.ts +12 -0
- package/dist/claim.js +20 -1
- package/dist/cli-help.d.ts +1 -1
- package/dist/cli-help.js +5 -1
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +24 -2
- package/dist/coding-ui.js +6 -4
- package/dist/console-instance.d.ts +124 -0
- package/dist/console-instance.js +337 -0
- package/dist/contracts/chat-actions.d.ts +16 -0
- package/dist/contracts/chat-actions.js +4 -0
- package/dist/contracts/chat-content.d.ts +19 -0
- package/dist/contracts/chat-content.js +6 -0
- package/dist/contracts/cli.d.ts +8 -0
- package/dist/contracts/cli.js +3 -1
- package/dist/contracts/console-api.d.ts +30 -4
- package/dist/contracts/console-api.js +13 -4
- package/dist/contracts/flow-card.d.ts +4 -0
- package/dist/contracts/flow-card.js +3 -0
- package/dist/contracts/flow.d.ts +46 -0
- package/dist/contracts/flow.js +11 -2
- package/dist/contracts/integration-metadata.d.ts +2 -1
- package/dist/contracts/integration-metadata.js +2 -1
- package/dist/contracts/lead-tools.d.ts +92 -0
- package/dist/contracts/lead-tools.js +13 -0
- package/dist/contracts/provider.d.ts +11 -1
- package/dist/contracts/provider.js +1 -1
- package/dist/contracts/route.d.ts +76 -1
- package/dist/contracts/route.js +1 -1
- package/dist/contracts/store-json.d.ts +2 -0
- package/dist/control-setup.d.ts +1 -1
- package/dist/control-ui.js +2 -2
- package/dist/converse.d.ts +16 -0
- package/dist/converse.js +32 -4
- package/dist/demo-chat.js +4 -4
- package/dist/demo.js +2 -2
- package/dist/desktop-update-gate.js +6 -0
- package/dist/discord-chat.js +4 -1
- package/dist/dispatch.js +7 -4
- package/dist/doctor.d.ts +53 -0
- package/dist/doctor.js +427 -0
- package/dist/egress-bridge.d.ts +17 -0
- package/dist/egress-bridge.js +111 -0
- package/dist/egress.d.ts +119 -0
- package/dist/egress.js +423 -0
- package/dist/evidence-pack.d.ts +1 -1
- package/dist/evidence-pack.js +2 -2
- package/dist/exec.d.ts +81 -0
- package/dist/exec.js +368 -9
- package/dist/export-ui.js +2 -2
- package/dist/flow-code.d.ts +2 -2
- package/dist/flow-code.js +4 -15
- package/dist/flow-engine.js +35 -11
- package/dist/flow-gallery-ui.js +2 -2
- package/dist/flow-gallery.js +27 -8
- package/dist/flow-share.js +16 -5
- package/dist/flow-triggers.d.ts +84 -11
- package/dist/flow-triggers.js +554 -87
- package/dist/flow-unattended-words.d.ts +28 -0
- package/dist/flow-unattended-words.js +50 -0
- package/dist/flow-unattended.d.ts +227 -0
- package/dist/flow-unattended.js +776 -0
- package/dist/flows-cli.d.ts +28 -1
- package/dist/flows-cli.js +63 -12
- package/dist/flows-ui.d.ts +2 -3
- package/dist/flows-ui.js +56 -5
- package/dist/flows.d.ts +4 -0
- package/dist/flows.js +9 -1
- package/dist/google-mail.d.ts +1 -0
- package/dist/google-mail.js +61 -18
- package/dist/html.d.ts +9 -0
- package/dist/html.js +11 -0
- package/dist/integrations-ui.d.ts +2 -1
- package/dist/integrations-ui.js +6 -3
- package/dist/integrations.d.ts +17 -3
- package/dist/integrations.js +38 -18
- package/dist/invoke.d.ts +17 -0
- package/dist/invoke.js +510 -182
- package/dist/keys.d.ts +18 -14
- package/dist/keys.js +5 -0
- package/dist/landed-cleanup.d.ts +3 -2
- package/dist/landed-cleanup.js +12 -5
- package/dist/lead-capabilities.js +3 -2
- package/dist/lead-contract.js +2 -0
- package/dist/lead-doors.d.ts +2 -0
- package/dist/lead-doors.js +7 -0
- package/dist/lead-follow.d.ts +2 -0
- package/dist/lead-follow.js +58 -2
- package/dist/lead-progress.js +1 -0
- package/dist/lead-queue-runtime.d.ts +46 -0
- package/dist/lead-queue-runtime.js +191 -0
- package/dist/lead-queue.d.ts +104 -0
- package/dist/lead-queue.js +187 -0
- package/dist/lead-sweep.d.ts +67 -0
- package/dist/lead-sweep.js +99 -0
- package/dist/lead-tools.d.ts +22 -0
- package/dist/lead-tools.js +163 -4
- package/dist/lead.d.ts +13 -5
- package/dist/lead.js +89 -29
- package/dist/limits-ui.d.ts +8 -1
- package/dist/limits-ui.js +26 -0
- package/dist/live-bus.d.ts +8 -0
- package/dist/live-bus.js +40 -4
- package/dist/live-detail.d.ts +27 -0
- package/dist/live-detail.js +57 -0
- package/dist/live-notes.d.ts +20 -0
- package/dist/live-notes.js +47 -0
- package/dist/live-records.d.ts +140 -0
- package/dist/live-records.js +935 -0
- package/dist/live-terminal.d.ts +258 -0
- package/dist/live-terminal.js +649 -0
- package/dist/live.d.ts +38 -27
- package/dist/live.js +142 -124
- package/dist/mcp-connect.d.ts +8 -7
- package/dist/mcp-connect.js +49 -51
- package/dist/mcp-oauth.d.ts +6 -3
- package/dist/mcp-oauth.js +8 -4
- package/dist/memory-pass.js +2 -2
- package/dist/mobile-viewport.d.ts +5 -1
- package/dist/mobile-viewport.js +18 -2
- package/dist/model-catalog.js +4 -0
- package/dist/monitoring-ui.js +2 -2
- package/dist/needs-you.d.ts +1 -1
- package/dist/needs-you.js +11 -4
- package/dist/oauth-run-token.d.ts +51 -0
- package/dist/oauth-run-token.js +127 -0
- package/dist/operate.d.ts +2 -0
- package/dist/operate.js +282 -18
- package/dist/passwordless.d.ts +23 -0
- package/dist/passwordless.js +26 -0
- package/dist/peek-cli.d.ts +12 -4
- package/dist/peek-cli.js +19 -11
- package/dist/phase-routing.js +10 -3
- package/dist/plan-auto.d.ts +11 -0
- package/dist/plan-auto.js +62 -11
- package/dist/planner-source.d.ts +3 -0
- package/dist/planner-source.js +14 -0
- package/dist/planner.d.ts +2 -1
- package/dist/planner.js +7 -1
- package/dist/policy-ui.js +2 -2
- package/dist/policy.d.ts +1 -20
- package/dist/policy.js +29 -2
- package/dist/process-recovery-eligibility.d.ts +1 -1
- package/dist/project-access.d.ts +3 -0
- package/dist/project-access.js +3 -0
- package/dist/project-delete-ui.d.ts +12 -0
- package/dist/project-delete-ui.js +41 -9
- package/dist/project-delete.d.ts +3 -1
- package/dist/project-delete.js +12 -6
- package/dist/project-file.d.ts +3 -0
- package/dist/project-file.js +16 -0
- package/dist/project-memory.d.ts +3 -1
- package/dist/project-memory.js +57 -25
- package/dist/project-tools.d.ts +103 -27
- package/dist/project-tools.js +399 -100
- package/dist/provider-auth.d.ts +4 -3
- package/dist/provider-auth.js +5 -4
- package/dist/provider-connection.d.ts +2 -1
- package/dist/provider-connection.js +5 -0
- package/dist/provider-home.d.ts +22 -0
- package/dist/provider-home.js +99 -0
- package/dist/provider.d.ts +42 -5
- package/dist/provider.js +97 -19
- package/dist/pull-request-ui.js +2 -2
- package/dist/push.js +1 -1
- package/dist/quick-start.d.ts +24 -0
- package/dist/quick-start.js +127 -0
- package/dist/release.d.ts +3 -0
- package/dist/release.js +14 -8
- package/dist/remote-main.d.ts +4 -0
- package/dist/remote-main.js +3 -3
- package/dist/request-budget-ui.js +2 -2
- package/dist/result-review.d.ts +1 -1
- package/dist/result-review.js +9 -3
- package/dist/retention-ui.js +2 -2
- package/dist/retention.js +10 -1
- package/dist/run-efficiency.d.ts +73 -0
- package/dist/run-efficiency.js +228 -0
- package/dist/runner.d.ts +5 -1
- package/dist/runner.js +22 -1
- package/dist/scope.d.ts +171 -6
- package/dist/scope.js +251 -31
- package/dist/scout-net.d.ts +4 -1
- package/dist/scout-net.js +37 -12
- package/dist/scout.d.ts +20 -6
- package/dist/scout.js +27 -3
- package/dist/serve.js +258 -43
- package/dist/server/chat.d.ts +1 -1
- package/dist/server/chat.js +122 -31
- package/dist/server/chrome.d.ts +6 -1
- package/dist/server/chrome.js +18 -7
- package/dist/server/console.css +52 -38
- package/dist/server/flows.js +65 -13
- package/dist/server/guards.d.ts +1 -0
- package/dist/server/guards.js +23 -9
- package/dist/server/http.d.ts +7 -0
- package/dist/server/http.js +3 -2
- package/dist/server/live.d.ts +34 -1
- package/dist/server/live.js +39 -17
- package/dist/server/pages-assets.js +5 -0
- package/dist/server/pages.js +90 -13
- package/dist/server/people-tokens.d.ts +3 -0
- package/dist/server/people-tokens.js +55 -17
- package/dist/server/remote-hooks.js +12 -6
- package/dist/server/remote.d.ts +2 -1
- package/dist/server/remote.js +14 -5
- package/dist/server/render-chat.d.ts +12 -3
- package/dist/server/render-chat.js +47 -16
- package/dist/server/render-pages.d.ts +4 -1
- package/dist/server/render-pages.js +5 -4
- package/dist/server/render-people.d.ts +3 -2
- package/dist/server/render-people.js +3 -2
- package/dist/server/render-results.d.ts +18 -2
- package/dist/server/render-results.js +37 -28
- package/dist/server/render-settings.js +6 -5
- package/dist/server/render-tasks.d.ts +35 -10
- package/dist/server/render-tasks.js +110 -27
- package/dist/server/request-context.d.ts +2 -0
- package/dist/server/request-context.js +2 -1
- package/dist/server/route-table.d.ts +3 -0
- package/dist/server/route-table.js +49 -5
- package/dist/server/runtime.d.ts +34 -3
- package/dist/server/session.d.ts +25 -1
- package/dist/server/session.js +18 -4
- package/dist/server/settings.js +132 -13
- package/dist/server/sweeps.d.ts +4 -0
- package/dist/server/sweeps.js +58 -0
- package/dist/server/task-views.d.ts +2 -1
- package/dist/server/task-views.js +96 -6
- package/dist/server/tasks.js +320 -74
- package/dist/server/terminals.d.ts +31 -0
- package/dist/server/terminals.js +365 -0
- package/dist/setup-guide.js +3 -2
- package/dist/slack-chat.js +4 -1
- package/dist/spend-ui.js +3 -3
- package/dist/spend.js +2 -1
- package/dist/steer-capabilities.d.ts +32 -0
- package/dist/steer-capabilities.js +60 -0
- package/dist/steer-controls.d.ts +14 -0
- package/dist/steer-controls.js +36 -0
- package/dist/steer-status.js +2 -2
- package/dist/storage-sweep-child.js +3 -1
- package/dist/storage-sweep-process.d.ts +12 -5
- package/dist/storage-sweep-process.js +23 -6
- package/dist/storage-ui.js +4 -4
- package/dist/store-baseline.d.ts +1 -1
- package/dist/store-baseline.js +1 -1
- package/dist/store.d.ts +466 -19
- package/dist/store.js +1185 -63
- package/dist/subagent-desk.js +1 -1
- package/dist/subagent-tools.d.ts +1 -1
- package/dist/summary.js +3 -0
- package/dist/surface.js +6 -4
- package/dist/task-source.d.ts +58 -0
- package/dist/task-source.js +93 -0
- package/dist/team-leads.js +1 -1
- package/dist/teams-chat.js +4 -1
- package/dist/teardown-git.d.ts +14 -0
- package/dist/teardown-git.js +119 -0
- package/dist/telegram-lead.d.ts +3 -0
- package/dist/telegram-lead.js +44 -7
- package/dist/telegram.d.ts +11 -2
- package/dist/telegram.js +51 -17
- package/dist/terminal-catalog.d.ts +11 -0
- package/dist/terminal-catalog.js +129 -0
- package/dist/text-limits.d.ts +2 -0
- package/dist/text-limits.js +2 -0
- package/dist/tool-broker.d.ts +39 -0
- package/dist/tool-broker.js +370 -0
- package/dist/tool-launcher.js +5 -4
- package/dist/tool-secret-store.d.ts +170 -0
- package/dist/tool-secret-store.js +834 -0
- package/dist/toolroll-update-ui.js +2 -2
- package/dist/toolroll-update.js +14 -3
- package/dist/tools-ui.d.ts +7 -1
- package/dist/tools-ui.js +19 -10
- package/dist/webhook-limits.d.ts +23 -0
- package/dist/webhook-limits.js +30 -0
- package/dist/webhook-readers.d.ts +56 -0
- package/dist/webhook-readers.js +138 -0
- package/dist/webhook-signing.d.ts +27 -0
- package/dist/webhook-signing.js +119 -0
- package/dist/work-index.d.ts +29 -0
- package/dist/work-index.js +21 -5
- package/dist/workspace-revision.js +3 -1
- package/dist/worktree.d.ts +10 -4
- package/dist/worktree.js +70 -29
- package/package.json +13 -2
- package/scripts/provider-canary.mjs +210 -31
package/dist/telegram.js
CHANGED
|
@@ -46,6 +46,10 @@ import { LEAD_MESSAGE_MAX_CHARS } from "./lead.js";
|
|
|
46
46
|
import { envValue } from "./names.js";
|
|
47
47
|
import { applyProposalTap, processTelegramConversations, phoneLinkButton, replyContextFor, telegramConversationRepos, telegramRequestId, tooLongText, whichTaskText, } from "./telegram-lead.js";
|
|
48
48
|
import { askOf, parseTelegramUpdate, pickOf, readTelegramButtonData, readTelegramUpdate, telegramButton } from "./contracts/telegram-callback.js";
|
|
49
|
+
/** Read the enrolled project list on demand. No callback means no task data,
|
|
50
|
+
* never an implicit all-database ceiling. Shared by pass and embedded follower. */
|
|
51
|
+
/** A delivery its person began watching after it was claimed: held or settled at the final fence (cockpit §3). */
|
|
52
|
+
const WATCHED_SINCE_CLAIM = "Held: its person is watching this agent now";
|
|
49
53
|
/** The environment name — and therefore the name the builder strips from agents (both names; see names.ts). */
|
|
50
54
|
export { TELEGRAM_TOKEN_ENV as TOKEN_ENV, TELEGRAM_TOKEN_ENVS as TOKEN_ENVS } from "./names.js";
|
|
51
55
|
/** BotFather's shape: numeric bot id, colon, secret. */
|
|
@@ -732,6 +736,9 @@ async function deliverOutboxTo(store, botId, binding, transport, owner, clock, r
|
|
|
732
736
|
const blocked = [];
|
|
733
737
|
const preflight = (await readAccess()) ?? channelProblem();
|
|
734
738
|
for (const row of group) {
|
|
739
|
+
// Watched since the claim (cockpit §3): held for later or settled quietly, never sent now, never a failure.
|
|
740
|
+
if (preflight === null && store.telegramWatchFence(row, binding, owner, clock()) !== "send")
|
|
741
|
+
continue;
|
|
735
742
|
const problem = preflight ?? store.telegramDeliveryProblem(row, binding, owner, projects, clock(), [...rows.map(one => one.id), row.id]);
|
|
736
743
|
if (problem === null)
|
|
737
744
|
rows.push(row);
|
|
@@ -747,7 +754,18 @@ async function deliverOutboxTo(store, botId, binding, transport, owner, clock, r
|
|
|
747
754
|
if (rows.length === 0)
|
|
748
755
|
continue;
|
|
749
756
|
const ids = rows.map(row => row.id);
|
|
750
|
-
|
|
757
|
+
// Once any part of this delivery has reached Telegram it is committed: a watch opening between its parts never
|
|
758
|
+
// releases it (that would send the parts already accepted again). Every transport call below goes through `wire`.
|
|
759
|
+
let sentAny = false;
|
|
760
|
+
const wire = async (...args) => {
|
|
761
|
+
const answer = await transport(...args);
|
|
762
|
+
if (answer.ok === true)
|
|
763
|
+
sentAny = true;
|
|
764
|
+
return answer;
|
|
765
|
+
};
|
|
766
|
+
// `beforeSend`: the fence before a transport call also asks the watch — until something of this delivery went out;
|
|
767
|
+
// the one after a confirmed send never does, so a message Telegram accepted is recorded as delivered.
|
|
768
|
+
const fence = (beforeSend = !sentAny) => {
|
|
751
769
|
const channel = channelProblem();
|
|
752
770
|
if (channel !== null)
|
|
753
771
|
return channel;
|
|
@@ -755,6 +773,10 @@ async function deliverOutboxTo(store, botId, binding, transport, owner, clock, r
|
|
|
755
773
|
const problem = store.telegramDeliveryProblem(row, binding, owner, projects, clock(), ids);
|
|
756
774
|
if (problem !== null)
|
|
757
775
|
return problem;
|
|
776
|
+
// The watch, at the last moment before the network: a held row's claim is released (its finalize is then a
|
|
777
|
+
// no-op), so nothing is spent on it.
|
|
778
|
+
if (beforeSend && store.telegramWatchFence(row, binding, owner, clock()) !== "send")
|
|
779
|
+
return WATCHED_SINCE_CLAIM;
|
|
758
780
|
}
|
|
759
781
|
return null;
|
|
760
782
|
};
|
|
@@ -763,7 +785,7 @@ async function deliverOutboxTo(store, botId, binding, transport, owner, clock, r
|
|
|
763
785
|
const problem = (await readAccess()) ?? fence();
|
|
764
786
|
if (problem !== null)
|
|
765
787
|
return { ok: false, error: problem };
|
|
766
|
-
const sent = await send(
|
|
788
|
+
const sent = await send(wire, binding.chatId, text, keyboard, entities, silent);
|
|
767
789
|
if (!sent.ok) {
|
|
768
790
|
if (sent.retryAfter !== undefined)
|
|
769
791
|
store.deferTelegram(botId, new Date(clock().getTime() + sent.retryAfter * 1_000).toISOString());
|
|
@@ -774,7 +796,7 @@ async function deliverOutboxTo(store, botId, binding, transport, owner, clock, r
|
|
|
774
796
|
for (const row of messageRows)
|
|
775
797
|
store.recordTelegramMessage(row, binding, sent.messageId, clock());
|
|
776
798
|
// Keep the old message's history but never acknowledge a replaced pairing.
|
|
777
|
-
const after = (await readAccess()) ?? fence();
|
|
799
|
+
const after = (await readAccess()) ?? fence(false);
|
|
778
800
|
return after === null ? sent : { ok: false, error: after };
|
|
779
801
|
};
|
|
780
802
|
const imageSender = async (row) => {
|
|
@@ -803,7 +825,7 @@ async function deliverOutboxTo(store, botId, binding, transport, owner, clock, r
|
|
|
803
825
|
// No await between the access fence, file validation and upload.
|
|
804
826
|
let answer;
|
|
805
827
|
try {
|
|
806
|
-
answer = await
|
|
828
|
+
answer = await wire("sendDocument", { chat_id: binding.chatId, caption: `${resultTaskLabel(row.taskId)} · result #${row.run} · screenshot for acceptance` }, undefined, {
|
|
807
829
|
field: "document", bytes: verified.bytes, contentType: verified.format === "png" ? "image/png" : "image/jpeg", fileName: resultImageFileName(row.taskId, row.run, artifact, verified.format),
|
|
808
830
|
});
|
|
809
831
|
}
|
|
@@ -819,7 +841,7 @@ async function deliverOutboxTo(store, botId, binding, transport, owner, clock, r
|
|
|
819
841
|
return { ok: false, error: answer.uncertain || (answer.ok && messageId == null) ? "Screenshot delivery is unconfirmed; retry may duplicate it" : "Telegram did not accept the screenshot" };
|
|
820
842
|
}
|
|
821
843
|
store.recordTelegramMessage(row, binding, String(messageId), clock());
|
|
822
|
-
const after = (await readAccess()) ?? fence();
|
|
844
|
+
const after = (await readAccess()) ?? fence(false);
|
|
823
845
|
return after === null ? { ok: true, receipt: receiptFor(botId, binding.chatId, String(messageId)) } : { ok: false, error: after };
|
|
824
846
|
};
|
|
825
847
|
/** Screenshots with a result (result-shots.ts): photos, several as one album, re-proved before every call. Each is
|
|
@@ -846,10 +868,10 @@ async function deliverOutboxTo(store, botId, binding, transport, owner, clock, r
|
|
|
846
868
|
let answer;
|
|
847
869
|
try {
|
|
848
870
|
answer = batch.length > 1
|
|
849
|
-
? await
|
|
871
|
+
? await wire("sendMediaGroup", { chat_id: binding.chatId, media: batch.map((one, index) => ({ type: "photo", media: `attach://shot${index}`, ...(index === 0 || one.numbered === true ? { caption: one.caption } : {}) })) }, undefined, upload(batch[0], "shot0"), batch.slice(1).map((one, index) => upload(one, `shot${index + 1}`)))
|
|
850
872
|
: batch[0].photo
|
|
851
|
-
? await
|
|
852
|
-
: await
|
|
873
|
+
? await wire("sendPhoto", { chat_id: binding.chatId, caption: batch[0].caption }, undefined, upload(batch[0], "photo"))
|
|
874
|
+
: await wire("sendDocument", { chat_id: binding.chatId, caption: batch[0].caption }, undefined, upload(batch[0], "document"));
|
|
853
875
|
}
|
|
854
876
|
catch {
|
|
855
877
|
answer = { ok: false, uncertain: true };
|
|
@@ -893,7 +915,7 @@ async function deliverOutboxTo(store, botId, binding, transport, owner, clock, r
|
|
|
893
915
|
const keyboard = button === null ? [] : [button];
|
|
894
916
|
if (messageId === null)
|
|
895
917
|
return sender(card.text, keyboard, rows, card.entities);
|
|
896
|
-
const edited = await editProgress(
|
|
918
|
+
const edited = await editProgress(wire, binding.chatId, messageId, card.text, keyboard, card.entities);
|
|
897
919
|
if (!edited.ok) {
|
|
898
920
|
if (edited.retryAfter !== undefined)
|
|
899
921
|
store.deferTelegram(botId, new Date(clock().getTime() + edited.retryAfter * 1000).toISOString());
|
|
@@ -905,7 +927,7 @@ async function deliverOutboxTo(store, botId, binding, transport, owner, clock, r
|
|
|
905
927
|
}
|
|
906
928
|
if (!onlyExisting)
|
|
907
929
|
store.recordTelegramMessage(row, binding, messageId, clock());
|
|
908
|
-
const after = (await readAccess()) ?? fence();
|
|
930
|
+
const after = (await readAccess()) ?? fence(false);
|
|
909
931
|
return after === null ? edited : { ok: false, error: after };
|
|
910
932
|
};
|
|
911
933
|
/** Quiet mode: the task's one card (or its group's), created silently and then only ever edited. */
|
|
@@ -929,11 +951,11 @@ async function deliverOutboxTo(store, botId, binding, transport, owner, clock, r
|
|
|
929
951
|
return { ok: true, messageId: card.message };
|
|
930
952
|
}
|
|
931
953
|
if (card.message !== null) {
|
|
932
|
-
const edited = await editProgress(
|
|
954
|
+
const edited = await editProgress(wire, binding.chatId, card.message, view.text, keyboard, view.entities);
|
|
933
955
|
if (edited.ok) {
|
|
934
956
|
store.setChatCardMessage(card.id, card.message, shown);
|
|
935
957
|
store.recordTelegramMessage(fact, binding, card.message, clock());
|
|
936
|
-
const after = (await readAccess()) ?? fence();
|
|
958
|
+
const after = (await readAccess()) ?? fence(false);
|
|
937
959
|
return after === null ? edited : { ok: false, error: after };
|
|
938
960
|
}
|
|
939
961
|
if (edited.retryAfter !== undefined)
|
|
@@ -1003,14 +1025,14 @@ async function deliverOutboxTo(store, botId, binding, transport, owner, clock, r
|
|
|
1003
1025
|
if (problem !== null)
|
|
1004
1026
|
return { ok: false, error: problem };
|
|
1005
1027
|
mintFor();
|
|
1006
|
-
const edited = await editProgress(
|
|
1028
|
+
const edited = await editProgress(wire, binding.chatId, batch.message, text, keyboard);
|
|
1007
1029
|
if (edited.ok) {
|
|
1008
1030
|
// The repaint landed: the old words' buttons retire and the new ones ride the message.
|
|
1009
1031
|
retireDecideTokens(store, "telegram", binding.chatId, batch.message, clock());
|
|
1010
1032
|
placeDecideTokens(store, tokens, batch.message);
|
|
1011
1033
|
store.setChatBatchMessage(batch.id, batch.message, shown);
|
|
1012
1034
|
store.recordTelegramMessage(fact, binding, batch.message, clock());
|
|
1013
|
-
const after = (await readAccess()) ?? fence();
|
|
1035
|
+
const after = (await readAccess()) ?? fence(false);
|
|
1014
1036
|
return after === null ? { ok: true, receipt: receiptFor(botId, binding.chatId, batch.message) } : { ok: false, error: after };
|
|
1015
1037
|
}
|
|
1016
1038
|
// A failed edit: the buttons it carried never act, and the card's old ones keep working.
|
|
@@ -1049,10 +1071,10 @@ async function deliverOutboxTo(store, botId, binding, transport, owner, clock, r
|
|
|
1049
1071
|
const problem = (await readAccess()) ?? fence();
|
|
1050
1072
|
if (problem !== null)
|
|
1051
1073
|
return { ok: false, error: problem };
|
|
1052
|
-
const edited = await editProgress(
|
|
1074
|
+
const edited = await editProgress(wire, binding.chatId, earlier, words, keyboard);
|
|
1053
1075
|
if (edited.ok) {
|
|
1054
1076
|
store.recordTelegramMessage(fact, binding, earlier, clock());
|
|
1055
|
-
const after = (await readAccess()) ?? fence();
|
|
1077
|
+
const after = (await readAccess()) ?? fence(false);
|
|
1056
1078
|
return after === null ? { ok: true, receipt: receiptFor(botId, binding.chatId, earlier) } : { ok: false, error: after };
|
|
1057
1079
|
}
|
|
1058
1080
|
if (edited.retryAfter !== undefined)
|
|
@@ -1082,7 +1104,7 @@ async function deliverOutboxTo(store, botId, binding, transport, owner, clock, r
|
|
|
1082
1104
|
? await deliverDigest(botId, binding, sender, rows, digest.lastSentAt, clock, taskId => chatTitle(store, taskId))
|
|
1083
1105
|
: await deliverOne(store, botId, binding, sender, rows[0], clock, phoneOrigin, evidenceRoot, projects);
|
|
1084
1106
|
}
|
|
1085
|
-
const finalProblem = outcome.ok ? (await readAccess()) ?? fence() : null;
|
|
1107
|
+
const finalProblem = outcome.ok ? (await readAccess()) ?? fence(false) : null;
|
|
1086
1108
|
const settled = finalProblem === null ? outcome : { ok: false, error: finalProblem };
|
|
1087
1109
|
// A skipped screenshot settles its row but nothing reached the phone.
|
|
1088
1110
|
const skipped = settled.ok && settled.receipt !== null && settled.receipt.startsWith("skipped:");
|
|
@@ -1748,6 +1770,8 @@ function applyMessage(context, update, effects) {
|
|
|
1748
1770
|
* message that carried several tasks (never guessed). Everything hostile is
|
|
1749
1771
|
* silence, as for every other inbound shape.
|
|
1750
1772
|
*/
|
|
1773
|
+
/** At most this many of a chat's messages wait for the lead at once (the personal lead queue's bound). */
|
|
1774
|
+
export const TELEGRAM_MAX_WAITING = 10;
|
|
1751
1775
|
function applyConversation(context, update, effects) {
|
|
1752
1776
|
const { store, botId, transport, clock, report } = context;
|
|
1753
1777
|
const message = update.message;
|
|
@@ -1812,6 +1836,12 @@ function applyConversation(context, update, effects) {
|
|
|
1812
1836
|
context_ = focusContextFor(focused);
|
|
1813
1837
|
}
|
|
1814
1838
|
}
|
|
1839
|
+
// The lead queue's bound, per chat: ten messages wait at most; the next is answered at once instead of queued.
|
|
1840
|
+
if (store.telegramMessagesWaiting(binding.id) >= TELEGRAM_MAX_WAITING) {
|
|
1841
|
+
say(`${TELEGRAM_MAX_WAITING} messages are already waiting for the lead. Wait for a reply, then send this again.`);
|
|
1842
|
+
report.ignored++;
|
|
1843
|
+
return;
|
|
1844
|
+
}
|
|
1815
1845
|
store.enqueueTelegramConversation({
|
|
1816
1846
|
binding, updateId: update.update_id, messageId: String(message.message_id), replyTo,
|
|
1817
1847
|
request: telegramRequestId(botId, binding.id, update.update_id), text, context: context_, taskId, sourceRun,
|
|
@@ -2248,6 +2278,10 @@ function applyCallback(context, update, effects) {
|
|
|
2248
2278
|
return;
|
|
2249
2279
|
}
|
|
2250
2280
|
const focused = store.chatFocus("telegram", binding.id);
|
|
2281
|
+
if (store.telegramMessagesWaiting(binding.id) >= TELEGRAM_MAX_WAITING) {
|
|
2282
|
+
ack(`${TELEGRAM_MAX_WAITING} messages are already waiting. Wait for a reply.`);
|
|
2283
|
+
return;
|
|
2284
|
+
}
|
|
2251
2285
|
store.enqueueTelegramConversation({
|
|
2252
2286
|
binding, updateId: update.update_id, messageId: String(message.message_id), replyTo: null,
|
|
2253
2287
|
request: telegramRequestId(botId, binding.id, update.update_id), text: option,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Database } from "./store.js";
|
|
2
|
+
import type { TerminalLedger } from "./live-terminal.js";
|
|
3
|
+
export declare function terminalCatalogPath(orders: Database): string | null;
|
|
4
|
+
/** The console's ledger: creates the catalog, drops rows of consoles that are gone, and writes its own. */
|
|
5
|
+
export declare function openTerminalLedger(orders: Database, alive?: (pid: number) => boolean): TerminalLedger | null;
|
|
6
|
+
export declare function installTerminalUpdateGate(orders: Database, id: string): void;
|
|
7
|
+
export declare function removeTerminalUpdateGate(orders: Database, id: string): void;
|
|
8
|
+
/** Terminals starting or running in a console that is still alive: active work an update waits for. */
|
|
9
|
+
export declare function activeTerminalUpdateWork(orders: Database, alive?: (pid: number) => boolean): {
|
|
10
|
+
terminals: number;
|
|
11
|
+
};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live terminals, written down for the updater (docs/plans/live-terminal.md, review round 2 finding 7). Terminals live
|
|
3
|
+
* in the console's memory, but an update is decided by another process, so each one is a row in a small catalog
|
|
4
|
+
* beside the database (`<db>.terminals.sqlite`), as the coding workspace keeps its own:
|
|
5
|
+
*
|
|
6
|
+
* - a start inserts its row before any work, and an update's gate (a trigger) refuses that insert while the update
|
|
7
|
+
* owns admission: no terminal starts during a drain;
|
|
8
|
+
* - a row is `starting` or `running` until the terminal's sweep ends, then deleted — or kept as `uncertain` when the
|
|
9
|
+
* sweep couldn't prove its processes gone, until the terminal is removed; the updater counts rows whose console is
|
|
10
|
+
* still alive as active work, so "update when idle" waits for running and unproved terminals;
|
|
11
|
+
* - rows of a console that is gone are stale (its programs went with it, or are swept at its next start).
|
|
12
|
+
*/
|
|
13
|
+
import { chmodSync, lstatSync } from "node:fs";
|
|
14
|
+
import { processMayBeAlive } from "./process-liveness.js";
|
|
15
|
+
import { sqliteRuntime } from "./sqlite-runtime.js";
|
|
16
|
+
const gatePrefix = "so_terminal_update_";
|
|
17
|
+
const PAUSED = "Toolroll is updating. Running terminals keep going; new ones can start after the update.";
|
|
18
|
+
const SCHEMA = "CREATE TABLE IF NOT EXISTS live_terminal (id TEXT PRIMARY KEY, console_pid INTEGER NOT NULL, state TEXT NOT NULL CHECK (state IN ('starting','running','uncertain')), at TEXT NOT NULL)";
|
|
19
|
+
export function terminalCatalogPath(orders) {
|
|
20
|
+
const main = orders.prepare("PRAGMA database_list").all().find(row => row["name"] === "main")?.["file"];
|
|
21
|
+
return typeof main === "string" && main !== "" ? `${main}.terminals.sqlite` : null;
|
|
22
|
+
}
|
|
23
|
+
function exists(file) {
|
|
24
|
+
try {
|
|
25
|
+
return lstatSync(file).isFile();
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function open(file, readOnly) {
|
|
32
|
+
if (exists(file) && lstatSync(file).isSymbolicLink())
|
|
33
|
+
throw Error("The terminal catalog is linked, not a regular database.");
|
|
34
|
+
const db = new (sqliteRuntime().DatabaseSync)(file, { readOnly });
|
|
35
|
+
db.exec("PRAGMA busy_timeout=1000");
|
|
36
|
+
return db;
|
|
37
|
+
}
|
|
38
|
+
/** The updater's read: never creates the catalog. */
|
|
39
|
+
function withCatalog(orders, absent, readOnly, action) {
|
|
40
|
+
const file = terminalCatalogPath(orders);
|
|
41
|
+
if (file === null || !exists(file))
|
|
42
|
+
return absent;
|
|
43
|
+
const db = open(file, readOnly);
|
|
44
|
+
try {
|
|
45
|
+
return action(db);
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
db.close();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** The console's ledger: creates the catalog, drops rows of consoles that are gone, and writes its own. */
|
|
52
|
+
export function openTerminalLedger(orders, alive = pid => processMayBeAlive(pid, false)) {
|
|
53
|
+
const file = terminalCatalogPath(orders);
|
|
54
|
+
if (file === null)
|
|
55
|
+
return null;
|
|
56
|
+
const db = open(file, false);
|
|
57
|
+
chmodSync(file, 0o600);
|
|
58
|
+
db.exec(SCHEMA);
|
|
59
|
+
for (const row of db.prepare("SELECT DISTINCT console_pid FROM live_terminal").all()) {
|
|
60
|
+
const pid = Number(row["console_pid"]);
|
|
61
|
+
if (pid !== process.pid && !alive(pid))
|
|
62
|
+
db.prepare("DELETE FROM live_terminal WHERE console_pid=?").run(pid);
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
reserve(id) {
|
|
66
|
+
// Fail closed even where this catalog had no gate to install (it didn't exist yet when the update began): the main
|
|
67
|
+
// database's own admission pause refuses too.
|
|
68
|
+
if (orders.prepare("SELECT 1 FROM sqlite_master WHERE type='trigger' AND name GLOB 'so_desktop_update_*' LIMIT 1").get() !== undefined)
|
|
69
|
+
throw new Error(PAUSED);
|
|
70
|
+
try {
|
|
71
|
+
db.prepare("INSERT INTO live_terminal (id, console_pid, state, at) VALUES (?, ?, 'starting', ?)").run(id, process.pid, new Date().toISOString());
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
throw new Error(error instanceof Error && error.message.includes(PAUSED) ? PAUSED : "No terminal can start right now (its record couldn't be written).");
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
running(id) { db.prepare("UPDATE live_terminal SET state='running', at=? WHERE id=?").run(new Date().toISOString(), id); },
|
|
78
|
+
// A sweep that couldn't prove its processes gone stays written down: the updater counts it while this console lives.
|
|
79
|
+
release(id, cleanup) {
|
|
80
|
+
try {
|
|
81
|
+
if (cleanup === "uncertain")
|
|
82
|
+
db.prepare("UPDATE live_terminal SET state='uncertain', at=? WHERE id=?").run(new Date().toISOString(), id);
|
|
83
|
+
else
|
|
84
|
+
db.prepare("DELETE FROM live_terminal WHERE id=?").run(id);
|
|
85
|
+
}
|
|
86
|
+
catch { /* the next start drops it as stale */ }
|
|
87
|
+
},
|
|
88
|
+
forget(id) { try {
|
|
89
|
+
db.prepare("DELETE FROM live_terminal WHERE id=?").run(id);
|
|
90
|
+
}
|
|
91
|
+
catch { /* stale later */ } },
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function gateStatement(id) {
|
|
95
|
+
if (!/^[a-f0-9-]{36}$/.test(id))
|
|
96
|
+
throw Error("Invalid update identity.");
|
|
97
|
+
return `CREATE TRIGGER ${gatePrefix}start BEFORE INSERT ON live_terminal BEGIN SELECT RAISE(ABORT, '${PAUSED} [${id}]'); END`;
|
|
98
|
+
}
|
|
99
|
+
function changeGate(db, id, install) {
|
|
100
|
+
const wanted = gateStatement(id);
|
|
101
|
+
db.exec("BEGIN IMMEDIATE");
|
|
102
|
+
try {
|
|
103
|
+
db.exec(SCHEMA);
|
|
104
|
+
const rows = db.prepare("SELECT name, sql FROM sqlite_master WHERE type='trigger' AND name GLOB 'so_terminal_update_*'").all();
|
|
105
|
+
if (rows.some(row => String(row["sql"]) !== wanted))
|
|
106
|
+
throw Error("Another or unrecognized update owns terminal admission. Its gate was preserved.");
|
|
107
|
+
if (install && rows.length === 0)
|
|
108
|
+
db.exec(wanted);
|
|
109
|
+
if (!install)
|
|
110
|
+
for (const row of rows)
|
|
111
|
+
db.exec(`DROP TRIGGER "${String(row["name"])}"`);
|
|
112
|
+
db.exec("COMMIT");
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
db.exec("ROLLBACK");
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
export function installTerminalUpdateGate(orders, id) { withCatalog(orders, undefined, false, db => changeGate(db, id, true)); }
|
|
120
|
+
export function removeTerminalUpdateGate(orders, id) { withCatalog(orders, undefined, false, db => changeGate(db, id, false)); }
|
|
121
|
+
/** Terminals starting or running in a console that is still alive: active work an update waits for. */
|
|
122
|
+
export function activeTerminalUpdateWork(orders, alive = pid => processMayBeAlive(pid, false)) {
|
|
123
|
+
return withCatalog(orders, { terminals: 0 }, true, db => {
|
|
124
|
+
if (!db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='live_terminal'").get())
|
|
125
|
+
return { terminals: 0 };
|
|
126
|
+
const rows = db.prepare("SELECT console_pid FROM live_terminal").all();
|
|
127
|
+
return { terminals: rows.filter(row => alive(Number(row["console_pid"]))).length };
|
|
128
|
+
});
|
|
129
|
+
}
|
package/dist/text-limits.d.ts
CHANGED
|
@@ -68,6 +68,8 @@ export declare const TEXT_LIMITS: {
|
|
|
68
68
|
readonly triggerState: 40;
|
|
69
69
|
readonly triggerWebhookTitle: 120;
|
|
70
70
|
readonly triggerWebhookField: 80;
|
|
71
|
+
/** v126: a Sentry or Vercel project (or Vercel team) a signed webhook is scoped to. */
|
|
72
|
+
readonly triggerWebhookScope: 120;
|
|
71
73
|
readonly triggerFolder: 100;
|
|
72
74
|
readonly triggerSender: 300;
|
|
73
75
|
readonly triggerSubject: 100;
|
package/dist/text-limits.js
CHANGED
|
@@ -68,6 +68,8 @@ export const TEXT_LIMITS = {
|
|
|
68
68
|
triggerState: 40,
|
|
69
69
|
triggerWebhookTitle: 120,
|
|
70
70
|
triggerWebhookField: 80,
|
|
71
|
+
/** v126: a Sentry or Vercel project (or Vercel team) a signed webhook is scoped to. */
|
|
72
|
+
triggerWebhookScope: 120,
|
|
71
73
|
triggerFolder: 100,
|
|
72
74
|
triggerSender: 300,
|
|
73
75
|
triggerSubject: 100,
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ToolSpec } from "./contracts/integration-metadata.js";
|
|
2
|
+
/** One tool a launch may use: its values (never leaving this process) and, for research, the only actions it offers. */
|
|
3
|
+
export type BrokeredTool = {
|
|
4
|
+
spec: ToolSpec;
|
|
5
|
+
values: Record<string, string>;
|
|
6
|
+
actions?: readonly string[];
|
|
7
|
+
};
|
|
8
|
+
export type BrokerOptions = {
|
|
9
|
+
/** Where a local server's environment comes from (the launch's): only SERVER_ENV's names are taken from it. */
|
|
10
|
+
env?: NodeJS.ProcessEnv;
|
|
11
|
+
omitEnv?: readonly string[];
|
|
12
|
+
/** Toolroll's own settings for a local server (the run's isolated database), passed as they are. */
|
|
13
|
+
pass?: Readonly<Record<string, string>>;
|
|
14
|
+
cwd?: string;
|
|
15
|
+
/** How a local server is started inside its run's fence, read when it starts (the fence is fixed after the tools). */
|
|
16
|
+
wrap?: () => ((file: string, args: readonly string[]) => {
|
|
17
|
+
file: string;
|
|
18
|
+
args: string[];
|
|
19
|
+
}) | null;
|
|
20
|
+
};
|
|
21
|
+
export type ToolBroker = {
|
|
22
|
+
/** The launch's token: the agent's only key to its tools. */
|
|
23
|
+
token: string;
|
|
24
|
+
url: (name: string) => string;
|
|
25
|
+
/** Its loopback port: the one a confined network leaves reachable for its agent (agent-fence.ts NetworkFence). */
|
|
26
|
+
port: number;
|
|
27
|
+
/** The local servers running now (the fence tests read them). */
|
|
28
|
+
pids: () => number[];
|
|
29
|
+
/** Revoke the token, stop every server, close the port. */
|
|
30
|
+
close: () => void;
|
|
31
|
+
};
|
|
32
|
+
/** What a local server inherits from the launch: enough to run and find its tools, and nothing that could be
|
|
33
|
+
* another tool's or a provider's secret. Its own configured values are added after. */
|
|
34
|
+
export declare const SERVER_ENV: readonly string[];
|
|
35
|
+
/** A local server's environment: the allowlisted names from `base` (and its LC_* locale), less `omit`, then `pass`,
|
|
36
|
+
* then the tool's own values. */
|
|
37
|
+
export declare function serverEnv(base: NodeJS.ProcessEnv, omit: readonly string[], pass: Readonly<Record<string, string>>, values: Readonly<Record<string, string>>): NodeJS.ProcessEnv;
|
|
38
|
+
/** Start one launch's broker on a loopback port. */
|
|
39
|
+
export declare function startToolBroker(tools: readonly BrokeredTool[], options?: BrokerOptions): Promise<ToolBroker>;
|