@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.
Files changed (123) hide show
  1. package/LICENSE +21 -0
  2. package/dist/approval.d.ts +37 -0
  3. package/dist/approval.d.ts.map +1 -0
  4. package/dist/approval.js +75 -0
  5. package/dist/approval.js.map +1 -0
  6. package/dist/channel/approval-prompt.d.ts +20 -0
  7. package/dist/channel/approval-prompt.d.ts.map +1 -0
  8. package/dist/channel/approval-prompt.js +34 -0
  9. package/dist/channel/approval-prompt.js.map +1 -0
  10. package/dist/channel/callback-handler.d.ts +32 -0
  11. package/dist/channel/callback-handler.d.ts.map +1 -0
  12. package/dist/channel/callback-handler.js +212 -0
  13. package/dist/channel/callback-handler.js.map +1 -0
  14. package/dist/channel/frame-pump.d.ts +58 -0
  15. package/dist/channel/frame-pump.d.ts.map +1 -0
  16. package/dist/channel/frame-pump.js +159 -0
  17. package/dist/channel/frame-pump.js.map +1 -0
  18. package/dist/channel/html.d.ts +24 -0
  19. package/dist/channel/html.d.ts.map +1 -0
  20. package/dist/channel/html.js +62 -0
  21. package/dist/channel/html.js.map +1 -0
  22. package/dist/channel/pairing-handler.d.ts +66 -0
  23. package/dist/channel/pairing-handler.d.ts.map +1 -0
  24. package/dist/channel/pairing-handler.js +147 -0
  25. package/dist/channel/pairing-handler.js.map +1 -0
  26. package/dist/channel/permission-prompt.d.ts +21 -0
  27. package/dist/channel/permission-prompt.d.ts.map +1 -0
  28. package/dist/channel/permission-prompt.js +36 -0
  29. package/dist/channel/permission-prompt.js.map +1 -0
  30. package/dist/channel/slash-handler.d.ts +47 -0
  31. package/dist/channel/slash-handler.d.ts.map +1 -0
  32. package/dist/channel/slash-handler.js +214 -0
  33. package/dist/channel/slash-handler.js.map +1 -0
  34. package/dist/channel/text-handler.d.ts +43 -0
  35. package/dist/channel/text-handler.d.ts.map +1 -0
  36. package/dist/channel/text-handler.js +116 -0
  37. package/dist/channel/text-handler.js.map +1 -0
  38. package/dist/channel/turn-runner.d.ts +40 -0
  39. package/dist/channel/turn-runner.d.ts.map +1 -0
  40. package/dist/channel/turn-runner.js +67 -0
  41. package/dist/channel/turn-runner.js.map +1 -0
  42. package/dist/channel/typing-indicator.d.ts +14 -0
  43. package/dist/channel/typing-indicator.d.ts.map +1 -0
  44. package/dist/channel/typing-indicator.js +29 -0
  45. package/dist/channel/typing-indicator.js.map +1 -0
  46. package/dist/channel/voice-handler.d.ts +42 -0
  47. package/dist/channel/voice-handler.d.ts.map +1 -0
  48. package/dist/channel/voice-handler.js +138 -0
  49. package/dist/channel/voice-handler.js.map +1 -0
  50. package/dist/channel.d.ts +118 -0
  51. package/dist/channel.d.ts.map +1 -0
  52. package/dist/channel.js +478 -0
  53. package/dist/channel.js.map +1 -0
  54. package/dist/channel.test-d.d.ts +2 -0
  55. package/dist/channel.test-d.d.ts.map +1 -0
  56. package/dist/channel.test-d.js +5 -0
  57. package/dist/channel.test-d.js.map +1 -0
  58. package/dist/format.d.ts +38 -0
  59. package/dist/format.d.ts.map +1 -0
  60. package/dist/format.js +237 -0
  61. package/dist/format.js.map +1 -0
  62. package/dist/index.d.ts +24 -0
  63. package/dist/index.d.ts.map +1 -0
  64. package/dist/index.js +304 -0
  65. package/dist/index.js.map +1 -0
  66. package/dist/keys.d.ts +37 -0
  67. package/dist/keys.d.ts.map +1 -0
  68. package/dist/keys.js +42 -0
  69. package/dist/keys.js.map +1 -0
  70. package/dist/pair-flow.d.ts +18 -0
  71. package/dist/pair-flow.d.ts.map +1 -0
  72. package/dist/pair-flow.js +119 -0
  73. package/dist/pair-flow.js.map +1 -0
  74. package/dist/pairing.d.ts +90 -0
  75. package/dist/pairing.d.ts.map +1 -0
  76. package/dist/pairing.js +97 -0
  77. package/dist/pairing.js.map +1 -0
  78. package/dist/permission.d.ts +25 -0
  79. package/dist/permission.d.ts.map +1 -0
  80. package/dist/permission.js +49 -0
  81. package/dist/permission.js.map +1 -0
  82. package/dist/render.d.ts +68 -0
  83. package/dist/render.d.ts.map +1 -0
  84. package/dist/render.js +439 -0
  85. package/dist/render.js.map +1 -0
  86. package/dist/setup-wizard.d.ts +21 -0
  87. package/dist/setup-wizard.d.ts.map +1 -0
  88. package/dist/setup-wizard.js +140 -0
  89. package/dist/setup-wizard.js.map +1 -0
  90. package/package.json +91 -0
  91. package/src/approval.test.ts +103 -0
  92. package/src/approval.ts +86 -0
  93. package/src/channel/approval-prompt.ts +46 -0
  94. package/src/channel/callback-handler.test.ts +188 -0
  95. package/src/channel/callback-handler.ts +256 -0
  96. package/src/channel/frame-pump.ts +176 -0
  97. package/src/channel/html.test.ts +40 -0
  98. package/src/channel/html.ts +60 -0
  99. package/src/channel/pairing-handler.ts +181 -0
  100. package/src/channel/permission-prompt.ts +51 -0
  101. package/src/channel/slash-handler.ts +259 -0
  102. package/src/channel/text-handler.test.ts +222 -0
  103. package/src/channel/text-handler.ts +174 -0
  104. package/src/channel/turn-runner.ts +102 -0
  105. package/src/channel/typing-indicator.ts +31 -0
  106. package/src/channel/voice-handler.test.ts +328 -0
  107. package/src/channel/voice-handler.ts +198 -0
  108. package/src/channel/voice-reply.test.ts +223 -0
  109. package/src/channel.test-d.ts +7 -0
  110. package/src/channel.ts +564 -0
  111. package/src/format.test.ts +159 -0
  112. package/src/format.ts +257 -0
  113. package/src/index.ts +347 -0
  114. package/src/keys.ts +52 -0
  115. package/src/pair-flow.ts +131 -0
  116. package/src/pairing.test.ts +113 -0
  117. package/src/pairing.ts +184 -0
  118. package/src/permission.test.ts +56 -0
  119. package/src/permission.ts +61 -0
  120. package/src/render.test.ts +350 -0
  121. package/src/render.ts +522 -0
  122. package/src/setup-wizard.ts +178 -0
  123. 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"}
@@ -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":"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"}
@@ -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"}