@valuya/telegram-bot-channel 0.2.0-beta.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.
@@ -0,0 +1,20 @@
1
+ HOST=0.0.0.0
2
+ PORT=8792
3
+ VALUYA_BASE=https://pay.gorilla.build
4
+ VALUYA_TENANT_TOKEN=ttok_example
5
+ TELEGRAM_LINKS_FILE=.data/telegram-links.json
6
+ TELEGRAM_CHANNEL_APP_ID=telegram_main
7
+ TELEGRAM_CHANNEL_RESOURCE=telegram:channel:concierge_demo
8
+ TELEGRAM_CHANNEL_PLAN=standard
9
+ TELEGRAM_CHANNEL_BOT=concierge_demo_bot
10
+ TELEGRAM_CHANNEL_NAME=Premium Concierge
11
+ TELEGRAM_CHANNEL_INVITE_URL=https://t.me/concierge_demo_bot
12
+ TELEGRAM_CHANNEL_MODE=agent
13
+ TELEGRAM_CHANNEL_MEMORY_FILE=.data/telegram-concierge-memory.json
14
+ TELEGRAM_CHANNEL_SOUL_ID=concierge
15
+ TELEGRAM_CHANNEL_SOUL_NAME=Premium Concierge
16
+ TELEGRAM_CHANNEL_SOUL_LOCALE=de
17
+ TELEGRAM_CHANNEL_SOUL_SYSTEM_PROMPT=Du bist ein diskreter, hilfreicher Premium-Concierge und hilfst dem Nutzer, passende Optionen schnell und angenehm zu finden.
18
+ TELEGRAM_CHANNEL_SOUL_RESPONSE_SCHEMA_JSON={"format":"json","replyKey":"concierge_reply","followUpQuestionKey":"clarifying_question","followUpQuestionsKey":"option_questions","nextStepKey":"recommended_next_step","summaryKey":"preference_summary","userProfileKey":"user_preferences","rootPatternKey":"decision_style"}
19
+ OPENAI_API_KEY=sk-...
20
+ OPENAI_MODEL=gpt-4.1-mini
package/.env.example ADDED
@@ -0,0 +1,27 @@
1
+ HOST=0.0.0.0
2
+ PORT=8792
3
+
4
+ VALUYA_BASE=https://pay.gorilla.build
5
+ VALUYA_TENANT_TOKEN=ttok_example
6
+
7
+ TELEGRAM_LINKS_FILE=.data/telegram-links.json
8
+ TELEGRAM_CHANNEL_APP_ID=telegram_main
9
+ TELEGRAM_CHANNEL_RESOURCE=telegram:channel:mentor_demo
10
+ TELEGRAM_CHANNEL_PLAN=standard
11
+ TELEGRAM_CHANNEL_BOT=mentor_demo_bot
12
+ TELEGRAM_CHANNEL_NAME=Mentor Demo
13
+ TELEGRAM_CHANNEL_INVITE_URL=https://t.me/mentor_demo_bot
14
+
15
+ TELEGRAM_CHANNEL_MODE=agent
16
+ TELEGRAM_CHANNEL_HUMAN_REPLY=Danke. Ein Mensch aus dem Team meldet sich hier bei dir.
17
+ TELEGRAM_CHANNEL_INTERNAL_API_TOKEN=internal_demo_token
18
+
19
+ TELEGRAM_CHANNEL_MEMORY_FILE=.data/telegram-channel-memory.json
20
+ TELEGRAM_CHANNEL_SOUL_ID=mentor
21
+ TELEGRAM_CHANNEL_SOUL_NAME=Mentor
22
+ TELEGRAM_CHANNEL_SOUL_LOCALE=de
23
+ TELEGRAM_CHANNEL_SOUL_SYSTEM_PROMPT=Du bist ein ruhiger, klarer Mentor und hilfst dem Nutzer, sein eigentliches Thema besser zu verstehen.
24
+ TELEGRAM_CHANNEL_SOUL_RESPONSE_SCHEMA_JSON={"format":"json","replyKey":"mentor_reply","followUpQuestionKey":"deep_question","followUpQuestionsKey":"follow_up_questions","nextStepKey":"next_step","summaryKey":"conversation_summary","userProfileKey":"user_profile","rootPatternKey":"root_pattern"}
25
+
26
+ OPENAI_API_KEY=sk-...
27
+ OPENAI_MODEL=gpt-4.1-mini
@@ -0,0 +1,20 @@
1
+ HOST=0.0.0.0
2
+ PORT=8792
3
+ VALUYA_BASE=https://pay.gorilla.build
4
+ VALUYA_TENANT_TOKEN=ttok_example
5
+ TELEGRAM_LINKS_FILE=.data/telegram-links.json
6
+ TELEGRAM_CHANNEL_APP_ID=telegram_main
7
+ TELEGRAM_CHANNEL_RESOURCE=telegram:channel:mentor_demo
8
+ TELEGRAM_CHANNEL_PLAN=standard
9
+ TELEGRAM_CHANNEL_BOT=mentor_demo_bot
10
+ TELEGRAM_CHANNEL_NAME=Mentor Demo
11
+ TELEGRAM_CHANNEL_INVITE_URL=https://t.me/mentor_demo_bot
12
+ TELEGRAM_CHANNEL_MODE=agent
13
+ TELEGRAM_CHANNEL_MEMORY_FILE=.data/telegram-channel-memory.json
14
+ TELEGRAM_CHANNEL_SOUL_ID=mentor
15
+ TELEGRAM_CHANNEL_SOUL_NAME=Mentor
16
+ TELEGRAM_CHANNEL_SOUL_LOCALE=de
17
+ TELEGRAM_CHANNEL_SOUL_SYSTEM_PROMPT=Du bist ein ruhiger, klarer Mentor und hilfst dem Nutzer, die Wurzel seines Themas besser zu verstehen.
18
+ TELEGRAM_CHANNEL_SOUL_RESPONSE_SCHEMA_JSON={"format":"json","replyKey":"mentor_reply","followUpQuestionKey":"deep_question","followUpQuestionsKey":"follow_up_questions","nextStepKey":"next_step","summaryKey":"conversation_summary","userProfileKey":"user_profile","rootPatternKey":"root_pattern"}
19
+ OPENAI_API_KEY=sk-...
20
+ OPENAI_MODEL=gpt-4.1-mini
@@ -0,0 +1,20 @@
1
+ HOST=0.0.0.0
2
+ PORT=8792
3
+ VALUYA_BASE=https://pay.gorilla.build
4
+ VALUYA_TENANT_TOKEN=ttok_example
5
+ TELEGRAM_LINKS_FILE=.data/telegram-links.json
6
+ TELEGRAM_CHANNEL_APP_ID=telegram_main
7
+ TELEGRAM_CHANNEL_RESOURCE=telegram:channel:support_demo
8
+ TELEGRAM_CHANNEL_PLAN=standard
9
+ TELEGRAM_CHANNEL_BOT=support_demo_bot
10
+ TELEGRAM_CHANNEL_NAME=Premium Support
11
+ TELEGRAM_CHANNEL_INVITE_URL=https://t.me/support_demo_bot
12
+ TELEGRAM_CHANNEL_MODE=agent
13
+ TELEGRAM_CHANNEL_MEMORY_FILE=.data/telegram-support-memory.json
14
+ TELEGRAM_CHANNEL_SOUL_ID=support
15
+ TELEGRAM_CHANNEL_SOUL_NAME=Premium Support
16
+ TELEGRAM_CHANNEL_SOUL_LOCALE=de
17
+ TELEGRAM_CHANNEL_SOUL_SYSTEM_PROMPT=Du bist ein klarer Premium-Support-Assistent und hilfst dem Nutzer, das Problem ruhig und loesungsorientiert zu klaeren.
18
+ TELEGRAM_CHANNEL_SOUL_RESPONSE_SCHEMA_JSON={"format":"json","replyKey":"support_reply","followUpQuestionKey":"clarifying_question","nextStepKey":"next_step","summaryKey":"case_summary","userProfileKey":"customer_context","rootPatternKey":"issue_pattern"}
19
+ OPENAI_API_KEY=sk-...
20
+ OPENAI_MODEL=gpt-4.1-mini
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Valuya
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.
package/README.md ADDED
@@ -0,0 +1,56 @@
1
+ # @valuya/telegram-bot-channel
2
+
3
+ `@valuya/telegram-bot-channel` is the Telegram companion to `@valuya/whatsapp-bot-channel`.
4
+
5
+ It provides a first concrete gated Telegram channel example with:
6
+
7
+ - Guard-gated access
8
+ - human or agent moderation
9
+ - schema-driven soul responses
10
+ - persistent conversational memory
11
+ - app-layer link interception for `/start <token>` and `LINK <token>`
12
+ - a simple internal message server entrypoint
13
+
14
+ This package builds on `@valuya/telegram-channel-access`.
15
+
16
+ ## Main pieces
17
+
18
+ - `TelegramBotChannel`
19
+ - `TelegramBotChannelApp`
20
+ - `GuardTelegramChannelLinkResolver`
21
+ - `SchemaDrivenSoulRuntime`
22
+ - `FileSoulMemoryStore`
23
+ - `server.ts`
24
+
25
+ ## Quick Start
26
+
27
+ 1. Start from [`.env.example`](/home/colt/Software/valuya-guard/packages/telegram-bot-channel/.env.example).
28
+ 2. Set the Guard values:
29
+ - `VALUYA_BASE`
30
+ - `VALUYA_TENANT_TOKEN`
31
+ - `TELEGRAM_CHANNEL_APP_ID`
32
+ - `TELEGRAM_CHANNEL_RESOURCE`
33
+ 3. Choose:
34
+ - `TELEGRAM_CHANNEL_MODE=human`
35
+ - or `TELEGRAM_CHANNEL_MODE=agent`
36
+ 4. If `agent`, set:
37
+ - `OPENAI_API_KEY`
38
+ - `TELEGRAM_CHANNEL_SOUL_SYSTEM_PROMPT`
39
+ - optional `TELEGRAM_CHANNEL_SOUL_RESPONSE_SCHEMA_JSON`
40
+ 5. Start it from the package folder:
41
+ - `pnpm start`
42
+ 6. Or use the repo-level launcher:
43
+ - `pnpm gated-channel:launch --channel telegram --preset mentor --slug mentor_demo`
44
+
45
+ ## Typical Uses
46
+
47
+ - paid mentor or coach bot
48
+ - premium Telegram community entrypoint
49
+ - gated human Q&A channel
50
+ - agent-moderated support channel
51
+
52
+ ## Starter Template
53
+
54
+ For the reusable rollout model, see:
55
+ - [gated-channel-starter-template.md](/home/colt/Software/valuya-guard/docs/gated-channel-starter-template.md)
56
+ - [gated-channel-demo-presets.md](/home/colt/Software/valuya-guard/docs/gated-channel-demo-presets.md)
@@ -0,0 +1,19 @@
1
+ import type { TelegramBotChannel } from "../runtime/TelegramBotChannel.js";
2
+ import type { GuardTelegramChannelLinkResolver } from "../linking/GuardTelegramChannelLinkResolver.js";
3
+ export declare class TelegramBotChannelApp {
4
+ private readonly deps;
5
+ private readonly app;
6
+ constructor(deps: {
7
+ channel: TelegramBotChannel;
8
+ linkResolver: GuardTelegramChannelLinkResolver;
9
+ });
10
+ handleInboundMessage(args: {
11
+ telegramUserId: string;
12
+ body: string;
13
+ telegramUsername?: string;
14
+ locale?: string;
15
+ }): Promise<{
16
+ reply: string;
17
+ metadata?: Record<string, unknown>;
18
+ }>;
19
+ }
@@ -0,0 +1,35 @@
1
+ import { BotChannelApp, extractLinkCommandToken, extractTelegramStartToken, } from "@valuya/bot-channel-app-core";
2
+ function extractTelegramLinkToken(body) {
3
+ return extractTelegramStartToken(body) || extractLinkCommandToken(body);
4
+ }
5
+ export class TelegramBotChannelApp {
6
+ deps;
7
+ app;
8
+ constructor(deps) {
9
+ this.deps = deps;
10
+ this.app = new BotChannelApp({
11
+ channel: deps.channel,
12
+ linkResolver: deps.linkResolver,
13
+ extractLinkToken: (args) => extractTelegramLinkToken(args.body),
14
+ buildLinkArgs: (args, linkToken) => ({
15
+ telegramUserId: args.telegramUserId,
16
+ linkToken,
17
+ telegramUsername: args.telegramUsername,
18
+ }),
19
+ buildChannelArgs: (args) => args,
20
+ getChannelReply: (result) => result.reply,
21
+ getChannelMetadata: (result) => ({
22
+ kind: result.kind,
23
+ ...(result.kind === "agent" ? { soulId: result.soulId } : {}),
24
+ }),
25
+ getLinkMetadata: (result) => ({
26
+ linkAttempt: true,
27
+ linked: result.linked,
28
+ ...(result.code ? { code: result.code } : {}),
29
+ }),
30
+ });
31
+ }
32
+ async handleInboundMessage(args) {
33
+ return this.app.handleInboundMessage(args);
34
+ }
35
+ }
@@ -0,0 +1,13 @@
1
+ import type { ChannelMode, MemoryStore, SoulDefinition, SoulResponse, SoulRuntime, TelegramChannelAccessConfig } from "@valuya/telegram-channel-access";
2
+ import type { ChannelSoulDefinition as CoreChannelSoulDefinition, SoulResponseSchema as CoreSoulResponseSchema, StructuredCompletionResult as CoreStructuredCompletionResult, StructuredCompletionRunner as CoreStructuredCompletionRunner } from "@valuya/bot-channel-core";
3
+ export type SoulResponseSchema = CoreSoulResponseSchema;
4
+ export type ChannelSoulDefinition = SoulDefinition & CoreChannelSoulDefinition;
5
+ export type StructuredCompletionResult = CoreStructuredCompletionResult | SoulResponse;
6
+ export type StructuredCompletionRunner = CoreStructuredCompletionRunner;
7
+ export type TelegramBotChannelConfig = TelegramChannelAccessConfig & {
8
+ mode?: ChannelMode;
9
+ souls?: ChannelSoulDefinition[];
10
+ memoryStore?: MemoryStore;
11
+ soulRuntime?: SoulRuntime;
12
+ humanReply?: string;
13
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ export { TelegramBotChannel } from "./runtime/TelegramBotChannel.js";
2
+ export { SchemaDrivenSoulRuntime } from "./runtime/SchemaDrivenSoulRuntime.js";
3
+ export { FileSoulMemoryStore } from "./memory/FileSoulMemoryStore.js";
4
+ export { createMentorSoulDefinition } from "./souls/createMentorSoulDefinition.js";
5
+ export { TelegramBotChannelApp } from "./app/TelegramBotChannelApp.js";
6
+ export { GuardTelegramChannelLinkResolver } from "./linking/GuardTelegramChannelLinkResolver.js";
7
+ export type { ChannelSoulDefinition, SoulResponseSchema, StructuredCompletionResult, StructuredCompletionRunner, TelegramBotChannelConfig, } from "./domain/types.js";
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export { TelegramBotChannel } from "./runtime/TelegramBotChannel.js";
2
+ export { SchemaDrivenSoulRuntime } from "./runtime/SchemaDrivenSoulRuntime.js";
3
+ export { FileSoulMemoryStore } from "./memory/FileSoulMemoryStore.js";
4
+ export { createMentorSoulDefinition } from "./souls/createMentorSoulDefinition.js";
5
+ export { TelegramBotChannelApp } from "./app/TelegramBotChannelApp.js";
6
+ export { GuardTelegramChannelLinkResolver } from "./linking/GuardTelegramChannelLinkResolver.js";
@@ -0,0 +1,32 @@
1
+ import type { TelegramLinkResolver } from "@valuya/telegram-channel-access";
2
+ export declare class GuardTelegramChannelLinkResolver implements TelegramLinkResolver {
3
+ private readonly service;
4
+ constructor(args: {
5
+ baseUrl: string;
6
+ tenantToken: string;
7
+ channelAppId: string;
8
+ linksFile: string;
9
+ redeemedFrom?: string;
10
+ });
11
+ ensureLinkedForPaymentAction(args: {
12
+ telegramUserId: string;
13
+ telegramUsername?: string;
14
+ }): Promise<{
15
+ allowed: true;
16
+ subject: import("./GuardTelegramLinkService.js").LinkedValuyaSubject;
17
+ link: import("./TelegramLinkStore.js").StoredTelegramChannelLink;
18
+ } | {
19
+ allowed: false;
20
+ reply: string;
21
+ code: import("./GuardTelegramLinkService.js").LinkErrorCode;
22
+ }>;
23
+ redeemLinkToken(args: {
24
+ telegramUserId: string;
25
+ linkToken: string;
26
+ telegramUsername?: string;
27
+ }): Promise<{
28
+ linked: boolean;
29
+ code?: string;
30
+ reply: string;
31
+ }>;
32
+ }
@@ -0,0 +1,37 @@
1
+ import { GuardTelegramLinkService } from "./GuardTelegramLinkService.js";
2
+ import { TelegramLinkStore } from "./TelegramLinkStore.js";
3
+ export class GuardTelegramChannelLinkResolver {
4
+ service;
5
+ constructor(args) {
6
+ const linkStore = new TelegramLinkStore(args.linksFile);
7
+ this.service = new GuardTelegramLinkService({
8
+ baseUrl: args.baseUrl,
9
+ tenantToken: args.tenantToken,
10
+ channelAppId: args.channelAppId,
11
+ linkStore,
12
+ redeemedFrom: args.redeemedFrom || "telegram_bot_channel",
13
+ });
14
+ }
15
+ async ensureLinkedForPaymentAction(args) {
16
+ return this.service.ensureLinkedForPaymentAction(args);
17
+ }
18
+ async redeemLinkToken(args) {
19
+ const result = await this.service.redeemLinkToken(args);
20
+ if (!result.linked) {
21
+ return {
22
+ linked: false,
23
+ code: result.code,
24
+ reply: result.message,
25
+ };
26
+ }
27
+ return {
28
+ linked: true,
29
+ reply: [
30
+ "Konto erfolgreich verknuepft.",
31
+ "",
32
+ "Dein Zugang fuer diesen Telegram-Channel ist jetzt aktiv.",
33
+ "Schreib mir einfach ganz normal. Wenn der Channel agent-moderiert ist, antworte ich dir direkt hier.",
34
+ ].join("\n"),
35
+ };
36
+ }
37
+ }
@@ -0,0 +1,60 @@
1
+ import type { StoredTelegramChannelLink, TelegramLinkStore } from "./TelegramLinkStore.js";
2
+ export type LinkErrorCode = "invalid_token" | "token_expired" | "token_already_used" | "tenant_mismatch" | "not_linked" | "guard_unavailable";
3
+ export type LinkedValuyaSubject = {
4
+ type: string;
5
+ externalId: string;
6
+ subjectId?: string;
7
+ privyUserId?: string;
8
+ linkedWalletAddress?: string;
9
+ };
10
+ export type LinkResult = {
11
+ linked: true;
12
+ source: "redeem" | "resolve" | "resolve_after_redeem_failure" | "local_cache";
13
+ subject: LinkedValuyaSubject;
14
+ link: StoredTelegramChannelLink;
15
+ } | {
16
+ linked: false;
17
+ code: LinkErrorCode;
18
+ message: string;
19
+ };
20
+ type LogFn = (event: string, fields: Record<string, unknown>) => void;
21
+ export declare class GuardTelegramLinkService {
22
+ private readonly baseUrl;
23
+ private readonly tenantToken;
24
+ private readonly channelAppId;
25
+ private readonly redeemedFrom;
26
+ private readonly linkStore;
27
+ private readonly log;
28
+ constructor(args: {
29
+ baseUrl: string;
30
+ tenantToken: string;
31
+ channelAppId: string;
32
+ linkStore: TelegramLinkStore;
33
+ redeemedFrom?: string;
34
+ logger?: LogFn;
35
+ });
36
+ redeemLinkToken(args: {
37
+ telegramUserId: string;
38
+ linkToken: string;
39
+ telegramUsername?: string;
40
+ }): Promise<LinkResult>;
41
+ resolveLinkedSubject(args: {
42
+ telegramUserId: string;
43
+ telegramUsername?: string;
44
+ }): Promise<LinkResult>;
45
+ ensureLinkedForPaymentAction(args: {
46
+ telegramUserId: string;
47
+ telegramUsername?: string;
48
+ }): Promise<{
49
+ allowed: true;
50
+ subject: LinkedValuyaSubject;
51
+ link: StoredTelegramChannelLink;
52
+ } | {
53
+ allowed: false;
54
+ reply: string;
55
+ code: LinkErrorCode;
56
+ }>;
57
+ private post;
58
+ private persistLink;
59
+ }
60
+ export {};
@@ -0,0 +1,303 @@
1
+ export class GuardTelegramLinkService {
2
+ baseUrl;
3
+ tenantToken;
4
+ channelAppId;
5
+ redeemedFrom;
6
+ linkStore;
7
+ log;
8
+ constructor(args) {
9
+ this.baseUrl = args.baseUrl.replace(/\/+$/, "");
10
+ this.tenantToken = args.tenantToken;
11
+ this.channelAppId = args.channelAppId;
12
+ this.linkStore = args.linkStore;
13
+ this.redeemedFrom = args.redeemedFrom?.trim() || "telegram_bot_channel";
14
+ this.log = args.logger || (() => { });
15
+ }
16
+ async redeemLinkToken(args) {
17
+ const telegramUserId = normalizeTelegramUserId(args.telegramUserId);
18
+ const linkToken = String(args.linkToken || "").trim();
19
+ if (!linkToken) {
20
+ return {
21
+ linked: false,
22
+ code: "invalid_token",
23
+ message: "Invalid link token. Please use the latest onboarding link.",
24
+ };
25
+ }
26
+ try {
27
+ const env = await this.post("guard_telegram_redeem_response", "/api/guard/channels/telegram/redeem", {
28
+ link_token: linkToken,
29
+ channel_user_id: telegramUserId,
30
+ channel_username: cleanOptional(args.telegramUsername),
31
+ redeemed_from: this.redeemedFrom,
32
+ });
33
+ const persisted = await this.persistLink(telegramUserId, args.telegramUsername, env.link);
34
+ if (!persisted) {
35
+ return {
36
+ linked: false,
37
+ code: "guard_unavailable",
38
+ message: "Could not confirm link result. Please try again.",
39
+ };
40
+ }
41
+ return {
42
+ linked: true,
43
+ source: "redeem",
44
+ subject: toLinkedSubject(persisted),
45
+ link: persisted,
46
+ };
47
+ }
48
+ catch (error) {
49
+ const guardError = toGuardError(error);
50
+ if (guardError.code === "token_already_used") {
51
+ const resolved = await this.resolveLinkedSubject({
52
+ telegramUserId,
53
+ telegramUsername: args.telegramUsername,
54
+ });
55
+ if (resolved.linked) {
56
+ return {
57
+ linked: true,
58
+ source: "resolve_after_redeem_failure",
59
+ subject: resolved.subject,
60
+ link: resolved.link,
61
+ };
62
+ }
63
+ }
64
+ return {
65
+ linked: false,
66
+ code: guardError.code,
67
+ message: guardError.message,
68
+ };
69
+ }
70
+ }
71
+ async resolveLinkedSubject(args) {
72
+ const telegramUserId = normalizeTelegramUserId(args.telegramUserId);
73
+ try {
74
+ const env = await this.post("guard_telegram_resolve_response", "/api/guard/channels/telegram/resolve", {
75
+ channel_user_id: telegramUserId,
76
+ channel_app_id: this.channelAppId,
77
+ });
78
+ const persisted = await this.persistLink(telegramUserId, args.telegramUsername, env.link);
79
+ if (!persisted || persisted.status !== "linked") {
80
+ return {
81
+ linked: false,
82
+ code: "not_linked",
83
+ message: buildUnlinkedMessage(),
84
+ };
85
+ }
86
+ return {
87
+ linked: true,
88
+ source: "resolve",
89
+ subject: toLinkedSubject(persisted),
90
+ link: persisted,
91
+ };
92
+ }
93
+ catch (error) {
94
+ const guardError = toGuardError(error);
95
+ return {
96
+ linked: false,
97
+ code: guardError.code,
98
+ message: guardError.message,
99
+ };
100
+ }
101
+ }
102
+ async ensureLinkedForPaymentAction(args) {
103
+ const telegramUserId = normalizeTelegramUserId(args.telegramUserId);
104
+ const local = await this.linkStore.getChannelLink(telegramUserId);
105
+ if (isLinked(local)) {
106
+ return {
107
+ allowed: true,
108
+ subject: toLinkedSubject(local),
109
+ link: local,
110
+ };
111
+ }
112
+ const resolved = await this.resolveLinkedSubject({
113
+ telegramUserId,
114
+ telegramUsername: args.telegramUsername,
115
+ });
116
+ if (resolved.linked) {
117
+ return {
118
+ allowed: true,
119
+ subject: resolved.subject,
120
+ link: resolved.link,
121
+ };
122
+ }
123
+ const reply = resolved.code === "not_linked"
124
+ ? buildUnlinkedMessage()
125
+ : `I could not verify your Valuya link (${resolved.code}). Please retry.`;
126
+ return {
127
+ allowed: false,
128
+ reply,
129
+ code: resolved.code,
130
+ };
131
+ }
132
+ async post(eventName, path, body) {
133
+ const response = await fetch(`${this.baseUrl}${path}`, {
134
+ method: "POST",
135
+ headers: {
136
+ "Content-Type": "application/json",
137
+ Accept: "application/json",
138
+ Authorization: `Bearer ${this.tenantToken}`,
139
+ },
140
+ body: JSON.stringify(body),
141
+ });
142
+ const payload = (await safeParseJson(response));
143
+ this.log(eventName, {
144
+ tenant: tenantRef(this.tenantToken),
145
+ path,
146
+ status: response.status,
147
+ ok: response.ok && payload?.ok !== false,
148
+ error_code: payload?.code || payload?.error || null,
149
+ });
150
+ if (!response.ok || payload?.ok === false) {
151
+ throw toGuardApiError(response.status, payload);
152
+ }
153
+ return payload;
154
+ }
155
+ async persistLink(telegramUserId, telegramUsername, link) {
156
+ const status = String(link?.status || "").trim() || "unlinked";
157
+ const normalizedWallet = normalizeWallet(link?.wallet_address);
158
+ const subjectType = String(link?.subject?.type || "").trim();
159
+ const subjectExternalId = String(link?.subject?.external_id || "").trim();
160
+ const protocolSubjectHeader = normalizeProtocolSubjectHeader(link?.protocol_subject?.header);
161
+ const parsedProtocolSubject = parseProtocolSubjectHeader(protocolSubjectHeader);
162
+ const protocolSubjectType = cleanOptional(link?.protocol_subject?.type) || parsedProtocolSubject?.type;
163
+ const protocolSubjectId = stringOrUndefined(link?.protocol_subject?.id) || parsedProtocolSubject?.id;
164
+ const channelAppId = String(link?.channel_app_id || this.channelAppId).trim();
165
+ if (status === "linked" && (!protocolSubjectHeader || !parsedProtocolSubject || !normalizedWallet)) {
166
+ return null;
167
+ }
168
+ return this.linkStore.upsertChannelLink(telegramUserId, {
169
+ telegram_user_id: telegramUserId,
170
+ telegram_username: cleanOptional(telegramUsername),
171
+ tenant_id: stringOrUndefined(link?.tenant_id),
172
+ channel_app_id: channelAppId,
173
+ valuya_subject_id: stringOrUndefined(link?.subject_id),
174
+ valuya_subject_type: subjectType || undefined,
175
+ valuya_subject_external_id: subjectExternalId || undefined,
176
+ valuya_privy_user_id: cleanOptional(link?.privy_user_id),
177
+ valuya_linked_wallet_address: normalizedWallet,
178
+ valuya_privy_wallet_id: cleanOptional(link?.privy_wallet_id),
179
+ valuya_protocol_subject_type: protocolSubjectType,
180
+ valuya_protocol_subject_id: protocolSubjectId,
181
+ valuya_protocol_subject_header: cleanOptional(protocolSubjectHeader),
182
+ status,
183
+ linked_at: status === "linked" ? new Date().toISOString() : undefined,
184
+ });
185
+ }
186
+ }
187
+ function buildUnlinkedMessage() {
188
+ return [
189
+ "Your Telegram account is not linked to Valuya yet.",
190
+ "Please open your latest onboarding link to Telegram and run /start again.",
191
+ ].join("\n");
192
+ }
193
+ function toLinkedSubject(link) {
194
+ const protocolSubject = requireProtocolSubject(link);
195
+ return {
196
+ type: protocolSubject.type,
197
+ externalId: protocolSubject.id,
198
+ ...(link.valuya_subject_id ? { subjectId: link.valuya_subject_id } : {}),
199
+ ...(link.valuya_privy_user_id ? { privyUserId: link.valuya_privy_user_id } : {}),
200
+ ...(link.valuya_linked_wallet_address ? { linkedWalletAddress: link.valuya_linked_wallet_address } : {}),
201
+ };
202
+ }
203
+ function isLinked(link) {
204
+ if (!link)
205
+ return false;
206
+ return (link.status === "linked" &&
207
+ parseProtocolSubjectHeader(link.valuya_protocol_subject_header || "") !== null &&
208
+ Boolean(link.valuya_linked_wallet_address?.trim()));
209
+ }
210
+ class GuardApiError extends Error {
211
+ code;
212
+ constructor(code, message) {
213
+ super(message);
214
+ this.code = code;
215
+ }
216
+ }
217
+ function toGuardApiError(status, payload) {
218
+ const backendCode = `${payload?.code || payload?.error || payload?.message || ""}`.toLowerCase();
219
+ if (status === 404 || backendCode.includes("not_linked")) {
220
+ return new GuardApiError("not_linked", buildUnlinkedMessage());
221
+ }
222
+ if (backendCode.includes("invalid") || backendCode.includes("malformed")) {
223
+ return new GuardApiError("invalid_token", "Invalid link token. Please use the latest onboarding link.");
224
+ }
225
+ if (backendCode.includes("expired")) {
226
+ return new GuardApiError("token_expired", "This link token has expired. Please restart onboarding.");
227
+ }
228
+ if (backendCode.includes("already") || backendCode.includes("used")) {
229
+ return new GuardApiError("token_already_used", "This link token was already used. Open your latest onboarding link and retry.");
230
+ }
231
+ if (backendCode.includes("tenant_mismatch")) {
232
+ return new GuardApiError("tenant_mismatch", "This onboarding link belongs to a different tenant. Generate a new link for this bot and retry.");
233
+ }
234
+ return new GuardApiError("guard_unavailable", "Valuya Guard is currently unavailable. Please retry.");
235
+ }
236
+ function toGuardError(error) {
237
+ if (error instanceof GuardApiError)
238
+ return error;
239
+ const message = error instanceof Error ? error.message : String(error);
240
+ return new GuardApiError("guard_unavailable", `Valuya Guard is currently unavailable. (${message})`);
241
+ }
242
+ async function safeParseJson(response) {
243
+ const text = await response.text();
244
+ if (!text)
245
+ return {};
246
+ try {
247
+ return JSON.parse(text);
248
+ }
249
+ catch {
250
+ return { message: text };
251
+ }
252
+ }
253
+ function cleanOptional(input) {
254
+ const value = String(input || "").trim();
255
+ return value || undefined;
256
+ }
257
+ function stringOrUndefined(input) {
258
+ if (typeof input === "number" && Number.isFinite(input))
259
+ return String(input);
260
+ const value = String(input || "").trim();
261
+ return value || undefined;
262
+ }
263
+ function normalizeTelegramUserId(input) {
264
+ const value = String(input || "").trim();
265
+ if (!/^\d+$/.test(value))
266
+ throw new Error("telegram_channel_user_id_invalid");
267
+ return value;
268
+ }
269
+ function tenantRef(token) {
270
+ const v = String(token || "").trim();
271
+ return v ? v.slice(0, 12) : "unknown";
272
+ }
273
+ function normalizeWallet(input) {
274
+ const value = String(input || "").trim().toLowerCase();
275
+ if (!value)
276
+ return undefined;
277
+ if (!/^0x[a-f0-9]{40}$/.test(value))
278
+ return undefined;
279
+ return value;
280
+ }
281
+ function normalizeProtocolSubjectHeader(input) {
282
+ return String(input || "").trim();
283
+ }
284
+ function requireProtocolSubject(link) {
285
+ const header = String(link.valuya_protocol_subject_header || "").trim();
286
+ const parsed = parseProtocolSubjectHeader(header);
287
+ if (!parsed)
288
+ throw new Error("guard_protocol_subject_missing");
289
+ return parsed;
290
+ }
291
+ function parseProtocolSubjectHeader(header) {
292
+ const value = String(header || "").trim();
293
+ const idx = value.indexOf(":");
294
+ if (idx <= 0 || idx === value.length - 1)
295
+ return null;
296
+ if (value.indexOf(":", idx + 1) !== -1)
297
+ return null;
298
+ const type = value.slice(0, idx).trim();
299
+ const id = value.slice(idx + 1).trim();
300
+ if (!type || !id)
301
+ return null;
302
+ return { type, id, header: `${type}:${id}` };
303
+ }
@@ -0,0 +1,26 @@
1
+ export type StoredTelegramChannelLink = {
2
+ telegram_user_id: string;
3
+ telegram_username?: string;
4
+ tenant_id?: string;
5
+ channel_app_id: string;
6
+ valuya_subject_id?: string;
7
+ valuya_subject_type?: string;
8
+ valuya_subject_external_id?: string;
9
+ valuya_privy_user_id?: string;
10
+ valuya_linked_wallet_address?: string;
11
+ valuya_privy_wallet_id?: string;
12
+ valuya_protocol_subject_type?: string;
13
+ valuya_protocol_subject_id?: string;
14
+ valuya_protocol_subject_header?: string;
15
+ status: string;
16
+ linked_at: string;
17
+ updated_at: string;
18
+ };
19
+ export declare class TelegramLinkStore {
20
+ private readonly filePath;
21
+ constructor(filePath: string);
22
+ getChannelLink(telegramUserId: string): Promise<StoredTelegramChannelLink | null>;
23
+ upsertChannelLink(telegramUserId: string, patch: Partial<StoredTelegramChannelLink>): Promise<StoredTelegramChannelLink>;
24
+ private readAll;
25
+ private writeAll;
26
+ }
@@ -0,0 +1,60 @@
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { dirname } from "node:path";
3
+ export class TelegramLinkStore {
4
+ filePath;
5
+ constructor(filePath) {
6
+ this.filePath = filePath;
7
+ }
8
+ async getChannelLink(telegramUserId) {
9
+ const state = await this.readAll();
10
+ return state.channelLinks[telegramUserId] ?? null;
11
+ }
12
+ async upsertChannelLink(telegramUserId, patch) {
13
+ const state = await this.readAll();
14
+ const current = state.channelLinks[telegramUserId];
15
+ const now = new Date().toISOString();
16
+ const nextStatus = patch.status ?? current?.status ?? "linked";
17
+ const linkedAt = patch.linked_at ?? current?.linked_at ?? now;
18
+ const merged = {
19
+ telegram_user_id: patch.telegram_user_id ?? current?.telegram_user_id ?? telegramUserId,
20
+ telegram_username: patch.telegram_username ?? current?.telegram_username,
21
+ tenant_id: patch.tenant_id ?? current?.tenant_id,
22
+ channel_app_id: patch.channel_app_id ?? current?.channel_app_id ?? "telegram_main",
23
+ valuya_subject_id: patch.valuya_subject_id ?? current?.valuya_subject_id,
24
+ valuya_subject_type: patch.valuya_subject_type ?? current?.valuya_subject_type,
25
+ valuya_subject_external_id: patch.valuya_subject_external_id ?? current?.valuya_subject_external_id,
26
+ valuya_privy_user_id: patch.valuya_privy_user_id ?? current?.valuya_privy_user_id,
27
+ valuya_linked_wallet_address: patch.valuya_linked_wallet_address ?? current?.valuya_linked_wallet_address,
28
+ valuya_privy_wallet_id: patch.valuya_privy_wallet_id ?? current?.valuya_privy_wallet_id,
29
+ valuya_protocol_subject_type: patch.valuya_protocol_subject_type ?? current?.valuya_protocol_subject_type,
30
+ valuya_protocol_subject_id: patch.valuya_protocol_subject_id ?? current?.valuya_protocol_subject_id,
31
+ valuya_protocol_subject_header: patch.valuya_protocol_subject_header ?? current?.valuya_protocol_subject_header,
32
+ status: nextStatus,
33
+ linked_at: linkedAt,
34
+ updated_at: now,
35
+ };
36
+ if (!merged.telegram_user_id.trim())
37
+ throw new Error("state_telegram_user_id_required");
38
+ if (!merged.channel_app_id.trim())
39
+ throw new Error("state_channel_app_id_required");
40
+ if (!merged.status.trim())
41
+ throw new Error("state_channel_link_status_required");
42
+ state.channelLinks[telegramUserId] = merged;
43
+ await this.writeAll(state);
44
+ return merged;
45
+ }
46
+ async readAll() {
47
+ try {
48
+ const raw = await readFile(this.filePath, "utf8");
49
+ const parsed = JSON.parse(raw);
50
+ return { channelLinks: parsed?.channelLinks ?? {} };
51
+ }
52
+ catch {
53
+ return { channelLinks: {} };
54
+ }
55
+ }
56
+ async writeAll(state) {
57
+ await mkdir(dirname(this.filePath), { recursive: true });
58
+ await writeFile(this.filePath, JSON.stringify(state, null, 2), "utf8");
59
+ }
60
+ }
@@ -0,0 +1 @@
1
+ export { FileSoulMemoryStore } from "@valuya/bot-channel-core";
@@ -0,0 +1 @@
1
+ export { FileSoulMemoryStore } from "@valuya/bot-channel-core";
@@ -0,0 +1 @@
1
+ export { SchemaDrivenSoulRuntime } from "@valuya/bot-channel-core";
@@ -0,0 +1 @@
1
+ export { SchemaDrivenSoulRuntime } from "@valuya/bot-channel-core";
@@ -0,0 +1,18 @@
1
+ import { TelegramChannelAccessService, type MemoryStore, type SoulRuntime, type TelegramChannelRuntimeResult } from "@valuya/telegram-channel-access";
2
+ import type { TelegramBotChannelConfig } from "../domain/types.js";
3
+ export declare class TelegramBotChannel {
4
+ private readonly config;
5
+ private readonly runtime;
6
+ private readonly humanReply?;
7
+ constructor(config: TelegramBotChannelConfig, deps?: {
8
+ access?: TelegramChannelAccessService;
9
+ memoryStore?: MemoryStore;
10
+ soulRuntime?: SoulRuntime;
11
+ });
12
+ handleMessage(args: {
13
+ telegramUserId: string;
14
+ body: string;
15
+ telegramUsername?: string;
16
+ locale?: string;
17
+ }): Promise<TelegramChannelRuntimeResult>;
18
+ }
@@ -0,0 +1,30 @@
1
+ import { InMemoryMemoryStore, TelegramChannelAccessService, TelegramChannelRuntime, } from "@valuya/telegram-channel-access";
2
+ export class TelegramBotChannel {
3
+ config;
4
+ runtime;
5
+ humanReply;
6
+ constructor(config, deps) {
7
+ this.config = config;
8
+ const access = deps?.access || new TelegramChannelAccessService(config);
9
+ const memoryStore = deps?.memoryStore || config.memoryStore || new InMemoryMemoryStore();
10
+ const soulRuntime = deps?.soulRuntime || config.soulRuntime;
11
+ this.runtime = new TelegramChannelRuntime({
12
+ access,
13
+ mode: config.mode,
14
+ memoryStore,
15
+ soulRuntime,
16
+ souls: config.souls,
17
+ });
18
+ this.humanReply = config.humanReply;
19
+ }
20
+ async handleMessage(args) {
21
+ const result = await this.runtime.handleMessage(args);
22
+ if (result.kind === "human" && this.humanReply) {
23
+ return {
24
+ ...result,
25
+ reply: this.humanReply,
26
+ };
27
+ }
28
+ return result;
29
+ }
30
+ }
@@ -0,0 +1 @@
1
+ export {};
package/dist/server.js ADDED
@@ -0,0 +1,123 @@
1
+ import { createServer } from "node:http";
2
+ import { createConfiguredSoul, createOptionalOpenAISoulRuntime, normalizeChannelMode, } from "@valuya/bot-channel-bootstrap-core";
3
+ import { createOpenAIResponsesRunner, getRequestPath, handleInternalJsonMessage, requiredEnv, } from "@valuya/bot-channel-server-core";
4
+ import { TelegramBotChannelApp } from "./app/TelegramBotChannelApp.js";
5
+ import { GuardTelegramChannelLinkResolver } from "./linking/GuardTelegramChannelLinkResolver.js";
6
+ import { FileSoulMemoryStore } from "./memory/FileSoulMemoryStore.js";
7
+ import { SchemaDrivenSoulRuntime } from "./runtime/SchemaDrivenSoulRuntime.js";
8
+ import { TelegramBotChannel } from "./runtime/TelegramBotChannel.js";
9
+ import { createMentorSoulDefinition } from "./souls/createMentorSoulDefinition.js";
10
+ const PORT = Number(process.env.PORT || 8792);
11
+ const HOST = process.env.HOST?.trim() || "0.0.0.0";
12
+ const LINKS_FILE = requiredEnv("TELEGRAM_LINKS_FILE");
13
+ const VALUYA_BASE = requiredEnv("VALUYA_BASE").replace(/\/+$/, "");
14
+ const VALUYA_TENANT_TOKEN = requiredEnv("VALUYA_TENANT_TOKEN");
15
+ const TELEGRAM_CHANNEL_APP_ID = requiredEnv("TELEGRAM_CHANNEL_APP_ID");
16
+ const CHANNEL_PLAN = process.env.TELEGRAM_CHANNEL_PLAN?.trim() || "standard";
17
+ const CHANNEL_RESOURCE = process.env.TELEGRAM_CHANNEL_RESOURCE?.trim() || "";
18
+ const CHANNEL_BOT = process.env.TELEGRAM_CHANNEL_BOT?.trim() || "";
19
+ const CHANNEL_NAME = process.env.TELEGRAM_CHANNEL_NAME?.trim() || "";
20
+ const CHANNEL_INVITE_URL = process.env.TELEGRAM_CHANNEL_INVITE_URL?.trim() || "";
21
+ const SOUL_MEMORY_FILE = process.env.TELEGRAM_CHANNEL_MEMORY_FILE?.trim() || ".data/telegram-channel-memory.json";
22
+ const SOUL_ID = process.env.TELEGRAM_CHANNEL_SOUL_ID?.trim() || "mentor";
23
+ const SOUL_NAME = process.env.TELEGRAM_CHANNEL_SOUL_NAME?.trim() || "Mentor";
24
+ const SOUL_LOCALE = process.env.TELEGRAM_CHANNEL_SOUL_LOCALE?.trim() || "de";
25
+ const SOUL_SYSTEM_PROMPT = process.env.TELEGRAM_CHANNEL_SOUL_SYSTEM_PROMPT?.trim() || "";
26
+ const SOUL_RESPONSE_SCHEMA_JSON = process.env.TELEGRAM_CHANNEL_SOUL_RESPONSE_SCHEMA_JSON?.trim() || "";
27
+ const CHANNEL_MODE = normalizeChannelMode({
28
+ value: process.env.TELEGRAM_CHANNEL_MODE,
29
+ soulId: SOUL_ID,
30
+ });
31
+ const OPENAI_API_KEY = process.env.OPENAI_API_KEY?.trim() || "";
32
+ const OPENAI_MODEL = process.env.OPENAI_MODEL?.trim() || "gpt-4.1-mini";
33
+ const HUMAN_REPLY = process.env.TELEGRAM_CHANNEL_HUMAN_REPLY?.trim() || "";
34
+ const INTERNAL_API_TOKEN = process.env.TELEGRAM_CHANNEL_INTERNAL_API_TOKEN?.trim() || "";
35
+ const linkResolver = new GuardTelegramChannelLinkResolver({
36
+ baseUrl: VALUYA_BASE,
37
+ tenantToken: VALUYA_TENANT_TOKEN,
38
+ channelAppId: TELEGRAM_CHANNEL_APP_ID,
39
+ linksFile: LINKS_FILE,
40
+ });
41
+ const souls = CHANNEL_MODE.kind === "agent"
42
+ ? [createConfiguredSoul({
43
+ baseSoul: createMentorSoulDefinition({
44
+ id: SOUL_ID,
45
+ name: SOUL_NAME,
46
+ locale: SOUL_LOCALE,
47
+ ...(SOUL_SYSTEM_PROMPT ? { systemPrompt: SOUL_SYSTEM_PROMPT } : {}),
48
+ }),
49
+ responseSchemaJson: SOUL_RESPONSE_SCHEMA_JSON,
50
+ })]
51
+ : [];
52
+ const channel = new TelegramBotChannel({
53
+ baseUrl: VALUYA_BASE,
54
+ tenantToken: VALUYA_TENANT_TOKEN,
55
+ channelResource: CHANNEL_RESOURCE || undefined,
56
+ channelBot: CHANNEL_BOT || undefined,
57
+ channelName: CHANNEL_NAME || undefined,
58
+ channelPlan: CHANNEL_PLAN,
59
+ channelInviteUrl: CHANNEL_INVITE_URL || undefined,
60
+ mode: CHANNEL_MODE,
61
+ souls,
62
+ memoryStore: new FileSoulMemoryStore(SOUL_MEMORY_FILE),
63
+ soulRuntime: createOptionalOpenAISoulRuntime({
64
+ mode: CHANNEL_MODE,
65
+ apiKey: OPENAI_API_KEY,
66
+ model: OPENAI_MODEL,
67
+ createRunner: createOpenAICompletionRunner,
68
+ createRuntime: (args) => new SchemaDrivenSoulRuntime(args),
69
+ }),
70
+ humanReply: HUMAN_REPLY || undefined,
71
+ linking: linkResolver,
72
+ });
73
+ const app = new TelegramBotChannelApp({
74
+ channel,
75
+ linkResolver,
76
+ });
77
+ const server = createServer(async (req, res) => {
78
+ try {
79
+ const requestPath = getRequestPath(req.url);
80
+ if (req.method !== "POST" || requestPath !== "/internal/message") {
81
+ res.writeHead(404, { "Content-Type": "application/json" });
82
+ res.end(JSON.stringify({ ok: false, error: "not_found" }));
83
+ return;
84
+ }
85
+ const response = await handleInternalJsonMessage({
86
+ req,
87
+ internalApiToken: INTERNAL_API_TOKEN || undefined,
88
+ providedToken: String(req.headers["x-agent-internal-token"] || "").trim() || null,
89
+ onMessage: async (body) => app.handleInboundMessage({
90
+ telegramUserId: normalizeTelegramUserId(String(body.telegramUserId || "")),
91
+ body: String(body.body || ""),
92
+ telegramUsername: typeof body.telegramUsername === "string" ? body.telegramUsername : undefined,
93
+ locale: typeof body.locale === "string" ? body.locale : undefined,
94
+ }),
95
+ });
96
+ res.writeHead(response.status, response.headers);
97
+ res.end(response.body);
98
+ }
99
+ catch (error) {
100
+ const message = error instanceof Error ? error.message : String(error);
101
+ console.error(JSON.stringify({ level: "error", event: "telegram_bot_channel_error", message }));
102
+ res.writeHead(500, { "Content-Type": "application/json" });
103
+ res.end(JSON.stringify({ ok: false, error: "internal_error", message: "Temporary error. Please retry." }));
104
+ }
105
+ });
106
+ server.listen(PORT, HOST, () => {
107
+ console.log(JSON.stringify({
108
+ level: "info",
109
+ event: "telegram_bot_channel_started",
110
+ host: HOST,
111
+ port: PORT,
112
+ internalPath: "/internal/message",
113
+ channelMode: CHANNEL_MODE.kind,
114
+ resource: CHANNEL_RESOURCE || null,
115
+ plan: CHANNEL_PLAN,
116
+ }));
117
+ });
118
+ function createOpenAICompletionRunner(args) {
119
+ return createOpenAIResponsesRunner(args);
120
+ }
121
+ function normalizeTelegramUserId(raw) {
122
+ return String(raw || "").trim();
123
+ }
@@ -0,0 +1 @@
1
+ export { createMentorSoulDefinition } from "@valuya/bot-channel-core";
@@ -0,0 +1 @@
1
+ export { createMentorSoulDefinition } from "@valuya/bot-channel-core";
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@valuya/telegram-bot-channel",
3
+ "version": "0.2.0-beta.1",
4
+ "description": "Gated Telegram channel example with human or soul-moderated runtime on top of Valuya Guard",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md",
17
+ ".env.example",
18
+ ".env.mentor.example",
19
+ ".env.support.example",
20
+ ".env.concierge.example"
21
+ ],
22
+ "dependencies": {
23
+ "@valuya/bot-channel-app-core": "^0.2.0-beta.1",
24
+ "@valuya/bot-channel-bootstrap-core": "^0.2.0-beta.1",
25
+ "@valuya/bot-channel-core": "^0.2.0-beta.1",
26
+ "@valuya/bot-channel-server-core": "^0.2.0-beta.1",
27
+ "@valuya/telegram-channel-access": "^0.2.0-beta.1"
28
+ },
29
+ "devDependencies": {
30
+ "@types/node": "^25.0.10"
31
+ },
32
+ "publishConfig": {
33
+ "access": "public",
34
+ "tag": "next"
35
+ },
36
+ "license": "MIT",
37
+ "scripts": {
38
+ "build": "rm -rf dist && tsc -p tsconfig.json",
39
+ "start": "pnpm run build && node dist/server.js",
40
+ "test": "pnpm run build && node --test dist/*.test.js dist/app/*.test.js"
41
+ }
42
+ }