@geminixiang/mikan 0.3.0-beta.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/adapter.d.ts +5 -0
  3. package/dist/adapter.d.ts.map +1 -1
  4. package/dist/adapter.js.map +1 -1
  5. package/dist/adapters/discord/context.d.ts +0 -1
  6. package/dist/adapters/discord/context.d.ts.map +1 -1
  7. package/dist/adapters/discord/context.js +62 -84
  8. package/dist/adapters/discord/context.js.map +1 -1
  9. package/dist/adapters/shared.d.ts +1 -2
  10. package/dist/adapters/shared.d.ts.map +1 -1
  11. package/dist/adapters/shared.js +3 -2
  12. package/dist/adapters/shared.js.map +1 -1
  13. package/dist/adapters/slack/bot.d.ts +9 -34
  14. package/dist/adapters/slack/bot.d.ts.map +1 -1
  15. package/dist/adapters/slack/bot.js +374 -358
  16. package/dist/adapters/slack/bot.js.map +1 -1
  17. package/dist/adapters/slack/context.d.ts +0 -1
  18. package/dist/adapters/slack/context.d.ts.map +1 -1
  19. package/dist/adapters/slack/context.js +110 -113
  20. package/dist/adapters/slack/context.js.map +1 -1
  21. package/dist/adapters/slack/session.d.ts +0 -3
  22. package/dist/adapters/slack/session.d.ts.map +1 -1
  23. package/dist/adapters/slack/session.js +2 -8
  24. package/dist/adapters/slack/session.js.map +1 -1
  25. package/dist/adapters/slack/thread-manager.d.ts +0 -1
  26. package/dist/adapters/slack/thread-manager.d.ts.map +1 -1
  27. package/dist/adapters/slack/thread-manager.js +1 -4
  28. package/dist/adapters/slack/thread-manager.js.map +1 -1
  29. package/dist/adapters/slack/tools/block-kit.d.ts +16 -0
  30. package/dist/adapters/slack/tools/block-kit.d.ts.map +1 -0
  31. package/dist/adapters/slack/tools/block-kit.js +105 -0
  32. package/dist/adapters/slack/tools/block-kit.js.map +1 -0
  33. package/dist/adapters/telegram/context.d.ts +0 -1
  34. package/dist/adapters/telegram/context.d.ts.map +1 -1
  35. package/dist/adapters/telegram/context.js +44 -54
  36. package/dist/adapters/telegram/context.js.map +1 -1
  37. package/dist/admin/portal.d.ts.map +1 -1
  38. package/dist/admin/portal.js +2 -3
  39. package/dist/admin/portal.js.map +1 -1
  40. package/dist/agent.d.ts +0 -1
  41. package/dist/agent.d.ts.map +1 -1
  42. package/dist/agent.js +47 -80
  43. package/dist/agent.js.map +1 -1
  44. package/dist/commands/admin.d.ts +0 -3
  45. package/dist/commands/admin.d.ts.map +1 -1
  46. package/dist/commands/admin.js +5 -30
  47. package/dist/commands/admin.js.map +1 -1
  48. package/dist/commands/session-view.d.ts.map +1 -1
  49. package/dist/commands/session-view.js +4 -17
  50. package/dist/commands/session-view.js.map +1 -1
  51. package/dist/commands/types.d.ts +3 -2
  52. package/dist/commands/types.d.ts.map +1 -1
  53. package/dist/commands/types.js.map +1 -1
  54. package/dist/commands/utils.d.ts +3 -1
  55. package/dist/commands/utils.d.ts.map +1 -1
  56. package/dist/commands/utils.js +15 -5
  57. package/dist/commands/utils.js.map +1 -1
  58. package/dist/context.d.ts +0 -1
  59. package/dist/context.d.ts.map +1 -1
  60. package/dist/context.js +1 -23
  61. package/dist/context.js.map +1 -1
  62. package/dist/html.d.ts +2 -0
  63. package/dist/html.d.ts.map +1 -0
  64. package/dist/html.js +4 -0
  65. package/dist/html.js.map +1 -0
  66. package/dist/login/index.d.ts +2 -1
  67. package/dist/login/index.d.ts.map +1 -1
  68. package/dist/login/index.js.map +1 -1
  69. package/dist/login/portal.d.ts +1 -1
  70. package/dist/login/portal.d.ts.map +1 -1
  71. package/dist/login/portal.js +2 -3
  72. package/dist/login/portal.js.map +1 -1
  73. package/dist/login/{session.d.ts → store.d.ts} +1 -1
  74. package/dist/login/store.d.ts.map +1 -0
  75. package/dist/login/{session.js → store.js} +1 -1
  76. package/dist/login/store.js.map +1 -0
  77. package/dist/main.d.ts.map +1 -1
  78. package/dist/main.js +1 -1
  79. package/dist/main.js.map +1 -1
  80. package/dist/portal-shell.d.ts +2 -2
  81. package/dist/portal-shell.d.ts.map +1 -1
  82. package/dist/portal-shell.js +11 -16
  83. package/dist/portal-shell.js.map +1 -1
  84. package/dist/sandbox/cloudflare.d.ts +0 -2
  85. package/dist/sandbox/cloudflare.d.ts.map +1 -1
  86. package/dist/sandbox/cloudflare.js +2 -2
  87. package/dist/sandbox/cloudflare.js.map +1 -1
  88. package/dist/sandbox/container.d.ts +0 -3
  89. package/dist/sandbox/container.d.ts.map +1 -1
  90. package/dist/sandbox/container.js +3 -3
  91. package/dist/sandbox/container.js.map +1 -1
  92. package/dist/sandbox/firecracker.d.ts +0 -2
  93. package/dist/sandbox/firecracker.d.ts.map +1 -1
  94. package/dist/sandbox/firecracker.js +2 -2
  95. package/dist/sandbox/firecracker.js.map +1 -1
  96. package/dist/sandbox/host.d.ts +0 -2
  97. package/dist/sandbox/host.d.ts.map +1 -1
  98. package/dist/sandbox/host.js +2 -2
  99. package/dist/sandbox/host.js.map +1 -1
  100. package/dist/sandbox/image.d.ts +0 -2
  101. package/dist/sandbox/image.d.ts.map +1 -1
  102. package/dist/sandbox/image.js +2 -2
  103. package/dist/sandbox/image.js.map +1 -1
  104. package/dist/sandbox/index.d.ts +1 -6
  105. package/dist/sandbox/index.d.ts.map +1 -1
  106. package/dist/sandbox/index.js +0 -5
  107. package/dist/sandbox/index.js.map +1 -1
  108. package/dist/sandbox/path-context.d.ts +0 -1
  109. package/dist/sandbox/path-context.d.ts.map +1 -1
  110. package/dist/sandbox/path-context.js +1 -1
  111. package/dist/sandbox/path-context.js.map +1 -1
  112. package/dist/sentry.d.ts +2 -2
  113. package/dist/sentry.d.ts.map +1 -1
  114. package/dist/sentry.js.map +1 -1
  115. package/dist/session-view/portal.d.ts.map +1 -1
  116. package/dist/session-view/portal.js +2 -8
  117. package/dist/session-view/portal.js.map +1 -1
  118. package/dist/sessions/chat-session-manager.d.ts.map +1 -1
  119. package/dist/sessions/chat-session-manager.js +5 -9
  120. package/dist/sessions/chat-session-manager.js.map +1 -1
  121. package/dist/tools/index.d.ts +2 -0
  122. package/dist/tools/index.d.ts.map +1 -1
  123. package/dist/tools/index.js +4 -0
  124. package/dist/tools/index.js.map +1 -1
  125. package/dist/vault-routing.d.ts +0 -1
  126. package/dist/vault-routing.d.ts.map +1 -1
  127. package/dist/vault-routing.js +1 -4
  128. package/dist/vault-routing.js.map +1 -1
  129. package/dist/vault.d.ts +2 -1
  130. package/dist/vault.d.ts.map +1 -1
  131. package/dist/vault.js.map +1 -1
  132. package/package.json +3 -1
  133. package/dist/login/session.d.ts.map +0 -1
  134. package/dist/login/session.js.map +0 -1
@@ -0,0 +1,16 @@
1
+ import type { AgentTool } from "@earendil-works/pi-agent-core";
2
+ type SlackBlockKitResponse = {
3
+ text: string;
4
+ blocks: object[];
5
+ };
6
+ declare const blockKitSchema: import("@sinclair/typebox").TObject<{
7
+ label: import("@sinclair/typebox").TString;
8
+ text: import("@sinclair/typebox").TString;
9
+ blocks: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TUnknown>;
10
+ }>;
11
+ export declare function createSlackBlockKitTool(): {
12
+ tool: AgentTool<typeof blockKitSchema>;
13
+ setBlockKitResponseFunction: (fn: (response: SlackBlockKitResponse) => Promise<void>) => void;
14
+ };
15
+ export {};
16
+ //# sourceMappingURL=block-kit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"block-kit.d.ts","sourceRoot":"","sources":["../../../../src/adapters/slack/tools/block-kit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAG/D,KAAK,qBAAqB,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC;AAEF,QAAA,MAAM,cAAc;;;;EAOlB,CAAC;AAkFH,wBAAgB,uBAAuB,IAAI;IACzC,IAAI,EAAE,SAAS,CAAC,OAAO,cAAc,CAAC,CAAC;IACvC,2BAA2B,EAAE,CAAC,EAAE,EAAE,CAAC,QAAQ,EAAE,qBAAqB,KAAK,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC;CAC/F,CAgCA","sourcesContent":["import type { AgentTool } from \"@earendil-works/pi-agent-core\";\nimport { Type } from \"@sinclair/typebox\";\n\ntype SlackBlockKitResponse = {\n text: string;\n blocks: object[];\n};\n\nconst blockKitSchema = Type.Object({\n label: Type.String({ description: \"Brief description of the Slack Block Kit response\" }),\n text: Type.String({ description: \"Plain-text fallback for notifications and non-Slack clients\" }),\n blocks: Type.Array(Type.Unknown(), {\n description:\n \"Slack Block Kit blocks. Supports section, context, divider, header, actions; buttons in actions.elements; static_select and multi_static_select in section.accessory.\",\n }),\n});\n\nconst ALLOWED_BLOCK_TYPES = new Set([\"section\", \"context\", \"divider\", \"header\", \"actions\"]);\nconst ALLOWED_INTERACTIVE_ELEMENT_TYPES = new Set([\n \"button\",\n \"static_select\",\n \"multi_static_select\",\n]);\n\nfunction assertPlainObject(\n value: unknown,\n label: string,\n): asserts value is Record<string, unknown> {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n throw new Error(`${label} must be an object`);\n }\n}\n\nfunction validateInteractiveElement(element: Record<string, unknown>, label: string): void {\n if (typeof element.type !== \"string\" || !ALLOWED_INTERACTIVE_ELEMENT_TYPES.has(element.type)) {\n return;\n }\n if (typeof element.action_id !== \"string\" || !element.action_id) {\n throw new Error(`${label}.action_id is required`);\n }\n if (element.type === \"button\" && typeof element.value !== \"string\") {\n throw new Error(`${label}.value is required for buttons`);\n }\n}\n\nfunction validateInteractiveElements(value: unknown, label: string): void {\n if (!value || typeof value !== \"object\") return;\n if (Array.isArray(value)) {\n value.forEach((item, index) => validateInteractiveElements(item, `${label}[${index}]`));\n return;\n }\n\n const obj = value as Record<string, unknown>;\n validateInteractiveElement(obj, label);\n for (const [key, nested] of Object.entries(obj)) {\n validateInteractiveElements(nested, `${label}.${key}`);\n }\n}\n\nfunction validateBlockPlacement(block: Record<string, unknown>, label: string): void {\n if (block.type === \"actions\") {\n if (!Array.isArray(block.elements)) throw new Error(`${label}.elements is required`);\n block.elements.forEach((element, index) => {\n assertPlainObject(element, `${label}.elements[${index}]`);\n if (element.type !== \"button\") {\n throw new Error(\n `${label}.elements[${index}] uses ${String(element.type)}; put static_select and multi_static_select in section.accessory instead`,\n );\n }\n });\n }\n\n if (block.type === \"section\" && block.accessory !== undefined) {\n assertPlainObject(block.accessory, `${label}.accessory`);\n const type = String(block.accessory.type);\n if (!ALLOWED_INTERACTIVE_ELEMENT_TYPES.has(type)) {\n throw new Error(`${label}.accessory has unsupported type: ${type}`);\n }\n }\n}\n\nfunction validateBlocks(blocks: unknown[]): object[] {\n if (blocks.length === 0) throw new Error(\"blocks must not be empty\");\n if (blocks.length > 20) throw new Error(\"blocks must contain at most 20 blocks\");\n\n return blocks.map((block, index) => {\n assertPlainObject(block, `blocks[${index}]`);\n const type = block.type;\n if (typeof type !== \"string\" || !ALLOWED_BLOCK_TYPES.has(type)) {\n throw new Error(`Unsupported Block Kit block type: ${String(type)}`);\n }\n validateBlockPlacement(block, `blocks[${index}]`);\n validateInteractiveElements(block, `blocks[${index}]`);\n return block;\n });\n}\n\nexport function createSlackBlockKitTool(): {\n tool: AgentTool<typeof blockKitSchema>;\n setBlockKitResponseFunction: (fn: (response: SlackBlockKitResponse) => Promise<void>) => void;\n} {\n let respondFn: ((response: SlackBlockKitResponse) => Promise<void>) | null = null;\n\n const tool: AgentTool<typeof blockKitSchema> = {\n name: \"slack_blockkit\",\n label: \"slack block kit\",\n description:\n \"Send a Slack Block Kit response. Use when structured Slack UI helps the user choose or inspect information. Supports buttons, static_select, and multi_static_select. Put buttons in actions.elements. Put static_select and multi_static_select in section.accessory.\",\n parameters: blockKitSchema,\n execute: async (\n _toolCallId: string,\n { text, blocks }: { label: string; text: string; blocks: unknown[] },\n signal?: AbortSignal,\n ) => {\n if (!respondFn) throw new Error(\"Slack Block Kit response function not configured\");\n if (signal?.aborted) throw new Error(\"Operation aborted\");\n\n await respondFn({ text, blocks: validateBlocks(blocks) });\n\n return {\n content: [{ type: \"text\" as const, text: \"Sent Slack Block Kit response\" }],\n details: undefined,\n };\n },\n };\n\n return {\n tool,\n setBlockKitResponseFunction: (fn) => {\n respondFn = fn;\n },\n };\n}\n"]}
@@ -0,0 +1,105 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ const blockKitSchema = Type.Object({
3
+ label: Type.String({ description: "Brief description of the Slack Block Kit response" }),
4
+ text: Type.String({ description: "Plain-text fallback for notifications and non-Slack clients" }),
5
+ blocks: Type.Array(Type.Unknown(), {
6
+ description: "Slack Block Kit blocks. Supports section, context, divider, header, actions; buttons in actions.elements; static_select and multi_static_select in section.accessory.",
7
+ }),
8
+ });
9
+ const ALLOWED_BLOCK_TYPES = new Set(["section", "context", "divider", "header", "actions"]);
10
+ const ALLOWED_INTERACTIVE_ELEMENT_TYPES = new Set([
11
+ "button",
12
+ "static_select",
13
+ "multi_static_select",
14
+ ]);
15
+ function assertPlainObject(value, label) {
16
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
17
+ throw new Error(`${label} must be an object`);
18
+ }
19
+ }
20
+ function validateInteractiveElement(element, label) {
21
+ if (typeof element.type !== "string" || !ALLOWED_INTERACTIVE_ELEMENT_TYPES.has(element.type)) {
22
+ return;
23
+ }
24
+ if (typeof element.action_id !== "string" || !element.action_id) {
25
+ throw new Error(`${label}.action_id is required`);
26
+ }
27
+ if (element.type === "button" && typeof element.value !== "string") {
28
+ throw new Error(`${label}.value is required for buttons`);
29
+ }
30
+ }
31
+ function validateInteractiveElements(value, label) {
32
+ if (!value || typeof value !== "object")
33
+ return;
34
+ if (Array.isArray(value)) {
35
+ value.forEach((item, index) => validateInteractiveElements(item, `${label}[${index}]`));
36
+ return;
37
+ }
38
+ const obj = value;
39
+ validateInteractiveElement(obj, label);
40
+ for (const [key, nested] of Object.entries(obj)) {
41
+ validateInteractiveElements(nested, `${label}.${key}`);
42
+ }
43
+ }
44
+ function validateBlockPlacement(block, label) {
45
+ if (block.type === "actions") {
46
+ if (!Array.isArray(block.elements))
47
+ throw new Error(`${label}.elements is required`);
48
+ block.elements.forEach((element, index) => {
49
+ assertPlainObject(element, `${label}.elements[${index}]`);
50
+ if (element.type !== "button") {
51
+ throw new Error(`${label}.elements[${index}] uses ${String(element.type)}; put static_select and multi_static_select in section.accessory instead`);
52
+ }
53
+ });
54
+ }
55
+ if (block.type === "section" && block.accessory !== undefined) {
56
+ assertPlainObject(block.accessory, `${label}.accessory`);
57
+ const type = String(block.accessory.type);
58
+ if (!ALLOWED_INTERACTIVE_ELEMENT_TYPES.has(type)) {
59
+ throw new Error(`${label}.accessory has unsupported type: ${type}`);
60
+ }
61
+ }
62
+ }
63
+ function validateBlocks(blocks) {
64
+ if (blocks.length === 0)
65
+ throw new Error("blocks must not be empty");
66
+ if (blocks.length > 20)
67
+ throw new Error("blocks must contain at most 20 blocks");
68
+ return blocks.map((block, index) => {
69
+ assertPlainObject(block, `blocks[${index}]`);
70
+ const type = block.type;
71
+ if (typeof type !== "string" || !ALLOWED_BLOCK_TYPES.has(type)) {
72
+ throw new Error(`Unsupported Block Kit block type: ${String(type)}`);
73
+ }
74
+ validateBlockPlacement(block, `blocks[${index}]`);
75
+ validateInteractiveElements(block, `blocks[${index}]`);
76
+ return block;
77
+ });
78
+ }
79
+ export function createSlackBlockKitTool() {
80
+ let respondFn = null;
81
+ const tool = {
82
+ name: "slack_blockkit",
83
+ label: "slack block kit",
84
+ description: "Send a Slack Block Kit response. Use when structured Slack UI helps the user choose or inspect information. Supports buttons, static_select, and multi_static_select. Put buttons in actions.elements. Put static_select and multi_static_select in section.accessory.",
85
+ parameters: blockKitSchema,
86
+ execute: async (_toolCallId, { text, blocks }, signal) => {
87
+ if (!respondFn)
88
+ throw new Error("Slack Block Kit response function not configured");
89
+ if (signal?.aborted)
90
+ throw new Error("Operation aborted");
91
+ await respondFn({ text, blocks: validateBlocks(blocks) });
92
+ return {
93
+ content: [{ type: "text", text: "Sent Slack Block Kit response" }],
94
+ details: undefined,
95
+ };
96
+ },
97
+ };
98
+ return {
99
+ tool,
100
+ setBlockKitResponseFunction: (fn) => {
101
+ respondFn = fn;
102
+ },
103
+ };
104
+ }
105
+ //# sourceMappingURL=block-kit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"block-kit.js","sourceRoot":"","sources":["../../../../src/adapters/slack/tools/block-kit.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAOzC,MAAM,cAAc,GAAG,IAAI,CAAC,MAAM,CAAC;IACjC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,mDAAmD,EAAE,CAAC;IACxF,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,6DAA6D,EAAE,CAAC;IACjG,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE;QACjC,WAAW,EACT,uKAAuK;KAC1K,CAAC;CACH,CAAC,CAAC;AAEH,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAC,CAAC,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC;AAC5F,MAAM,iCAAiC,GAAG,IAAI,GAAG,CAAC;IAChD,QAAQ;IACR,eAAe;IACf,qBAAqB;CACtB,CAAC,CAAC;AAEH,SAAS,iBAAiB,CACxB,KAAc,EACd,KAAa;IAEb,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAChE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,oBAAoB,CAAC,CAAC;IAChD,CAAC;AACH,CAAC;AAED,SAAS,0BAA0B,CAAC,OAAgC,EAAE,KAAa;IACjF,IAAI,OAAO,OAAO,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,iCAAiC,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7F,OAAO;IACT,CAAC;IACD,IAAI,OAAO,OAAO,CAAC,SAAS,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC;QAChE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,wBAAwB,CAAC,CAAC;IACpD,CAAC;IACD,IAAI,OAAO,CAAC,IAAI,KAAK,QAAQ,IAAI,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;QACnE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,gCAAgC,CAAC,CAAC;IAC5D,CAAC;AACH,CAAC;AAED,SAAS,2BAA2B,CAAC,KAAc,EAAE,KAAa;IAChE,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO;IAChD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,2BAA2B,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC;QACxF,OAAO;IACT,CAAC;IAED,MAAM,GAAG,GAAG,KAAgC,CAAC;IAC7C,0BAA0B,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACvC,KAAK,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QAChD,2BAA2B,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,GAAG,EAAE,CAAC,CAAC;IACzD,CAAC;AACH,CAAC;AAED,SAAS,sBAAsB,CAAC,KAA8B,EAAE,KAAa;IAC3E,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC7B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,uBAAuB,CAAC,CAAC;QACrF,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE;YACxC,iBAAiB,CAAC,OAAO,EAAE,GAAG,KAAK,aAAa,KAAK,GAAG,CAAC,CAAC;YAC1D,IAAI,OAAO,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC9B,MAAM,IAAI,KAAK,CACb,GAAG,KAAK,aAAa,KAAK,UAAU,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,0EAA0E,CACnI,CAAC;YACJ,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,KAAK,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;QAC9D,iBAAiB,CAAC,KAAK,CAAC,SAAS,EAAE,GAAG,KAAK,YAAY,CAAC,CAAC;QACzD,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QAC1C,IAAI,CAAC,iCAAiC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACjD,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,oCAAoC,IAAI,EAAE,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CAAC,MAAiB;IACvC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;IACrE,IAAI,MAAM,CAAC,MAAM,GAAG,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;IAEjF,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;QACjC,iBAAiB,CAAC,KAAK,EAAE,UAAU,KAAK,GAAG,CAAC,CAAC;QAC7C,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;QACxB,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAC/D,MAAM,IAAI,KAAK,CAAC,qCAAqC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACvE,CAAC;QACD,sBAAsB,CAAC,KAAK,EAAE,UAAU,KAAK,GAAG,CAAC,CAAC;QAClD,2BAA2B,CAAC,KAAK,EAAE,UAAU,KAAK,GAAG,CAAC,CAAC;QACvD,OAAO,KAAK,CAAC;IACf,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,uBAAuB;IAIrC,IAAI,SAAS,GAAgE,IAAI,CAAC;IAElF,MAAM,IAAI,GAAqC;QAC7C,IAAI,EAAE,gBAAgB;QACtB,KAAK,EAAE,iBAAiB;QACxB,WAAW,EACT,wQAAwQ;QAC1Q,UAAU,EAAE,cAAc;QAC1B,OAAO,EAAE,KAAK,EACZ,WAAmB,EACnB,EAAE,IAAI,EAAE,MAAM,EAAsD,EACpE,MAAoB,EACpB,EAAE;YACF,IAAI,CAAC,SAAS;gBAAE,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;YACpF,IAAI,MAAM,EAAE,OAAO;gBAAE,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC;YAE1D,MAAM,SAAS,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAE1D,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,+BAA+B,EAAE,CAAC;gBAC3E,OAAO,EAAE,SAAS;aACnB,CAAC;QACJ,CAAC;KACF,CAAC;IAEF,OAAO;QACL,IAAI;QACJ,2BAA2B,EAAE,CAAC,EAAE,EAAE,EAAE;YAClC,SAAS,GAAG,EAAE,CAAC;QACjB,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import type { AgentTool } from \"@earendil-works/pi-agent-core\";\nimport { Type } from \"@sinclair/typebox\";\n\ntype SlackBlockKitResponse = {\n text: string;\n blocks: object[];\n};\n\nconst blockKitSchema = Type.Object({\n label: Type.String({ description: \"Brief description of the Slack Block Kit response\" }),\n text: Type.String({ description: \"Plain-text fallback for notifications and non-Slack clients\" }),\n blocks: Type.Array(Type.Unknown(), {\n description:\n \"Slack Block Kit blocks. Supports section, context, divider, header, actions; buttons in actions.elements; static_select and multi_static_select in section.accessory.\",\n }),\n});\n\nconst ALLOWED_BLOCK_TYPES = new Set([\"section\", \"context\", \"divider\", \"header\", \"actions\"]);\nconst ALLOWED_INTERACTIVE_ELEMENT_TYPES = new Set([\n \"button\",\n \"static_select\",\n \"multi_static_select\",\n]);\n\nfunction assertPlainObject(\n value: unknown,\n label: string,\n): asserts value is Record<string, unknown> {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n throw new Error(`${label} must be an object`);\n }\n}\n\nfunction validateInteractiveElement(element: Record<string, unknown>, label: string): void {\n if (typeof element.type !== \"string\" || !ALLOWED_INTERACTIVE_ELEMENT_TYPES.has(element.type)) {\n return;\n }\n if (typeof element.action_id !== \"string\" || !element.action_id) {\n throw new Error(`${label}.action_id is required`);\n }\n if (element.type === \"button\" && typeof element.value !== \"string\") {\n throw new Error(`${label}.value is required for buttons`);\n }\n}\n\nfunction validateInteractiveElements(value: unknown, label: string): void {\n if (!value || typeof value !== \"object\") return;\n if (Array.isArray(value)) {\n value.forEach((item, index) => validateInteractiveElements(item, `${label}[${index}]`));\n return;\n }\n\n const obj = value as Record<string, unknown>;\n validateInteractiveElement(obj, label);\n for (const [key, nested] of Object.entries(obj)) {\n validateInteractiveElements(nested, `${label}.${key}`);\n }\n}\n\nfunction validateBlockPlacement(block: Record<string, unknown>, label: string): void {\n if (block.type === \"actions\") {\n if (!Array.isArray(block.elements)) throw new Error(`${label}.elements is required`);\n block.elements.forEach((element, index) => {\n assertPlainObject(element, `${label}.elements[${index}]`);\n if (element.type !== \"button\") {\n throw new Error(\n `${label}.elements[${index}] uses ${String(element.type)}; put static_select and multi_static_select in section.accessory instead`,\n );\n }\n });\n }\n\n if (block.type === \"section\" && block.accessory !== undefined) {\n assertPlainObject(block.accessory, `${label}.accessory`);\n const type = String(block.accessory.type);\n if (!ALLOWED_INTERACTIVE_ELEMENT_TYPES.has(type)) {\n throw new Error(`${label}.accessory has unsupported type: ${type}`);\n }\n }\n}\n\nfunction validateBlocks(blocks: unknown[]): object[] {\n if (blocks.length === 0) throw new Error(\"blocks must not be empty\");\n if (blocks.length > 20) throw new Error(\"blocks must contain at most 20 blocks\");\n\n return blocks.map((block, index) => {\n assertPlainObject(block, `blocks[${index}]`);\n const type = block.type;\n if (typeof type !== \"string\" || !ALLOWED_BLOCK_TYPES.has(type)) {\n throw new Error(`Unsupported Block Kit block type: ${String(type)}`);\n }\n validateBlockPlacement(block, `blocks[${index}]`);\n validateInteractiveElements(block, `blocks[${index}]`);\n return block;\n });\n}\n\nexport function createSlackBlockKitTool(): {\n tool: AgentTool<typeof blockKitSchema>;\n setBlockKitResponseFunction: (fn: (response: SlackBlockKitResponse) => Promise<void>) => void;\n} {\n let respondFn: ((response: SlackBlockKitResponse) => Promise<void>) | null = null;\n\n const tool: AgentTool<typeof blockKitSchema> = {\n name: \"slack_blockkit\",\n label: \"slack block kit\",\n description:\n \"Send a Slack Block Kit response. Use when structured Slack UI helps the user choose or inspect information. Supports buttons, static_select, and multi_static_select. Put buttons in actions.elements. Put static_select and multi_static_select in section.accessory.\",\n parameters: blockKitSchema,\n execute: async (\n _toolCallId: string,\n { text, blocks }: { label: string; text: string; blocks: unknown[] },\n signal?: AbortSignal,\n ) => {\n if (!respondFn) throw new Error(\"Slack Block Kit response function not configured\");\n if (signal?.aborted) throw new Error(\"Operation aborted\");\n\n await respondFn({ text, blocks: validateBlocks(blocks) });\n\n return {\n content: [{ type: \"text\" as const, text: \"Sent Slack Block Kit response\" }],\n details: undefined,\n };\n },\n };\n\n return {\n tool,\n setBlockKitResponseFunction: (fn) => {\n respondFn = fn;\n },\n };\n}\n"]}
@@ -1,6 +1,5 @@
1
1
  import type { ChatMessage, ChatResponseContext, PlatformInfo } from "../../adapter.js";
2
2
  import type { TelegramBot, TelegramEvent } from "./bot.js";
3
- export declare const TELEGRAM_FORMATTING_GUIDE = "## Telegram Formatting (HTML mode)\nBold: <b>text</b>, Italic: <i>text</i>, Code: <code>code</code>, Pre: <pre>code</pre>\nLinks: <a href=\"url\">text</a>\nDo NOT use Markdown asterisks or backtick syntax.\nDo NOT use <table> tags \u2014 they are unsupported. Use <pre> with ASCII art for tables instead.";
4
3
  export declare function createTelegramAdapters(event: TelegramEvent, bot: TelegramBot): {
5
4
  message: ChatMessage;
6
5
  responseCtx: ChatResponseContext;
@@ -1 +1 @@
1
- {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/adapters/telegram/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EACX,mBAAmB,EAEnB,YAAY,EACb,MAAM,kBAAkB,CAAC;AAI1B,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE3D,eAAO,MAAM,yBAAyB,qTAIuD,CAAC;AA+B9F,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,aAAa,EACpB,GAAG,EAAE,WAAW,GACf;IACD,OAAO,EAAE,WAAW,CAAC;IACrB,WAAW,EAAE,mBAAmB,CAAC;IACjC,QAAQ,EAAE,YAAY,CAAC;CACxB,CA4LA","sourcesContent":["import type {\n ChatMessage,\n ChatResponseContext,\n ChatToolResult,\n PlatformInfo,\n} from \"../../adapter.js\";\nimport * as log from \"../../log.js\";\nimport { createChatResponseErrorReporter, formatToolArgs, splitText } from \"../shared.js\";\nimport { sanitizeTelegramHtml } from \"./html.js\";\nimport type { TelegramBot, TelegramEvent } from \"./bot.js\";\n\nexport const TELEGRAM_FORMATTING_GUIDE = `## Telegram Formatting (HTML mode)\nBold: <b>text</b>, Italic: <i>text</i>, Code: <code>code</code>, Pre: <pre>code</pre>\nLinks: <a href=\"url\">text</a>\nDo NOT use Markdown asterisks or backtick syntax.\nDo NOT use <table> tags — they are unsupported. Use <pre> with ASCII art for tables instead.`;\n\n// Telegram message length limit is 4096 chars; 3800 leaves headroom for HTML escapes.\nconst MAX_LENGTH = 3800;\n\nconst formatTelegramContinuation = (partNum: number): string => `(continued ${partNum})`;\n\nasync function notifyError(\n bot: TelegramBot,\n chatId: number,\n label: string,\n err: unknown,\n report?: () => void,\n): Promise<void> {\n const errMsg = err instanceof Error ? err.message : String(err);\n log.logWarning(`Telegram ${label} error`, errMsg);\n report?.();\n try {\n await bot.postPlainMessage(chatId, `⚠️ 發送失敗:${errMsg}`);\n } catch {\n // ignore secondary failure\n }\n}\n\nfunction formatToolResult(result: ChatToolResult): string {\n const argsFormatted = formatToolArgs(result.args);\n const duration = (result.durationMs / 1000).toFixed(1);\n const title = `${result.isError ? \"Error\" : \"Done\"} ${result.toolName}${result.label ? `: ${result.label}` : \"\"} (${duration}s)`;\n return [title, argsFormatted, result.result].filter(Boolean).join(\"\\n\\n\");\n}\n\nexport function createTelegramAdapters(\n event: TelegramEvent,\n bot: TelegramBot,\n): {\n message: ChatMessage;\n responseCtx: ChatResponseContext;\n platform: PlatformInfo;\n} {\n let messageId: number | null = null;\n let accumulatedText = \"\";\n let updatePromise = Promise.resolve();\n let typingInterval: ReturnType<typeof setInterval> | null = null;\n let typingFailureWarned = false;\n\n function stopTyping() {\n if (typingInterval !== null) {\n clearInterval(typingInterval);\n typingInterval = null;\n }\n }\n\n const conversationId = event.conversationId;\n const chatId = parseInt(conversationId);\n const replyToId = event.thread_ts ? parseInt(event.thread_ts) : null;\n\n const message: ChatMessage = {\n id: event.ts,\n sessionKey: event.sessionKey ?? `${conversationId}:${event.thread_ts ?? event.ts}`,\n conversationKind: event.conversationKind,\n userId: event.user,\n userName: event.userName,\n text: event.text,\n attachments: event.attachments,\n threadTs: event.thread_ts,\n };\n\n const platform: PlatformInfo = {\n name: \"telegram\",\n formattingGuide: TELEGRAM_FORMATTING_GUIDE,\n channels: [],\n users: [],\n };\n\n async function sendContinuation(text: string): Promise<void> {\n await bot.postMessageRaw(chatId, text);\n }\n\n async function sendOrUpdate(displayText: string): Promise<void> {\n if (messageId !== null) {\n await bot.updateMessage(conversationId, String(messageId), displayText);\n } else if (replyToId !== null) {\n messageId = await bot.postReply(chatId, replyToId, displayText);\n } else {\n messageId = await bot.postMessageRaw(chatId, displayText);\n }\n }\n\n const reportResponseError = createChatResponseErrorReporter(() => ({\n platform: \"telegram\",\n conversationId,\n chatId,\n messageId: message.id,\n sessionKey: message.sessionKey,\n responseMessageId: messageId,\n replyToId,\n conversationKind: message.conversationKind,\n }));\n\n const responseCtx: ChatResponseContext = {\n respond: async (text: string) => {\n updatePromise = updatePromise.then(async () => {\n try {\n const sanitized = sanitizeTelegramHtml(text);\n accumulatedText = accumulatedText ? `${accumulatedText}\\n${sanitized}` : sanitized;\n const [firstPart, ...extraParts] = splitText(\n accumulatedText,\n MAX_LENGTH,\n formatTelegramContinuation,\n );\n await sendOrUpdate(firstPart);\n for (const part of extraParts) {\n await sendContinuation(part);\n }\n if (messageId !== null) {\n bot.logBotResponse(conversationId, text, String(messageId));\n }\n } catch (err) {\n await notifyError(bot, chatId, \"respond\", err, () =>\n reportResponseError(err, \"respond\", {\n phase: messageId ? \"update\" : \"initial_post\",\n textLength: text.length,\n accumulatedLength: accumulatedText.length,\n }),\n );\n }\n });\n await updatePromise;\n },\n\n replaceResponse: async (text: string) => {\n updatePromise = updatePromise.then(async () => {\n try {\n accumulatedText = sanitizeTelegramHtml(text);\n const [firstPart, ...extraParts] = splitText(\n accumulatedText,\n MAX_LENGTH,\n formatTelegramContinuation,\n );\n await sendOrUpdate(firstPart);\n for (const part of extraParts) {\n await sendContinuation(part);\n }\n } catch (err) {\n await notifyError(bot, chatId, \"replaceResponse\", err, () =>\n reportResponseError(err, \"replace_response\", {\n textLength: text.length,\n hadExistingResponse: Boolean(messageId),\n }),\n );\n }\n });\n await updatePromise;\n },\n\n respondDiagnostic: async (text: string, options?: { style?: \"muted\" | \"error\" }) => {\n updatePromise = updatePromise.then(async () => {\n try {\n const prefix = options?.style === \"error\" ? \"Error: \" : \"\";\n for (const part of splitText(\n sanitizeTelegramHtml(`${prefix}${text}`),\n MAX_LENGTH,\n formatTelegramContinuation,\n )) {\n await sendContinuation(part);\n }\n } catch (err) {\n await notifyError(bot, chatId, \"respondDiagnostic\", err, () =>\n reportResponseError(err, \"respond_diagnostic\", {\n textLength: text.length,\n style: options?.style,\n }),\n );\n }\n });\n await updatePromise;\n },\n\n respondToolResult: async (result: ChatToolResult) => {\n await responseCtx.respondDiagnostic(formatToolResult(result));\n },\n\n setTyping: async (isTyping: boolean) => {\n const onTypingError = (err: unknown): void => {\n if (typingFailureWarned) return;\n typingFailureWarned = true;\n log.logWarning(\n \"Telegram sendTyping failed (further occurrences suppressed for this session)\",\n err instanceof Error ? err.message : String(err),\n );\n };\n if (isTyping && typingInterval === null) {\n // Send immediately and repeat every 4s (Telegram clears indicator after ~5s)\n bot.sendTyping(chatId).catch(onTypingError);\n typingInterval = setInterval(() => {\n bot.sendTyping(chatId).catch(onTypingError);\n }, 4000);\n } else if (!isTyping) {\n stopTyping();\n }\n },\n\n setWorking: async (working: boolean) => {\n if (!working) stopTyping();\n },\n\n uploadFile: async (filePath: string, title?: string) => {\n await bot.uploadFile(conversationId, filePath, title);\n },\n\n deleteResponse: async () => {\n updatePromise = updatePromise.then(async () => {\n if (messageId !== null) {\n try {\n await bot.deleteMessageRaw(chatId, messageId);\n } catch {\n // Ignore errors\n }\n messageId = null;\n }\n });\n await updatePromise;\n },\n };\n\n return { message, responseCtx, platform };\n}\n"]}
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/adapters/telegram/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EACX,mBAAmB,EAEnB,YAAY,EACb,MAAM,kBAAkB,CAAC;AAI1B,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAqC3D,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,aAAa,EACpB,GAAG,EAAE,WAAW,GACf;IACD,OAAO,EAAE,WAAW,CAAC;IACrB,WAAW,EAAE,mBAAmB,CAAC;IACjC,QAAQ,EAAE,YAAY,CAAC;CACxB,CA0LA","sourcesContent":["import type {\n ChatMessage,\n ChatResponseContext,\n ChatToolResult,\n PlatformInfo,\n} from \"../../adapter.js\";\nimport * as log from \"../../log.js\";\nimport { createChatResponseErrorReporter, formatToolArgs, splitText } from \"../shared.js\";\nimport { sanitizeTelegramHtml } from \"./html.js\";\nimport type { TelegramBot, TelegramEvent } from \"./bot.js\";\n\nconst TELEGRAM_FORMATTING_GUIDE = `## Telegram Formatting (HTML mode)\nBold: <b>text</b>, Italic: <i>text</i>, Code: <code>code</code>, Pre: <pre>code</pre>\nLinks: <a href=\"url\">text</a>\nDo NOT use Markdown asterisks or backtick syntax.\nDo NOT use <table> tags — they are unsupported. Use <pre> with ASCII art for tables instead.`;\n\n// Telegram message length limit is 4096 chars; 3800 leaves headroom for HTML escapes.\nconst MAX_LENGTH = 3800;\n\nconst formatTelegramContinuation = (partNum: number): string => `(continued ${partNum})`;\n\nasync function notifyError(\n bot: TelegramBot,\n chatId: number,\n label: string,\n err: unknown,\n report?: () => void,\n): Promise<void> {\n const errMsg = err instanceof Error ? err.message : String(err);\n log.logWarning(`Telegram ${label} error`, errMsg);\n report?.();\n try {\n await bot.postPlainMessage(chatId, `⚠️ 發送失敗:${errMsg}`);\n } catch {\n // ignore secondary failure\n }\n}\n\nfunction formatToolResult(result: ChatToolResult): string {\n const argsFormatted = formatToolArgs(result.args);\n const duration = (result.durationMs / 1000).toFixed(1);\n const title = `${result.isError ? \"Error\" : \"Done\"} ${result.toolName}${result.label ? `: ${result.label}` : \"\"} (${duration}s)`;\n return [title, argsFormatted, result.result].filter(Boolean).join(\"\\n\\n\");\n}\n\nexport function createTelegramAdapters(\n event: TelegramEvent,\n bot: TelegramBot,\n): {\n message: ChatMessage;\n responseCtx: ChatResponseContext;\n platform: PlatformInfo;\n} {\n let messageId: number | null = null;\n let accumulatedText = \"\";\n let updatePromise = Promise.resolve();\n let typingInterval: ReturnType<typeof setInterval> | null = null;\n let typingFailureWarned = false;\n\n function stopTyping() {\n if (typingInterval !== null) {\n clearInterval(typingInterval);\n typingInterval = null;\n }\n }\n\n const conversationId = event.conversationId;\n const chatId = parseInt(conversationId);\n const replyToId = event.thread_ts ? parseInt(event.thread_ts) : null;\n\n const message: ChatMessage = {\n id: event.ts,\n sessionKey: event.sessionKey ?? `${conversationId}:${event.thread_ts ?? event.ts}`,\n conversationKind: event.conversationKind,\n userId: event.user,\n userName: event.userName,\n text: event.text,\n attachments: event.attachments,\n threadTs: event.thread_ts,\n };\n\n const platform: PlatformInfo = {\n name: \"telegram\",\n formattingGuide: TELEGRAM_FORMATTING_GUIDE,\n channels: [],\n users: [],\n };\n\n async function sendContinuation(text: string): Promise<void> {\n await bot.postMessageRaw(chatId, text);\n }\n\n async function sendOrUpdate(displayText: string): Promise<void> {\n if (messageId !== null) {\n await bot.updateMessage(conversationId, String(messageId), displayText);\n } else if (replyToId !== null) {\n messageId = await bot.postReply(chatId, replyToId, displayText);\n } else {\n messageId = await bot.postMessageRaw(chatId, displayText);\n }\n }\n\n const reportResponseError = createChatResponseErrorReporter(() => ({\n platform: \"telegram\",\n conversationId,\n chatId,\n messageId: message.id,\n sessionKey: message.sessionKey,\n responseMessageId: messageId,\n replyToId,\n conversationKind: message.conversationKind,\n }));\n\n const sendSplitText = async (\n text: string,\n firstPart: (part: string) => Promise<void>,\n ): Promise<void> => {\n const [head, ...tail] = splitText(text, MAX_LENGTH, formatTelegramContinuation);\n await firstPart(head);\n for (const part of tail) {\n await sendContinuation(part);\n }\n };\n\n const queueTelegramSend = async (\n label: string,\n work: () => Promise<void>,\n report: (err: unknown) => void,\n ): Promise<void> => {\n updatePromise = updatePromise.then(async () => {\n try {\n await work();\n } catch (err) {\n await notifyError(bot, chatId, label, err, () => report(err));\n }\n });\n await updatePromise;\n };\n\n const responseCtx: ChatResponseContext = {\n respond: async (text: string) => {\n await queueTelegramSend(\n \"respond\",\n async () => {\n const sanitized = sanitizeTelegramHtml(text);\n accumulatedText = accumulatedText ? `${accumulatedText}\\n${sanitized}` : sanitized;\n await sendSplitText(accumulatedText, sendOrUpdate);\n if (messageId !== null) {\n bot.logBotResponse(conversationId, text, String(messageId));\n }\n },\n (err) =>\n reportResponseError(err, \"respond\", {\n phase: messageId ? \"update\" : \"initial_post\",\n textLength: text.length,\n accumulatedLength: accumulatedText.length,\n }),\n );\n },\n\n replaceResponse: async (text: string) => {\n await queueTelegramSend(\n \"replaceResponse\",\n async () => {\n accumulatedText = sanitizeTelegramHtml(text);\n await sendSplitText(accumulatedText, sendOrUpdate);\n },\n (err) =>\n reportResponseError(err, \"replace_response\", {\n textLength: text.length,\n hadExistingResponse: Boolean(messageId),\n }),\n );\n },\n\n respondDiagnostic: async (text: string, options?: { style?: \"muted\" | \"error\" }) => {\n await queueTelegramSend(\n \"respondDiagnostic\",\n async () => {\n const prefix = options?.style === \"error\" ? \"Error: \" : \"\";\n await sendSplitText(sanitizeTelegramHtml(`${prefix}${text}`), sendContinuation);\n },\n (err) =>\n reportResponseError(err, \"respond_diagnostic\", {\n textLength: text.length,\n style: options?.style,\n }),\n );\n },\n\n respondToolResult: async (result: ChatToolResult) => {\n await responseCtx.respondDiagnostic(formatToolResult(result));\n },\n\n setTyping: async (isTyping: boolean) => {\n const onTypingError = (err: unknown): void => {\n if (typingFailureWarned) return;\n typingFailureWarned = true;\n log.logWarning(\n \"Telegram sendTyping failed (further occurrences suppressed for this session)\",\n err instanceof Error ? err.message : String(err),\n );\n };\n if (isTyping && typingInterval === null) {\n // Send immediately and repeat every 4s (Telegram clears indicator after ~5s)\n bot.sendTyping(chatId).catch(onTypingError);\n typingInterval = setInterval(() => {\n bot.sendTyping(chatId).catch(onTypingError);\n }, 4000);\n } else if (!isTyping) {\n stopTyping();\n }\n },\n\n setWorking: async (working: boolean) => {\n if (!working) stopTyping();\n },\n\n uploadFile: async (filePath: string, title?: string) => {\n await bot.uploadFile(conversationId, filePath, title);\n },\n\n deleteResponse: async () => {\n updatePromise = updatePromise.then(async () => {\n if (messageId !== null) {\n try {\n await bot.deleteMessageRaw(chatId, messageId);\n } catch {\n // Ignore errors\n }\n messageId = null;\n }\n });\n await updatePromise;\n },\n };\n\n return { message, responseCtx, platform };\n}\n"]}
@@ -1,7 +1,7 @@
1
1
  import * as log from "../../log.js";
2
2
  import { createChatResponseErrorReporter, formatToolArgs, splitText } from "../shared.js";
3
3
  import { sanitizeTelegramHtml } from "./html.js";
4
- export const TELEGRAM_FORMATTING_GUIDE = `## Telegram Formatting (HTML mode)
4
+ const TELEGRAM_FORMATTING_GUIDE = `## Telegram Formatting (HTML mode)
5
5
  Bold: <b>text</b>, Italic: <i>text</i>, Code: <code>code</code>, Pre: <pre>code</pre>
6
6
  Links: <a href="url">text</a>
7
7
  Do NOT use Markdown asterisks or backtick syntax.
@@ -81,66 +81,56 @@ export function createTelegramAdapters(event, bot) {
81
81
  replyToId,
82
82
  conversationKind: message.conversationKind,
83
83
  }));
84
+ const sendSplitText = async (text, firstPart) => {
85
+ const [head, ...tail] = splitText(text, MAX_LENGTH, formatTelegramContinuation);
86
+ await firstPart(head);
87
+ for (const part of tail) {
88
+ await sendContinuation(part);
89
+ }
90
+ };
91
+ const queueTelegramSend = async (label, work, report) => {
92
+ updatePromise = updatePromise.then(async () => {
93
+ try {
94
+ await work();
95
+ }
96
+ catch (err) {
97
+ await notifyError(bot, chatId, label, err, () => report(err));
98
+ }
99
+ });
100
+ await updatePromise;
101
+ };
84
102
  const responseCtx = {
85
103
  respond: async (text) => {
86
- updatePromise = updatePromise.then(async () => {
87
- try {
88
- const sanitized = sanitizeTelegramHtml(text);
89
- accumulatedText = accumulatedText ? `${accumulatedText}\n${sanitized}` : sanitized;
90
- const [firstPart, ...extraParts] = splitText(accumulatedText, MAX_LENGTH, formatTelegramContinuation);
91
- await sendOrUpdate(firstPart);
92
- for (const part of extraParts) {
93
- await sendContinuation(part);
94
- }
95
- if (messageId !== null) {
96
- bot.logBotResponse(conversationId, text, String(messageId));
97
- }
98
- }
99
- catch (err) {
100
- await notifyError(bot, chatId, "respond", err, () => reportResponseError(err, "respond", {
101
- phase: messageId ? "update" : "initial_post",
102
- textLength: text.length,
103
- accumulatedLength: accumulatedText.length,
104
- }));
104
+ await queueTelegramSend("respond", async () => {
105
+ const sanitized = sanitizeTelegramHtml(text);
106
+ accumulatedText = accumulatedText ? `${accumulatedText}\n${sanitized}` : sanitized;
107
+ await sendSplitText(accumulatedText, sendOrUpdate);
108
+ if (messageId !== null) {
109
+ bot.logBotResponse(conversationId, text, String(messageId));
105
110
  }
106
- });
107
- await updatePromise;
111
+ }, (err) => reportResponseError(err, "respond", {
112
+ phase: messageId ? "update" : "initial_post",
113
+ textLength: text.length,
114
+ accumulatedLength: accumulatedText.length,
115
+ }));
108
116
  },
109
117
  replaceResponse: async (text) => {
110
- updatePromise = updatePromise.then(async () => {
111
- try {
112
- accumulatedText = sanitizeTelegramHtml(text);
113
- const [firstPart, ...extraParts] = splitText(accumulatedText, MAX_LENGTH, formatTelegramContinuation);
114
- await sendOrUpdate(firstPart);
115
- for (const part of extraParts) {
116
- await sendContinuation(part);
117
- }
118
- }
119
- catch (err) {
120
- await notifyError(bot, chatId, "replaceResponse", err, () => reportResponseError(err, "replace_response", {
121
- textLength: text.length,
122
- hadExistingResponse: Boolean(messageId),
123
- }));
124
- }
125
- });
126
- await updatePromise;
118
+ await queueTelegramSend("replaceResponse", async () => {
119
+ accumulatedText = sanitizeTelegramHtml(text);
120
+ await sendSplitText(accumulatedText, sendOrUpdate);
121
+ }, (err) => reportResponseError(err, "replace_response", {
122
+ textLength: text.length,
123
+ hadExistingResponse: Boolean(messageId),
124
+ }));
127
125
  },
128
126
  respondDiagnostic: async (text, options) => {
129
- updatePromise = updatePromise.then(async () => {
130
- try {
131
- const prefix = options?.style === "error" ? "Error: " : "";
132
- for (const part of splitText(sanitizeTelegramHtml(`${prefix}${text}`), MAX_LENGTH, formatTelegramContinuation)) {
133
- await sendContinuation(part);
134
- }
135
- }
136
- catch (err) {
137
- await notifyError(bot, chatId, "respondDiagnostic", err, () => reportResponseError(err, "respond_diagnostic", {
138
- textLength: text.length,
139
- style: options?.style,
140
- }));
141
- }
142
- });
143
- await updatePromise;
127
+ await queueTelegramSend("respondDiagnostic", async () => {
128
+ const prefix = options?.style === "error" ? "Error: " : "";
129
+ await sendSplitText(sanitizeTelegramHtml(`${prefix}${text}`), sendContinuation);
130
+ }, (err) => reportResponseError(err, "respond_diagnostic", {
131
+ textLength: text.length,
132
+ style: options?.style,
133
+ }));
144
134
  },
145
135
  respondToolResult: async (result) => {
146
136
  await responseCtx.respondDiagnostic(formatToolResult(result));
@@ -1 +1 @@
1
- {"version":3,"file":"context.js","sourceRoot":"","sources":["../../../src/adapters/telegram/context.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n ChatMessage,\n ChatResponseContext,\n ChatToolResult,\n PlatformInfo,\n} from \"../../adapter.js\";\nimport * as log from \"../../log.js\";\nimport { createChatResponseErrorReporter, formatToolArgs, splitText } from \"../shared.js\";\nimport { sanitizeTelegramHtml } from \"./html.js\";\nimport type { TelegramBot, TelegramEvent } from \"./bot.js\";\n\nexport const TELEGRAM_FORMATTING_GUIDE = `## Telegram Formatting (HTML mode)\nBold: <b>text</b>, Italic: <i>text</i>, Code: <code>code</code>, Pre: <pre>code</pre>\nLinks: <a href=\"url\">text</a>\nDo NOT use Markdown asterisks or backtick syntax.\nDo NOT use <table> tags — they are unsupported. Use <pre> with ASCII art for tables instead.`;\n\n// Telegram message length limit is 4096 chars; 3800 leaves headroom for HTML escapes.\nconst MAX_LENGTH = 3800;\n\nconst formatTelegramContinuation = (partNum: number): string => `(continued ${partNum})`;\n\nasync function notifyError(\n bot: TelegramBot,\n chatId: number,\n label: string,\n err: unknown,\n report?: () => void,\n): Promise<void> {\n const errMsg = err instanceof Error ? err.message : String(err);\n log.logWarning(`Telegram ${label} error`, errMsg);\n report?.();\n try {\n await bot.postPlainMessage(chatId, `⚠️ 發送失敗:${errMsg}`);\n } catch {\n // ignore secondary failure\n }\n}\n\nfunction formatToolResult(result: ChatToolResult): string {\n const argsFormatted = formatToolArgs(result.args);\n const duration = (result.durationMs / 1000).toFixed(1);\n const title = `${result.isError ? \"Error\" : \"Done\"} ${result.toolName}${result.label ? `: ${result.label}` : \"\"} (${duration}s)`;\n return [title, argsFormatted, result.result].filter(Boolean).join(\"\\n\\n\");\n}\n\nexport function createTelegramAdapters(\n event: TelegramEvent,\n bot: TelegramBot,\n): {\n message: ChatMessage;\n responseCtx: ChatResponseContext;\n platform: PlatformInfo;\n} {\n let messageId: number | null = null;\n let accumulatedText = \"\";\n let updatePromise = Promise.resolve();\n let typingInterval: ReturnType<typeof setInterval> | null = null;\n let typingFailureWarned = false;\n\n function stopTyping() {\n if (typingInterval !== null) {\n clearInterval(typingInterval);\n typingInterval = null;\n }\n }\n\n const conversationId = event.conversationId;\n const chatId = parseInt(conversationId);\n const replyToId = event.thread_ts ? parseInt(event.thread_ts) : null;\n\n const message: ChatMessage = {\n id: event.ts,\n sessionKey: event.sessionKey ?? `${conversationId}:${event.thread_ts ?? event.ts}`,\n conversationKind: event.conversationKind,\n userId: event.user,\n userName: event.userName,\n text: event.text,\n attachments: event.attachments,\n threadTs: event.thread_ts,\n };\n\n const platform: PlatformInfo = {\n name: \"telegram\",\n formattingGuide: TELEGRAM_FORMATTING_GUIDE,\n channels: [],\n users: [],\n };\n\n async function sendContinuation(text: string): Promise<void> {\n await bot.postMessageRaw(chatId, text);\n }\n\n async function sendOrUpdate(displayText: string): Promise<void> {\n if (messageId !== null) {\n await bot.updateMessage(conversationId, String(messageId), displayText);\n } else if (replyToId !== null) {\n messageId = await bot.postReply(chatId, replyToId, displayText);\n } else {\n messageId = await bot.postMessageRaw(chatId, displayText);\n }\n }\n\n const reportResponseError = createChatResponseErrorReporter(() => ({\n platform: \"telegram\",\n conversationId,\n chatId,\n messageId: message.id,\n sessionKey: message.sessionKey,\n responseMessageId: messageId,\n replyToId,\n conversationKind: message.conversationKind,\n }));\n\n const responseCtx: ChatResponseContext = {\n respond: async (text: string) => {\n updatePromise = updatePromise.then(async () => {\n try {\n const sanitized = sanitizeTelegramHtml(text);\n accumulatedText = accumulatedText ? `${accumulatedText}\\n${sanitized}` : sanitized;\n const [firstPart, ...extraParts] = splitText(\n accumulatedText,\n MAX_LENGTH,\n formatTelegramContinuation,\n );\n await sendOrUpdate(firstPart);\n for (const part of extraParts) {\n await sendContinuation(part);\n }\n if (messageId !== null) {\n bot.logBotResponse(conversationId, text, String(messageId));\n }\n } catch (err) {\n await notifyError(bot, chatId, \"respond\", err, () =>\n reportResponseError(err, \"respond\", {\n phase: messageId ? \"update\" : \"initial_post\",\n textLength: text.length,\n accumulatedLength: accumulatedText.length,\n }),\n );\n }\n });\n await updatePromise;\n },\n\n replaceResponse: async (text: string) => {\n updatePromise = updatePromise.then(async () => {\n try {\n accumulatedText = sanitizeTelegramHtml(text);\n const [firstPart, ...extraParts] = splitText(\n accumulatedText,\n MAX_LENGTH,\n formatTelegramContinuation,\n );\n await sendOrUpdate(firstPart);\n for (const part of extraParts) {\n await sendContinuation(part);\n }\n } catch (err) {\n await notifyError(bot, chatId, \"replaceResponse\", err, () =>\n reportResponseError(err, \"replace_response\", {\n textLength: text.length,\n hadExistingResponse: Boolean(messageId),\n }),\n );\n }\n });\n await updatePromise;\n },\n\n respondDiagnostic: async (text: string, options?: { style?: \"muted\" | \"error\" }) => {\n updatePromise = updatePromise.then(async () => {\n try {\n const prefix = options?.style === \"error\" ? \"Error: \" : \"\";\n for (const part of splitText(\n sanitizeTelegramHtml(`${prefix}${text}`),\n MAX_LENGTH,\n formatTelegramContinuation,\n )) {\n await sendContinuation(part);\n }\n } catch (err) {\n await notifyError(bot, chatId, \"respondDiagnostic\", err, () =>\n reportResponseError(err, \"respond_diagnostic\", {\n textLength: text.length,\n style: options?.style,\n }),\n );\n }\n });\n await updatePromise;\n },\n\n respondToolResult: async (result: ChatToolResult) => {\n await responseCtx.respondDiagnostic(formatToolResult(result));\n },\n\n setTyping: async (isTyping: boolean) => {\n const onTypingError = (err: unknown): void => {\n if (typingFailureWarned) return;\n typingFailureWarned = true;\n log.logWarning(\n \"Telegram sendTyping failed (further occurrences suppressed for this session)\",\n err instanceof Error ? err.message : String(err),\n );\n };\n if (isTyping && typingInterval === null) {\n // Send immediately and repeat every 4s (Telegram clears indicator after ~5s)\n bot.sendTyping(chatId).catch(onTypingError);\n typingInterval = setInterval(() => {\n bot.sendTyping(chatId).catch(onTypingError);\n }, 4000);\n } else if (!isTyping) {\n stopTyping();\n }\n },\n\n setWorking: async (working: boolean) => {\n if (!working) stopTyping();\n },\n\n uploadFile: async (filePath: string, title?: string) => {\n await bot.uploadFile(conversationId, filePath, title);\n },\n\n deleteResponse: async () => {\n updatePromise = updatePromise.then(async () => {\n if (messageId !== null) {\n try {\n await bot.deleteMessageRaw(chatId, messageId);\n } catch {\n // Ignore errors\n }\n messageId = null;\n }\n });\n await updatePromise;\n },\n };\n\n return { message, responseCtx, platform };\n}\n"]}
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../../../src/adapters/telegram/context.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n ChatMessage,\n ChatResponseContext,\n ChatToolResult,\n PlatformInfo,\n} from \"../../adapter.js\";\nimport * as log from \"../../log.js\";\nimport { createChatResponseErrorReporter, formatToolArgs, splitText } from \"../shared.js\";\nimport { sanitizeTelegramHtml } from \"./html.js\";\nimport type { TelegramBot, TelegramEvent } from \"./bot.js\";\n\nconst TELEGRAM_FORMATTING_GUIDE = `## Telegram Formatting (HTML mode)\nBold: <b>text</b>, Italic: <i>text</i>, Code: <code>code</code>, Pre: <pre>code</pre>\nLinks: <a href=\"url\">text</a>\nDo NOT use Markdown asterisks or backtick syntax.\nDo NOT use <table> tags — they are unsupported. Use <pre> with ASCII art for tables instead.`;\n\n// Telegram message length limit is 4096 chars; 3800 leaves headroom for HTML escapes.\nconst MAX_LENGTH = 3800;\n\nconst formatTelegramContinuation = (partNum: number): string => `(continued ${partNum})`;\n\nasync function notifyError(\n bot: TelegramBot,\n chatId: number,\n label: string,\n err: unknown,\n report?: () => void,\n): Promise<void> {\n const errMsg = err instanceof Error ? err.message : String(err);\n log.logWarning(`Telegram ${label} error`, errMsg);\n report?.();\n try {\n await bot.postPlainMessage(chatId, `⚠️ 發送失敗:${errMsg}`);\n } catch {\n // ignore secondary failure\n }\n}\n\nfunction formatToolResult(result: ChatToolResult): string {\n const argsFormatted = formatToolArgs(result.args);\n const duration = (result.durationMs / 1000).toFixed(1);\n const title = `${result.isError ? \"Error\" : \"Done\"} ${result.toolName}${result.label ? `: ${result.label}` : \"\"} (${duration}s)`;\n return [title, argsFormatted, result.result].filter(Boolean).join(\"\\n\\n\");\n}\n\nexport function createTelegramAdapters(\n event: TelegramEvent,\n bot: TelegramBot,\n): {\n message: ChatMessage;\n responseCtx: ChatResponseContext;\n platform: PlatformInfo;\n} {\n let messageId: number | null = null;\n let accumulatedText = \"\";\n let updatePromise = Promise.resolve();\n let typingInterval: ReturnType<typeof setInterval> | null = null;\n let typingFailureWarned = false;\n\n function stopTyping() {\n if (typingInterval !== null) {\n clearInterval(typingInterval);\n typingInterval = null;\n }\n }\n\n const conversationId = event.conversationId;\n const chatId = parseInt(conversationId);\n const replyToId = event.thread_ts ? parseInt(event.thread_ts) : null;\n\n const message: ChatMessage = {\n id: event.ts,\n sessionKey: event.sessionKey ?? `${conversationId}:${event.thread_ts ?? event.ts}`,\n conversationKind: event.conversationKind,\n userId: event.user,\n userName: event.userName,\n text: event.text,\n attachments: event.attachments,\n threadTs: event.thread_ts,\n };\n\n const platform: PlatformInfo = {\n name: \"telegram\",\n formattingGuide: TELEGRAM_FORMATTING_GUIDE,\n channels: [],\n users: [],\n };\n\n async function sendContinuation(text: string): Promise<void> {\n await bot.postMessageRaw(chatId, text);\n }\n\n async function sendOrUpdate(displayText: string): Promise<void> {\n if (messageId !== null) {\n await bot.updateMessage(conversationId, String(messageId), displayText);\n } else if (replyToId !== null) {\n messageId = await bot.postReply(chatId, replyToId, displayText);\n } else {\n messageId = await bot.postMessageRaw(chatId, displayText);\n }\n }\n\n const reportResponseError = createChatResponseErrorReporter(() => ({\n platform: \"telegram\",\n conversationId,\n chatId,\n messageId: message.id,\n sessionKey: message.sessionKey,\n responseMessageId: messageId,\n replyToId,\n conversationKind: message.conversationKind,\n }));\n\n const sendSplitText = async (\n text: string,\n firstPart: (part: string) => Promise<void>,\n ): Promise<void> => {\n const [head, ...tail] = splitText(text, MAX_LENGTH, formatTelegramContinuation);\n await firstPart(head);\n for (const part of tail) {\n await sendContinuation(part);\n }\n };\n\n const queueTelegramSend = async (\n label: string,\n work: () => Promise<void>,\n report: (err: unknown) => void,\n ): Promise<void> => {\n updatePromise = updatePromise.then(async () => {\n try {\n await work();\n } catch (err) {\n await notifyError(bot, chatId, label, err, () => report(err));\n }\n });\n await updatePromise;\n };\n\n const responseCtx: ChatResponseContext = {\n respond: async (text: string) => {\n await queueTelegramSend(\n \"respond\",\n async () => {\n const sanitized = sanitizeTelegramHtml(text);\n accumulatedText = accumulatedText ? `${accumulatedText}\\n${sanitized}` : sanitized;\n await sendSplitText(accumulatedText, sendOrUpdate);\n if (messageId !== null) {\n bot.logBotResponse(conversationId, text, String(messageId));\n }\n },\n (err) =>\n reportResponseError(err, \"respond\", {\n phase: messageId ? \"update\" : \"initial_post\",\n textLength: text.length,\n accumulatedLength: accumulatedText.length,\n }),\n );\n },\n\n replaceResponse: async (text: string) => {\n await queueTelegramSend(\n \"replaceResponse\",\n async () => {\n accumulatedText = sanitizeTelegramHtml(text);\n await sendSplitText(accumulatedText, sendOrUpdate);\n },\n (err) =>\n reportResponseError(err, \"replace_response\", {\n textLength: text.length,\n hadExistingResponse: Boolean(messageId),\n }),\n );\n },\n\n respondDiagnostic: async (text: string, options?: { style?: \"muted\" | \"error\" }) => {\n await queueTelegramSend(\n \"respondDiagnostic\",\n async () => {\n const prefix = options?.style === \"error\" ? \"Error: \" : \"\";\n await sendSplitText(sanitizeTelegramHtml(`${prefix}${text}`), sendContinuation);\n },\n (err) =>\n reportResponseError(err, \"respond_diagnostic\", {\n textLength: text.length,\n style: options?.style,\n }),\n );\n },\n\n respondToolResult: async (result: ChatToolResult) => {\n await responseCtx.respondDiagnostic(formatToolResult(result));\n },\n\n setTyping: async (isTyping: boolean) => {\n const onTypingError = (err: unknown): void => {\n if (typingFailureWarned) return;\n typingFailureWarned = true;\n log.logWarning(\n \"Telegram sendTyping failed (further occurrences suppressed for this session)\",\n err instanceof Error ? err.message : String(err),\n );\n };\n if (isTyping && typingInterval === null) {\n // Send immediately and repeat every 4s (Telegram clears indicator after ~5s)\n bot.sendTyping(chatId).catch(onTypingError);\n typingInterval = setInterval(() => {\n bot.sendTyping(chatId).catch(onTypingError);\n }, 4000);\n } else if (!isTyping) {\n stopTyping();\n }\n },\n\n setWorking: async (working: boolean) => {\n if (!working) stopTyping();\n },\n\n uploadFile: async (filePath: string, title?: string) => {\n await bot.uploadFile(conversationId, filePath, title);\n },\n\n deleteResponse: async () => {\n updatePromise = updatePromise.then(async () => {\n if (messageId !== null) {\n try {\n await bot.deleteMessageRaw(chatId, messageId);\n } catch {\n // Ignore errors\n }\n messageId = null;\n }\n });\n await updatePromise;\n },\n };\n\n return { message, responseCtx, platform };\n}\n"]}