@volter/twin-telegram 1.0.0 → 1.0.1
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 +2 -2
- package/dist/journeys/customer-life.json +21 -21
- package/dist/journeys/customer-life.outline.md +17 -17
- package/dist/journeys/customer-life.outline.verdict.json +17 -52
- package/dist/journeys/customer-life.verdict.json +24 -98
- package/dist/journeys/decisions.json +49 -13
- package/dist/journeys/demand.json +0 -91
- package/dist/journeys/first-use.json +21 -0
- package/dist/journeys/unreachable.json +1 -1
- package/dist/journeys/vendor-examples.json +14 -6
- package/dist/spec/SOURCE.md +5 -6
- package/dist/spec/patches.json +20 -20
- package/dist/spec/sources.json +15 -0
- package/dist/src/cli.js +1 -1
- package/dist/src/engine/media.d.ts +6 -12
- package/dist/src/engine/media.js +28 -66
- package/dist/src/engine/text.d.ts +0 -7
- package/dist/src/engine/text.js +2 -2
- package/dist/src/index.js +2 -1
- package/dist/src/manifest.js +7 -6
- package/dist/src/semantics/available-methods.js +50 -40
- package/dist/src/semantics/doors.d.ts +0 -1
- package/dist/src/semantics/doors.js +82 -5
- package/dist/src/semantics/shared.d.ts +10 -6
- package/dist/src/semantics/shared.js +29 -8
- package/dist/src/server.js +2 -19
- package/generated/pack-facts.json +10 -0
- package/journeys/customer-life.json +21 -21
- package/journeys/customer-life.outline.md +17 -17
- package/journeys/customer-life.outline.verdict.json +17 -52
- package/journeys/customer-life.verdict.json +24 -98
- package/journeys/decisions.json +49 -13
- package/journeys/demand.json +0 -91
- package/journeys/first-use.json +21 -0
- package/journeys/unreachable.json +1 -1
- package/journeys/vendor-examples.json +14 -6
- package/package.json +12 -9
- package/spec/SOURCE.md +5 -6
- package/spec/patches.json +20 -20
- package/spec/sources.json +15 -0
- package/src/cli.ts +1 -1
- package/src/engine/media.ts +21 -58
- package/src/engine/text.ts +2 -2
- package/src/index.ts +2 -1
- package/src/manifest.ts +7 -6
- package/src/semantics/available-methods.ts +55 -41
- package/src/semantics/doors.ts +74 -8
- package/src/semantics/shared.ts +27 -8
- package/src/server.ts +2 -11
- package/dist/generated/pack-facts.json +0 -53
- package/dist/spec/page-to-openapi.ts +0 -258
- package/spec/page-to-openapi.ts +0 -258
|
@@ -1,133 +1,59 @@
|
|
|
1
1
|
{
|
|
2
2
|
"judge": {
|
|
3
|
-
"agent": "Claude
|
|
4
|
-
"name": "
|
|
3
|
+
"agent": "Claude journey judge (Opus)",
|
|
4
|
+
"name": "Independent journey judge"
|
|
5
5
|
},
|
|
6
6
|
"authors": [
|
|
7
|
-
"
|
|
7
|
+
"yueranyuan",
|
|
8
|
+
"Aaron (A-001)",
|
|
9
|
+
"Claude Opus 5.5",
|
|
10
|
+
"Codex"
|
|
8
11
|
],
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"judgedAt": "2026-10-02",
|
|
12
|
+
"sha256": "d4f45ec531a3cafd7ec746629b2fb110cf5bb5adb0664f52a5d279eaf7f59ad3",
|
|
13
|
+
"judgedAt": "2026-10-15",
|
|
12
14
|
"plausible": true,
|
|
13
15
|
"rulings": [
|
|
14
16
|
{
|
|
15
|
-
"id": "
|
|
17
|
+
"id": "reply-to-message-id-era",
|
|
16
18
|
"steps": [
|
|
17
|
-
|
|
18
|
-
20,
|
|
19
|
-
22,
|
|
20
|
-
30,
|
|
21
|
-
58,
|
|
22
|
-
62,
|
|
23
|
-
66,
|
|
24
|
-
70,
|
|
25
|
-
73
|
|
19
|
+
61
|
|
26
20
|
],
|
|
27
|
-
"barItem": "
|
|
28
|
-
"reason": "
|
|
29
|
-
"correction": "
|
|
21
|
+
"barItem": "the wrong era: building on an API the vendor had deprecated by the World's date; a call through a door the vendor does not document",
|
|
22
|
+
"reason": "Step 61 (Monday 12 January 2026) replies to the album with the form field reply_to_message_id={album}. Telegram replaced that parameter in Bot API 7.0, December 29, 2023 (https://core.telegram.org/bots/api-changelog: 'Replaced parameters reply_to_message_id and allow_sending_without_reply in the methods ... with the field reply_parameters'), and the page the spec is written from (https://core.telegram.org/bots/api#sendmessage, Bot API 10.3) no longer documents it; spec/SOURCE.md admits the page 'no longer lists it' and patches it in. A step through a parameter the vendor withdrew two years before the World's date is rejected even where the twin answers it.",
|
|
23
|
+
"correction": "Step 61 sends reply_parameters={\"message_id\":{album}} (JSON-serialized ReplyParameters, as https://core.telegram.org/bots/api#sendmessage documents) in place of reply_to_message_id, keeping its expectation result.reply_to_message.message_id equals {album}."
|
|
30
24
|
},
|
|
31
25
|
{
|
|
32
|
-
"id": "
|
|
26
|
+
"id": "photo-sizes-order",
|
|
33
27
|
"steps": [
|
|
34
|
-
33,
|
|
35
|
-
37,
|
|
36
|
-
40,
|
|
37
|
-
41,
|
|
38
|
-
42,
|
|
39
|
-
43,
|
|
40
|
-
45,
|
|
41
|
-
46,
|
|
42
|
-
47,
|
|
43
|
-
48,
|
|
44
|
-
50,
|
|
45
28
|
59,
|
|
46
|
-
61,
|
|
47
|
-
63,
|
|
48
|
-
65,
|
|
49
|
-
67,
|
|
50
29
|
69
|
|
51
30
|
],
|
|
52
|
-
"barItem": "
|
|
53
|
-
"reason": "
|
|
54
|
-
"correction": "
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
"id": "running-harness-polls-without-offset",
|
|
58
|
-
"steps": [
|
|
59
|
-
76
|
|
60
|
-
],
|
|
61
|
-
"barItem": "content that does not bear out the step's name",
|
|
62
|
-
"reason": "Step 76 is Harness \"still running on her laptop\". The adapter it runs keeps its offset in its polling loop and sends it on every poll: `offset = update.update_id + 1` (@chat-adapter/telegram 4.39.0, dist/index.js:3327-3345, pollingLoop; demand.json: \"confirming the last with offset\"). The life last confirmed one at step 53. Step 76's body, {\"limit\": 100, \"timeout\": 30}, is the request a freshly started adapter sends (step 9), not the request of the process the step names.",
|
|
63
|
-
"correction": "Step 76's body: {\"offset\": \"{agent_next}\", \"limit\": 100, \"timeout\": 30}."
|
|
31
|
+
"barItem": "vendor behaviour the vendor's documentation contradicts",
|
|
32
|
+
"reason": "Step 59 expects result.0.photo.0.width 480 and result.2.photo.0.height 300 for 480x300 PNG uploads, and step 69 expects result.photo.0.width 640 and height 480 for a 640x480 JPEG: the first PhotoSize being the uploaded image itself. Telegram 'attaches a vector of thumbnails with reduced resolution to all uploaded media', the smallest type s 'bounded by 100x100 pixels' and m by 320x320 (https://core.telegram.org/api/files, image thumbnail types; Message.photo is 'available sizes of the photo', https://core.telegram.org/bots/api#message), and the Bot API answers them in ascending order (TDLib at 42e6a52, td/telegram/PhotoSize.cpp get_photo_sizes_object, stable_sort by size then width*height, the TDLib source the spec cites). photo.0 is the 100-pixel thumbnail; the original's dimensions are on the last size.",
|
|
33
|
+
"correction": "Steps 59 and 69 expect the uploaded dimensions on the last PhotoSize (step 59: result.0.photo.2.width 480 and result.2.photo.2.height 300; step 69: result.photo.2.width 640 and height 480), with photo.0 the s thumbnail, bounded by 100x100."
|
|
64
34
|
}
|
|
65
35
|
],
|
|
66
36
|
"applied": {
|
|
67
|
-
"from": "
|
|
37
|
+
"from": "8251bda4592d9f3dae1bb5067727bee657d212ebd5aca36c32e8a7f4d1beb222",
|
|
68
38
|
"wasPlausible": false,
|
|
69
39
|
"steps": [
|
|
70
|
-
"
|
|
71
|
-
"
|
|
72
|
-
"
|
|
73
|
-
"70",
|
|
74
|
-
"71",
|
|
75
|
-
"72",
|
|
76
|
-
"73",
|
|
77
|
-
"74",
|
|
78
|
-
"76"
|
|
40
|
+
"59",
|
|
41
|
+
"61",
|
|
42
|
+
"69"
|
|
79
43
|
],
|
|
80
44
|
"rulings": [
|
|
81
45
|
{
|
|
82
|
-
"id": "three-tuesdays-skipped",
|
|
83
46
|
"steps": [
|
|
84
|
-
|
|
85
|
-
20,
|
|
86
|
-
22,
|
|
87
|
-
30,
|
|
88
|
-
58,
|
|
89
|
-
62,
|
|
90
|
-
66,
|
|
91
|
-
70,
|
|
92
|
-
73
|
|
47
|
+
61
|
|
93
48
|
],
|
|
94
|
-
"
|
|
95
|
-
"reason": "Steps 16, 20 and 22 set the standup for 9:00 every Tuesday, with items posted in the group, and the agent promises the digest at 9:15. From step 19 the agent is an administrator, and an administrator is sent every group message (https://core.telegram.org/bots/features#privacy-mode, \"bot admins always receive all messages\"). The job runs once, Tuesday 6 January (step 30). The team keeps the group standup until Monday 2 February (step 70: \"the standups move to a video call\"), yet Tuesdays 13, 20 and 27 January pass inside steps 58-70 with no items posted, no poll and no digest. Step 73's digest then says \"No standup items were posted this week\", which implies the earlier weeks had items. Three of the four weekly runs the story promises never happen, and nothing in the story explains why.",
|
|
96
|
-
"correction": "Move the standups' move and the bot's removal to Monday 12 January, 18:00, after step 61. The job's next run, Tuesday 13 January at 09:15, is then the refused one, and the waits to Wednesday 14 January 16:00 and onward are adjusted. Alternatively, show the Tuesday standups of 13, 20 and 27 January: items posted, a poll, and a digest each week."
|
|
49
|
+
"why": "Step 61 sends reply_parameters={\"message_id\":{album}} (JSON-serialized ReplyParameters, as https://core.telegram.org/bots/api#sendmessage documents) in place of reply_to_message_id, keeping its expectation result.reply_to_message.message_id equals {album}."
|
|
97
50
|
},
|
|
98
51
|
{
|
|
99
|
-
"id": "postiz-never-given-token-n",
|
|
100
52
|
"steps": [
|
|
101
|
-
33,
|
|
102
|
-
37,
|
|
103
|
-
40,
|
|
104
|
-
41,
|
|
105
|
-
42,
|
|
106
|
-
43,
|
|
107
|
-
45,
|
|
108
|
-
46,
|
|
109
|
-
47,
|
|
110
|
-
48,
|
|
111
|
-
50,
|
|
112
53
|
59,
|
|
113
|
-
61,
|
|
114
|
-
63,
|
|
115
|
-
65,
|
|
116
|
-
67,
|
|
117
54
|
69
|
|
118
55
|
],
|
|
119
|
-
"
|
|
120
|
-
"reason": "Postiz's Telegram provider calls with one bot, the one its deployment configures: `new TelegramBot(process.env.TELEGRAM_TOKEN!)` (libraries/nestjs-libraries/src/integrations/social/telegram.provider.ts:19, postiz-app e0a08a7; demand.json, Postiz's client: \"TELEGRAM_TOKEN\"). In hosted Postiz, that is Postiz's own bot and not one a customer makes. Step 33 has Ana make @tidelabs_news_bot \"for Postiz\", and from step 37 on Postiz calls with {news_token}. But no step or name says that Ana runs Postiz herself, or that token N is set as its TELEGRAM_TOKEN. The life never shows how the credential Postiz uses reaches it, and as written it reads as the hosted product calling with a bot that belongs to the customer.",
|
|
121
|
-
"correction": "In step 33 or a step beside it, say that Ana runs Postiz herself (docker compose, local storage, as demand.json reads it) and sets TELEGRAM_TOKEN to the news bot's token before she adds Telegram. Use the same wording in the outline."
|
|
122
|
-
},
|
|
123
|
-
{
|
|
124
|
-
"id": "running-harness-polls-without-offset",
|
|
125
|
-
"steps": [
|
|
126
|
-
76
|
|
127
|
-
],
|
|
128
|
-
"barItem": "content that does not bear out the step's name",
|
|
129
|
-
"reason": "Step 76 is Harness \"still running on her laptop\". The adapter it runs keeps its offset in its polling loop and sends it on every poll: `offset = update.update_id + 1` (@chat-adapter/telegram 4.39.0, dist/index.js:3327-3345, pollingLoop; demand.json: \"confirming the last with offset\"). The life last confirmed one at step 53. Step 76's body, {\"limit\": 100, \"timeout\": 30}, is the request a freshly started adapter sends (step 9), not the request of the process the step names.",
|
|
130
|
-
"correction": "Step 76's body: {\"offset\": \"{agent_next}\", \"limit\": 100, \"timeout\": 30}."
|
|
56
|
+
"why": "Steps 59 and 69 expect the uploaded dimensions on the last PhotoSize (step 59: result.0.photo.2.width 480 and result.2.photo.2.height 300; step 69: result.photo.2.width 640 and height 480), with photo.0 the s thumbnail, bounded by 100x100."
|
|
131
57
|
}
|
|
132
58
|
]
|
|
133
59
|
}
|
package/journeys/decisions.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"about": "telegram's decision table (the P3 procedure: demand, then this table, then the code): each method the pack serves, the demand it answers (journeys/demand.json, or the manifest's refresh scope) and the mechanism that serves it. The Bot API is an RPC wire (POST /bot<token>/<method>, every answer {ok, result}), so no method is a create, read, update or delete the derived core could serve: each is a handler over the bot the token names (./src/semantics/around.ts resolves it, before routing, as the Bot API server does). Every other method of the page answers the server's unknown method (the manifest's gap), the URL-media sends
|
|
2
|
+
"about": "telegram's decision table (the P3 procedure: demand, then this table, then the code): each method the pack serves, the demand it answers (journeys/demand.json, or the manifest's refresh scope) and the mechanism that serves it. The Bot API is an RPC wire (POST /bot<token>/<method>, every answer {ok, result}), so no method is a create, read, update or delete the derived core could serve: each is a handler over the bot the token names (./src/semantics/around.ts resolves it, before routing, as the Bot API server does). Every other method of the page answers the server's unknown method (the manifest's gap), the URL-media sends included: a URL the World cannot reach is refused as Telegram refuses one it cannot download. People, bots and chats are made through the World's doors (the Telegram app and BotFather, which no Bot API method stands for).",
|
|
3
3
|
"decisions": [
|
|
4
4
|
{
|
|
5
5
|
"operation": "getMe",
|
|
6
|
-
"demand": "Volter Harness's adapter reads its bot as it starts
|
|
6
|
+
"demand": "Volter Harness's adapter reads its bot as it starts",
|
|
7
7
|
"mechanism": "handler",
|
|
8
8
|
"why": "a computed read: the bot the token names, its User with the bot-only fields (can_join_groups, can_read_all_group_messages for privacy mode, supports_inline_queries, …)"
|
|
9
9
|
},
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
},
|
|
16
16
|
{
|
|
17
17
|
"operation": "getUpdates",
|
|
18
|
-
"demand": "Volter Harness's adapter polls in a loop (timeout 30, limit 100)
|
|
18
|
+
"demand": "Volter Harness's adapter polls in a loop (timeout 30, limit 100)",
|
|
19
19
|
"mechanism": "handler",
|
|
20
20
|
"why": "an action: the bot's queue read from the offset (confirming, and so deleting, every update below it), filtered by the allowed_updates in force when each was made, at most limit; with nothing waiting and a timeout, the read held until an update is written (the kernel's held read)"
|
|
21
21
|
},
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
29
|
"operation": "sendMessage",
|
|
30
|
-
"demand": "Volter Harness posts its answers and its scheduled reports
|
|
30
|
+
"demand": "Volter Harness posts its answers and its scheduled reports",
|
|
31
31
|
"mechanism": "handler",
|
|
32
32
|
"why": "a message made in a chat the bot may write in: its message_id counted per chat, its text parsed for the parse_mode's entities (HTML, MarkdownV2 or Markdown), checked for length (1-4096 after parsing), a reply resolved; it writes no update for its own bot (bots do not receive their own messages)",
|
|
33
33
|
"sourceRules": [
|
|
@@ -39,57 +39,93 @@
|
|
|
39
39
|
},
|
|
40
40
|
{
|
|
41
41
|
"operation": "sendPhoto",
|
|
42
|
-
"demand": "
|
|
42
|
+
"demand": "No scanned application calls it; the customer life walks it.",
|
|
43
43
|
"mechanism": "handler",
|
|
44
44
|
"why": "a message with a photo: the upload's bytes stored as a file (its width and height read from the image), the caption parsed and checked (0-1024); a URL the World cannot reach is refused as the server refuses one it cannot download"
|
|
45
45
|
},
|
|
46
46
|
{
|
|
47
47
|
"operation": "sendVideo",
|
|
48
|
-
"demand": "
|
|
48
|
+
"demand": "No scanned application calls it; the customer life walks it.",
|
|
49
49
|
"mechanism": "handler",
|
|
50
50
|
"why": "a message with a video: the bytes stored, its width, height and duration read from the file's own header where it is an MP4"
|
|
51
51
|
},
|
|
52
52
|
{
|
|
53
53
|
"operation": "sendDocument",
|
|
54
|
-
"demand": "
|
|
54
|
+
"demand": "Volter Harness's board sends a done card's artifact with its notice as a MarkdownV2 caption",
|
|
55
55
|
"mechanism": "handler",
|
|
56
56
|
"why": "a message with a document: the bytes stored with its name and media type; its caption parsed as sendMessage's text is (HTML, MarkdownV2 or Markdown) and checked (0-1024)"
|
|
57
57
|
},
|
|
58
58
|
{
|
|
59
59
|
"operation": "sendMediaGroup",
|
|
60
|
-
"demand": "
|
|
60
|
+
"demand": "Volter Harness's board sends a done card's several artifacts as one album of documents",
|
|
61
61
|
"mechanism": "handler",
|
|
62
62
|
"why": "2-10 messages sharing one media_group_id, each its own file; the array of sent messages answered"
|
|
63
63
|
},
|
|
64
64
|
{
|
|
65
65
|
"operation": "deleteMessage",
|
|
66
|
-
"demand": "
|
|
66
|
+
"demand": "No scanned application calls it; the customer life walks it.",
|
|
67
67
|
"mechanism": "handler",
|
|
68
68
|
"why": "a message deleted where the bot may delete it: its own, or another's in a channel or group where it is an administrator with can_delete_messages; 'message to delete not found' for one that is not there"
|
|
69
69
|
},
|
|
70
70
|
{
|
|
71
71
|
"operation": "getChat",
|
|
72
|
-
"demand": "
|
|
72
|
+
"demand": "the vendor-backed refresh reads each chat again (the manifest's refresh of chat)",
|
|
73
73
|
"mechanism": "handler",
|
|
74
74
|
"why": "a computed read: the chat as ChatFullInfo, its access checked as the server checks a read"
|
|
75
75
|
},
|
|
76
76
|
{
|
|
77
77
|
"operation": "getChatMember",
|
|
78
|
-
"demand": "
|
|
78
|
+
"demand": "No scanned application calls it; the customer life walks it.",
|
|
79
79
|
"mechanism": "handler",
|
|
80
80
|
"why": "a computed read: the member's status and rights with its user"
|
|
81
81
|
},
|
|
82
82
|
{
|
|
83
83
|
"operation": "getFile",
|
|
84
|
-
"demand": "
|
|
84
|
+
"demand": "No scanned application calls it; the customer life walks it.",
|
|
85
85
|
"mechanism": "handler",
|
|
86
86
|
"why": "an action: the file's download path issued (file_path), valid for the bot that asked"
|
|
87
87
|
},
|
|
88
88
|
{
|
|
89
89
|
"operation": "downloadFile",
|
|
90
|
-
"demand": "
|
|
90
|
+
"demand": "No scanned application calls it; the customer life walks it.",
|
|
91
91
|
"mechanism": "handler",
|
|
92
92
|
"why": "the stored bytes of the file a getFile path names, for the bot whose token is in the path"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"operation": "door:people",
|
|
96
|
+
"demand": "the World's own door, POST /_twin/people: { first_name, last_name?, username?, language_code? }: a Telegram account, as a person signs up in the app",
|
|
97
|
+
"mechanism": "door",
|
|
98
|
+
"why": "{ first_name, last_name?, username?, language_code? }: a Telegram account, as a person signs up in the app, which no vendor API makes."
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"operation": "door:bots",
|
|
102
|
+
"demand": "the World's own door, POST /_twin/bots: { owner?, name?, username? }: BotFather's /newbot, for the person `owner` (a user id): the bot and its token; the same username answers the same bot",
|
|
103
|
+
"mechanism": "door",
|
|
104
|
+
"why": "{ owner?, name?, username? }: BotFather's /newbot, for the person `owner` (a user id): the bot and its token; the same username answers the same bot, which no vendor API makes."
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"operation": "door:botToken",
|
|
108
|
+
"demand": "the World's own door, POST /_twin/bots/{username}/token: { owner }: BotFather's /token: a new token, the old one revoked",
|
|
109
|
+
"mechanism": "door",
|
|
110
|
+
"why": "{ owner }: BotFather's /token: a new token, the old one revoked, which no vendor API makes."
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"operation": "door:chats",
|
|
114
|
+
"demand": "the World's own door, POST /_twin/chats: { creator, type: group | supergroup | channel, title, username?, description?, photo? (base64 bytes), members?: [{ user, status?: member | administrator, rights? }] }: a person makes a chat in the app",
|
|
115
|
+
"mechanism": "door",
|
|
116
|
+
"why": "{ creator, type: group | supergroup | channel, title, username?, description?, photo? (base64 bytes), members?: [{ user, status?: member | administrator, rights? }] }: a person makes a chat in the app, which no vendor API makes."
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"operation": "door:members",
|
|
120
|
+
"demand": "the World's own door, POST /_twin/chats/{chat_id}/members: { by, user, status: member | administrator | kicked, rights? }: a person adds, promotes or removes a member in the app",
|
|
121
|
+
"mechanism": "door",
|
|
122
|
+
"why": "{ by, user, status: member | administrator | kicked, rights? }: a person adds, promotes or removes a member in the app, which no vendor API makes."
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"operation": "door:messages",
|
|
126
|
+
"demand": "the World's own door, POST /_twin/messages: { from, chat (a chat id, a chat's @username, or a bot's @username for a private chat with it), text, reply_to_message_id? }: a person writes in the app",
|
|
127
|
+
"mechanism": "door",
|
|
128
|
+
"why": "{ from, chat (a chat id, a chat's @username, or a bot's @username for a private chat with it), text, reply_to_message_id? }: a person writes in the app, which no vendor API makes."
|
|
93
129
|
}
|
|
94
130
|
]
|
|
95
131
|
}
|
package/journeys/demand.json
CHANGED
|
@@ -55,97 +55,6 @@
|
|
|
55
55
|
"client": "@chat-adapter/telegram@4.39.0:dist/index.js:2802"
|
|
56
56
|
}
|
|
57
57
|
]
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
"commit": "e0a08a7d9592fdc1ef1e82cb7cb781489fceade3",
|
|
61
|
-
"client": "node-telegram-bot-api 0.66.0 (POST https://api.telegram.org/bot<token>/<method>, form-urlencoded, multipart for a file; rows marked node-telegram-bot-api@0.66.0: are its published src), TELEGRAM_TOKEN",
|
|
62
|
-
"backend": [
|
|
63
|
-
{
|
|
64
|
-
"call": "the connect screen polls for the person's /connect message (allowed_updates message and channel_post, from the last offset)",
|
|
65
|
-
"operation": "getUpdates",
|
|
66
|
-
"evidence": "libraries/nestjs-libraries/src/integrations/social/telegram.provider.ts:87",
|
|
67
|
-
"client": "node-telegram-bot-api@0.66.0:src/telegram.js:849"
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
"call": "found: the bot's own id",
|
|
71
|
-
"operation": "getMe",
|
|
72
|
-
"evidence": "libraries/nestjs-libraries/src/integrations/social/telegram.provider.ts:105",
|
|
73
|
-
"client": "node-telegram-bot-api@0.66.0:src/telegram.js:931"
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
"call": "...whether it is an administrator that can delete messages there",
|
|
77
|
-
"operation": "getChatMember",
|
|
78
|
-
"evidence": "libraries/nestjs-libraries/src/integrations/social/telegram.provider.ts:356",
|
|
79
|
-
"client": "node-telegram-bot-api@0.66.0:src/telegram.js:1966"
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
"call": "an administrator: the /connect message deleted, a success notice posted and deleted ten seconds later",
|
|
83
|
-
"operation": "deleteMessage",
|
|
84
|
-
"evidence": "libraries/nestjs-libraries/src/integrations/social/telegram.provider.ts:120",
|
|
85
|
-
"client": "node-telegram-bot-api@0.66.0:src/telegram.js:3093"
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
"call": "the success notice (or, without the right to delete, the notice asking the person to remove the messages)",
|
|
89
|
-
"operation": "sendMessage",
|
|
90
|
-
"evidence": "libraries/nestjs-libraries/src/integrations/social/telegram.provider.ts:122",
|
|
91
|
-
"client": "node-telegram-bot-api@0.66.0:src/telegram.js:970"
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
"call": "the channel added: its chat read (id, title, username, photo)",
|
|
95
|
-
"operation": "getChat",
|
|
96
|
-
"evidence": "libraries/nestjs-libraries/src/integrations/social/telegram.provider.ts:62",
|
|
97
|
-
"client": "node-telegram-bot-api@0.66.0:src/telegram.js:1924"
|
|
98
|
-
},
|
|
99
|
-
{
|
|
100
|
-
"call": "...and its photo's file, whose path makes the channel's picture link",
|
|
101
|
-
"operation": "getFile",
|
|
102
|
-
"evidence": "libraries/nestjs-libraries/src/integrations/social/telegram.provider.ts:71",
|
|
103
|
-
"client": "node-telegram-bot-api@0.66.0:src/telegram.js:1522"
|
|
104
|
-
},
|
|
105
|
-
{
|
|
106
|
-
"call": "...and Postiz copies the picture into its own storage from that link",
|
|
107
|
-
"operation": "downloadFile",
|
|
108
|
-
"evidence": "libraries/nestjs-libraries/src/database/prisma/integrations/integration.service.ts:119",
|
|
109
|
-
"client": "libraries/nestjs-libraries/src/upload/local.storage.ts:62"
|
|
110
|
-
},
|
|
111
|
-
{
|
|
112
|
-
"call": "a post with no media: its text, parse_mode HTML",
|
|
113
|
-
"operation": "sendMessage",
|
|
114
|
-
"evidence": "libraries/nestjs-libraries/src/integrations/social/telegram.provider.ts:224",
|
|
115
|
-
"client": "node-telegram-bot-api@0.66.0:src/telegram.js:970"
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
"call": "a post with one image: the photo uploaded with the text as its caption",
|
|
119
|
-
"operation": "sendPhoto",
|
|
120
|
-
"evidence": "libraries/nestjs-libraries/src/integrations/social/telegram.provider.ts:247",
|
|
121
|
-
"client": "node-telegram-bot-api@0.66.0:src/telegram.js:1075"
|
|
122
|
-
},
|
|
123
|
-
{
|
|
124
|
-
"call": "a post with one video",
|
|
125
|
-
"operation": "sendVideo",
|
|
126
|
-
"evidence": "libraries/nestjs-libraries/src/integrations/social/telegram.provider.ts:240",
|
|
127
|
-
"client": "node-telegram-bot-api@0.66.0:src/telegram.js:1164"
|
|
128
|
-
},
|
|
129
|
-
{
|
|
130
|
-
"call": "a post with one file that is neither",
|
|
131
|
-
"operation": "sendDocument",
|
|
132
|
-
"evidence": "libraries/nestjs-libraries/src/integrations/social/telegram.provider.ts:253",
|
|
133
|
-
"client": "node-telegram-bot-api@0.66.0:src/telegram.js:1136"
|
|
134
|
-
},
|
|
135
|
-
{
|
|
136
|
-
"call": "a post with several media: an album of up to ten, the text on the first",
|
|
137
|
-
"operation": "sendMediaGroup",
|
|
138
|
-
"evidence": "libraries/nestjs-libraries/src/integrations/social/telegram.provider.ts:272",
|
|
139
|
-
"client": "node-telegram-bot-api@0.66.0:src/telegram.js:1296"
|
|
140
|
-
},
|
|
141
|
-
{
|
|
142
|
-
"call": "a comment: the next post sent as a reply to the last (reply_to_message_id)",
|
|
143
|
-
"operation": "sendMessage",
|
|
144
|
-
"evidence": "libraries/nestjs-libraries/src/integrations/social/telegram.provider.ts:226",
|
|
145
|
-
"client": "node-telegram-bot-api@0.66.0:src/telegram.js:970"
|
|
146
|
-
}
|
|
147
|
-
],
|
|
148
|
-
"name": "gitroomhq/postiz-app"
|
|
149
58
|
}
|
|
150
59
|
],
|
|
151
60
|
"optional": []
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"source": "https://chat-sdk.dev/docs/adapters/telegram",
|
|
4
|
+
"about": "Volter Harness's Telegram channel as it ships (supercode 62620578c014, sdk/orchestrator/adapters/chat-sdk.mjs): the unchanged Chat SDK (chat 4.39.0, @chat-adapter/telegram 4.39.0, @chat-adapter/state-memory 4.39.0) polls with the bot token the World's credential door issues as TELEGRAM_BOT_TOKEN (getMe, deleteWebhook, getUpdates), hears Ada's private message (the World's people and messages doors stand in for the Telegram app) and answers in her chat (thread.post: sendMessage). The readback reads her private chat back (getChat) without polling or sending.",
|
|
5
|
+
"files": {
|
|
6
|
+
"package.json": "{\n \"name\": \"telegram-harness-customer\",\n \"private\": true,\n \"type\": \"module\",\n \"dependencies\": {\n \"chat\": \"4.39.0\",\n \"@chat-adapter/telegram\": \"4.39.0\",\n \"@chat-adapter/state-memory\": \"4.39.0\"\n }\n}\n",
|
|
7
|
+
".env.example": "TELEGRAM_BOT_TOKEN=\nTELEGRAM_BOT_USERNAME=\n",
|
|
8
|
+
"agent.mjs": "import assert from 'node:assert/strict';\nimport { readFileSync, writeFileSync } from 'node:fs';\nimport { Chat } from 'chat';\nimport { createTelegramAdapter } from '@chat-adapter/telegram';\nimport { createMemoryState } from '@chat-adapter/state-memory';\nif (!process.env.VOLTER_WORLD) throw new Error('Run this app through its World.');\n// Volter Harness's Telegram channel: the Chat SDK's adapter from TELEGRAM_BOT_TOKEN, polling (sdk/orchestrator/adapters/chat-sdk.mjs)\nconst telegram = createTelegramAdapter({ botToken: process.env.TELEGRAM_BOT_TOKEN, mode: 'polling', logger: console });\nconst receiptFile = 'telegram-chat.json';\nif (process.argv[2] === 'readback') {\n const saved = JSON.parse(readFileSync(receiptFile, 'utf8'));\n const thread = await telegram.fetchThread(saved.thread);\n assert.equal(thread.id, saved.thread);\n assert.equal(thread.isDM, true);\n assert.equal(thread.metadata.chat.first_name, 'Ada');\n console.log(JSON.stringify({ retained: true, thread: thread.id, chat: thread.metadata.chat }));\n} else {\n // Ada opens the bot in the Telegram app and writes to it: the World's doors stand in for the app\n const api = (process.env.TELEGRAM_API_BASE_URL ?? 'https://api.telegram.org').replace(/\\/+$/, '');\n const door = async (path, body) => {\n const response = await fetch(api + path, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) });\n if (!response.ok) throw new Error(path + ' was refused: ' + response.status + ' ' + await response.text());\n return response.json();\n };\n const ada = await door('/_twin/people', { first_name: 'Ada', username: 'ada_lovelace' });\n await door('/_twin/messages', { from: ada.id, chat: '@' + process.env.TELEGRAM_BOT_USERNAME, text: 'Hi! Can you keep notes of our standups?' });\n const answered = new Promise((resolve, reject) => {\n const chat = new Chat({ adapters: { telegram }, state: createMemoryState(), concurrency: 'concurrent', logger: 'silent' });\n chat.onDirectMessage(async (thread, message) => {\n try {\n const sent = await thread.post('Noted, Ada: I will keep the standup notes here.');\n resolve({ chat, thread, message, sent });\n } catch (error) { reject(error); }\n });\n chat.initialize().catch(reject);\n });\n const { chat, thread, message, sent } = await answered;\n await telegram.stopPolling();\n await chat.shutdown();\n assert.equal(thread.isDM, true);\n assert.equal(message.text, 'Hi! Can you keep notes of our standups?');\n assert.equal(message.author.userId, String(ada.id));\n assert.equal(message.author.isMe, false);\n assert.equal(thread.id, 'telegram:' + ada.id);\n assert.match(String(sent.id), new RegExp('^' + ada.id + ':\\\\d+$'), 'the answer names the message Telegram gave it in her chat');\n assert.equal(sent.text, 'Noted, Ada: I will keep the standup notes here.');\n writeFileSync(receiptFile, JSON.stringify({ thread: thread.id, message: sent.id }) + '\\n');\n console.log(JSON.stringify({ answered: true, thread: thread.id, heard: message.text, sent: sent.id }));\n}\n"
|
|
9
|
+
},
|
|
10
|
+
"run": [
|
|
11
|
+
"node",
|
|
12
|
+
"agent.mjs"
|
|
13
|
+
],
|
|
14
|
+
"retention": {
|
|
15
|
+
"readback": [
|
|
16
|
+
"node",
|
|
17
|
+
"agent.mjs",
|
|
18
|
+
"readback"
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"urlUnreachable"
|
|
23
23
|
],
|
|
24
24
|
"world": "sealed-refusal",
|
|
25
|
-
"why": "A photo, video or document sent by an HTTP URL is downloaded by Telegram; a World is sealed, so the twin reaches no URL outside it and answers Telegram's failure for a URL it cannot get. No World of this surface replays the download (spec/doc-examples.json's sendPhoto: an HTTP URL is recorded not replayed for the same reason)
|
|
25
|
+
"why": "A photo, video or document sent by an HTTP URL is downloaded by Telegram; a World is sealed, so the twin reaches no URL outside it and answers Telegram's failure for a URL it cannot get. No World of this surface replays the download (spec/doc-examples.json's sendPhoto: an HTTP URL is recorded not replayed for the same reason)."
|
|
26
26
|
}
|
|
27
27
|
],
|
|
28
28
|
"state": []
|
|
@@ -1221,12 +1221,20 @@
|
|
|
1221
1221
|
},
|
|
1222
1222
|
"status": 200,
|
|
1223
1223
|
"capture": {
|
|
1224
|
-
"notes_photo": "result.photo.
|
|
1225
|
-
"notes_unique": "result.photo.
|
|
1224
|
+
"notes_photo": "result.photo.1.file_id",
|
|
1225
|
+
"notes_unique": "result.photo.1.file_unique_id"
|
|
1226
1226
|
},
|
|
1227
1227
|
"expect": [
|
|
1228
1228
|
{
|
|
1229
|
-
"path": "result.photo.
|
|
1229
|
+
"path": "result.photo.0.width",
|
|
1230
|
+
"equals": 100
|
|
1231
|
+
},
|
|
1232
|
+
{
|
|
1233
|
+
"path": "result.photo.1.width",
|
|
1234
|
+
"equals": 320
|
|
1235
|
+
},
|
|
1236
|
+
{
|
|
1237
|
+
"path": "result.photo.2",
|
|
1230
1238
|
"exists": false
|
|
1231
1239
|
}
|
|
1232
1240
|
]
|
|
@@ -1242,15 +1250,15 @@
|
|
|
1242
1250
|
},
|
|
1243
1251
|
"status": 200,
|
|
1244
1252
|
"undecided": {
|
|
1245
|
-
"result.photo.
|
|
1253
|
+
"result.photo.1.file_id": "a resent file may answer another valid file_id; the twin answers the one sent, which only coincides (Sending files: \"a file can have different valid file_ids even for the same bot\")"
|
|
1246
1254
|
},
|
|
1247
1255
|
"expect": [
|
|
1248
1256
|
{
|
|
1249
|
-
"path": "result.photo.
|
|
1257
|
+
"path": "result.photo.1.file_unique_id",
|
|
1250
1258
|
"equals": "{notes_unique}"
|
|
1251
1259
|
},
|
|
1252
1260
|
{
|
|
1253
|
-
"path": "result.photo.
|
|
1261
|
+
"path": "result.photo.2",
|
|
1254
1262
|
"exists": false
|
|
1255
1263
|
},
|
|
1256
1264
|
{
|
package/package.json
CHANGED
|
@@ -1,37 +1,40 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/twin-telegram",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "The telegram twin (Protocol 3).",
|
|
5
5
|
"author": "Volter (https://github.com/volter-ai)",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"exports": {
|
|
9
|
-
".":
|
|
10
|
-
"types": "./dist/src/index.d.ts",
|
|
11
|
-
"default": "./dist/src/index.js"
|
|
12
|
-
}
|
|
9
|
+
".": "./dist/src/index.js"
|
|
13
10
|
},
|
|
14
11
|
"bin": {
|
|
15
12
|
"world-telegram": "dist/src/cli.js"
|
|
16
13
|
},
|
|
17
14
|
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"generated",
|
|
18
17
|
"src",
|
|
19
18
|
"journeys",
|
|
20
19
|
"spec",
|
|
21
20
|
"README.md",
|
|
22
21
|
"!**/*.test.ts",
|
|
23
|
-
"
|
|
24
|
-
"generated"
|
|
22
|
+
"LICENSE"
|
|
25
23
|
],
|
|
26
24
|
"peerDependencies": {
|
|
27
|
-
"@volter/world-core": "
|
|
25
|
+
"@volter/world-core": ">=3.0.164",
|
|
26
|
+
"@volter/world-runtime": ">=3.0.184"
|
|
28
27
|
},
|
|
29
28
|
"engines": {
|
|
30
29
|
"node": ">=22.3"
|
|
31
30
|
},
|
|
32
31
|
"repository": {
|
|
33
32
|
"type": "git",
|
|
34
|
-
"url": "git+https://github.com/volter-ai/twin-packs-
|
|
33
|
+
"url": "git+https://github.com/volter-ai/twin-packs-open.git",
|
|
35
34
|
"directory": "telegram"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://github.com/volter-ai/twin-packs-open/tree/main/telegram#readme",
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/volter-ai/twin-packs-open/issues"
|
|
36
39
|
}
|
|
37
40
|
}
|
package/spec/SOURCE.md
CHANGED
|
@@ -4,15 +4,14 @@
|
|
|
4
4
|
from its reference page, https://core.telegram.org/bots/api, as Telegram serves it (fetched 2026-10-02, HTML,
|
|
5
5
|
860074 bytes, sha256 `9da688c4213864d7911fcf0f501d2d7a8c3bbc351b8b4325e85e509bcc9d969f`; the page's "Recent changes"
|
|
6
6
|
head it **Bot API 10.3**, August 24, 2026).
|
|
7
|
-
- **How it was written:** mechanically from the page's HTML
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
Stickers, Rich messages, Inline mode, Payments, Telegram Passport, Games): 185 methods and 400 types.
|
|
7
|
+
- **How it was written:** mechanically from the page's HTML, captured as `bot-api.html.gz` with the bytes named above.
|
|
8
|
+
It covers the page's methods and types across Getting updates, Available types, Available methods, Updating messages,
|
|
9
|
+
Stickers, Rich messages, Inline mode, Payments, Telegram Passport and Games: 185 methods and 400 types.
|
|
11
10
|
- A method (an `h4` with a lower-case name and a Parameter/Type/Required/Description table) is
|
|
12
11
|
`POST /bot{token}/<method>` (the page's "https://api.telegram.org/bot<token>/METHOD_NAME"), its operationId the
|
|
13
12
|
method's name, its tag the page's section; its parameters a body taken as JSON, form or multipart, and the same
|
|
14
13
|
parameters again in the query string, none required there (the page's "four ways of passing parameters", the
|
|
15
|
-
first of them the URL query string:
|
|
14
|
+
first of them the URL query string: node-telegram-bot-api sends a medium's chat_id and caption in the
|
|
16
15
|
query beside its multipart file), `Required: Yes` required in the body; its answer `{ ok: true, result }` with the result the
|
|
17
16
|
method's text says is returned (a linked type, "Array of", True, String or Integer; several, `oneOf`), and any
|
|
18
17
|
other answer the error envelope `{ ok: false, error_code, description, parameters? }` (Making requests).
|
|
@@ -35,7 +34,7 @@
|
|
|
35
34
|
- **Corrections:** `patches.json` (RFC 6902, each with its reason and source): `reply_to_message_id` added to the
|
|
36
35
|
bodies and the query parameters of sendMessage, sendPhoto, sendVideo, sendDocument and sendMediaGroup. The page no longer lists it, but the Bot
|
|
37
36
|
API server still reads it when a request carries no `reply_parameters` (tdlib/telegram-bot-api, Client.cpp at
|
|
38
|
-
e3e9dd8), and node-telegram-bot-api 0.66.0
|
|
37
|
+
e3e9dd8), and node-telegram-bot-api 0.66.0 sends it.
|
|
39
38
|
- **Grouping:** `grouping.json` (`{ "by": "tag" }`): every path begins `/bot{token}/`, so an operation's family is the
|
|
40
39
|
page's section it is documented in.
|
|
41
40
|
- **Other sources read:** the Bot API server's own source, which the page names ("Using a Local Bot API Server"),
|