@agentshouse/kit 0.1.0-alpha.28 → 0.1.0-alpha.29
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/README.md +88 -42
- package/bootstrap.json +3 -3
- package/package.json +1 -1
- package/src/binding.js +11 -0
- package/src/blocks.js +2 -26
- package/src/bridge.js +23 -13
- package/src/contract.js +3 -7
- package/src/draft.js +4 -82
- package/src/house.js +63 -47
- package/src/kit-work-stream.js +9 -1
- package/src/launcher.js +21 -6
- package/src/pacing.js +41 -0
- package/src/plan.js +37 -45
- package/src/telegram-view.js +214 -0
- package/src/telegram.js +124 -53
- package/src/turn-frames.js +69 -0
- package/src/worker.js +45 -37
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ The Skill set Kit installs is Matt Pocock's
|
|
|
12
12
|
On Linux `amd64` or `arm64`, run the published, versioned bootstrap once:
|
|
13
13
|
|
|
14
14
|
```sh
|
|
15
|
-
bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.
|
|
15
|
+
bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.29/connect-linux.sh)
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
The bootstrap first establishes Docker Engine from what it observes on the
|
|
@@ -564,8 +564,12 @@ command returns exactly one JSON object such as `{ "authorized": true }`.
|
|
|
564
564
|
The resident Kit holds one `/kit/stream` WebSocket for its Environment,
|
|
565
565
|
authenticated with its Environment control lease and the Kit contract header,
|
|
566
566
|
for as long as it holds that lease; `kit worker run` holds one for the length
|
|
567
|
-
of its pass to receive its owner's commands
|
|
568
|
-
|
|
567
|
+
of its pass to receive its owner's commands, and `kit telegram serve` holds one
|
|
568
|
+
while it serves the gateway under a lease of its own. The Kit sends no
|
|
569
|
+
subscribe frame and accepts every frame House sends. What it writes there is
|
|
570
|
+
the live view of the turns it runs, as draft and plan frames the
|
|
571
|
+
[Worker runtime](#the-worker-runtime) describes, and a `gap` frame when the
|
|
572
|
+
[Telegram gateway](#the-telegram-gateway) needs a turn's whole view again.
|
|
569
573
|
|
|
570
574
|
Each time the socket connects, the resident acquires everything a signal could
|
|
571
575
|
have named, because a signal House sent while the socket was down is lost: its
|
|
@@ -866,37 +870,55 @@ what it has seen so far, and seals the turn itself. No Agent is asked to report
|
|
|
866
870
|
text a deterministic observer already sees, and no agent-facing tool admits final
|
|
867
871
|
text.
|
|
868
872
|
|
|
869
|
-
While the adapter generates, the
|
|
870
|
-
of the turn so far
|
|
873
|
+
While the adapter generates, the supervisor writes the whole User-visible
|
|
874
|
+
assistant text of the turn so far as `draft` frames on the Environment's
|
|
875
|
+
[control stream](#the-kit-control-stream); it calls no draft route. Only
|
|
871
876
|
adapter-classified assistant message text enters that stream; reasoning, tool
|
|
872
|
-
calls,
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
and
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
877
|
+
calls, and terminal output never do. The Kit maps the structure it can read in
|
|
878
|
+
that text - paragraphs, headings, fenced code, ordered and unordered lists, pipe
|
|
879
|
+
tables, and `$$` formulas - into House's blocks, and leaves anything it did not
|
|
880
|
+
parse as the paragraph it observed. It invents no structure, renders nothing,
|
|
881
|
+
and holds back or refuses no draft for its length: a frame carries every block
|
|
882
|
+
of the text however long it is.
|
|
883
|
+
|
|
884
|
+
A frame names the turn it belongs to, as House answered the Session's `started`
|
|
885
|
+
observation with the turn's `conversation_id` and `turn_id`, together with the
|
|
886
|
+
Execution attempt as `attempt_id`. It carries a `sequence` that grows by one
|
|
887
|
+
with each paced view of the turn, and replaces the turn's view from the block
|
|
888
|
+
index `from` onward with the `blocks` it carries, so a frame for text that only
|
|
889
|
+
grew carries the blocks from the first one that changed, and a view that
|
|
890
|
+
changed no block is a frame that replaces nothing. The launcher numbers the
|
|
891
|
+
views, so a Kit that restarts mid-turn continues the turn's sequence. The Kit
|
|
892
|
+
coalesces and paces that stream rather than mirroring every chunk: it writes
|
|
893
|
+
the first observed view at once and then at most one view in each
|
|
894
|
+
500-millisecond pacing window, the latest one, even when the adapter completes
|
|
895
|
+
a message inside a window. A frame written while the socket is down is lost,
|
|
896
|
+
so the first frame after each reconnect, and after any frame the socket could not carry, replaces the view
|
|
897
|
+
from block 0. A Session House names no conversation turn for gets no frames. A
|
|
898
|
+
draft persists nowhere - not in this Kit's local state, not as House history -
|
|
899
|
+
is never retried, and can neither delay nor prove turn completion.
|
|
900
|
+
|
|
901
|
+
A `plan` session update is one snapshot of the agent's steps. The Kit writes
|
|
902
|
+
each snapshot as a `plan` frame paced by the same rule, carrying every step's
|
|
903
|
+
`label`, `priority` and `status` exactly as the adapter gave them, its own
|
|
904
|
+
`sequence`, and the step index `from` it replaces the plan onward. The act that
|
|
905
|
+
ends the turn carries the last snapshot the Kit saw as its final `plan`: the
|
|
906
|
+
seal, the observed stop, the Run outcome and an `interrupted` interrupt outcome
|
|
907
|
+
each name the provider turn and every step's `label` and `status`, however many
|
|
908
|
+
steps and however long their text, and a turn that never planned carries none.
|
|
909
|
+
There is no per-snapshot plan route.
|
|
889
910
|
|
|
890
911
|
When the adapter completes the turn, the Kit seals it with one
|
|
891
|
-
`sessions/<session>/assistant-messages` call carrying the same blocks
|
|
892
|
-
UUIDv7 `operation_id`. The exact request - that identity
|
|
893
|
-
written into the WorkerSession binding before the
|
|
894
|
-
call returns the outcome House recorded and never
|
|
895
|
-
House's answer names the turn it closed and the
|
|
896
|
-
the Kit never received leaves the Run unsettled
|
|
897
|
-
next `kit worker run` replays exactly that
|
|
898
|
-
|
|
899
|
-
House did decide is the Run's
|
|
912
|
+
`sessions/<session>/assistant-messages` call carrying the same blocks, the final
|
|
913
|
+
plan, and a UUIDv7 `operation_id`. The exact request - that identity, those
|
|
914
|
+
blocks and that plan - is written into the WorkerSession binding before the
|
|
915
|
+
call leaves, so a recovered call returns the outcome House recorded and never
|
|
916
|
+
seals a turn twice, and House's answer names the turn it closed and the
|
|
917
|
+
delivery it queued. An answer the Kit never received leaves the Run unsettled
|
|
918
|
+
with the request retained: the next `kit worker run` replays exactly that
|
|
919
|
+
operation, those blocks and that plan before settling anything, rather than
|
|
920
|
+
reporting a failure it cannot prove. A refusal House did decide is the Run's
|
|
921
|
+
reported failure.
|
|
900
922
|
|
|
901
923
|
That seal is the one durable record of the turn, and only a seal House accepted
|
|
902
924
|
completes a turn: the Kit reports whole-turn completion to House after the seal
|
|
@@ -1125,7 +1147,7 @@ A restore of House's database starts a new log epoch. Until this Environment
|
|
|
1125
1147
|
has reported in it, House refuses every act that touches a Run, acquisition and
|
|
1126
1148
|
lease release included, as `epoch_report_required` naming the epoch. The first
|
|
1127
1149
|
such refusal ends whatever the pass was doing. One raised inside a supervised
|
|
1128
|
-
Run's bridge, while it admits or settles a question,
|
|
1150
|
+
Run's bridge, while it admits or settles a question, reports an interrupt, or
|
|
1129
1151
|
replaces Session authority for a forwarded call, never becomes an answer to the
|
|
1130
1152
|
Agent: it ends the bridge and the pass at once. The Kit then reports, under the
|
|
1131
1153
|
lease it holds and at `POST /kit/sessions/report`, every Run it holds a spawn
|
|
@@ -1683,7 +1705,7 @@ kit telegram serve
|
|
|
1683
1705
|
|
|
1684
1706
|
The command holds one Environment control lease for the whole run and performs
|
|
1685
1707
|
three activities inside it, each paced on its own so none of them blocks
|
|
1686
|
-
another:
|
|
1708
|
+
another, beside the live drafts described below:
|
|
1687
1709
|
|
|
1688
1710
|
- **ingress** reads updates from the stored offset, normalizes each one into the
|
|
1689
1711
|
update House accepts, submits it, and advances the stored offset only after
|
|
@@ -1704,15 +1726,39 @@ another:
|
|
|
1704
1726
|
original, and reported the same way; a pull that fails reports
|
|
1705
1727
|
`transfer_failed` and an ambiguous upload answer reports `delivery_unknown`.
|
|
1706
1728
|
Only an image ever becomes a file item, because House offers every other file
|
|
1707
|
-
as a `conversation_file_link` message this queue delivers like any other.
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
the
|
|
1729
|
+
as a `conversation_file_link` message this queue delivers like any other. The
|
|
1730
|
+
queue carries no draft: an answer holding a draft item is refused as
|
|
1731
|
+
`kit_telegram_response_invalid`. House caps no message, so the gateway alone
|
|
1732
|
+
fits one to Telegram's rich message limits, 32,768 characters and 500 blocks:
|
|
1733
|
+
a rich message past them is sent as consecutive messages in order, split
|
|
1734
|
+
between blocks and inside a block only when that block alone exceeds them,
|
|
1735
|
+
and reported once, `delivered` with the first message's id when every part
|
|
1736
|
+
was delivered and otherwise with the answer of the first part that was not.
|
|
1737
|
+
Each answer also names the `threads` the bot's topics carry, a
|
|
1738
|
+
`conversation_id` with its `chat_id` and `topic_id`, which is where live
|
|
1739
|
+
drafts go.
|
|
1740
|
+
|
|
1741
|
+
Live drafts come from the control stream, not from House's queue. Run on its
|
|
1742
|
+
own, the command holds the Environment's [control stream](#the-kit-control-stream)
|
|
1743
|
+
for its lease; served by `kit resident`, it receives the draft and plan frames
|
|
1744
|
+
the resident's socket receives, through the resident, and never opens a second
|
|
1745
|
+
socket. The gateway Environment's socket carries every conversation bound to a
|
|
1746
|
+
topic of the bot, with no subscribe frame. The gateway keeps the latest view of
|
|
1747
|
+
each such conversation's turn: a frame that starts at block 0 replaces a view,
|
|
1748
|
+
the next frame in sequence applies from its `from`, and a frame after a gap in
|
|
1749
|
+
the sequence, or one for a turn it holds no view of, is applied nowhere and
|
|
1750
|
+
answered with one `gap` frame naming the conversation and turn, so House sends
|
|
1751
|
+
the whole view again. It renders each view as one Telegram draft per turn with
|
|
1752
|
+
`sendRichMessageDraft` on the bound topic, the live plan above the assistant
|
|
1753
|
+
text, under one draft id derived from the turn, because Telegram shows one bot
|
|
1754
|
+
draft per topic and a second id would replace the first. A view past the rich
|
|
1755
|
+
message limits is shortened inside that one draft, never split into a second.
|
|
1756
|
+
A live plan is part of the draft and never a sent message; the frozen plan
|
|
1757
|
+
House queues at the turn's end is the turn's one plan message. Drafts coalesce
|
|
1758
|
+
to the latest view, one Bot API call at a time, and are never reported back:
|
|
1759
|
+
the same bot-wide `retry_after` gate drops a draft while it is closed, and a
|
|
1760
|
+
draft answered with `retry_after` closes it. A closed gate drops a preview,
|
|
1761
|
+
never a durable delivery.
|
|
1716
1762
|
|
|
1717
1763
|
Each serve run first asks House to settle the attempts an earlier run abandoned,
|
|
1718
1764
|
so a restarted gateway never leaves a delivery attempted forever. A Telegram
|
package/bootstrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.29",
|
|
4
4
|
"hosts": {
|
|
5
5
|
"linux": {
|
|
6
|
-
"command": "bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.
|
|
6
|
+
"command": "bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.29/connect-linux.sh)",
|
|
7
7
|
"asset": {
|
|
8
8
|
"name": "connect-linux.sh",
|
|
9
|
-
"url": "https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.
|
|
9
|
+
"url": "https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.29/connect-linux.sh"
|
|
10
10
|
},
|
|
11
11
|
"workspace": {
|
|
12
12
|
"argument": "--workspace",
|
package/package.json
CHANGED
package/src/binding.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readdir, rm } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { finalPlan, planSnapshot } from "./plan.js";
|
|
3
4
|
import { KitRefusal } from "./refusal.js";
|
|
4
5
|
import { readNote, writeNote } from "./state.js";
|
|
5
6
|
import { reportedNote, retainedReport } from "./terminal.js";
|
|
@@ -43,6 +44,7 @@ export async function writeBinding(directory, binding) {
|
|
|
43
44
|
: { operation: binding.sealed.operation, blocks: binding.sealed.blocks },
|
|
44
45
|
stopped: stoppedNote(binding.stopped),
|
|
45
46
|
reported: reported === null ? null : reportedNote(reported),
|
|
47
|
+
plan: binding.plan ?? null,
|
|
46
48
|
});
|
|
47
49
|
}
|
|
48
50
|
|
|
@@ -58,6 +60,12 @@ function retainedSeal(stored) {
|
|
|
58
60
|
return { operation: stored.operation, blocks: stored.blocks };
|
|
59
61
|
}
|
|
60
62
|
|
|
63
|
+
function retainedPlan(stored) {
|
|
64
|
+
if (stored === null || stored === undefined) return null;
|
|
65
|
+
const plan = finalPlan(planSnapshot(stored));
|
|
66
|
+
return plan === null || JSON.stringify(plan) !== JSON.stringify(stored) ? undefined : plan;
|
|
67
|
+
}
|
|
68
|
+
|
|
61
69
|
function retainedFailure(stored) {
|
|
62
70
|
if (stored === null) return null;
|
|
63
71
|
if (typeof stored !== "object" || Array.isArray(stored)) return undefined;
|
|
@@ -96,6 +104,8 @@ function completeBinding(directory, stored) {
|
|
|
96
104
|
if (stopped === undefined) return null;
|
|
97
105
|
const reported = retainedReport(stored.reported);
|
|
98
106
|
if (reported === undefined) return null;
|
|
107
|
+
const plan = retainedPlan(stored.plan);
|
|
108
|
+
if (plan === undefined) return null;
|
|
99
109
|
return {
|
|
100
110
|
run: stored.run,
|
|
101
111
|
session: stored.session,
|
|
@@ -107,6 +117,7 @@ function completeBinding(directory, stored) {
|
|
|
107
117
|
sealed,
|
|
108
118
|
stopped,
|
|
109
119
|
reported,
|
|
120
|
+
plan,
|
|
110
121
|
};
|
|
111
122
|
}
|
|
112
123
|
|
package/src/blocks.js
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ASSISTANT_BLOCKS_MAX,
|
|
3
|
-
ASSISTANT_TABLE_COLUMNS_MAX,
|
|
4
|
-
ASSISTANT_TEXT_MAX_BYTES,
|
|
5
|
-
} from "./contract.js";
|
|
1
|
+
import { ASSISTANT_TABLE_COLUMNS_MAX } from "./contract.js";
|
|
6
2
|
|
|
7
3
|
const HEADING = /^(#{1,6})\s+(.+)$/;
|
|
8
4
|
const BULLET = /^ {0,3}[-*+]\s+(.+)$/;
|
|
@@ -132,25 +128,5 @@ export function assistantBlocks(text) {
|
|
|
132
128
|
index += 1;
|
|
133
129
|
}
|
|
134
130
|
endParagraph();
|
|
135
|
-
|
|
136
|
-
if (blocks.length <= ASSISTANT_BLOCKS_MAX) return blocks;
|
|
137
|
-
const whole = text.trim();
|
|
138
|
-
return whole === "" ? null : [{ type: "paragraph", text: whole }];
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
export function blocksText(blocks) {
|
|
142
|
-
return blocks
|
|
143
|
-
.map((block) => {
|
|
144
|
-
if (block.type === "formula") return block.expression;
|
|
145
|
-
if (block.type === "list") return block.items.join("\n");
|
|
146
|
-
if (block.type === "table") {
|
|
147
|
-
return [block.headers, ...block.rows].map((row) => row.join("\t")).join("\n");
|
|
148
|
-
}
|
|
149
|
-
return block.text;
|
|
150
|
-
})
|
|
151
|
-
.join("\n\n");
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
export function withinAssistantBound(text) {
|
|
155
|
-
return Buffer.byteLength(text, "utf8") <= ASSISTANT_TEXT_MAX_BYTES;
|
|
131
|
+
return blocks.length === 0 ? null : blocks;
|
|
156
132
|
}
|
package/src/bridge.js
CHANGED
|
@@ -16,8 +16,13 @@ import {
|
|
|
16
16
|
TURN_OUTCOMES,
|
|
17
17
|
} from "./contract.js";
|
|
18
18
|
import { operationId } from "./operation.js";
|
|
19
|
+
import { planSnapshot } from "./plan.js";
|
|
19
20
|
import { houseFailed } from "./refusal.js";
|
|
20
21
|
|
|
22
|
+
function sequenced(value) {
|
|
23
|
+
return Number.isSafeInteger(value) && value > 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
21
26
|
export function bridgedFrame(request, operation) {
|
|
22
27
|
if (typeof request.method !== "string") return request;
|
|
23
28
|
const params = typeof request.params === "object" && request.params !== null ? request.params : {};
|
|
@@ -50,6 +55,7 @@ export async function openBridge(options) {
|
|
|
50
55
|
closed: false,
|
|
51
56
|
questions: new Map(),
|
|
52
57
|
interrupts: new Set(),
|
|
58
|
+
plan: null,
|
|
53
59
|
};
|
|
54
60
|
const connections = new Set();
|
|
55
61
|
const clients = new Map();
|
|
@@ -201,29 +207,31 @@ export async function openBridge(options) {
|
|
|
201
207
|
return;
|
|
202
208
|
}
|
|
203
209
|
if (frame.type === "draft") {
|
|
204
|
-
if (typeof frame.text === "string" && frame.text !== ""
|
|
210
|
+
if (typeof frame.text === "string" && frame.text !== "" && sequenced(frame.sequence)) {
|
|
211
|
+
options.draft?.({ text: frame.text, sequence: frame.sequence });
|
|
212
|
+
}
|
|
205
213
|
return;
|
|
206
214
|
}
|
|
207
|
-
if (
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
) {
|
|
213
|
-
void options.plan?.({
|
|
214
|
-
provider_turn: frame.provider_turn,
|
|
215
|
-
steps: frame.steps,
|
|
216
|
-
terminal: frame.terminal,
|
|
217
|
-
});
|
|
215
|
+
if (frame.type === "plan") {
|
|
216
|
+
const snapshot = planSnapshot(frame);
|
|
217
|
+
if (snapshot === null || !sequenced(frame.sequence)) return;
|
|
218
|
+
state.plan = snapshot;
|
|
219
|
+
options.plan?.({ ...snapshot, sequence: frame.sequence });
|
|
218
220
|
return;
|
|
219
221
|
}
|
|
222
|
+
const carried = planSnapshot(frame.plan);
|
|
223
|
+
if (carried !== null) state.plan = carried;
|
|
220
224
|
if (
|
|
221
225
|
frame.type === "interrupt_applied" &&
|
|
222
226
|
typeof frame.interrupt === "string" &&
|
|
223
227
|
["interrupted", "nothing_to_interrupt"].includes(frame.outcome) &&
|
|
224
228
|
state.interrupts.has(frame.interrupt)
|
|
225
229
|
) {
|
|
226
|
-
const reported = options.interrupt?.(
|
|
230
|
+
const reported = options.interrupt?.(
|
|
231
|
+
frame.interrupt,
|
|
232
|
+
frame.outcome,
|
|
233
|
+
frame.outcome === "interrupted" ? state.plan : null,
|
|
234
|
+
);
|
|
227
235
|
if (reported === undefined) state.interrupts.delete(frame.interrupt);
|
|
228
236
|
else void Promise.resolve(reported).finally(() => state.interrupts.delete(frame.interrupt));
|
|
229
237
|
return;
|
|
@@ -237,6 +245,7 @@ export async function openBridge(options) {
|
|
|
237
245
|
providerContinuation: frame.provider_continuation === true,
|
|
238
246
|
providerSessionId: typeof frame.provider_session_id === "string" && frame.provider_session_id.length > 0 && frame.provider_session_id.length <= 512 ? frame.provider_session_id : null,
|
|
239
247
|
stopOutcome: TURN_OUTCOMES.includes(frame.stop_outcome) ? frame.stop_outcome : null,
|
|
248
|
+
plan: carried,
|
|
240
249
|
};
|
|
241
250
|
return;
|
|
242
251
|
}
|
|
@@ -436,6 +445,7 @@ export async function openBridge(options) {
|
|
|
436
445
|
.filter((interaction) => interaction !== null),
|
|
437
446
|
assistant: () => [...state.assistant],
|
|
438
447
|
stop: () => state.stop,
|
|
448
|
+
plan: () => state.plan,
|
|
439
449
|
failure: () => state.failure,
|
|
440
450
|
stopRecovery: () => lifetime.abort(),
|
|
441
451
|
async close() {
|
package/src/contract.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CLAUDE_PASTED_CODE, CODEX_DEVICE_CODE, GROK_DEVICE_CODE } from "./agent-logins.js";
|
|
2
2
|
|
|
3
|
-
export const KIT_CONTRACT_VERSION = "
|
|
3
|
+
export const KIT_CONTRACT_VERSION = "3";
|
|
4
4
|
export const KIT_CAPTURE_BATCH_MAX = 100;
|
|
5
5
|
export const COMMAND_INPUT_MAX_BYTES = 4 * 1024;
|
|
6
6
|
export const AUTHORIZATION_OUTPUT_MAX_BYTES = 4 * 1024;
|
|
@@ -48,14 +48,8 @@ export function runRequestPath(run) {
|
|
|
48
48
|
export const KIT_SESSIONS_REPORT_PATH = "/kit/sessions/report";
|
|
49
49
|
export const KIT_SESSIONS_REPORT_MAX = 256;
|
|
50
50
|
|
|
51
|
-
export const ASSISTANT_TEXT_MAX_BYTES = 4096;
|
|
52
|
-
export const ASSISTANT_BLOCKS_MAX = 500;
|
|
53
51
|
export const ASSISTANT_TABLE_COLUMNS_MAX = 20;
|
|
54
52
|
|
|
55
|
-
export function assistantDraftsPath(workerSession) {
|
|
56
|
-
return `/kit/sessions/${encodeURIComponent(workerSession)}/drafts`;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
53
|
export function assistantTextPath(workerSession) {
|
|
60
54
|
return `/kit/sessions/${encodeURIComponent(workerSession)}/assistant-messages`;
|
|
61
55
|
}
|
|
@@ -66,6 +60,8 @@ export const TELEGRAM_DELIVERY_BATCH_MAX = 10;
|
|
|
66
60
|
export const TELEGRAM_DOWNLOAD_CEILING_BYTES = 20 * 1024 * 1024;
|
|
67
61
|
export const TELEGRAM_BOT_API_TIMEOUT_MS = 30_000;
|
|
68
62
|
export const TELEGRAM_GATEWAY_POLL_MS = 2_000;
|
|
63
|
+
export const TELEGRAM_RICH_MESSAGE_CHARACTERS_MAX = 32_768;
|
|
64
|
+
export const TELEGRAM_RICH_MESSAGE_BLOCKS_MAX = 500;
|
|
69
65
|
export const TELEGRAM_CONTROL_KEYBOARD_COMMANDS = ["/stop", "/kill"];
|
|
70
66
|
export const TELEGRAM_MEDIA_KINDS = [
|
|
71
67
|
"document",
|
package/src/draft.js
CHANGED
|
@@ -1,89 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
const DRAFT_INTERVAL_MS = 500;
|
|
1
|
+
import { pacedLatest } from "./pacing.js";
|
|
4
2
|
|
|
5
3
|
export function observedAssistantDrafts(options) {
|
|
6
|
-
const
|
|
7
|
-
const now = options.now ?? (() => Date.now());
|
|
8
|
-
let timer = null;
|
|
9
|
-
let sentAt = null;
|
|
10
|
-
let sent = null;
|
|
11
|
-
let latest = null;
|
|
12
|
-
|
|
13
|
-
function write() {
|
|
14
|
-
timer = null;
|
|
15
|
-
if (latest === null || latest === sent) return;
|
|
16
|
-
sent = latest;
|
|
17
|
-
sentAt = now();
|
|
18
|
-
options.send(sent);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function cancel() {
|
|
22
|
-
if (timer === null) return;
|
|
23
|
-
clearTimeout(timer);
|
|
24
|
-
timer = null;
|
|
25
|
-
}
|
|
26
|
-
|
|
4
|
+
const paced = pacedLatest(options);
|
|
27
5
|
return {
|
|
28
6
|
observe(view) {
|
|
29
|
-
if (typeof view
|
|
30
|
-
latest = view;
|
|
31
|
-
if (timer !== null) return;
|
|
32
|
-
const wait = sentAt === null ? 0 : sentAt + intervalMs - now();
|
|
33
|
-
if (wait <= 0) {
|
|
34
|
-
write();
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
timer = setTimeout(write, wait);
|
|
38
|
-
timer.unref?.();
|
|
39
|
-
},
|
|
40
|
-
complete(view) {
|
|
41
|
-
if (typeof view !== "string" || view === "") return;
|
|
42
|
-
latest = view;
|
|
43
|
-
cancel();
|
|
44
|
-
write();
|
|
45
|
-
},
|
|
46
|
-
close: cancel,
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function openAssistantDrafts(options) {
|
|
51
|
-
let pending = null;
|
|
52
|
-
let sending = null;
|
|
53
|
-
let sent = null;
|
|
54
|
-
let closed = false;
|
|
55
|
-
|
|
56
|
-
function pump() {
|
|
57
|
-
if (sending !== null || pending === null) return;
|
|
58
|
-
const view = pending;
|
|
59
|
-
pending = null;
|
|
60
|
-
sent = view.text;
|
|
61
|
-
let started;
|
|
62
|
-
try {
|
|
63
|
-
started = Promise.resolve(options.send(view.blocks));
|
|
64
|
-
} catch {
|
|
65
|
-
started = Promise.resolve();
|
|
66
|
-
}
|
|
67
|
-
sending = started
|
|
68
|
-
.catch(() => undefined)
|
|
69
|
-
.finally(() => {
|
|
70
|
-
sending = null;
|
|
71
|
-
pump();
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return {
|
|
76
|
-
update(text) {
|
|
77
|
-
if (closed || typeof text !== "string" || text === "") return;
|
|
78
|
-
if (text === sent || text === pending?.text) return;
|
|
79
|
-
const blocks = assistantBlocks(text);
|
|
80
|
-
if (blocks === null || !withinAssistantBound(blocksText(blocks))) return;
|
|
81
|
-
pending = { text, blocks };
|
|
82
|
-
pump();
|
|
83
|
-
},
|
|
84
|
-
close() {
|
|
85
|
-
closed = true;
|
|
86
|
-
pump();
|
|
7
|
+
if (typeof view === "string" && view !== "") paced.observe(view);
|
|
87
8
|
},
|
|
9
|
+
close: paced.close,
|
|
88
10
|
};
|
|
89
11
|
}
|