@timqi/pier 0.1.0 → 0.1.2
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 +58 -125
- package/dist/agent/config.js +6 -15
- package/dist/agent/credentials.js +11 -23
- package/dist/agent/events.js +42 -64
- package/dist/agent/listing.js +39 -92
- package/dist/agent/pi.js +103 -252
- package/dist/boards/boards.js +19 -29
- package/dist/channels/attach.js +14 -42
- package/dist/channels/chains.js +33 -37
- package/dist/channels/chunk.js +8 -28
- package/dist/channels/commands.js +3 -14
- package/dist/channels/config.js +33 -52
- package/dist/channels/control.js +4 -13
- package/dist/channels/conversations.js +8 -25
- package/dist/channels/dedup.js +8 -17
- package/dist/channels/gatekeeper.js +13 -23
- package/dist/channels/lark-api.js +23 -63
- package/dist/channels/lark-outbound.js +12 -44
- package/dist/channels/lark-panel.js +7 -23
- package/dist/channels/lark-render.js +18 -62
- package/dist/channels/lark.js +52 -141
- package/dist/channels/lines.js +13 -15
- package/dist/channels/panel.js +16 -36
- package/dist/channels/receipts.js +29 -52
- package/dist/channels/routes.js +3 -9
- package/dist/channels/runtime.js +12 -23
- package/dist/channels/slack-api.js +34 -86
- package/dist/channels/slack-directory.js +7 -23
- package/dist/channels/slack-outbound.js +12 -56
- package/dist/channels/slack-panel.js +4 -13
- package/dist/channels/slack-render.js +23 -91
- package/dist/channels/slack-tool.js +48 -171
- package/dist/channels/slack.js +73 -239
- package/dist/channels/telegram-api.js +8 -20
- package/dist/channels/telegram-panel.js +5 -21
- package/dist/channels/telegram-render.js +13 -40
- package/dist/channels/telegram.js +54 -146
- package/dist/channels/types.js +5 -16
- package/dist/cli.js +17 -41
- package/dist/config-sync.js +87 -4
- package/dist/core/hub.js +7 -20
- package/dist/core/identity.js +20 -59
- package/dist/core/inbound-file.js +15 -49
- package/dist/core/inbox.js +12 -34
- package/dist/core/queue.js +3 -5
- package/dist/core/reply.js +41 -142
- package/dist/core/router.js +209 -264
- package/dist/core/types.js +4 -0
- package/dist/db.js +88 -272
- package/dist/drain.js +57 -50
- package/dist/extensions/index.js +3 -11
- package/dist/extensions/web/anthropic.js +3 -9
- package/dist/extensions/web/artifacts.js +2 -5
- package/dist/extensions/web/content.js +4 -12
- package/dist/extensions/web/http.js +2 -6
- package/dist/extensions/web/language.js +8 -18
- package/dist/extensions/web/openai.js +1 -1
- package/dist/extensions/web/provider.js +5 -18
- package/dist/extensions/web/tools.js +19 -63
- package/dist/lock.js +98 -0
- package/dist/log.js +9 -26
- package/dist/main.js +84 -183
- package/dist/paths.js +10 -26
- package/dist/secrets.js +18 -45
- package/dist/service.js +31 -73
- package/dist/settings.js +19 -63
- package/dist/tasks/agent.js +24 -45
- package/dist/tasks/callbacks.js +8 -16
- package/dist/tasks/command.js +2 -6
- package/dist/tasks/definitions.js +39 -62
- package/dist/tasks/execution.js +41 -39
- package/dist/tasks/groups.js +8 -11
- package/dist/tasks/messages.js +88 -155
- package/dist/tasks/outbox.js +33 -54
- package/dist/tasks/routes.js +4 -7
- package/dist/tasks/runs.js +4 -9
- package/dist/tasks/service.js +29 -42
- package/dist/tasks/store.js +36 -27
- package/dist/tasks/tool.js +59 -60
- package/dist/tools-task.js +20 -60
- package/dist/tools.js +98 -325
- package/dist/update.js +20 -43
- package/dist/web/auth.js +118 -179
- package/dist/web/config-sync.js +2 -2
- package/dist/web/config.js +3 -7
- package/dist/web/explorer.js +10 -21
- package/dist/web/fs.js +20 -42
- package/dist/web/instance.js +35 -82
- package/dist/web/providers.js +5 -11
- package/dist/web/public/assets/{activity-Bl3vZukb.js → activity-DOr8dWeX.js} +1 -1
- package/dist/web/public/assets/activity-DOr8dWeX.js.br +0 -0
- package/dist/web/public/assets/activity-DOr8dWeX.js.gz +0 -0
- package/dist/web/public/assets/{boards-DYuf4Mlj.js → boards-CneyR23E.js} +1 -1
- package/dist/web/public/assets/boards-CneyR23E.js.br +0 -0
- package/dist/web/public/assets/boards-CneyR23E.js.gz +0 -0
- package/dist/web/public/assets/explorer-D0srXT1c.js +4 -0
- package/dist/web/public/assets/explorer-D0srXT1c.js.br +0 -0
- package/dist/web/public/assets/explorer-D0srXT1c.js.gz +0 -0
- package/dist/web/public/assets/index-DbFu15NN.js +85 -0
- package/dist/web/public/assets/index-DbFu15NN.js.br +0 -0
- package/dist/web/public/assets/index-DbFu15NN.js.gz +0 -0
- package/dist/web/public/assets/index-eqQLVS8Q.css +2 -0
- package/dist/web/public/assets/index-eqQLVS8Q.css.br +0 -0
- package/dist/web/public/assets/index-eqQLVS8Q.css.gz +0 -0
- package/dist/web/public/assets/{runs-BLJu7EXN.js → runs-Cv0A-e08.js} +1 -1
- package/dist/web/public/assets/runs-Cv0A-e08.js.br +0 -0
- package/dist/web/public/assets/runs-Cv0A-e08.js.gz +0 -0
- package/dist/web/public/assets/{settings-BrdVh-Zi.js → settings-VmCjhGBd.js} +1 -1
- package/dist/web/public/assets/settings-VmCjhGBd.js.br +0 -0
- package/dist/web/public/assets/settings-VmCjhGBd.js.gz +0 -0
- package/dist/web/public/assets/{task-runs-CeQS1rxa.js → task-runs-CqpTV644.js} +1 -1
- package/dist/web/public/assets/task-runs-CqpTV644.js.br +0 -0
- package/dist/web/public/assets/task-runs-CqpTV644.js.gz +0 -0
- package/dist/web/public/assets/tasks-BffPVgXg.js +4 -0
- package/dist/web/public/assets/tasks-BffPVgXg.js.br +0 -0
- package/dist/web/public/assets/tasks-BffPVgXg.js.gz +0 -0
- package/dist/web/public/index.html +30 -16
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/sw.js +14 -2
- package/dist/web/public/sw.js.br +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/push.js +55 -77
- package/dist/web/route.js +3 -7
- package/dist/web/server.js +109 -190
- package/dist/web/session-state.js +13 -53
- package/dist/web/types.js +2 -4
- package/dist/web/webpush.js +10 -25
- package/docs/deploy.md +115 -330
- package/package.json +1 -1
- package/skills/pier-boards/SKILL.md +81 -160
- package/skills/pier-help/SKILL.md +23 -20
- package/skills/pier-slack/SKILL.md +2 -2
- package/skills/pier-tasks/SKILL.md +23 -15
- package/dist/config-sync-fetch.js +0 -84
- package/dist/limits.js +0 -14
- package/dist/web/public/assets/activity-Bl3vZukb.js.br +0 -0
- package/dist/web/public/assets/activity-Bl3vZukb.js.gz +0 -0
- package/dist/web/public/assets/boards-DYuf4Mlj.js.br +0 -0
- package/dist/web/public/assets/boards-DYuf4Mlj.js.gz +0 -0
- package/dist/web/public/assets/explorer-qJH_9nTE.js +0 -4
- package/dist/web/public/assets/explorer-qJH_9nTE.js.br +0 -0
- package/dist/web/public/assets/explorer-qJH_9nTE.js.gz +0 -0
- package/dist/web/public/assets/index-Dqdb-Eqt.js +0 -85
- package/dist/web/public/assets/index-Dqdb-Eqt.js.br +0 -0
- package/dist/web/public/assets/index-Dqdb-Eqt.js.gz +0 -0
- package/dist/web/public/assets/index-DzmMzvi_.css +0 -2
- package/dist/web/public/assets/index-DzmMzvi_.css.br +0 -0
- package/dist/web/public/assets/index-DzmMzvi_.css.gz +0 -0
- package/dist/web/public/assets/runs-BLJu7EXN.js.br +0 -0
- package/dist/web/public/assets/runs-BLJu7EXN.js.gz +0 -0
- package/dist/web/public/assets/settings-BrdVh-Zi.js.br +0 -0
- package/dist/web/public/assets/settings-BrdVh-Zi.js.gz +0 -0
- package/dist/web/public/assets/task-runs-CeQS1rxa.js.br +0 -0
- package/dist/web/public/assets/task-runs-CeQS1rxa.js.gz +0 -0
- package/dist/web/public/assets/tasks-bcb3fYdK.js +0 -4
- package/dist/web/public/assets/tasks-bcb3fYdK.js.br +0 -0
- package/dist/web/public/assets/tasks-bcb3fYdK.js.gz +0 -0
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
// Thin Telegram Bot API client: HTTP and payload shapes only, no policy
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// one local process with no inbound HTTP requirement.
|
|
5
|
-
//
|
|
6
|
-
// Behind a proxy, run node with NODE_USE_ENV_PROXY=1 and HTTPS_PROXY set;
|
|
7
|
-
// nothing here needs to know.
|
|
1
|
+
// Thin Telegram Bot API client: HTTP and payload shapes only, no policy; the
|
|
2
|
+
// one file that talks to api.telegram.org. Long polling, so Pier needs no
|
|
3
|
+
// inbound HTTP. Behind a proxy: NODE_USE_ENV_PROXY=1 and HTTPS_PROXY.
|
|
8
4
|
import { readCapped } from "../core/inbox.js";
|
|
9
5
|
const BASE = "https://api.telegram.org";
|
|
10
6
|
export class TelegramApi {
|
|
@@ -13,9 +9,7 @@ export class TelegramApi {
|
|
|
13
9
|
this.token = token;
|
|
14
10
|
}
|
|
15
11
|
async call(method, payload, timeoutMs = 30_000, retry = true) {
|
|
16
|
-
//
|
|
17
|
-
// and fetch sets its own multipart boundary. It is re-sendable, so the
|
|
18
|
-
// flood retry below still works on it.
|
|
12
|
+
// FormData is re-sendable, so the flood retry below still works on it.
|
|
19
13
|
const multipart = payload instanceof FormData;
|
|
20
14
|
const res = await fetch(`${BASE}/bot${this.token}/${method}`, {
|
|
21
15
|
method: "POST",
|
|
@@ -26,9 +20,8 @@ export class TelegramApi {
|
|
|
26
20
|
const body = (await res.json());
|
|
27
21
|
if (body.ok)
|
|
28
22
|
return body.result;
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
// late one; a second 429 is a real problem and surfaces as an error.
|
|
23
|
+
// A long turn split into chunks hits ~1 msg/s per chat; `retry_after` is
|
|
24
|
+
// the exact wait. Obeyed once; a second 429 throws.
|
|
32
25
|
const after = body.parameters?.retry_after;
|
|
33
26
|
if (retry && after !== undefined && after <= 60) {
|
|
34
27
|
await new Promise((r) => setTimeout(r, (after + 1) * 1000));
|
|
@@ -45,19 +38,15 @@ export class TelegramApi {
|
|
|
45
38
|
sendMessage(payload) {
|
|
46
39
|
return this.call("sendMessage", payload);
|
|
47
40
|
}
|
|
48
|
-
/** Bytes, not a file_id or a URL: the file is local to this machine, which
|
|
49
|
-
* is the whole reason the agent could not just link it. */
|
|
50
41
|
async sendFile({ chat_id, message_thread_id, file }) {
|
|
51
42
|
const form = new FormData();
|
|
52
43
|
form.set("chat_id", String(chat_id));
|
|
53
44
|
if (message_thread_id !== undefined)
|
|
54
45
|
form.set("message_thread_id", String(message_thread_id));
|
|
55
46
|
const field = file.image ? "photo" : "document";
|
|
56
|
-
//
|
|
57
|
-
// and a Buffer read off disk carries the wider ArrayBufferLike type.
|
|
47
|
+
// A Blob part must be backed by an ArrayBuffer, not a Buffer's ArrayBufferLike.
|
|
58
48
|
form.set(field, new Blob([new Uint8Array(file.bytes)]), file.name);
|
|
59
|
-
//
|
|
60
|
-
// for a JSON call, not for megabytes.
|
|
49
|
+
// 30s is the budget for a JSON call, not for megabytes on a slow link.
|
|
61
50
|
await this.call(file.image ? "sendPhoto" : "sendDocument", form, 120_000);
|
|
62
51
|
}
|
|
63
52
|
async editMessage(payload) {
|
|
@@ -82,7 +71,6 @@ export class TelegramApi {
|
|
|
82
71
|
async answerCallbackQuery(id, text) {
|
|
83
72
|
await this.call("answerCallbackQuery", { callback_query_id: id, text });
|
|
84
73
|
}
|
|
85
|
-
/** Bounded mid-stream: metadata size is the platform's word, not a cap. */
|
|
86
74
|
async downloadFile(fileId, maxBytes) {
|
|
87
75
|
const file = await this.call("getFile", { file_id: fileId });
|
|
88
76
|
if (!file.file_path)
|
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
// Telegram's half of the settings panel
|
|
2
|
-
//
|
|
3
|
-
// `panel.ts`.
|
|
4
|
-
//
|
|
5
|
-
// Asking for a working directory costs a Map here: a forced reply arrives as
|
|
6
|
-
// an ordinary message, so the prompt's message id has to be remembered to
|
|
7
|
-
// recognize the answer. Slack's modal carries that context itself.
|
|
1
|
+
// Telegram's half of the settings panel (panel.ts has the rest). A forced reply
|
|
2
|
+
// arrives as an ordinary message, so the prompt's id is remembered to recognize it.
|
|
8
3
|
import { ChatPanel, CWD_PLACEHOLDER, CWD_TAIL, PANEL_PREFIX, } from "./panel.js";
|
|
9
4
|
import { escapeHtml as esc } from "./telegram-render.js";
|
|
10
5
|
const button = (b) => ({ text: b.label, callback_data: `${PANEL_PREFIX}${b.action}` });
|
|
@@ -12,7 +7,6 @@ export class TelegramPanel extends ChatPanel {
|
|
|
12
7
|
deps;
|
|
13
8
|
platform = "telegram";
|
|
14
9
|
fence = ["<code>", "</code>"];
|
|
15
|
-
/** Conversations waiting for a typed working directory (ForceReply). */
|
|
16
10
|
cwdPrompts = new Map();
|
|
17
11
|
constructor(deps) {
|
|
18
12
|
super(deps);
|
|
@@ -21,7 +15,6 @@ export class TelegramPanel extends ChatPanel {
|
|
|
21
15
|
esc(text) {
|
|
22
16
|
return esc(text);
|
|
23
17
|
}
|
|
24
|
-
/** Topics are a Telegram-only gate, and only on a forum. */
|
|
25
18
|
gateExtras(chat, policy) {
|
|
26
19
|
return chat.kind === "forum" ? ` · topics ${policy.topicMode ? "on" : "off"}` : "";
|
|
27
20
|
}
|
|
@@ -41,7 +34,6 @@ export class TelegramPanel extends ChatPanel {
|
|
|
41
34
|
],
|
|
42
35
|
};
|
|
43
36
|
}
|
|
44
|
-
/** Open a fresh panel, replacing whichever one this conversation had. */
|
|
45
37
|
async open(key, chatId, topicId) {
|
|
46
38
|
const view = await this.view(key, chatId);
|
|
47
39
|
const sent = await this.deps.api.sendMessage({
|
|
@@ -67,10 +59,7 @@ export class TelegramPanel extends ChatPanel {
|
|
|
67
59
|
.catch((err) => this.deps.log(`panel close failed: ${String(err)}`));
|
|
68
60
|
}
|
|
69
61
|
// --- actions -----------------------------------------------------------------
|
|
70
|
-
/**
|
|
71
|
-
* Handle a `cfg:` button. Returns false when the payload is not ours, so the
|
|
72
|
-
* caller can treat it as one of the agent's next-step labels instead.
|
|
73
|
-
*/
|
|
62
|
+
/** Returns false when the payload is not ours. */
|
|
74
63
|
async onCallback(query, key) {
|
|
75
64
|
return this.dispatch(key, query.data ?? "", undefined, async () => {
|
|
76
65
|
const message = query.message;
|
|
@@ -83,16 +72,12 @@ export class TelegramPanel extends ChatPanel {
|
|
|
83
72
|
const sent = await this.deps.api.sendMessage({
|
|
84
73
|
chat_id: state.chatId,
|
|
85
74
|
message_thread_id: state.topicId,
|
|
86
|
-
// Said plainly: this is not an edit, it is a new session.
|
|
87
75
|
text: `Reply with an absolute path. ${CWD_TAIL}`,
|
|
88
76
|
reply_markup: { force_reply: true, input_field_placeholder: CWD_PLACEHOLDER },
|
|
89
77
|
});
|
|
90
78
|
this.cwdPrompts.set(key.conversationId, sent.message_id);
|
|
91
79
|
}
|
|
92
|
-
/**
|
|
93
|
-
* Consume a reply to the working-directory prompt. Returns true when this
|
|
94
|
-
* message was that answer and must not reach the agent.
|
|
95
|
-
*/
|
|
80
|
+
/** True when this message was the answer and must not reach the agent. */
|
|
96
81
|
async consumeCwdReply(msg, key) {
|
|
97
82
|
const pending = this.cwdPrompts.get(key.conversationId);
|
|
98
83
|
if (!pending || msg.reply_to_message?.message_id !== pending)
|
|
@@ -100,8 +85,7 @@ export class TelegramPanel extends ChatPanel {
|
|
|
100
85
|
this.cwdPrompts.delete(key.conversationId);
|
|
101
86
|
const path = (msg.text ?? "").trim();
|
|
102
87
|
const started = await this.startSessionIn(key, path);
|
|
103
|
-
// The answer was typed in the chat, so the outcome is said
|
|
104
|
-
// a panel note alone would be easy to miss under one's own message.
|
|
88
|
+
// The answer was typed in the chat, so the outcome is said there too.
|
|
105
89
|
await this.deps.api.sendMessage({
|
|
106
90
|
chat_id: msg.chat.id,
|
|
107
91
|
message_thread_id: msg.message_thread_id,
|
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
import { chunkText } from "./chunk.js";
|
|
2
|
-
// How a reply looks on Telegram:
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// and rejects the whole message on anything else, so we escape first and
|
|
6
|
-
// reintroduce exactly the tags it documents: b, i, s, code, pre, a.
|
|
7
|
-
// Unsupported markdown (tables, images, nested lists) degrades to plain text
|
|
8
|
-
// rather than losing the message.
|
|
2
|
+
// How a reply looks on Telegram: markdown → Bot API HTML. Telegram's parser
|
|
3
|
+
// rejects the whole message on any tag outside its documented set (b, i, s,
|
|
4
|
+
// code, pre, a), so text is escaped first and only those are reintroduced.
|
|
9
5
|
const MAX_CHARS = 3800; // Telegram's hard limit is 4096; leave room for tags.
|
|
10
|
-
/** Shared: the adapter and the panel escape plain text with this too. */
|
|
11
6
|
export const escapeHtml = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
12
7
|
/** Inline emphasis, applied after escaping so `<b>` can only come from us. */
|
|
13
8
|
function inline(text) {
|
|
@@ -17,17 +12,11 @@ function inline(text) {
|
|
|
17
12
|
.replace(/\*\*([^\n*]+)\*\*/g, "<b>$1</b>")
|
|
18
13
|
.replace(/~~([^\n~]+)~~/g, "<s>$1</s>")
|
|
19
14
|
.replace(/(^|[\s(])[*_]([^\n*_]+)[*_](?=[\s).,!?:;]|$)/g, "$1<i>$2</i>")
|
|
20
|
-
// Headings carry no size in Telegram; bold is the closest honest render.
|
|
21
15
|
.replace(/^#{1,6}[ \t]+(.+)$/gm, "<b>$1</b>");
|
|
22
16
|
}
|
|
23
|
-
/**
|
|
24
|
-
* Render one assistant turn. Code spans and fences are extracted before
|
|
25
|
-
* escaping so emphasis inside them stays literal.
|
|
26
|
-
*/
|
|
17
|
+
/** Code is stashed before escaping so emphasis inside it stays literal. */
|
|
27
18
|
export function toTelegramHtml(markdown) {
|
|
28
19
|
const stash = [];
|
|
29
|
-
// Private-use sentinels: markdown can't contain them, so a stashed block
|
|
30
|
-
// can't be re-matched by the escaping and emphasis passes that follow.
|
|
31
20
|
const keep = (html) => `\uE000${stash.push(html) - 1}\uE001`;
|
|
32
21
|
let out = markdown.replace(/```([\w.+-]*)\n?([\s\S]*?)```/g, (_m, lang, code) => keep(lang
|
|
33
22
|
? `<pre><code class="language-${escapeHtml(lang)}">${escapeHtml(code.replace(/\n+$/, ""))}</code></pre>`
|
|
@@ -36,31 +25,20 @@ export function toTelegramHtml(markdown) {
|
|
|
36
25
|
out = inline(escapeHtml(out));
|
|
37
26
|
return out.replace(/\uE000(\d+)\uE001/g, (_m, i) => stash[Number(i)] ?? "");
|
|
38
27
|
}
|
|
39
|
-
/**
|
|
40
|
-
* Split rendered HTML into sendable chunks. A turn long enough to need this can
|
|
41
|
-
* still be cut inside a <pre> block; Telegram closes the tag on its own and the
|
|
42
|
-
* text survives, which beats dropping the turn. (Slack cannot do that, which is
|
|
43
|
-
* why its renderer re-balances fences after the cut.)
|
|
44
|
-
*/
|
|
28
|
+
/** A cut inside a <pre> block is fine: Telegram closes the tag on its own. */
|
|
45
29
|
export const chunk = (html) => chunkText(html, MAX_CHARS);
|
|
46
30
|
// --- next-step buttons -------------------------------------------------------
|
|
47
|
-
/**
|
|
48
|
-
*
|
|
49
|
-
* too little to carry a label. Buttons send an index instead, and the label is
|
|
50
|
-
* read back off the message's own keyboard.
|
|
51
|
-
*/
|
|
31
|
+
/** `callback_data` caps at 64 bytes (~21 CJK characters), so buttons send an
|
|
32
|
+
* index and the label is read back off the message's own keyboard. */
|
|
52
33
|
export const OFFER_PREFIX = "sg:";
|
|
53
|
-
// Buttons in one row
|
|
54
|
-
// width
|
|
34
|
+
// Buttons in one row split its width evenly, so packing is budgeted by display
|
|
35
|
+
// width, not count.
|
|
55
36
|
const ROW_WIDTH = 26;
|
|
56
37
|
const ROW_BUTTONS = 3;
|
|
57
|
-
/**
|
|
38
|
+
/** CJK, fullwidth punctuation and emoji take two cells. */
|
|
58
39
|
const displayWidth = (label) => [...label].reduce((n, ch) => n + ((ch.codePointAt(0) ?? 0) > 0x2e7f ? 2 : 1), 0);
|
|
59
|
-
/**
|
|
60
|
-
*
|
|
61
|
-
* already wide stops accepting: buttons in one Telegram row split the width
|
|
62
|
-
* evenly, so squeezing a long label in truncates everything beside it.
|
|
63
|
-
*/
|
|
40
|
+
/** A row that is already wide stops accepting: a long label squeezed in
|
|
41
|
+
* truncates everything beside it. */
|
|
64
42
|
export function keyboard(labels) {
|
|
65
43
|
if (!labels.length)
|
|
66
44
|
return undefined;
|
|
@@ -80,12 +58,7 @@ export function keyboard(labels) {
|
|
|
80
58
|
});
|
|
81
59
|
return { inline_keyboard: rows };
|
|
82
60
|
}
|
|
83
|
-
/**
|
|
84
|
-
* The label a next-step payload stands for. Read off the tapped message's own
|
|
85
|
-
* keyboard, which Telegram echoes back — so a button keeps working across a
|
|
86
|
-
* restart or a config reload, where an in-memory offer list would not.
|
|
87
|
-
* A payload we never wrote (an older label-as-payload button) passes through.
|
|
88
|
-
*/
|
|
61
|
+
/** A payload we never wrote (an older label-as-payload button) passes through. */
|
|
89
62
|
export function offeredLabel(msg, data) {
|
|
90
63
|
if (!data.startsWith(OFFER_PREFIX))
|
|
91
64
|
return data;
|