toolroll 0.9.63 → 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 +51 -0
- package/README.md +15 -5
- 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 +50 -2
- package/dist/agent-fence.js +157 -10
- 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 +235 -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 +197 -15
- 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/checkout-proof.d.ts +28 -0
- package/dist/checkout-proof.js +62 -0
- package/dist/claim.d.ts +12 -0
- package/dist/claim.js +19 -0
- package/dist/cli-help.d.ts +1 -1
- package/dist/cli-help.js +4 -1
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +21 -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/console-api.d.ts +12 -4
- package/dist/contracts/console-api.js +12 -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/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/desktop-update.d.ts +1 -1
- package/dist/discord-chat.js +4 -1
- package/dist/doctor.js +1 -1
- 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/exec.d.ts +78 -0
- package/dist/exec.js +367 -9
- package/dist/export-ui.js +2 -2
- package/dist/flow-engine.js +24 -5
- 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 +77 -8
- package/dist/flow-triggers.js +517 -70
- package/dist/flows-cli.js +8 -2
- package/dist/flows-ui.d.ts +0 -2
- package/dist/flows-ui.js +52 -4
- package/dist/flows.d.ts +4 -0
- package/dist/flows.js +9 -1
- package/dist/html.d.ts +9 -0
- package/dist/html.js +11 -0
- package/dist/invoke.d.ts +17 -0
- package/dist/invoke.js +217 -21
- package/dist/keys.d.ts +18 -14
- package/dist/keys.js +5 -0
- 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 +152 -1
- 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-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/operate.d.ts +2 -0
- package/dist/operate.js +275 -17
- 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/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/project-delete-ui.d.ts +12 -0
- package/dist/project-delete-ui.js +41 -9
- package/dist/project-delete.js +2 -2
- package/dist/project-memory.d.ts +3 -1
- package/dist/project-memory.js +57 -25
- package/dist/project-tools.d.ts +5 -3
- package/dist/project-tools.js +12 -9
- 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.d.ts +25 -2
- package/dist/provider.js +63 -9
- 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/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 +170 -5
- 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 +247 -40
- 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 +38 -8
- 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 +46 -5
- package/dist/server/runtime.d.ts +33 -2
- package/dist/server/session.d.ts +25 -1
- package/dist/server/session.js +18 -4
- package/dist/server/settings.js +121 -10
- 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-ui.js +4 -4
- package/dist/store-baseline.d.ts +1 -1
- package/dist/store-baseline.js +1 -1
- package/dist/store.d.ts +430 -15
- package/dist/store.js +1041 -52
- package/dist/subagent-desk.js +1 -1
- package/dist/summary.js +3 -0
- 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/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 +2 -0
- package/dist/tool-broker.js +1 -0
- package/dist/toolroll-update-ui.js +2 -2
- package/dist/toolroll-update.js +13 -3
- package/dist/tools-ui.js +2 -2
- 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 +16 -2
- package/dist/workspace-revision.js +3 -1
- package/package.json +11 -2
package/dist/subagent-desk.js
CHANGED
|
@@ -74,7 +74,7 @@ export function askSubagent(store, given, from, text, now) {
|
|
|
74
74
|
return { ok: false, said: `${name}'s desk can't take cards right now.` };
|
|
75
75
|
const firstLine = said.split("\n")[0].trim();
|
|
76
76
|
const title = firstLine.length <= 100 ? firstLine : `${firstLine.slice(0, 99)}…`;
|
|
77
|
-
const card = store.addFlowCard({ flow: desk.id, title, description: said === title ? null : said, stage: zone.id, by: from.who, source: { kind: "message", label: `Asked through ${from.via}`, url: null } }, now);
|
|
77
|
+
const card = store.addFlowCard({ flow: desk.id, title, description: said === title ? null : said, stage: zone.id, by: from.who, byKind: "person", source: { kind: "message", label: `Asked through ${from.via}`, url: null } }, now);
|
|
78
78
|
store.setFlowCardWatcher(card, from.who, true, now);
|
|
79
79
|
return { ok: true, said: `${name} has it. The answer comes to you when it's done.`, card, link: { label: "Open the card", path: flowCardHref(desk.id, card) } };
|
|
80
80
|
}
|
package/dist/summary.js
CHANGED
|
@@ -32,6 +32,9 @@ export function tally(runs) {
|
|
|
32
32
|
* key spend and must never be presented as a separate charge.
|
|
33
33
|
*/
|
|
34
34
|
export function runCostWords(run, live = false) {
|
|
35
|
+
// An ACP agent signs in its own way and reports no price Toolroll trusts.
|
|
36
|
+
if (run.authMode === "agent-login")
|
|
37
|
+
return live ? "the agent's own sign-in · in progress" : "the agent's own sign-in · cost not measured";
|
|
35
38
|
if (run.authMode === "subscription") {
|
|
36
39
|
return run.costUsd === null
|
|
37
40
|
? live
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a task came from (docs/plans/webhook-tasks.md §5): an outside event's delivery, kept with the task. A card's
|
|
3
|
+
* source is written once at receipt (who proved it, the outside thing's identity and link); the task filed from that
|
|
4
|
+
* card keeps a snapshot of it (task_source), which its revisions copy, so the reference outlives the card's own
|
|
5
|
+
* task link and zone. Every word shown comes from that snapshot — never from today's trigger settings.
|
|
6
|
+
*/
|
|
7
|
+
import type { FlowCardSource, Store } from "./store.js";
|
|
8
|
+
/** What a task keeps of the card it was filed from. */
|
|
9
|
+
export type TaskSourceSnapshot = {
|
|
10
|
+
label: string;
|
|
11
|
+
verified: string;
|
|
12
|
+
external: {
|
|
13
|
+
id: string | null;
|
|
14
|
+
url: string | null;
|
|
15
|
+
};
|
|
16
|
+
receipt: string | null;
|
|
17
|
+
};
|
|
18
|
+
/** As much of a card's source as a reference reads. */
|
|
19
|
+
export type SourceLike = Pick<FlowCardSource, "label"> & Partial<Pick<FlowCardSource, "verified" | "external" | "receipt">>;
|
|
20
|
+
/** The snapshot an outside event's card gives its task, or null when the card didn't come from one (a person's, a poll's). */
|
|
21
|
+
export declare function snapshotOf(source: SourceLike | null): TaskSourceSnapshot | null;
|
|
22
|
+
/** A snapshot as saved, read back strictly: anything else is no snapshot at all. */
|
|
23
|
+
export declare function readSnapshot(json: string): TaskSourceSnapshot | null;
|
|
24
|
+
/** The host a link names, for words that always show where it goes. */
|
|
25
|
+
export declare function hostOf(url: string | null): string | null;
|
|
26
|
+
/** Where a link goes, as it is shown: its host and path (no scheme), the middle elided when long, so the destination is
|
|
27
|
+
* always visible beside the label. */
|
|
28
|
+
export declare function linkWords(url: string, max?: number): string;
|
|
29
|
+
/** How a snapshot's delivery was proved, in words: "signed by Sentry", or "unsigned". */
|
|
30
|
+
export declare function provedWords(snapshot: TaskSourceSnapshot): string;
|
|
31
|
+
/**
|
|
32
|
+
* The reference a task's brief carries: what the outside thing is, from where, and that it is data, not instructions.
|
|
33
|
+
* Bounded (an identity is at most 120 characters and a link 2,000), and said plainly when it was unsigned.
|
|
34
|
+
*/
|
|
35
|
+
/** A reference's words, at most: every reader of them (the planner's source, the scout's brief) accepts exactly this. The
|
|
36
|
+
* largest there can be (a 253-character host, 120-character label and identity, a 2,000-character link) fits. */
|
|
37
|
+
export declare const REFERENCE_LIMIT = 3000;
|
|
38
|
+
export declare function referenceWords(snapshot: TaskSourceSnapshot): string;
|
|
39
|
+
/** The same reference for a card that hasn't filed its task yet ({{card.source}}), or "" for a card from no outside event. */
|
|
40
|
+
export declare function cardReference(source: SourceLike | null | undefined): string;
|
|
41
|
+
/** Where a task came from, in the words a page shows: the flow (named and linked only for a viewer who can see it) and
|
|
42
|
+
* the outside event (its label, how it was proved, its link and that link's host), all from what was kept. */
|
|
43
|
+
export type TaskOrigin = {
|
|
44
|
+
flow: {
|
|
45
|
+
name: string;
|
|
46
|
+
href: string;
|
|
47
|
+
} | "hidden" | "removed" | null;
|
|
48
|
+
source: {
|
|
49
|
+
label: string;
|
|
50
|
+
proved: string;
|
|
51
|
+
url: string | null;
|
|
52
|
+
host: string | null;
|
|
53
|
+
} | null;
|
|
54
|
+
};
|
|
55
|
+
/** A task's origin, or null when it came from neither a flow nor an outside event. */
|
|
56
|
+
export declare function originOf(store: Store, taskId: string, canSee: (repo: string) => boolean): TaskOrigin | null;
|
|
57
|
+
/** The one line an approval shows about an outside event that started its work: context, never a term agreed to. */
|
|
58
|
+
export declare function originNote(origin: TaskOrigin | null | undefined): string | null;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { SENDER_WORDS } from "./webhook-signing.js";
|
|
2
|
+
/** The snapshot an outside event's card gives its task, or null when the card didn't come from one (a person's, a poll's). */
|
|
3
|
+
export function snapshotOf(source) {
|
|
4
|
+
if (source === null || typeof source.verified !== "string")
|
|
5
|
+
return null;
|
|
6
|
+
return { label: source.label.slice(0, 120), verified: source.verified, external: { id: source.external?.id ?? null, url: source.external?.url ?? null }, receipt: source.receipt ?? null };
|
|
7
|
+
}
|
|
8
|
+
/** A snapshot as saved, read back strictly: anything else is no snapshot at all. */
|
|
9
|
+
export function readSnapshot(json) {
|
|
10
|
+
try {
|
|
11
|
+
const raw = JSON.parse(json);
|
|
12
|
+
const external = raw["external"];
|
|
13
|
+
const optional = (value) => typeof value === "string" ? value : null;
|
|
14
|
+
if (typeof raw["label"] !== "string" || typeof raw["verified"] !== "string" || external === undefined || external === null || typeof external !== "object")
|
|
15
|
+
return null;
|
|
16
|
+
return { label: raw["label"], verified: raw["verified"], external: { id: optional(external["id"]), url: optional(external["url"]) }, receipt: optional(raw["receipt"]) };
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** The host a link names, for words that always show where it goes. */
|
|
23
|
+
export function hostOf(url) {
|
|
24
|
+
if (url === null)
|
|
25
|
+
return null;
|
|
26
|
+
try {
|
|
27
|
+
return new URL(url).host;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** Where a link goes, as it is shown: its host and path (no scheme), the middle elided when long, so the destination is
|
|
34
|
+
* always visible beside the label. */
|
|
35
|
+
export function linkWords(url, max = 72) {
|
|
36
|
+
let host, path;
|
|
37
|
+
try {
|
|
38
|
+
const parsed = new URL(url);
|
|
39
|
+
host = parsed.host;
|
|
40
|
+
path = parsed.pathname === "/" ? "" : parsed.pathname;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return "a link";
|
|
44
|
+
}
|
|
45
|
+
// The host is never shortened (a cut inside it could show one site's name for another's address; URL.host is already
|
|
46
|
+
// punycode for any non-ASCII name): only the path's middle is elided when long.
|
|
47
|
+
const room = Math.max(12, max - host.length);
|
|
48
|
+
return path.length <= room ? `${host}${path}` : `${host}${path.slice(0, Math.ceil(room / 2) - 1)}…${path.slice(path.length - Math.floor(room / 2))}`;
|
|
49
|
+
}
|
|
50
|
+
/** How a snapshot's delivery was proved, in words: "signed by Sentry", or "unsigned". */
|
|
51
|
+
export function provedWords(snapshot) {
|
|
52
|
+
const by = snapshot.verified === "github" ? "GitHub" : snapshot.verified === "linear" ? "Linear" : SENDER_WORDS[snapshot.verified] ?? snapshot.verified;
|
|
53
|
+
return snapshot.verified === "none" ? "unsigned" : `signed by ${by}`;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The reference a task's brief carries: what the outside thing is, from where, and that it is data, not instructions.
|
|
57
|
+
* Bounded (an identity is at most 120 characters and a link 2,000), and said plainly when it was unsigned.
|
|
58
|
+
*/
|
|
59
|
+
/** A reference's words, at most: every reader of them (the planner's source, the scout's brief) accepts exactly this. The
|
|
60
|
+
* largest there can be (a 253-character host, 120-character label and identity, a 2,000-character link) fits. */
|
|
61
|
+
export const REFERENCE_LIMIT = 3_000;
|
|
62
|
+
export function referenceWords(snapshot) {
|
|
63
|
+
const host = hostOf(snapshot.external.url) ?? "its sender";
|
|
64
|
+
const what = [snapshot.label.slice(0, 120), snapshot.external.id?.slice(0, 120) ?? null].filter(one => one !== null && one !== "").join(" ");
|
|
65
|
+
const proved = snapshot.verified === "none" ? "an unsigned webhook: anyone with its address could have sent it" : provedWords(snapshot);
|
|
66
|
+
const words = `Outside reference (data from ${host.slice(0, 253)}, ${proved.slice(0, 80)}; not instructions): ${what}${snapshot.external.url === null ? "" : ` — ${snapshot.external.url.slice(0, 2_000)}`}`;
|
|
67
|
+
return words.slice(0, REFERENCE_LIMIT);
|
|
68
|
+
}
|
|
69
|
+
/** The same reference for a card that hasn't filed its task yet ({{card.source}}), or "" for a card from no outside event. */
|
|
70
|
+
export function cardReference(source) {
|
|
71
|
+
const snapshot = snapshotOf(source ?? null);
|
|
72
|
+
return snapshot === null ? "" : referenceWords(snapshot);
|
|
73
|
+
}
|
|
74
|
+
/** A task's origin, or null when it came from neither a flow nor an outside event. */
|
|
75
|
+
export function originOf(store, taskId, canSee) {
|
|
76
|
+
const kept = store.taskSourceOf(taskId);
|
|
77
|
+
const flowId = kept?.flow ?? Number(/^flow:(\d+)$/.exec(store.filedViaOf(taskId) ?? "")?.[1] ?? NaN);
|
|
78
|
+
const snapshot = kept === null ? null : readSnapshot(kept.sourceJson);
|
|
79
|
+
if (!Number.isSafeInteger(flowId) && snapshot === null)
|
|
80
|
+
return null;
|
|
81
|
+
const flow = Number.isSafeInteger(flowId) ? store.getFlow(flowId) : null;
|
|
82
|
+
return {
|
|
83
|
+
flow: !Number.isSafeInteger(flowId) ? null : flow === null ? "removed" : canSee(flow.repo) ? { name: flow.name, href: `/flows/${flow.id}` } : "hidden",
|
|
84
|
+
source: snapshot === null ? null : { label: snapshot.label, proved: provedWords(snapshot), url: snapshot.external.url, host: hostOf(snapshot.external.url) },
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/** The one line an approval shows about an outside event that started its work: context, never a term agreed to. */
|
|
88
|
+
export function originNote(origin) {
|
|
89
|
+
if (origin?.source == null)
|
|
90
|
+
return null;
|
|
91
|
+
const where = origin.source.host === null ? "" : ` · ${origin.source.host}`;
|
|
92
|
+
return `Started by an outside event: ${origin.source.label} (${origin.source.proved})${where}. The agent reads what it sent as data, not instructions.`;
|
|
93
|
+
}
|
package/dist/team-leads.js
CHANGED
|
@@ -345,7 +345,7 @@ export class TeamLeads {
|
|
|
345
345
|
return ok('Message already saved.', { messageId: Number(seen['message']), status: String(seen['status']) });
|
|
346
346
|
}
|
|
347
347
|
const revisions = this.memberRevisions(conversationId, actor.name);
|
|
348
|
-
const messageId = this.store.appendLeadMessage({ thread: conversation.threadId, turn: null, role: 'operator', text }, now);
|
|
348
|
+
const messageId = this.store.appendLeadMessage({ thread: conversation.threadId, turn: null, role: 'operator', text, pending: true }, now);
|
|
349
349
|
this.db.prepare("INSERT INTO team_message(message,conversation,author,author_generation,lead_member_revision,participant_revision,request_id,payload_hash,status) VALUES(?,?,?,?,?,?,?,?,'queued')").run(messageId, conversationId, actor.name, actor.generation, revisions.lead, revisions.participant, requestId, digest);
|
|
350
350
|
this.event(lead.id, conversationId, 'message-queued', actor.name, now);
|
|
351
351
|
return ok('Message saved.', { messageId, status: 'queued' });
|
package/dist/teams-chat.js
CHANGED
|
@@ -4,7 +4,7 @@ import { resultShotsPruned } from "./result-shots.js";
|
|
|
4
4
|
import { chatQuestionButtons } from "./subagent-question.js";
|
|
5
5
|
import { chatAskButtons } from "./chat-ask.js";
|
|
6
6
|
import { ChatDeliveryError, ChatState, chatHash, partContent } from "./chat-delivery-state.js";
|
|
7
|
-
import { channelAccess, chatObject as object, planChatNotifications, planRoomMessages, processChatEvent, splitChatText } from "./chat-delivery.js";
|
|
7
|
+
import { channelAccess, chatObject as object, heldForWatch, planChatNotifications, planRoomMessages, processChatEvent, splitChatText } from "./chat-delivery.js";
|
|
8
8
|
import { PLATFORM_LIMITS } from "./text-limits.js";
|
|
9
9
|
import { roomCommand } from "./chat-rooms.js";
|
|
10
10
|
import { LEAD_MESSAGE_MAX_CHARS } from "./lead.js";
|
|
@@ -236,6 +236,9 @@ export async function deliverTeamsPart(options) {
|
|
|
236
236
|
const bodies = teamsMessages(voiced ?? text, voiced !== null, one => actions.length || content.proposal ? teamsCard(one, actions) : { type: "message", text: one, textFormat: voiced !== null ? "markdown" : "plain" });
|
|
237
237
|
const activities = `/v3/conversations/${encodeURIComponent(destination)}/activities`;
|
|
238
238
|
// An edit replaces its one message with the first part; any more follow it as new messages.
|
|
239
|
+
// The watch, at the last moment before the network (cockpit §3).
|
|
240
|
+
if (heldForWatch(state, store, row.id, event, binding.approver, content, now))
|
|
241
|
+
return true;
|
|
239
242
|
let answer = await options.api(target ? "PUT" : "POST", serviceUrl, target ? `${activities}/${encodeURIComponent(target)}` : activities, bodies[0]);
|
|
240
243
|
for (const more of bodies.slice(1))
|
|
241
244
|
answer = await options.api("POST", serviceUrl, activities, more);
|
package/dist/telegram-lead.d.ts
CHANGED
|
@@ -9,6 +9,9 @@ export declare const CONVERSATION_CLAIM_MS: number;
|
|
|
9
9
|
/** A busy engine (one turn at a time) defers a queued message this long, up to the age bound below. */
|
|
10
10
|
export declare const CONVERSATION_RETRY_MS = 5000;
|
|
11
11
|
export declare const CONVERSATION_MAX_AGE_MS: number;
|
|
12
|
+
/** Waiting for the lead itself (it is answering, or earlier messages wait in the person's lead queue) is bounded longer:
|
|
13
|
+
* two hours from the message's arrival, never reset as it moves from waiting behind the queue to waiting on a turn. */
|
|
14
|
+
export declare const CONVERSATION_WAIT_MAX_AGE_MS: number;
|
|
12
15
|
/** A card's buttons and an armed irreversible challenge live this long. */
|
|
13
16
|
export declare const CARD_TTL_MS: number;
|
|
14
17
|
export declare const CHALLENGE_TTL_MS: number;
|
package/dist/telegram-lead.js
CHANGED
|
@@ -39,6 +39,11 @@ export const CONVERSATION_CLAIM_MS = 2 * 60_000;
|
|
|
39
39
|
/** A busy engine (one turn at a time) defers a queued message this long, up to the age bound below. */
|
|
40
40
|
export const CONVERSATION_RETRY_MS = 5_000;
|
|
41
41
|
export const CONVERSATION_MAX_AGE_MS = 10 * 60_000;
|
|
42
|
+
/** Waiting for the lead itself (it is answering, or earlier messages wait in the person's lead queue) is bounded longer:
|
|
43
|
+
* two hours from the message's arrival, never reset as it moves from waiting behind the queue to waiting on a turn. */
|
|
44
|
+
export const CONVERSATION_WAIT_MAX_AGE_MS = 2 * 3_600_000;
|
|
45
|
+
/** A message whose turn was admitted, waiting for the project records to deliver its answer (no age bound). */
|
|
46
|
+
const ADMITTED_REGISTRY_WAIT = "registry-unavailable:admitted";
|
|
42
47
|
/** A card's buttons and an armed irreversible challenge live this long. */
|
|
43
48
|
export const CARD_TTL_MS = 24 * 3_600_000;
|
|
44
49
|
export const CHALLENGE_TTL_MS = 10 * 60_000;
|
|
@@ -367,12 +372,19 @@ async function runTelegramConversation(row, args) {
|
|
|
367
372
|
const held = () => store.renewTelegramConversation(row.id, owner, CONVERSATION_CLAIM_MS, clock());
|
|
368
373
|
const ageMs = clock().getTime() - Date.parse(row.createdAt);
|
|
369
374
|
const requeue = async (outcome) => {
|
|
370
|
-
|
|
375
|
+
// A turn already admitted for it waits for that turn's own outcome (bounded by the turn's deadline); waiting for the
|
|
376
|
+
// lead gets two hours from arrival; anything else, ten minutes.
|
|
377
|
+
const limit = outcome === "replayed:running" || outcome === ADMITTED_REGISTRY_WAIT ? Number.POSITIVE_INFINITY
|
|
378
|
+
: outcome === "busy" || outcome === "backlog" ? CONVERSATION_WAIT_MAX_AGE_MS : CONVERSATION_MAX_AGE_MS;
|
|
379
|
+
if (ageMs > limit) {
|
|
371
380
|
await notify(couldNotAnswerText(outcome === "registry-unavailable" ? "your projects couldn't be read" : "the assistant stayed busy with another message"));
|
|
372
381
|
finish({ state: "failed", outcome: `${outcome}:gave-up` });
|
|
373
382
|
report.refused++;
|
|
374
383
|
return;
|
|
375
384
|
}
|
|
385
|
+
// Behind the person's own earlier messages: said once, then it waits quietly.
|
|
386
|
+
if (outcome === "backlog" && row.outcome !== "backlog")
|
|
387
|
+
await notify("Queued behind your earlier messages. It goes to the lead once they're answered.");
|
|
376
388
|
finish({ state: "queued", outcome, nextAttemptAt: new Date(clock().getTime() + CONVERSATION_RETRY_MS).toISOString() });
|
|
377
389
|
};
|
|
378
390
|
// 1. The exact binding the message arrived under must still be the live one.
|
|
@@ -435,7 +447,9 @@ async function runTelegramConversation(row, args) {
|
|
|
435
447
|
continue;
|
|
436
448
|
}
|
|
437
449
|
}
|
|
438
|
-
|
|
450
|
+
// A day from when this part was planned (its card's tokens were minted then), not from the message's arrival: an
|
|
451
|
+
// answer that waited out a registry outage still gets its full day to go out.
|
|
452
|
+
if (now.getTime() - Date.parse(part.createdAt) > DELIVERY_MAX_AGE_MS) {
|
|
439
453
|
report.problems.push(`telegram chat reply for update ${row.updateId} was not sent within a day: ${part.lastError ?? "unsent"}`);
|
|
440
454
|
await notify(couldNotAnswerText("my reply couldn't be sent for a day"));
|
|
441
455
|
finish({ state: "failed", outcome: `unsent:gave-up:${part.lastError ?? "unsent"}` });
|
|
@@ -644,7 +658,7 @@ async function runTelegramConversation(row, args) {
|
|
|
644
658
|
return;
|
|
645
659
|
}
|
|
646
660
|
if (turn.state !== "answered") {
|
|
647
|
-
await notify(leadFailureText(turn.failureReason));
|
|
661
|
+
await notify(leadFailureText(turn.failureReason, store.leadTurnCostUnconfirmed(turn.id)));
|
|
648
662
|
finish({ state: "failed", outcome: `replayed:${turn.failureReason ?? "failed"}` });
|
|
649
663
|
report.refused++;
|
|
650
664
|
return;
|
|
@@ -668,14 +682,31 @@ async function runTelegramConversation(row, args) {
|
|
|
668
682
|
await deliver(row.outcome === ANSWERED_SENDING ? "answered" : "replayed");
|
|
669
683
|
return;
|
|
670
684
|
}
|
|
671
|
-
// 3.
|
|
685
|
+
// 3. A turn already admitted for it and still working: wait for that turn's own outcome. Nothing here needs the
|
|
686
|
+
// project records, so a registry that can't be read never fails a message whose turn is running.
|
|
687
|
+
if (row.session !== null) {
|
|
688
|
+
const admitted = store.leadRequestReceipt(row.session, row.request);
|
|
689
|
+
if (admitted !== null) {
|
|
690
|
+
store.sweepStaleLeadTurns(clock());
|
|
691
|
+
const turn = store.getLeadTurn(admitted.turn);
|
|
692
|
+
if (turn !== null && (turn.state === "queued" || turn.state === "running")) {
|
|
693
|
+
store.bindTelegramConversationTurn(row.id, owner, row.session, admitted.turn);
|
|
694
|
+
await requeue("replayed:running");
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
// The ceiling, reloaded now.
|
|
672
700
|
let registry;
|
|
673
701
|
try {
|
|
674
702
|
registry = await readProjects();
|
|
675
703
|
}
|
|
676
704
|
catch {
|
|
677
705
|
report.problems.push("telegram chat could not read the current project records");
|
|
678
|
-
|
|
706
|
+
// A turn already took it (its receipt is in the bound session): its answer exists or will, and is delivered once
|
|
707
|
+
// the projects can be read again — never abandoned to the outage.
|
|
708
|
+
const admitted = row.session !== null && store.leadRequestReceipt(row.session, row.request) !== null;
|
|
709
|
+
await requeue(admitted ? ADMITTED_REGISTRY_WAIT : "registry-unavailable");
|
|
679
710
|
return;
|
|
680
711
|
}
|
|
681
712
|
const repos = telegramConversationRepos(store, binding.approver, registry);
|
|
@@ -688,6 +719,12 @@ async function runTelegramConversation(row, args) {
|
|
|
688
719
|
return;
|
|
689
720
|
}
|
|
690
721
|
}
|
|
722
|
+
// It waited (for the lead, or behind earlier messages), was never admitted, and is past the wait from its arrival:
|
|
723
|
+
// it is never sent this late, even when the lead is free now.
|
|
724
|
+
if ((row.outcome === "busy" || row.outcome === "backlog") && ageMs > CONVERSATION_WAIT_MAX_AGE_MS) {
|
|
725
|
+
await requeue(row.outcome);
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
691
728
|
// 5. The session and thread — shared with the console and the CLI.
|
|
692
729
|
const resolved = resolveTelegramLead(store, binding, repos, clock());
|
|
693
730
|
if (!resolved.ok && resolved.reason === "busy") {
|
|
@@ -743,8 +780,8 @@ async function runTelegramConversation(row, args) {
|
|
|
743
780
|
if (receipt !== null)
|
|
744
781
|
store.bindTelegramConversationTurn(row.id, owner, session.id, receipt.turn);
|
|
745
782
|
if (!outcome.ok && "refused" in outcome) {
|
|
746
|
-
if (outcome.refused === "concurrent") {
|
|
747
|
-
await requeue("busy");
|
|
783
|
+
if (outcome.refused === "concurrent" || outcome.refused === "backlog") {
|
|
784
|
+
await requeue(outcome.refused === "backlog" ? "backlog" : "busy");
|
|
748
785
|
return;
|
|
749
786
|
}
|
|
750
787
|
await notify(phoneText(outcome.message, 1_000));
|
package/dist/telegram.d.ts
CHANGED
|
@@ -20,8 +20,6 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import { type Store, type Notification } from "./store.js";
|
|
22
22
|
import { type TelegramConversationOptions } from "./telegram-lead.js";
|
|
23
|
-
/** Read the enrolled project list on demand. No callback means no task data,
|
|
24
|
-
* never an implicit all-database ceiling. Shared by pass and embedded follower. */
|
|
25
23
|
export type TelegramReadProjects = () => Promise<readonly string[]>;
|
|
26
24
|
/** The environment name — and therefore the name the builder strips from agents (both names; see names.ts). */
|
|
27
25
|
export { TELEGRAM_TOKEN_ENV as TOKEN_ENV, TELEGRAM_TOKEN_ENVS as TOKEN_ENVS } from "./names.js";
|
|
@@ -244,3 +242,14 @@ export declare function followBridge(store: Store, options: {
|
|
|
244
242
|
* fact per entry — its subject, then its body's first line, indented.
|
|
245
243
|
* Plain text, no buttons: nothing in a digest is tappable. */
|
|
246
244
|
export declare function digestText(rows: readonly Notification[], since: string | null, now: Date, titleOf?: (taskId: string) => string): string;
|
|
245
|
+
/**
|
|
246
|
+
* An ordinary message from the paired person: persisted for the shared
|
|
247
|
+
* assistant in THIS transaction — before the cursor moves — with the exact
|
|
248
|
+
* binding, sender, update, message and the request identity the engine
|
|
249
|
+
* receipts its turn under. Two things are answered here and now without a
|
|
250
|
+
* model: text over the engine's bound (never truncated), and a reply to a
|
|
251
|
+
* message that carried several tasks (never guessed). Everything hostile is
|
|
252
|
+
* silence, as for every other inbound shape.
|
|
253
|
+
*/
|
|
254
|
+
/** At most this many of a chat's messages wait for the lead at once (the personal lead queue's bound). */
|
|
255
|
+
export declare const TELEGRAM_MAX_WAITING = 10;
|
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
|
+
};
|