@agentshouse/kit 0.1.0-alpha.4 → 0.1.0-alpha.40
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 +41 -1671
- package/bin/connect-linux.sh +501 -53
- package/bin/connect-windows.ps1 +430 -0
- package/bin/kit.js +200 -159
- package/bootstrap.json +28 -0
- package/docs/configuration.md +104 -0
- package/docs/google.md +33 -0
- package/docs/linux.md +77 -0
- package/docs/macos.md +26 -0
- package/docs/telegram.md +29 -0
- package/docs/windows.md +36 -0
- package/package.json +7 -2
- package/src/adapter-mcp.js +92 -12
- package/src/agents.js +63 -8
- package/src/binding.js +52 -0
- package/src/blocks.js +2 -26
- package/src/bridge.js +28 -14
- package/src/bytes.js +19 -8
- package/src/configuration.js +14 -211
- package/src/connector-login.js +3 -84
- package/src/contract.js +44 -12
- package/src/conversation-originals.js +1 -3
- package/src/delivery.js +4 -7
- package/src/directory.js +6 -26
- package/src/draft.js +4 -82
- package/src/git-main.js +138 -0
- package/src/git.js +62 -0
- package/src/google/calendar.js +39 -45
- package/src/google/drive.js +61 -186
- package/src/google/entries.js +349 -0
- package/src/google/gateway.js +3 -6
- package/src/google/gmail.js +33 -23
- package/src/google/oauth.js +106 -66
- package/src/google/source.js +21 -23
- package/src/host.js +6 -2
- package/src/house-main.js +65 -8
- package/src/house.js +446 -127
- package/src/how-we-work.md +25 -0
- package/src/inbound.js +2 -18
- package/src/kit-work-stream.js +120 -0
- package/src/launcher.js +46 -10
- package/src/local-files.js +3 -11
- package/src/login.js +27 -14
- package/src/mcp.js +18 -0
- package/src/mount.js +9 -18
- package/src/original.js +4 -4
- package/src/pacing.js +41 -0
- package/src/plan.js +37 -45
- package/src/repository.js +3 -61
- package/src/session-bin/git +2 -0
- package/src/setup.js +14 -15
- package/src/skill-set/ask-matt/PHASE-BOUNDARIES.md +55 -0
- package/src/skill-set/ask-matt/SKILL.md +91 -0
- package/src/skill-set/ask-matt/agents/openai.yaml +4 -0
- package/src/skill-set/code-review/LICENSE +21 -0
- package/src/skill-set/code-review/SKILL.md +89 -0
- package/src/skill-set/code-review/agents/openai.yaml +3 -0
- package/src/skill-set/codebase-design/DEEPENING.md +37 -0
- package/src/skill-set/codebase-design/DESIGN-IT-TWICE.md +44 -0
- package/src/skill-set/codebase-design/LICENSE +21 -0
- package/src/skill-set/codebase-design/SKILL.md +116 -0
- package/src/skill-set/codebase-design/agents/openai.yaml +3 -0
- package/src/skill-set/diagnosing-bugs/LICENSE +21 -0
- package/src/skill-set/diagnosing-bugs/SKILL.md +140 -0
- package/src/skill-set/diagnosing-bugs/agents/openai.yaml +3 -0
- package/src/skill-set/diagnosing-bugs/scripts/hitl-loop.template.sh +44 -0
- package/src/skill-set/domain-modeling/ADR-FORMAT.md +47 -0
- package/src/skill-set/domain-modeling/CONTEXT-FORMAT.md +60 -0
- package/src/skill-set/domain-modeling/LICENSE +21 -0
- package/src/skill-set/domain-modeling/SKILL.md +76 -0
- package/src/skill-set/domain-modeling/agents/openai.yaml +3 -0
- package/src/skill-set/grill-me/LICENSE +21 -0
- package/src/skill-set/grill-me/SKILL.md +8 -0
- package/src/skill-set/grill-me/agents/openai.yaml +4 -0
- package/src/skill-set/grill-with-docs/LICENSE +21 -0
- package/src/skill-set/grill-with-docs/SKILL.md +8 -0
- package/src/skill-set/grill-with-docs/agents/openai.yaml +4 -0
- package/src/skill-set/grilling/LICENSE +21 -0
- package/src/{starter-set → skill-set}/grilling/SKILL.md +1 -1
- package/src/skill-set/handoff/LICENSE +21 -0
- package/src/skill-set/handoff/SKILL.md +17 -0
- package/src/skill-set/handoff/agents/openai.yaml +4 -0
- package/src/skill-set/implement/LICENSE +21 -0
- package/src/skill-set/implement/SKILL.md +16 -0
- package/src/skill-set/implement/agents/openai.yaml +4 -0
- package/src/skill-set/improve-codebase-architecture/HTML-REPORT.md +123 -0
- package/src/skill-set/improve-codebase-architecture/LICENSE +21 -0
- package/src/skill-set/improve-codebase-architecture/SKILL.md +72 -0
- package/src/skill-set/improve-codebase-architecture/agents/openai.yaml +4 -0
- package/src/skill-set/prototype/LICENSE +21 -0
- package/src/skill-set/prototype/LOGIC.md +67 -0
- package/src/skill-set/prototype/SKILL.md +28 -0
- package/src/skill-set/prototype/UI.md +112 -0
- package/src/skill-set/prototype/agents/openai.yaml +3 -0
- package/src/skill-set/research/LICENSE +21 -0
- package/src/skill-set/research/SKILL.md +14 -0
- package/src/skill-set/research/agents/openai.yaml +3 -0
- package/src/skill-set/resolving-merge-conflicts/LICENSE +21 -0
- package/src/skill-set/resolving-merge-conflicts/SKILL.md +16 -0
- package/src/skill-set/resolving-merge-conflicts/agents/openai.yaml +3 -0
- package/src/skill-set/tdd/LICENSE +21 -0
- package/src/skill-set/tdd/SKILL.md +40 -0
- package/src/skill-set/tdd/agents/openai.yaml +3 -0
- package/src/skill-set/tdd/mocking.md +59 -0
- package/src/skill-set/tdd/tests.md +77 -0
- package/src/skill-set/teach/GLOSSARY-FORMAT.md +35 -0
- package/src/skill-set/teach/LEARNING-RECORD-FORMAT.md +46 -0
- package/src/skill-set/teach/LICENSE +21 -0
- package/src/skill-set/teach/MISSION-FORMAT.md +31 -0
- package/src/skill-set/teach/RESOURCES-FORMAT.md +32 -0
- package/src/skill-set/teach/SKILL.md +141 -0
- package/src/skill-set/teach/agents/openai.yaml +4 -0
- package/src/skill-set/to-questionnaire/LICENSE +21 -0
- package/src/skill-set/to-questionnaire/SKILL.md +55 -0
- package/src/skill-set/to-questionnaire/agents/openai.yaml +4 -0
- package/src/skill-set/to-spec/LICENSE +21 -0
- package/src/skill-set/to-spec/SKILL.md +76 -0
- package/src/skill-set/to-spec/agents/openai.yaml +4 -0
- package/src/skill-set/to-tickets/LICENSE +21 -0
- package/src/skill-set/to-tickets/SKILL.md +106 -0
- package/src/skill-set/to-tickets/agents/openai.yaml +4 -0
- package/src/skill-set/wait-what/LICENSE +21 -0
- package/src/skill-set/wait-what/SKILL.md +8 -0
- package/src/skill-set/wait-what/agents/openai.yaml +4 -0
- package/src/skill-set/wayfinder/LICENSE +21 -0
- package/src/skill-set/wayfinder/SKILL.md +129 -0
- package/src/skill-set/wayfinder/agents/openai.yaml +4 -0
- package/src/skill-set/writing-for-agents/LICENSE +21 -0
- package/src/skill-set/writing-for-agents/SKILL-MECHANICS.md +22 -0
- package/src/skill-set/writing-for-agents/SKILL.md +83 -0
- package/src/skill-set/writing-for-agents/agents/openai.yaml +3 -0
- package/src/skill-set.js +85 -0
- package/src/source.js +93 -54
- package/src/state.js +19 -1
- package/src/sync-library.js +7 -11
- package/src/sync-state.js +12 -3
- package/src/sync.js +142 -43
- package/src/tasks.js +92 -0
- package/src/telegram-view.js +217 -0
- package/src/telegram.js +204 -69
- package/src/turn-frames.js +69 -0
- package/src/worker.js +738 -189
- package/src/working-copy.js +1274 -0
- package/src/workspace.js +299 -88
- package/src/google/declared.js +0 -146
- package/src/google/id.js +0 -1
- package/src/instance/google.js +0 -1
- package/src/starter-set.js +0 -17
- /package/src/{starter-set/grilling → skill-set/ask-matt}/LICENSE +0 -0
- /package/src/{starter-set → skill-set}/grilling/agents/openai.yaml +0 -0
package/src/telegram.js
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
PERSONAL_BOT_OFFSET_RELATIVE_PATH,
|
|
11
11
|
PERSONAL_BOT_TOKEN_RELATIVE_PATH,
|
|
12
12
|
TELEGRAM_BOT_API_TIMEOUT_MS,
|
|
13
|
+
TELEGRAM_CAPTION_CHARACTERS_MAX,
|
|
13
14
|
TELEGRAM_CONTROL_KEYBOARD_COMMANDS,
|
|
14
15
|
TELEGRAM_DELIVERY_BATCH_MAX,
|
|
15
16
|
TELEGRAM_DELIVERY_KINDS,
|
|
@@ -18,14 +19,18 @@ import {
|
|
|
18
19
|
TELEGRAM_IMAGE_UPLOAD_CEILING_BYTES,
|
|
19
20
|
TELEGRAM_INGRESS_BATCH_MAX,
|
|
20
21
|
TELEGRAM_MEDIA_KINDS,
|
|
22
|
+
TELEGRAM_MESSAGE_CHARACTERS_MAX,
|
|
21
23
|
TELEGRAM_UNSUPPORTED_MEDIA_FIELDS,
|
|
22
24
|
TELEGRAM_UPDATE_OUTCOMES,
|
|
23
25
|
telegramApiUrl,
|
|
24
26
|
telegramFileUrl,
|
|
25
27
|
} from "./contract.js";
|
|
26
28
|
import { heldControl, houseRequest, maintainControl, openSession } from "./house.js";
|
|
29
|
+
import { watchKitWork } from "./kit-work-stream.js";
|
|
27
30
|
import { KitRefusal } from "./refusal.js";
|
|
28
31
|
import { writeText } from "./state.js";
|
|
32
|
+
import { richPieces, slices, turnViews } from "./telegram-view.js";
|
|
33
|
+
import { kitStreamWriter } from "./turn-frames.js";
|
|
29
34
|
|
|
30
35
|
const TOKEN_PROMPT = "Paste the BotFather token for this Environment: ";
|
|
31
36
|
const AMBIGUOUS_DELIVERY = Object.freeze({ telegram_message_id: null, telegram_topic_id: null });
|
|
@@ -282,6 +287,28 @@ export function deliveryCall(delivery) {
|
|
|
282
287
|
return { method: "sendMessage", body: sendBody(delivery) };
|
|
283
288
|
}
|
|
284
289
|
|
|
290
|
+
function topicMessage(chat, topic, text) {
|
|
291
|
+
const body = { chat_id: Number(chat), text };
|
|
292
|
+
if (topic !== null) body.message_thread_id = Number(topic);
|
|
293
|
+
return { method: "sendMessage", body };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function deliveryCalls(delivery) {
|
|
297
|
+
const call = deliveryCall(delivery);
|
|
298
|
+
if (call.method === "sendMessage" && call.body.text.length > TELEGRAM_MESSAGE_CHARACTERS_MAX) {
|
|
299
|
+
const [first, ...following] = slices(call.body.text, TELEGRAM_MESSAGE_CHARACTERS_MAX);
|
|
300
|
+
return [
|
|
301
|
+
{ method: call.method, body: { ...call.body, text: first } },
|
|
302
|
+
...following.map((text) => topicMessage(delivery.chat_id, delivery.topic_id, text)),
|
|
303
|
+
];
|
|
304
|
+
}
|
|
305
|
+
if (call.method !== "sendRichMessage") return [call];
|
|
306
|
+
return richPieces(call.body.rich_message).map((rich_message) => ({
|
|
307
|
+
method: call.method,
|
|
308
|
+
body: { ...call.body, rich_message },
|
|
309
|
+
}));
|
|
310
|
+
}
|
|
311
|
+
|
|
285
312
|
function acceptedDelivery(delivery, result) {
|
|
286
313
|
if (delivery.kind === "conversation_topic") {
|
|
287
314
|
const threadId = plain(result) ? result.message_thread_id : undefined;
|
|
@@ -353,12 +380,15 @@ function queuedDelivery(value) {
|
|
|
353
380
|
return value;
|
|
354
381
|
}
|
|
355
382
|
|
|
356
|
-
function
|
|
357
|
-
if (!
|
|
358
|
-
|
|
359
|
-
if (
|
|
360
|
-
|
|
361
|
-
|
|
383
|
+
function threadsOf(answer) {
|
|
384
|
+
if (!Array.isArray(answer?.threads)) refuseAnswer();
|
|
385
|
+
return answer.threads.map((thread) => {
|
|
386
|
+
if (!plain(thread)) refuseAnswer();
|
|
387
|
+
for (const named of ["conversation_id", "chat_id", "topic_id"]) {
|
|
388
|
+
if (typeof thread[named] !== "string" || thread[named].length === 0) refuseAnswer();
|
|
389
|
+
}
|
|
390
|
+
return { conversation_id: thread.conversation_id, chat_id: thread.chat_id, topic_id: thread.topic_id };
|
|
391
|
+
});
|
|
362
392
|
}
|
|
363
393
|
|
|
364
394
|
function queuedUpload(value) {
|
|
@@ -374,7 +404,6 @@ function queueOf(answer) {
|
|
|
374
404
|
return answer.queue.map((item) => {
|
|
375
405
|
if (!plain(item)) refuseAnswer();
|
|
376
406
|
if (item.type === "message") return { type: "message", delivery: queuedDelivery(item.delivery) };
|
|
377
|
-
if (item.type === "draft") return { type: "draft", draft: queuedDraft(item.draft) };
|
|
378
407
|
if (item.type !== "file") refuseAnswer();
|
|
379
408
|
return { type: "file", upload: queuedUpload(item.upload) };
|
|
380
409
|
});
|
|
@@ -722,7 +751,7 @@ async function relayInboundFiles(gateway) {
|
|
|
722
751
|
gateway.summary.relayed.push({ attachment: file.id, bytes: bytes.length });
|
|
723
752
|
} catch (error) {
|
|
724
753
|
if (cancelled(error) || !(error instanceof KitRefusal)) throw error;
|
|
725
|
-
gateway
|
|
754
|
+
refusedPass(gateway, { activity: "inbound", item: file.id, code: error.code });
|
|
726
755
|
}
|
|
727
756
|
}
|
|
728
757
|
}
|
|
@@ -751,23 +780,51 @@ async function reportUpload(gateway, upload, settled) {
|
|
|
751
780
|
|
|
752
781
|
async function sendQueuedMessage(gateway, token, delivery) {
|
|
753
782
|
await houseCall(gateway, `deliveries/${delivery.id}/attempt`);
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
783
|
+
let first = null;
|
|
784
|
+
for (const call of deliveryCalls(delivery)) {
|
|
785
|
+
let answered;
|
|
786
|
+
try {
|
|
787
|
+
answered = await telegramPost(gateway, telegramApiUrl(token, call.method), call.body);
|
|
788
|
+
} catch (error) {
|
|
789
|
+
if (cancelled(error)) throw error;
|
|
790
|
+
await reportDelivery(gateway, delivery, {
|
|
791
|
+
outcome: "delivery_unknown",
|
|
792
|
+
report: AMBIGUOUS_DELIVERY,
|
|
793
|
+
});
|
|
794
|
+
return false;
|
|
795
|
+
}
|
|
796
|
+
const settled = deliveryOutcome(delivery, answered);
|
|
797
|
+
const retryAfter = telegramRetryAfter(answered);
|
|
798
|
+
if (retryAfter !== null) gateway.gate.hold(retryAfter);
|
|
799
|
+
if (settled.outcome !== "delivered") {
|
|
800
|
+
await reportDelivery(gateway, delivery, settled);
|
|
801
|
+
return retryAfter !== null;
|
|
802
|
+
}
|
|
803
|
+
first ??= settled;
|
|
765
804
|
}
|
|
766
|
-
await reportDelivery(gateway, delivery,
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
805
|
+
await reportDelivery(gateway, delivery, first);
|
|
806
|
+
return false;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
function captionPieces(caption) {
|
|
810
|
+
if (caption === null || caption.length <= TELEGRAM_CAPTION_CHARACTERS_MAX) return [caption];
|
|
811
|
+
const [head] = slices(caption, TELEGRAM_CAPTION_CHARACTERS_MAX);
|
|
812
|
+
return [head, ...slices(caption.slice(head.length), TELEGRAM_MESSAGE_CHARACTERS_MAX)];
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
async function sentOverflow(gateway, token, upload, overflow) {
|
|
816
|
+
for (const text of overflow) {
|
|
817
|
+
const call = topicMessage(upload.chat_id, upload.topic_id, text);
|
|
818
|
+
let answered;
|
|
819
|
+
try {
|
|
820
|
+
answered = await telegramPost(gateway, telegramApiUrl(token, call.method), call.body);
|
|
821
|
+
} catch (error) {
|
|
822
|
+
if (cancelled(error)) throw error;
|
|
823
|
+
return { settled: uploadOutcome(null), answered: null };
|
|
824
|
+
}
|
|
825
|
+
if (!botApiAccepted(answered)) return { settled: uploadOutcome(answered), answered };
|
|
826
|
+
}
|
|
827
|
+
return null;
|
|
771
828
|
}
|
|
772
829
|
|
|
773
830
|
async function sendQueuedUpload(gateway, token, upload) {
|
|
@@ -789,12 +846,13 @@ async function sendQueuedUpload(gateway, token, upload) {
|
|
|
789
846
|
});
|
|
790
847
|
return false;
|
|
791
848
|
}
|
|
849
|
+
const [caption, ...overflow] = captionPieces(upload.caption);
|
|
792
850
|
let answered;
|
|
793
851
|
try {
|
|
794
852
|
answered = await telegramUpload(
|
|
795
853
|
gateway,
|
|
796
854
|
telegramApiUrl(token, "sendPhoto"),
|
|
797
|
-
uploadFields(upload),
|
|
855
|
+
uploadFields({ ...upload, caption }),
|
|
798
856
|
{ field: "photo", name: upload.file_name, bytes },
|
|
799
857
|
);
|
|
800
858
|
} catch (error) {
|
|
@@ -805,78 +863,121 @@ async function sendQueuedUpload(gateway, token, upload) {
|
|
|
805
863
|
});
|
|
806
864
|
return false;
|
|
807
865
|
}
|
|
808
|
-
|
|
809
|
-
const
|
|
866
|
+
const photographed = uploadOutcome(answered);
|
|
867
|
+
const unsent =
|
|
868
|
+
photographed.outcome === "delivered" ? await sentOverflow(gateway, token, upload, overflow) : null;
|
|
869
|
+
await reportUpload(gateway, upload, unsent?.settled ?? photographed);
|
|
870
|
+
const retryAfter = telegramRetryAfter(unsent === null ? answered : unsent.answered);
|
|
810
871
|
if (retryAfter === null) return false;
|
|
811
872
|
gateway.gate.hold(retryAfter);
|
|
812
873
|
return true;
|
|
813
874
|
}
|
|
814
875
|
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
876
|
+
function liveDrafts(gateway) {
|
|
877
|
+
const stream = kitStreamWriter();
|
|
878
|
+
const views = turnViews({ gap: (frame) => stream.write(frame) });
|
|
879
|
+
const dirty = new Set();
|
|
880
|
+
let pumping = null;
|
|
881
|
+
|
|
882
|
+
async function pump() {
|
|
883
|
+
while (dirty.size > 0) {
|
|
884
|
+
const [conversation] = dirty;
|
|
885
|
+
dirty.delete(conversation);
|
|
886
|
+
if (gateway.gate.open()) {
|
|
887
|
+
dirty.clear();
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
const draft = views.draft(conversation);
|
|
891
|
+
if (draft === null) continue;
|
|
892
|
+
const call = draftCall(draft);
|
|
893
|
+
let answered;
|
|
894
|
+
try {
|
|
895
|
+
const token = await gatewayToken(gateway);
|
|
896
|
+
answered = await telegramPost(gateway, telegramApiUrl(token, call.method), call.body);
|
|
897
|
+
} catch (error) {
|
|
898
|
+
if (!(error instanceof KitRefusal)) throw error;
|
|
899
|
+
if (cancelled(error)) dirty.clear();
|
|
900
|
+
continue;
|
|
901
|
+
}
|
|
902
|
+
const retryAfter = telegramRetryAfter(answered);
|
|
903
|
+
if (retryAfter !== null) {
|
|
904
|
+
gateway.gate.hold(retryAfter);
|
|
905
|
+
dirty.clear();
|
|
906
|
+
}
|
|
830
907
|
}
|
|
831
908
|
}
|
|
909
|
+
|
|
910
|
+
function start() {
|
|
911
|
+
if (pumping !== null || dirty.size === 0) return;
|
|
912
|
+
pumping = pump().finally(() => {
|
|
913
|
+
pumping = null;
|
|
914
|
+
start();
|
|
915
|
+
});
|
|
916
|
+
pumping.catch(() => undefined);
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
return {
|
|
920
|
+
heard: {
|
|
921
|
+
available: () => undefined,
|
|
922
|
+
changed: () => undefined,
|
|
923
|
+
opened: stream.opened,
|
|
924
|
+
closed: stream.closed,
|
|
925
|
+
viewed: (frame) => {
|
|
926
|
+
const conversation = views.receive(frame);
|
|
927
|
+
if (conversation === null) return;
|
|
928
|
+
dirty.add(conversation);
|
|
929
|
+
start();
|
|
930
|
+
},
|
|
931
|
+
},
|
|
932
|
+
address: (threads) => views.address(threads),
|
|
933
|
+
settled: () => pumping ?? Promise.resolve(),
|
|
934
|
+
};
|
|
832
935
|
}
|
|
833
936
|
|
|
834
|
-
|
|
835
|
-
if (
|
|
836
|
-
|
|
837
|
-
telegramPost(gateway, telegramApiUrl(token, call.method), call.body),
|
|
838
|
-
);
|
|
937
|
+
function watchGatewayStream(session, signal, heard) {
|
|
938
|
+
if (session.relay !== undefined) return session.relay(signal, heard);
|
|
939
|
+
return (session.dependencies.watchKitWork ?? watchKitWork)(session, signal, heard, session.dependencies);
|
|
839
940
|
}
|
|
840
941
|
|
|
841
942
|
async function drainOutboundQueue(gateway) {
|
|
842
943
|
if (gateway.gate.open()) return;
|
|
843
944
|
const token = await gatewayToken(gateway);
|
|
844
|
-
const
|
|
845
|
-
|
|
846
|
-
);
|
|
847
|
-
const drafts = [];
|
|
945
|
+
const answer = await houseCall(gateway, "queue/acquire", { max: TELEGRAM_DELIVERY_BATCH_MAX });
|
|
946
|
+
const queue = queueOf(answer);
|
|
947
|
+
gateway.live?.address(threadsOf(answer));
|
|
848
948
|
for (const item of queue) {
|
|
849
|
-
if (item.type === "draft") {
|
|
850
|
-
drafts.push(item.draft);
|
|
851
|
-
continue;
|
|
852
|
-
}
|
|
853
949
|
const gated =
|
|
854
950
|
item.type === "file"
|
|
855
951
|
? await sendQueuedUpload(gateway, token, item.upload)
|
|
856
952
|
: await sendQueuedMessage(gateway, token, item.delivery);
|
|
857
953
|
if (gated) return;
|
|
858
954
|
}
|
|
859
|
-
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
function refusedPass(gateway, failure) {
|
|
958
|
+
gateway.summary.failures.push(failure);
|
|
959
|
+
gateway.refused(failure);
|
|
860
960
|
}
|
|
861
961
|
|
|
862
962
|
async function onePass(gateway, activity, work) {
|
|
863
963
|
try {
|
|
864
964
|
await work(gateway);
|
|
865
965
|
} catch (error) {
|
|
866
|
-
if (cancelled(error) || !(error instanceof KitRefusal)) throw error;
|
|
867
|
-
gateway
|
|
966
|
+
if (cancelled(error) || !(error instanceof KitRefusal) || error.code === "kit_update_draining") throw error;
|
|
967
|
+
refusedPass(gateway, { activity, item: null, code: error.code });
|
|
868
968
|
}
|
|
869
969
|
}
|
|
870
970
|
|
|
871
971
|
async function paceActivity(gateway, stopping, activity, work) {
|
|
872
972
|
try {
|
|
873
|
-
for (let index = 0; index < gateway.passes; index += 1) {
|
|
973
|
+
for (let index = 0; index < gateway.passes && !gateway.draining; index += 1) {
|
|
874
974
|
if (gateway.signal?.aborted === true) throw new KitRefusal("kit_command_cancelled");
|
|
875
975
|
await onePass(gateway, activity, work);
|
|
876
976
|
if (index + 1 < gateway.passes) await gateway.wait(TELEGRAM_GATEWAY_POLL_MS, gateway.signal);
|
|
877
977
|
}
|
|
878
978
|
} catch (error) {
|
|
879
|
-
|
|
979
|
+
if (error.code === "kit_update_draining") gateway.draining = true;
|
|
980
|
+
else stopping.abort();
|
|
880
981
|
throw error;
|
|
881
982
|
}
|
|
882
983
|
}
|
|
@@ -955,22 +1056,32 @@ async function proveOwner(gateway, token, telegramBotUserId) {
|
|
|
955
1056
|
throw new KitRefusal("telegram_owner_not_proven");
|
|
956
1057
|
}
|
|
957
1058
|
|
|
958
|
-
function gatewayContext(session, options,
|
|
1059
|
+
function gatewayContext(session, options, signal, summary = emptySummary()) {
|
|
959
1060
|
return {
|
|
960
1061
|
session,
|
|
961
1062
|
directory: options.directory,
|
|
962
|
-
dependencies,
|
|
1063
|
+
dependencies: session.dependencies,
|
|
963
1064
|
signal,
|
|
964
1065
|
gate: rateGate(() => session.now().getTime()),
|
|
965
|
-
wait: dependencies.wait ?? pause,
|
|
1066
|
+
wait: session.dependencies.wait ?? pause,
|
|
966
1067
|
passes: options.passes ?? Number.POSITIVE_INFINITY,
|
|
1068
|
+
draining: false,
|
|
967
1069
|
summary,
|
|
1070
|
+
refused: options.refused,
|
|
968
1071
|
};
|
|
969
1072
|
}
|
|
970
1073
|
|
|
971
1074
|
export async function connectPersonalBot(options, dependencies = {}) {
|
|
972
1075
|
const session = await openSession(options, dependencies);
|
|
973
|
-
|
|
1076
|
+
return maintainControl(
|
|
1077
|
+
session,
|
|
1078
|
+
(signal) => connectBot(session, options, dependencies, signal),
|
|
1079
|
+
options.signal,
|
|
1080
|
+
);
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
async function connectBot(session, options, dependencies, signal) {
|
|
1084
|
+
const gateway = gatewayContext(session, options, signal);
|
|
974
1085
|
const ask = dependencies.prompt ?? (() => askForToken(dependencies));
|
|
975
1086
|
const token = (await ask()).trim();
|
|
976
1087
|
if (token.length === 0) throw new KitRefusal("telegram_bot_token_required");
|
|
@@ -989,7 +1100,7 @@ export async function connectPersonalBot(options, dependencies = {}) {
|
|
|
989
1100
|
owner_telegram_user_id: await proveOwner(gateway, token, identity.telegramBotUserId),
|
|
990
1101
|
has_topics_enabled: true,
|
|
991
1102
|
};
|
|
992
|
-
await writeOwned(tokenPath(options.directory), `${token}\n`,
|
|
1103
|
+
await writeOwned(tokenPath(options.directory), `${token}\n`, signal);
|
|
993
1104
|
try {
|
|
994
1105
|
await houseCall(gateway, "personal-bot", setup);
|
|
995
1106
|
} catch (error) {
|
|
@@ -1001,7 +1112,15 @@ export async function connectPersonalBot(options, dependencies = {}) {
|
|
|
1001
1112
|
|
|
1002
1113
|
export async function disconnectPersonalBot(options, dependencies = {}) {
|
|
1003
1114
|
const session = await openSession(options, dependencies);
|
|
1004
|
-
|
|
1115
|
+
return maintainControl(
|
|
1116
|
+
session,
|
|
1117
|
+
(signal) => disconnectBot(session, options, signal),
|
|
1118
|
+
options.signal,
|
|
1119
|
+
);
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
async function disconnectBot(session, options, signal) {
|
|
1123
|
+
const gateway = gatewayContext(session, options, signal);
|
|
1005
1124
|
const directory = options.directory;
|
|
1006
1125
|
const marked = await disconnecting(directory);
|
|
1007
1126
|
let token = null;
|
|
@@ -1010,7 +1129,7 @@ export async function disconnectPersonalBot(options, dependencies = {}) {
|
|
|
1010
1129
|
} catch (error) {
|
|
1011
1130
|
if (!marked) throw error;
|
|
1012
1131
|
}
|
|
1013
|
-
if (!marked) await writeOwned(disconnectPath(directory), "{}\n",
|
|
1132
|
+
if (!marked) await writeOwned(disconnectPath(directory), "{}\n", signal);
|
|
1014
1133
|
if (token !== null) {
|
|
1015
1134
|
await telegramCall(gateway, token, "deleteWebhook", { drop_pending_updates: false });
|
|
1016
1135
|
await Promise.all([
|
|
@@ -1032,9 +1151,25 @@ export async function serveTelegramGateway(options, dependencies = {}) {
|
|
|
1032
1151
|
async (signal) => {
|
|
1033
1152
|
const stopping = new AbortController();
|
|
1034
1153
|
const shared = AbortSignal.any([signal, stopping.signal]);
|
|
1035
|
-
const gateway = gatewayContext(session, options,
|
|
1154
|
+
const gateway = gatewayContext(session, options, shared, summary);
|
|
1036
1155
|
summary.abandoned = abandonedOf(await houseCall(gateway, "deliveries/abandoned"));
|
|
1037
|
-
|
|
1156
|
+
gateway.live = liveDrafts(gateway);
|
|
1157
|
+
const watcher = new AbortController();
|
|
1158
|
+
const watching = watchGatewayStream(
|
|
1159
|
+
session,
|
|
1160
|
+
AbortSignal.any([shared, watcher.signal]),
|
|
1161
|
+
gateway.live.heard,
|
|
1162
|
+
).catch((error) => {
|
|
1163
|
+
if (cancelled(error)) return;
|
|
1164
|
+
refusedPass(gateway, { activity: "drafts", item: null, code: error?.code ?? "kit_stream_failed" });
|
|
1165
|
+
});
|
|
1166
|
+
try {
|
|
1167
|
+
await serveActivities(gateway, stopping);
|
|
1168
|
+
} finally {
|
|
1169
|
+
watcher.abort();
|
|
1170
|
+
await watching;
|
|
1171
|
+
await gateway.live.settled();
|
|
1172
|
+
}
|
|
1038
1173
|
},
|
|
1039
1174
|
options.signal,
|
|
1040
1175
|
);
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { assistantBlocks } from "./blocks.js";
|
|
2
|
+
|
|
3
|
+
export function kitStreamWriter() {
|
|
4
|
+
let send = null;
|
|
5
|
+
let generation = 0;
|
|
6
|
+
return {
|
|
7
|
+
get generation() {
|
|
8
|
+
return generation;
|
|
9
|
+
},
|
|
10
|
+
opened(write) {
|
|
11
|
+
generation += 1;
|
|
12
|
+
send = write;
|
|
13
|
+
},
|
|
14
|
+
closed() {
|
|
15
|
+
send = null;
|
|
16
|
+
},
|
|
17
|
+
write(frame) {
|
|
18
|
+
if (send === null) return false;
|
|
19
|
+
send(frame);
|
|
20
|
+
return true;
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function firstChange(written, items) {
|
|
26
|
+
const shared = Math.min(written.length, items.length);
|
|
27
|
+
for (let index = 0; index < shared; index += 1) {
|
|
28
|
+
if (JSON.stringify(written[index]) !== JSON.stringify(items[index])) return index;
|
|
29
|
+
}
|
|
30
|
+
return shared;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function turnFrames(stream) {
|
|
34
|
+
let identity = null;
|
|
35
|
+
const parts = {
|
|
36
|
+
draft: { key: "blocks", latest: null, written: null, generation: null },
|
|
37
|
+
plan: { key: "steps", latest: null, written: null, generation: null },
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
function write(type) {
|
|
41
|
+
const part = parts[type];
|
|
42
|
+
if (identity === null || part.latest === null) return;
|
|
43
|
+
const { sequence, items } = part.latest;
|
|
44
|
+
const whole = part.written === null || part.generation !== stream.generation;
|
|
45
|
+
const from = whole ? 0 : firstChange(part.written, items);
|
|
46
|
+
const generation = stream.generation;
|
|
47
|
+
const sent = stream.write({ type, ...identity, sequence, from, [part.key]: items.slice(from) });
|
|
48
|
+
part.written = sent ? items : null;
|
|
49
|
+
part.generation = generation;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
identify(named) {
|
|
54
|
+
identity = named;
|
|
55
|
+
write("draft");
|
|
56
|
+
write("plan");
|
|
57
|
+
},
|
|
58
|
+
draft(view) {
|
|
59
|
+
const blocks = assistantBlocks(view.text);
|
|
60
|
+
if (blocks === null) return;
|
|
61
|
+
parts.draft.latest = { sequence: view.sequence, items: blocks };
|
|
62
|
+
write("draft");
|
|
63
|
+
},
|
|
64
|
+
plan(snapshot) {
|
|
65
|
+
parts.plan.latest = { sequence: snapshot.sequence, items: snapshot.steps };
|
|
66
|
+
write("plan");
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|