@moxxy/plugin-telegram 0.27.0
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/LICENSE +21 -0
- package/dist/approval.d.ts +37 -0
- package/dist/approval.d.ts.map +1 -0
- package/dist/approval.js +75 -0
- package/dist/approval.js.map +1 -0
- package/dist/channel/approval-prompt.d.ts +20 -0
- package/dist/channel/approval-prompt.d.ts.map +1 -0
- package/dist/channel/approval-prompt.js +34 -0
- package/dist/channel/approval-prompt.js.map +1 -0
- package/dist/channel/callback-handler.d.ts +32 -0
- package/dist/channel/callback-handler.d.ts.map +1 -0
- package/dist/channel/callback-handler.js +212 -0
- package/dist/channel/callback-handler.js.map +1 -0
- package/dist/channel/frame-pump.d.ts +58 -0
- package/dist/channel/frame-pump.d.ts.map +1 -0
- package/dist/channel/frame-pump.js +159 -0
- package/dist/channel/frame-pump.js.map +1 -0
- package/dist/channel/html.d.ts +24 -0
- package/dist/channel/html.d.ts.map +1 -0
- package/dist/channel/html.js +62 -0
- package/dist/channel/html.js.map +1 -0
- package/dist/channel/pairing-handler.d.ts +66 -0
- package/dist/channel/pairing-handler.d.ts.map +1 -0
- package/dist/channel/pairing-handler.js +147 -0
- package/dist/channel/pairing-handler.js.map +1 -0
- package/dist/channel/permission-prompt.d.ts +21 -0
- package/dist/channel/permission-prompt.d.ts.map +1 -0
- package/dist/channel/permission-prompt.js +36 -0
- package/dist/channel/permission-prompt.js.map +1 -0
- package/dist/channel/slash-handler.d.ts +47 -0
- package/dist/channel/slash-handler.d.ts.map +1 -0
- package/dist/channel/slash-handler.js +214 -0
- package/dist/channel/slash-handler.js.map +1 -0
- package/dist/channel/text-handler.d.ts +43 -0
- package/dist/channel/text-handler.d.ts.map +1 -0
- package/dist/channel/text-handler.js +116 -0
- package/dist/channel/text-handler.js.map +1 -0
- package/dist/channel/turn-runner.d.ts +40 -0
- package/dist/channel/turn-runner.d.ts.map +1 -0
- package/dist/channel/turn-runner.js +67 -0
- package/dist/channel/turn-runner.js.map +1 -0
- package/dist/channel/typing-indicator.d.ts +14 -0
- package/dist/channel/typing-indicator.d.ts.map +1 -0
- package/dist/channel/typing-indicator.js +29 -0
- package/dist/channel/typing-indicator.js.map +1 -0
- package/dist/channel/voice-handler.d.ts +42 -0
- package/dist/channel/voice-handler.d.ts.map +1 -0
- package/dist/channel/voice-handler.js +138 -0
- package/dist/channel/voice-handler.js.map +1 -0
- package/dist/channel.d.ts +118 -0
- package/dist/channel.d.ts.map +1 -0
- package/dist/channel.js +478 -0
- package/dist/channel.js.map +1 -0
- package/dist/channel.test-d.d.ts +2 -0
- package/dist/channel.test-d.d.ts.map +1 -0
- package/dist/channel.test-d.js +5 -0
- package/dist/channel.test-d.js.map +1 -0
- package/dist/format.d.ts +38 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +237 -0
- package/dist/format.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +304 -0
- package/dist/index.js.map +1 -0
- package/dist/keys.d.ts +37 -0
- package/dist/keys.d.ts.map +1 -0
- package/dist/keys.js +42 -0
- package/dist/keys.js.map +1 -0
- package/dist/pair-flow.d.ts +18 -0
- package/dist/pair-flow.d.ts.map +1 -0
- package/dist/pair-flow.js +119 -0
- package/dist/pair-flow.js.map +1 -0
- package/dist/pairing.d.ts +90 -0
- package/dist/pairing.d.ts.map +1 -0
- package/dist/pairing.js +97 -0
- package/dist/pairing.js.map +1 -0
- package/dist/permission.d.ts +25 -0
- package/dist/permission.d.ts.map +1 -0
- package/dist/permission.js +49 -0
- package/dist/permission.js.map +1 -0
- package/dist/render.d.ts +68 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +439 -0
- package/dist/render.js.map +1 -0
- package/dist/setup-wizard.d.ts +21 -0
- package/dist/setup-wizard.d.ts.map +1 -0
- package/dist/setup-wizard.js +140 -0
- package/dist/setup-wizard.js.map +1 -0
- package/package.json +91 -0
- package/src/approval.test.ts +103 -0
- package/src/approval.ts +86 -0
- package/src/channel/approval-prompt.ts +46 -0
- package/src/channel/callback-handler.test.ts +188 -0
- package/src/channel/callback-handler.ts +256 -0
- package/src/channel/frame-pump.ts +176 -0
- package/src/channel/html.test.ts +40 -0
- package/src/channel/html.ts +60 -0
- package/src/channel/pairing-handler.ts +181 -0
- package/src/channel/permission-prompt.ts +51 -0
- package/src/channel/slash-handler.ts +259 -0
- package/src/channel/text-handler.test.ts +222 -0
- package/src/channel/text-handler.ts +174 -0
- package/src/channel/turn-runner.ts +102 -0
- package/src/channel/typing-indicator.ts +31 -0
- package/src/channel/voice-handler.test.ts +328 -0
- package/src/channel/voice-handler.ts +198 -0
- package/src/channel/voice-reply.test.ts +223 -0
- package/src/channel.test-d.ts +7 -0
- package/src/channel.ts +564 -0
- package/src/format.test.ts +159 -0
- package/src/format.ts +257 -0
- package/src/index.ts +347 -0
- package/src/keys.ts +52 -0
- package/src/pair-flow.ts +131 -0
- package/src/pairing.test.ts +113 -0
- package/src/pairing.ts +184 -0
- package/src/permission.test.ts +56 -0
- package/src/permission.ts +61 -0
- package/src/render.test.ts +350 -0
- package/src/render.ts +522 -0
- package/src/setup-wizard.ts +178 -0
- package/src/subcommands.test.ts +153 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Moxxy (moxxy.ai)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ApprovalDecision, ApprovalRequest, ApprovalResolver } from '@moxxy/sdk';
|
|
2
|
+
export interface PendingApproval {
|
|
3
|
+
readonly id: string;
|
|
4
|
+
readonly request: ApprovalRequest;
|
|
5
|
+
readonly resolve: (decision: ApprovalDecision) => void;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Generic approval resolver for the Telegram channel — counterpart to the
|
|
9
|
+
* TUI's ApprovalDialog. The channel renders the request as a message with
|
|
10
|
+
* an inline-keyboard option list and routes the user's button click (or
|
|
11
|
+
* follow-up text, for options with `requestsText: true`) back here via
|
|
12
|
+
* `resolvePending` / `resolvePendingWithText`.
|
|
13
|
+
*
|
|
14
|
+
* Mirrors TelegramPermissionResolver in shape: a Map<callId, pending>
|
|
15
|
+
* plus a `setDecider` callback the channel uses to actually render the
|
|
16
|
+
* UI. The strategy never knows whether the dialog is rendered as Ink
|
|
17
|
+
* components, inline keyboards, or a CLI prompt — it just awaits a
|
|
18
|
+
* promise.
|
|
19
|
+
*/
|
|
20
|
+
export declare class TelegramApprovalResolver implements ApprovalResolver {
|
|
21
|
+
readonly name = "telegram-approval";
|
|
22
|
+
private readonly pending;
|
|
23
|
+
private deciderFn;
|
|
24
|
+
private nextId;
|
|
25
|
+
setDecider(fn: (id: string, request: ApprovalRequest) => Promise<void>): void;
|
|
26
|
+
confirm(request: ApprovalRequest): Promise<ApprovalDecision>;
|
|
27
|
+
/** Look up a pending approval without resolving it. Used by the channel
|
|
28
|
+
* when the user clicks an option that needs text follow-up — we keep
|
|
29
|
+
* the approval pending and capture the next message. */
|
|
30
|
+
getPending(id: string): PendingApproval | undefined;
|
|
31
|
+
/** Resolve directly with an option id (no text). */
|
|
32
|
+
resolvePending(id: string, optionId: string): boolean;
|
|
33
|
+
/** Resolve with an option id AND a free-text follow-up. */
|
|
34
|
+
resolvePendingWithText(id: string, optionId: string, text: string): boolean;
|
|
35
|
+
abortAll(reason?: string): void;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=approval.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval.d.ts","sourceRoot":"","sources":["../src/approval.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEtF,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,gBAAgB,KAAK,IAAI,CAAC;CACxD;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,wBAAyB,YAAW,gBAAgB;IAC/D,QAAQ,CAAC,IAAI,uBAAuB;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsC;IAC9D,OAAO,CAAC,SAAS,CAA0E;IAC3F,OAAO,CAAC,MAAM,CAAK;IAEnB,UAAU,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAIvE,OAAO,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAoBlE;;6DAEyD;IACzD,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS;IAInD,oDAAoD;IACpD,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO;IAQrD,2DAA2D;IAC3D,sBAAsB,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO;IAQ3E,QAAQ,CAAC,MAAM,SAAmB,GAAG,IAAI;CAS1C"}
|
package/dist/approval.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic approval resolver for the Telegram channel — counterpart to the
|
|
3
|
+
* TUI's ApprovalDialog. The channel renders the request as a message with
|
|
4
|
+
* an inline-keyboard option list and routes the user's button click (or
|
|
5
|
+
* follow-up text, for options with `requestsText: true`) back here via
|
|
6
|
+
* `resolvePending` / `resolvePendingWithText`.
|
|
7
|
+
*
|
|
8
|
+
* Mirrors TelegramPermissionResolver in shape: a Map<callId, pending>
|
|
9
|
+
* plus a `setDecider` callback the channel uses to actually render the
|
|
10
|
+
* UI. The strategy never knows whether the dialog is rendered as Ink
|
|
11
|
+
* components, inline keyboards, or a CLI prompt — it just awaits a
|
|
12
|
+
* promise.
|
|
13
|
+
*/
|
|
14
|
+
export class TelegramApprovalResolver {
|
|
15
|
+
name = 'telegram-approval';
|
|
16
|
+
pending = new Map();
|
|
17
|
+
deciderFn = null;
|
|
18
|
+
nextId = 1;
|
|
19
|
+
setDecider(fn) {
|
|
20
|
+
this.deciderFn = fn;
|
|
21
|
+
}
|
|
22
|
+
async confirm(request) {
|
|
23
|
+
if (!this.deciderFn) {
|
|
24
|
+
// No bot wired up — best fallback is the request's defaultOptionId.
|
|
25
|
+
// For plan-execute, the default is "approve" which preserves the
|
|
26
|
+
// headless behavior (run the plan as drafted).
|
|
27
|
+
return { optionId: request.defaultOptionId ?? request.options[0]?.id ?? 'approve' };
|
|
28
|
+
}
|
|
29
|
+
const id = `appr_${this.nextId++}`;
|
|
30
|
+
return new Promise((resolve) => {
|
|
31
|
+
this.pending.set(id, { id, request, resolve });
|
|
32
|
+
this.deciderFn(id, request).catch((err) => {
|
|
33
|
+
this.pending.delete(id);
|
|
34
|
+
resolve({
|
|
35
|
+
optionId: request.defaultOptionId ?? request.options[0]?.id ?? 'cancel',
|
|
36
|
+
text: `decider failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/** Look up a pending approval without resolving it. Used by the channel
|
|
42
|
+
* when the user clicks an option that needs text follow-up — we keep
|
|
43
|
+
* the approval pending and capture the next message. */
|
|
44
|
+
getPending(id) {
|
|
45
|
+
return this.pending.get(id);
|
|
46
|
+
}
|
|
47
|
+
/** Resolve directly with an option id (no text). */
|
|
48
|
+
resolvePending(id, optionId) {
|
|
49
|
+
const pending = this.pending.get(id);
|
|
50
|
+
if (!pending)
|
|
51
|
+
return false;
|
|
52
|
+
this.pending.delete(id);
|
|
53
|
+
pending.resolve({ optionId });
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
/** Resolve with an option id AND a free-text follow-up. */
|
|
57
|
+
resolvePendingWithText(id, optionId, text) {
|
|
58
|
+
const pending = this.pending.get(id);
|
|
59
|
+
if (!pending)
|
|
60
|
+
return false;
|
|
61
|
+
this.pending.delete(id);
|
|
62
|
+
pending.resolve({ optionId, text });
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
abortAll(reason = 'channel closed') {
|
|
66
|
+
for (const pending of this.pending.values()) {
|
|
67
|
+
pending.resolve({
|
|
68
|
+
optionId: pending.request.defaultOptionId ?? pending.request.options[0]?.id ?? 'cancel',
|
|
69
|
+
text: reason,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
this.pending.clear();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=approval.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval.js","sourceRoot":"","sources":["../src/approval.ts"],"names":[],"mappings":"AAQA;;;;;;;;;;;;GAYG;AACH,MAAM,OAAO,wBAAwB;IAC1B,IAAI,GAAG,mBAAmB,CAAC;IACnB,OAAO,GAAG,IAAI,GAAG,EAA2B,CAAC;IACtD,SAAS,GAAqE,IAAI,CAAC;IACnF,MAAM,GAAG,CAAC,CAAC;IAEnB,UAAU,CAAC,EAA2D;QACpE,IAAI,CAAC,SAAS,GAAG,EAAE,CAAC;IACtB,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,OAAwB;QACpC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACpB,oEAAoE;YACpE,iEAAiE;YACjE,+CAA+C;YAC/C,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,SAAS,EAAE,CAAC;QACtF,CAAC;QACD,MAAM,EAAE,GAAG,QAAQ,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;QACnC,OAAO,IAAI,OAAO,CAAmB,CAAC,OAAO,EAAE,EAAE;YAC/C,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;YAC/C,IAAI,CAAC,SAAU,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;gBACzC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;gBACxB,OAAO,CAAC;oBACN,QAAQ,EAAE,OAAO,CAAC,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,QAAQ;oBACvE,IAAI,EAAE,mBAAmB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;iBAC5E,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;6DAEyD;IACzD,UAAU,CAAC,EAAU;QACnB,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC9B,CAAC;IAED,oDAAoD;IACpD,cAAc,CAAC,EAAU,EAAE,QAAgB;QACzC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACrC,IAAI,CAAC,OAAO;YAAE,OAAO,KAAK,CAAC;QAC3B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACxB,OAAO,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC9B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,2DAA2D;IAC3D,sBAAsB,CAAC,EAAU,EAAE,QAAgB,EAAE,IAAY;QAC/D,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACrC,IAAI,CAAC,OAAO;YAAE,OAAO,KAAK,CAAC;QAC3B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACxB,OAAO,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QACpC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,QAAQ,CAAC,MAAM,GAAG,gBAAgB;QAChC,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;YAC5C,OAAO,CAAC,OAAO,CAAC;gBACd,QAAQ,EAAE,OAAO,CAAC,OAAO,CAAC,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,QAAQ;gBACvF,IAAI,EAAE,MAAM;aACb,CAAC,CAAC;QACL,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;CACF"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type Bot } from 'grammy';
|
|
2
|
+
import type { ApprovalRequest } from '@moxxy/sdk';
|
|
3
|
+
import type { TelegramApprovalResolver } from '../approval.js';
|
|
4
|
+
export interface ApprovalPromptDeps {
|
|
5
|
+
readonly bot: Bot | null;
|
|
6
|
+
readonly chatId: number | null;
|
|
7
|
+
readonly resolver: TelegramApprovalResolver;
|
|
8
|
+
readonly logger?: {
|
|
9
|
+
warn(msg: string, meta?: Record<string, unknown>): void;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Render an approval request (e.g. plan-execute "validate plan") as a
|
|
14
|
+
* message + inline-keyboard option list. Options with `requestsText`
|
|
15
|
+
* are still picked here; the callback handler then captures the user's
|
|
16
|
+
* NEXT message as the follow-up text via the channel's awaiting-text
|
|
17
|
+
* state.
|
|
18
|
+
*/
|
|
19
|
+
export declare function askForApproval(id: string, request: ApprovalRequest, deps: ApprovalPromptDeps): Promise<void>;
|
|
20
|
+
//# sourceMappingURL=approval-prompt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval-prompt.d.ts","sourceRoot":"","sources":["../../src/channel/approval-prompt.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,GAAG,EAAkB,MAAM,QAAQ,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAG/D,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,wBAAwB,CAAC;IAC5C,QAAQ,CAAC,MAAM,CAAC,EAAE;QAAE,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;KAAE,CAAC;CAC/E;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,eAAe,EACxB,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,IAAI,CAAC,CAsBf"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { InlineKeyboard } from 'grammy';
|
|
2
|
+
import { truncate } from './html.js';
|
|
3
|
+
/**
|
|
4
|
+
* Render an approval request (e.g. plan-execute "validate plan") as a
|
|
5
|
+
* message + inline-keyboard option list. Options with `requestsText`
|
|
6
|
+
* are still picked here; the callback handler then captures the user's
|
|
7
|
+
* NEXT message as the follow-up text via the channel's awaiting-text
|
|
8
|
+
* state.
|
|
9
|
+
*/
|
|
10
|
+
export async function askForApproval(id, request, deps) {
|
|
11
|
+
if (!deps.bot || !deps.chatId)
|
|
12
|
+
return;
|
|
13
|
+
const keyboard = new InlineKeyboard();
|
|
14
|
+
for (const opt of request.options) {
|
|
15
|
+
const label = `${opt.hotkey ? `[${opt.hotkey}] ` : ''}${opt.label}`;
|
|
16
|
+
keyboard.text(label, `appr:${id}:${opt.id}`).row();
|
|
17
|
+
}
|
|
18
|
+
// Telegram has a ~4096-char message limit; truncate the body so a
|
|
19
|
+
// verbose plan doesn't fail to send. The full plan is also in the
|
|
20
|
+
// chat scrollback (the assistant_message events get streamed in).
|
|
21
|
+
const bodySnippet = truncate(request.body, 3000);
|
|
22
|
+
const summary = `📋 ${request.title}\n\n${bodySnippet}\n\n` +
|
|
23
|
+
`Pick an option below. Some options (e.g. Redraft) will prompt for follow-up text after you click.`;
|
|
24
|
+
try {
|
|
25
|
+
await deps.bot.api.sendMessage(deps.chatId, summary, { reply_markup: keyboard });
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
deps.logger?.warn('approval send failed', { err: String(err) });
|
|
29
|
+
// Default-resolve on send failure so the loop strategy doesn't hang.
|
|
30
|
+
const fallback = request.defaultOptionId ?? request.options[0]?.id ?? 'cancel';
|
|
31
|
+
deps.resolver.resolvePending(id, fallback);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=approval-prompt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval-prompt.js","sourceRoot":"","sources":["../../src/channel/approval-prompt.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,cAAc,EAAE,MAAM,QAAQ,CAAC;AAGlD,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AASrC;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,EAAU,EACV,OAAwB,EACxB,IAAwB;IAExB,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM;QAAE,OAAO;IACtC,MAAM,QAAQ,GAAG,IAAI,cAAc,EAAE,CAAC;IACtC,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QAClC,MAAM,KAAK,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,KAAK,EAAE,CAAC;QACpE,QAAQ,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC;IACrD,CAAC;IACD,kEAAkE;IAClE,kEAAkE;IAClE,kEAAkE;IAClE,MAAM,WAAW,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACjD,MAAM,OAAO,GACX,MAAM,OAAO,CAAC,KAAK,OAAO,WAAW,MAAM;QAC3C,mGAAmG,CAAC;IACtG,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,CAAC,CAAC;IACnF,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,sBAAsB,EAAE,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChE,qEAAqE;QACrE,MAAM,QAAQ,GAAG,OAAO,CAAC,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,QAAQ,CAAC;QAC/E,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;IAC7C,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Bot, Context } from 'grammy';
|
|
2
|
+
import type { ClientSession as Session } from '@moxxy/sdk';
|
|
3
|
+
import type { TelegramPermissionResolver } from '../permission.js';
|
|
4
|
+
import type { TelegramApprovalResolver } from '../approval.js';
|
|
5
|
+
export interface AwaitingApprovalText {
|
|
6
|
+
approvalId: string;
|
|
7
|
+
optionId: string;
|
|
8
|
+
}
|
|
9
|
+
export interface CallbackState {
|
|
10
|
+
readonly bot: Bot | null;
|
|
11
|
+
readonly session: Session | null;
|
|
12
|
+
readonly chatId: number | null;
|
|
13
|
+
readonly permissionResolver: TelegramPermissionResolver;
|
|
14
|
+
readonly approvalResolver: TelegramApprovalResolver;
|
|
15
|
+
/** Pairing gate — the same authorization check the text/voice handlers enforce. */
|
|
16
|
+
readonly pairing: {
|
|
17
|
+
isAuthorized(chatId: number): boolean;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export interface CallbackCallbacks {
|
|
21
|
+
/** Latch an in-flight approval awaiting a text follow-up. */
|
|
22
|
+
setAwaitingApprovalText(state: AwaitingApprovalText | null): void;
|
|
23
|
+
setActiveModelOverride(modelId: string): void;
|
|
24
|
+
}
|
|
25
|
+
/** Inline-keyboard callback router. Dispatches by prefix:
|
|
26
|
+
* - `perm:` → permission resolver
|
|
27
|
+
* - `appr:` → approval resolver
|
|
28
|
+
* - `model:` → provider+model switch (with credential resolve)
|
|
29
|
+
* - `mode:` → mode switch
|
|
30
|
+
*/
|
|
31
|
+
export declare function handleCallback(ctx: Context, state: CallbackState, cb: CallbackCallbacks): Promise<void>;
|
|
32
|
+
//# sourceMappingURL=callback-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callback-handler.d.ts","sourceRoot":"","sources":["../../src/channel/callback-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAG3C,OAAO,KAAK,EAAE,aAAa,IAAI,OAAO,EAAE,MAAM,YAAY,CAAC;AAE3D,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;AACnE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAE/D,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,kBAAkB,EAAE,0BAA0B,CAAC;IACxD,QAAQ,CAAC,gBAAgB,EAAE,wBAAwB,CAAC;IACpD,mFAAmF;IACnF,QAAQ,CAAC,OAAO,EAAE;QAAE,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;CAC7D;AAED,MAAM,WAAW,iBAAiB;IAChC,6DAA6D;IAC7D,uBAAuB,CAAC,KAAK,EAAE,oBAAoB,GAAG,IAAI,GAAG,IAAI,CAAC;IAClE,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/C;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,aAAa,EACpB,EAAE,EAAE,iBAAiB,GACpB,OAAO,CAAC,IAAI,CAAC,CAkCf"}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { setCategoryDefault, setProviderModel } from '@moxxy/config';
|
|
2
|
+
import { isSelectableMode } from '@moxxy/sdk';
|
|
3
|
+
/** Inline-keyboard callback router. Dispatches by prefix:
|
|
4
|
+
* - `perm:` → permission resolver
|
|
5
|
+
* - `appr:` → approval resolver
|
|
6
|
+
* - `model:` → provider+model switch (with credential resolve)
|
|
7
|
+
* - `mode:` → mode switch
|
|
8
|
+
*/
|
|
9
|
+
export async function handleCallback(ctx, state, cb) {
|
|
10
|
+
const data = ctx.callbackQuery?.data;
|
|
11
|
+
if (!data)
|
|
12
|
+
return;
|
|
13
|
+
// Authorization gate — mirror the text/voice handlers. Button clicks can
|
|
14
|
+
// resolve permission prompts, approve plans, and switch provider/model/mode,
|
|
15
|
+
// so an unpaired chat's callbacks must be refused just like its messages
|
|
16
|
+
// (inline-keyboard messages can be forwarded to arbitrary chats).
|
|
17
|
+
const chatId = ctx.chat?.id ?? ctx.callbackQuery?.message?.chat?.id;
|
|
18
|
+
if (chatId === undefined || !state.pairing.isAuthorized(chatId)) {
|
|
19
|
+
try {
|
|
20
|
+
await ctx.answerCallbackQuery({ text: 'This bot is paired with a different chat.' });
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
/* ignore */
|
|
24
|
+
}
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (data.startsWith('perm:')) {
|
|
28
|
+
await handlePerm(ctx, data, state.permissionResolver);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (data.startsWith('appr:')) {
|
|
32
|
+
await handleAppr(ctx, data, state, cb);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (data.startsWith('model:')) {
|
|
36
|
+
await handleModel(ctx, data, state.session, cb);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (data.startsWith('mode:')) {
|
|
40
|
+
await handleMode(ctx, data, state.session);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async function handlePerm(ctx, data, resolver) {
|
|
45
|
+
const parts = data.split(':');
|
|
46
|
+
if (parts.length !== 3)
|
|
47
|
+
return;
|
|
48
|
+
const [, callId, choice] = parts;
|
|
49
|
+
if (!callId || !choice)
|
|
50
|
+
return;
|
|
51
|
+
const decision = mapChoice(choice);
|
|
52
|
+
const handled = resolver.resolvePending(callId, decision);
|
|
53
|
+
await ctx.answerCallbackQuery({ text: handled ? choice : 'no pending permission' });
|
|
54
|
+
if (handled && ctx.callbackQuery?.message) {
|
|
55
|
+
try {
|
|
56
|
+
await ctx.editMessageReplyMarkup({});
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
/* ignore */
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async function handleAppr(ctx, data, state, cb) {
|
|
64
|
+
// Format: appr:<approvalId>:<optionId>
|
|
65
|
+
const idx = data.indexOf(':', 5);
|
|
66
|
+
if (idx < 0)
|
|
67
|
+
return;
|
|
68
|
+
const approvalId = data.slice(5, idx);
|
|
69
|
+
const optionId = data.slice(idx + 1);
|
|
70
|
+
const pending = state.approvalResolver.getPending(approvalId);
|
|
71
|
+
if (!pending) {
|
|
72
|
+
await ctx.answerCallbackQuery({ text: 'no pending approval' });
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const option = pending.request.options.find((o) => o.id === optionId);
|
|
76
|
+
if (!option) {
|
|
77
|
+
await ctx.answerCallbackQuery({ text: 'unknown option' });
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
// Clear the inline keyboard so the user can't double-click.
|
|
81
|
+
if (ctx.callbackQuery?.message) {
|
|
82
|
+
try {
|
|
83
|
+
await ctx.editMessageReplyMarkup({});
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
/* ignore */
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (option.requestsText) {
|
|
90
|
+
// Don't resolve yet — capture the user's next message as the
|
|
91
|
+
// follow-up text. Mirrors the TUI dialog's text-entry sub-mode.
|
|
92
|
+
cb.setAwaitingApprovalText({ approvalId, optionId });
|
|
93
|
+
await ctx.answerCallbackQuery({ text: option.label });
|
|
94
|
+
const prompt = option.textPrompt ??
|
|
95
|
+
`Send your message — the next text you type becomes the ${optionId} input.`;
|
|
96
|
+
if (state.chatId && state.bot) {
|
|
97
|
+
try {
|
|
98
|
+
await state.bot.api.sendMessage(state.chatId, `✏️ ${prompt}`);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
/* ignore */
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
state.approvalResolver.resolvePending(approvalId, optionId);
|
|
107
|
+
await ctx.answerCallbackQuery({ text: option.label });
|
|
108
|
+
}
|
|
109
|
+
async function handleModel(ctx, data, session, cb) {
|
|
110
|
+
// Format: model:<providerName>::<modelId>
|
|
111
|
+
const payload = data.slice(6);
|
|
112
|
+
const [providerId, modelId] = payload.split('::');
|
|
113
|
+
if (!providerId || !modelId || !session) {
|
|
114
|
+
await ctx.answerCallbackQuery({ text: 'invalid model selection' });
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
// Intercept switches to unconfigured providers — otherwise OAuth-
|
|
118
|
+
// backed providers (openai-codex) would surface a credential
|
|
119
|
+
// error on the next turn. Match the TUI's wording so the user
|
|
120
|
+
// sees the same setup command in both channels.
|
|
121
|
+
const ready = session.readyProviders ?? new Set();
|
|
122
|
+
if (!ready.has(providerId)) {
|
|
123
|
+
const cmd = providerId === 'openai-codex'
|
|
124
|
+
? 'moxxy login openai-codex'
|
|
125
|
+
: `moxxy init # (will prompt for ${providerId.toUpperCase()}_API_KEY)`;
|
|
126
|
+
await ctx.answerCallbackQuery({ text: `${providerId} not connected` });
|
|
127
|
+
if (ctx.callbackQuery?.message) {
|
|
128
|
+
try {
|
|
129
|
+
await ctx.editMessageText(`${providerId} isn't connected.\n\nRun \`${cmd}\` then restart moxxy.`, { parse_mode: 'Markdown' });
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
/* ignore */
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
if (session.providers.getActiveName() !== providerId) {
|
|
139
|
+
// Resolve credentials and drop the cached instance, same as
|
|
140
|
+
// the TUI. Without this the new provider gets createClient({})
|
|
141
|
+
// and openai-codex throws "no OAuth credentials" on next turn.
|
|
142
|
+
const resolver = session.credentialResolver;
|
|
143
|
+
const cfg = resolver ? await resolver(providerId) : {};
|
|
144
|
+
const def = session.providers.list().find((p) => p.name === providerId);
|
|
145
|
+
if (def)
|
|
146
|
+
session.providers.replace(def);
|
|
147
|
+
session.providers.setActive(providerId, cfg);
|
|
148
|
+
}
|
|
149
|
+
cb.setActiveModelOverride(modelId);
|
|
150
|
+
// Persist to the unified manifest for the next CLI run — same config the TUI
|
|
151
|
+
// writes. Await so a write failure (disk/permission/lock) is caught below and
|
|
152
|
+
// reported, instead of telling the user "✓ switched" while it never landed.
|
|
153
|
+
await setCategoryDefault('provider', providerId);
|
|
154
|
+
await setProviderModel(providerId, modelId);
|
|
155
|
+
await ctx.answerCallbackQuery({ text: `→ ${providerId}:${modelId}` });
|
|
156
|
+
if (ctx.callbackQuery?.message) {
|
|
157
|
+
try {
|
|
158
|
+
await ctx.editMessageText(`✓ switched to ${providerId}:${modelId}`);
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
/* ignore */
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
await ctx.answerCallbackQuery({
|
|
167
|
+
text: `failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
async function handleMode(ctx, data, session) {
|
|
172
|
+
const modeName = data.slice(5);
|
|
173
|
+
if (!modeName || !session) {
|
|
174
|
+
await ctx.answerCallbackQuery({ text: 'invalid mode' });
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
// Refuse a special mode reached by name (e.g. a stale callback) — special
|
|
178
|
+
// modes are entered only via their own command. Mirrors the TUI guard.
|
|
179
|
+
const target = session.modes.list().find((m) => m.name === modeName);
|
|
180
|
+
if (target && !isSelectableMode(target)) {
|
|
181
|
+
await ctx.answerCallbackQuery({ text: `"${modeName}" is a special mode` });
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
try {
|
|
185
|
+
session.modes.setActive(modeName);
|
|
186
|
+
// Await so a persistence failure is caught below (and reported) rather than
|
|
187
|
+
// claiming success while it silently never landed.
|
|
188
|
+
await setCategoryDefault('mode', modeName);
|
|
189
|
+
await ctx.answerCallbackQuery({ text: `mode → ${modeName}` });
|
|
190
|
+
if (ctx.callbackQuery?.message) {
|
|
191
|
+
try {
|
|
192
|
+
await ctx.editMessageText(`✓ mode → ${modeName}`);
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
/* ignore */
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
catch (err) {
|
|
200
|
+
await ctx.answerCallbackQuery({
|
|
201
|
+
text: `failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function mapChoice(choice) {
|
|
206
|
+
if (choice === 'allow')
|
|
207
|
+
return { mode: 'allow' };
|
|
208
|
+
if (choice === 'allow_session')
|
|
209
|
+
return { mode: 'allow_session' };
|
|
210
|
+
return { mode: 'deny', reason: 'denied by user' };
|
|
211
|
+
}
|
|
212
|
+
//# sourceMappingURL=callback-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callback-handler.js","sourceRoot":"","sources":["../../src/channel/callback-handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { Bot } from 'grammy';
|
|
2
|
+
import { TurnRenderer } from '../render.js';
|
|
3
|
+
export interface FramePumpLogger {
|
|
4
|
+
warn(msg: string, meta?: Record<string, unknown>): void;
|
|
5
|
+
}
|
|
6
|
+
export interface FramePumpOptions {
|
|
7
|
+
readonly editFrameMs: number;
|
|
8
|
+
readonly logger?: FramePumpLogger;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Drives the throttled "compose snapshot → send/edit one message" loop
|
|
12
|
+
* for a turn. Owns the renderer and the Telegram-specific delivery —
|
|
13
|
+
* HTML parse-mode fallback + 4096-char message splitting — while the
|
|
14
|
+
* throttle/send-once-then-edit mechanics live in `@moxxy/channel-kit`'s
|
|
15
|
+
* {@link StreamPump} (one instance per turn).
|
|
16
|
+
*
|
|
17
|
+
* Lifecycle per turn:
|
|
18
|
+
* 1. `beginTurn(chatId)` resets state.
|
|
19
|
+
* 2. Renderer updates schedule edits via `scheduleEdit()`.
|
|
20
|
+
* 3. `flush(final)` drains the latest snapshot to Telegram.
|
|
21
|
+
* 4. `endTurn()` clears timers + chat binding.
|
|
22
|
+
*/
|
|
23
|
+
export declare class FramePump {
|
|
24
|
+
private readonly editFrameMs;
|
|
25
|
+
private readonly logger?;
|
|
26
|
+
private bot;
|
|
27
|
+
private renderer;
|
|
28
|
+
private pump;
|
|
29
|
+
constructor(opts: FramePumpOptions);
|
|
30
|
+
attachBot(bot: Bot | null): void;
|
|
31
|
+
/** Renderer the channel feeds events into. Owned here so reset/snapshot
|
|
32
|
+
* cycles stay coordinated with the message-id state. */
|
|
33
|
+
get renderState(): TurnRenderer;
|
|
34
|
+
resetRenderer(): void;
|
|
35
|
+
beginTurn(chatId: number): void;
|
|
36
|
+
endTurn(): void;
|
|
37
|
+
scheduleEdit(): void;
|
|
38
|
+
flush(final: boolean): Promise<void>;
|
|
39
|
+
/** First real content of this turn — send (don't edit a placeholder). On the
|
|
40
|
+
* final frame, overflow beyond Telegram's message limit goes out as
|
|
41
|
+
* follow-up messages. */
|
|
42
|
+
private sendFrame;
|
|
43
|
+
private editFrame;
|
|
44
|
+
/**
|
|
45
|
+
* `text` is already Telegram-flavored HTML (produced by
|
|
46
|
+
* `composeFrame`). Try HTML; on parse-entity errors, strip tags and
|
|
47
|
+
* send plain text so the message still lands instead of looping on
|
|
48
|
+
* the same edit forever.
|
|
49
|
+
*/
|
|
50
|
+
safeEdit(chatId: number, messageId: number, text: string): Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* Send a new message (first frame of a turn or split-tail).
|
|
53
|
+
* Returns the new message_id on success so callers can set
|
|
54
|
+
* messageId for future edits.
|
|
55
|
+
*/
|
|
56
|
+
safeSend(chatId: number, text: string): Promise<number | null>;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=frame-pump.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frame-pump.d.ts","sourceRoot":"","sources":["../../src/channel/frame-pump.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAC;AAGlC,OAAO,EAAE,YAAY,EAAoB,MAAM,cAAc,CAAC;AAG9D,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CACzD;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,eAAe,CAAC;CACnC;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAkB;IAC1C,OAAO,CAAC,GAAG,CAAoB;IAC/B,OAAO,CAAC,QAAQ,CAAoC;IACpD,OAAO,CAAC,IAAI,CAAmC;gBAEnC,IAAI,EAAE,gBAAgB;IAKlC,SAAS,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,GAAG,IAAI;IAIhC;6DACyD;IACzD,IAAI,WAAW,IAAI,YAAY,CAE9B;IAED,aAAa,IAAI,IAAI;IAIrB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAqB/B,OAAO,IAAI,IAAI;IAKf,YAAY,IAAI,IAAI;IAId,KAAK,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAK1C;;8BAE0B;YACZ,SAAS;YAYT,SAAS;IAgBvB;;;;;OAKG;IACG,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAuB9E;;;;OAIG;IACG,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;CAqBrE"}
|