ping-a-human 0.1.1 → 0.1.3
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/dist/channels/telegram.js +26 -1
- package/dist/index.js +2 -2
- package/package.json +1 -1
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram bot commands (text beginning with "/", e.g. "/start", "/help") are
|
|
3
|
+
* client/protocol messages, never a human's answer to a free-text question.
|
|
4
|
+
* The most common offender is the "/start" Telegram auto-sends when a user
|
|
5
|
+
* first opens the bot, which would otherwise be returned as the reply.
|
|
6
|
+
*/
|
|
7
|
+
function isBotCommand(text) {
|
|
8
|
+
return /^\/[A-Za-z0-9_]+(@\w+)?(\s|$)/.test(text.trim());
|
|
9
|
+
}
|
|
1
10
|
/**
|
|
2
11
|
* Telegram implementation of the Channel interface using the Bot API over
|
|
3
12
|
* plain HTTPS (Node's built-in fetch). No third-party Telegram SDK.
|
|
@@ -58,6 +67,15 @@ export class TelegramChannel {
|
|
|
58
67
|
}
|
|
59
68
|
async awaitReply(options) {
|
|
60
69
|
const deadline = Date.now() + options.timeoutMs;
|
|
70
|
+
// Anchor: only accept replies that arrive AFTER the question was sent.
|
|
71
|
+
// Telegram message ids are monotonically increasing per chat, so any
|
|
72
|
+
// update whose message predates the question (e.g. a queued `/start` from
|
|
73
|
+
// first opening the bot) must be ignored — otherwise it would be wrongly
|
|
74
|
+
// returned as the human's answer. Honors AwaitReplyOptions.sinceRef.
|
|
75
|
+
const sinceMessageId = options.sinceRef != null ? Number(options.sinceRef.id) : undefined;
|
|
76
|
+
const isStale = (messageId) => sinceMessageId != null &&
|
|
77
|
+
messageId != null &&
|
|
78
|
+
messageId <= sinceMessageId;
|
|
61
79
|
while (Date.now() < deadline) {
|
|
62
80
|
const remainingMs = deadline - Date.now();
|
|
63
81
|
// Don't long-poll longer than the time we have left.
|
|
@@ -73,6 +91,13 @@ export class TelegramChannel {
|
|
|
73
91
|
// Free-text reply.
|
|
74
92
|
const msg = update.message;
|
|
75
93
|
if (msg?.text && this.fromConfiguredChat(msg.chat)) {
|
|
94
|
+
// Skip backlog that predates the question (e.g. a stale `/start`).
|
|
95
|
+
if (isStale(msg.message_id))
|
|
96
|
+
continue;
|
|
97
|
+
// Bot commands ("/start", "/help", ...) are never valid answers to a
|
|
98
|
+
// free-text question; treat them as noise and keep waiting.
|
|
99
|
+
if (isBotCommand(msg.text))
|
|
100
|
+
continue;
|
|
76
101
|
return {
|
|
77
102
|
status: "answered",
|
|
78
103
|
answer: msg.text,
|
|
@@ -81,7 +106,7 @@ export class TelegramChannel {
|
|
|
81
106
|
}
|
|
82
107
|
// Inline-button tap.
|
|
83
108
|
const cb = update.callback_query;
|
|
84
|
-
if (cb) {
|
|
109
|
+
if (cb && !isStale(cb.message?.message_id)) {
|
|
85
110
|
// Best-effort: clear the client's loading spinner.
|
|
86
111
|
try {
|
|
87
112
|
await this.api("answerCallbackQuery", { callback_query_id: cb.id });
|
package/dist/index.js
CHANGED
|
@@ -21,12 +21,12 @@ export function createServer(channelOptions = {}) {
|
|
|
21
21
|
// 1.x registerTool: inputSchema is a ZodRawShape (plain object), NOT z.object(...).
|
|
22
22
|
server.registerTool("notify_human", {
|
|
23
23
|
title: "Notify human",
|
|
24
|
-
description: "Send a fire-and-forget message to the configured human (via Telegram) and return
|
|
24
|
+
description: "Send a one-way, fire-and-forget message to the configured human (via Telegram) and return IMMEDIATELY. Use this ONLY to inform the human (status updates, 'task finished', 'deploy succeeded', FYIs) when you do NOT need anything back. The human's reply, if any, is NOT captured or returned. If you need a decision, approval, or any answer before continuing, DO NOT use this — use ask_human instead.",
|
|
25
25
|
inputSchema: { message: z.string() },
|
|
26
26
|
}, async ({ message }) => notifyHuman(resolveChannel(), { message }));
|
|
27
27
|
server.registerTool("ask_human", {
|
|
28
28
|
title: "Ask human",
|
|
29
|
-
description: "
|
|
29
|
+
description: "Ask the configured human a question and BLOCK until they reply on their messaging app (Telegram) or a timeout elapses. Use this whenever you need a human decision, approval, confirmation, clarification, or any answer before you can continue — the human's reply is captured and returned to you. Optionally provide `choices` to render tappable buttons (the tapped value is returned). Returns the human's answer, or a clear timed-out result if they don't respond in time. If you only need to inform the human and do NOT need a response, use notify_human instead.",
|
|
30
30
|
inputSchema: {
|
|
31
31
|
question: z.string(),
|
|
32
32
|
choices: z.array(z.string()).optional(),
|
package/package.json
CHANGED