@rubytech/create-maxy-code 0.1.550 → 0.1.551
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/payload/platform/lib/agent-dispatch-rule/dist/index.d.ts +22 -8
- package/payload/platform/lib/agent-dispatch-rule/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/agent-dispatch-rule/dist/index.js +47 -13
- package/payload/platform/lib/agent-dispatch-rule/dist/index.js.map +1 -1
- package/payload/platform/lib/agent-dispatch-rule/src/__tests__/rule.test.ts +68 -9
- package/payload/platform/lib/agent-dispatch-rule/src/index.ts +60 -16
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +3 -3
- package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +4 -0
- package/payload/platform/plugins/docs/references/admin-ui.md +2 -2
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/agent-dispatch.test.js +2 -2
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/agent-dispatch.test.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/connector-sync-provision.test.js +2 -2
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/connector-sync-provision.test.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/connector-sync-provision.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/connector-sync-provision.js +21 -8
- package/payload/platform/plugins/scheduling/mcp/dist/lib/connector-sync-provision.js.map +1 -1
- package/payload/platform/plugins/telegram/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/telegram/PLUGIN.md +6 -4
- package/payload/platform/plugins/telegram/mcp/dist/index.js +62 -38
- package/payload/platform/plugins/telegram/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/telegram/mcp/dist/lib/account-token.d.ts +18 -5
- package/payload/platform/plugins/telegram/mcp/dist/lib/account-token.d.ts.map +1 -1
- package/payload/platform/plugins/telegram/mcp/dist/lib/account-token.js +34 -6
- package/payload/platform/plugins/telegram/mcp/dist/lib/account-token.js.map +1 -1
- package/payload/platform/plugins/telegram/mcp/dist/lib/account-write.d.ts +25 -8
- package/payload/platform/plugins/telegram/mcp/dist/lib/account-write.d.ts.map +1 -1
- package/payload/platform/plugins/telegram/mcp/dist/lib/account-write.js +28 -16
- package/payload/platform/plugins/telegram/mcp/dist/lib/account-write.js.map +1 -1
- package/payload/platform/plugins/telegram/mcp/dist/lib/agent-active.d.ts +12 -0
- package/payload/platform/plugins/telegram/mcp/dist/lib/agent-active.d.ts.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/lib/agent-active.js +26 -0
- package/payload/platform/plugins/telegram/mcp/dist/lib/agent-active.js.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/lib/secret-path.d.ts +16 -13
- package/payload/platform/plugins/telegram/mcp/dist/lib/secret-path.d.ts.map +1 -1
- package/payload/platform/plugins/telegram/mcp/dist/lib/secret-path.js +19 -23
- package/payload/platform/plugins/telegram/mcp/dist/lib/secret-path.js.map +1 -1
- package/payload/platform/plugins/telegram/mcp/dist/lib/webhook-url.d.ts +9 -7
- package/payload/platform/plugins/telegram/mcp/dist/lib/webhook-url.d.ts.map +1 -1
- package/payload/platform/plugins/telegram/mcp/dist/lib/webhook-url.js +11 -8
- package/payload/platform/plugins/telegram/mcp/dist/lib/webhook-url.js.map +1 -1
- package/payload/platform/plugins/telegram/mcp/dist/tools/message.d.ts +4 -0
- package/payload/platform/plugins/telegram/mcp/dist/tools/message.d.ts.map +1 -1
- package/payload/platform/plugins/telegram/mcp/dist/tools/message.js +13 -3
- package/payload/platform/plugins/telegram/mcp/dist/tools/message.js.map +1 -1
- package/payload/platform/plugins/telegram/references/setup-guide.md +16 -8
- package/payload/platform/plugins/telegram/skills/configure/SKILL.md +30 -32
- package/payload/server/server.js +510 -240
|
@@ -4,11 +4,28 @@ import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
4
4
|
* with platform/ui/server/public-host-gate.ts PUBLIC_ALLOWED_PREFIXES — a unit
|
|
5
5
|
* test asserts they agree. The official plugin's /channels/telegram is NOT this. */
|
|
6
6
|
export const CANONICAL_TELEGRAM_PREFIX = "/api/telegram/";
|
|
7
|
-
/**
|
|
7
|
+
/** Task 2368 — duplicated from platform/ui/app/lib/telegram/bot-registry.ts.
|
|
8
|
+
* The plugin cannot import from platform/ui/app, the same boundary that makes
|
|
9
|
+
* configDirName and the secret path duplicates; telegram-account-write.test.ts
|
|
10
|
+
* asserts the two derivations agree. */
|
|
11
|
+
export function botIdFromToken(token) {
|
|
12
|
+
const i = token.indexOf(":");
|
|
13
|
+
if (i <= 0)
|
|
14
|
+
return null;
|
|
15
|
+
const id = token.slice(0, i);
|
|
16
|
+
return /^\d+$/.test(id) ? id : null;
|
|
17
|
+
}
|
|
18
|
+
/** Persist one bot entry into account.json's `telegram.bots` array, replacing
|
|
19
|
+
* any entry with the same id and leaving every other entry byte-identical.
|
|
20
|
+
*
|
|
21
|
+
* Task 2368 — registering, re-pointing or removing one bot cannot disturb
|
|
22
|
+
* another, which the two-slot shape could not promise: a second admin bot
|
|
23
|
+
* overwrote the first's token AND its adminUsers in one write.
|
|
24
|
+
*
|
|
8
25
|
* Brand-isolated: account.json lives under the install's data/accounts. Returns
|
|
9
|
-
* the resolved account.json directory so the caller can log + assert it is
|
|
10
|
-
* the brand install (never ~/.claude). */
|
|
11
|
-
export function
|
|
26
|
+
* the resolved account.json directory so the caller can log + assert it is
|
|
27
|
+
* under the brand install (never ~/.claude). */
|
|
28
|
+
export function persistTelegramBot(platformRoot, accountId, entry) {
|
|
12
29
|
const accountsDir = resolve(platformRoot, "..", "data", "accounts");
|
|
13
30
|
const dir = join(accountsDir, accountId);
|
|
14
31
|
const configPath = join(dir, "account.json");
|
|
@@ -33,18 +50,13 @@ export function persistTelegramConfig(platformRoot, accountId, input) {
|
|
|
33
50
|
return { ok: false, error: `account.json parse failed: ${e.message}` };
|
|
34
51
|
}
|
|
35
52
|
const telegram = config.telegram ?? {};
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (input.dmPolicy)
|
|
44
|
-
telegram.dmPolicy = input.dmPolicy;
|
|
45
|
-
if (input.allowFrom)
|
|
46
|
-
telegram.allowFrom = input.allowFrom;
|
|
47
|
-
}
|
|
53
|
+
const bots = Array.isArray(telegram.bots) ? telegram.bots : [];
|
|
54
|
+
const at = bots.findIndex((b) => b && typeof b === "object" && b.id === entry.id);
|
|
55
|
+
if (at >= 0)
|
|
56
|
+
bots[at] = entry;
|
|
57
|
+
else
|
|
58
|
+
bots.push(entry);
|
|
59
|
+
telegram.bots = bots;
|
|
48
60
|
config.telegram = telegram;
|
|
49
61
|
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
50
62
|
return { ok: true, dir };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"account-write.js","sourceRoot":"","sources":["../../src/lib/account-write.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAElE;;qFAEqF;AACrF,MAAM,CAAC,MAAM,yBAAyB,GAAG,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"account-write.js","sourceRoot":"","sources":["../../src/lib/account-write.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAElE;;qFAEqF;AACrF,MAAM,CAAC,MAAM,yBAAyB,GAAG,gBAAgB,CAAC;AAE1D;;;yCAGyC;AACzC,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,MAAM,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC7B,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACxB,MAAM,EAAE,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC7B,OAAO,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AACtC,CAAC;AAaD;;;;;;;;;iDASiD;AACjD,MAAM,UAAU,kBAAkB,CAChC,YAAoB,EACpB,SAAiB,EACjB,KAA4B;IAE5B,MAAM,WAAW,GAAG,OAAO,CAAC,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC;IACpE,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;IACzC,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;IAC7C,0EAA0E;IAC1E,4EAA4E;IAC5E,8EAA8E;IAC9E,+CAA+C;IAC/C,MAAM,SAAS,GAAG,OAAO,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;IAC9C,MAAM,GAAG,GAAG,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9C,MAAM,UAAU,GAAG,GAAG,KAAK,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;IAC7E,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,gBAAgB,EAAE,GAAG,EAAE,CAAC;IACrD,CAAC;IACD,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,6BAA6B,UAAU,EAAE,EAAE,CAAC;IACzE,CAAC;IACD,IAAI,MAA+B,CAAC;IACpC,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC;IACzD,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,8BAA+B,CAAW,CAAC,OAAO,EAAE,EAAE,CAAC;IACpF,CAAC;IACD,MAAM,QAAQ,GAAI,MAAM,CAAC,QAAoC,IAAI,EAAE,CAAC;IACpE,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAE,QAAQ,CAAC,IAAgC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC5F,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,EAAE,KAAK,KAAK,CAAC,EAAE,CAAC,CAAC;IAClF,IAAI,EAAE,IAAI,CAAC;QAAE,IAAI,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC;;QACzB,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACtB,QAAQ,CAAC,IAAI,GAAG,IAAI,CAAC;IACrB,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC;IAC3E,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;AAC3B,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** The account's own directory under the brand install. */
|
|
2
|
+
export declare function accountDirFor(platformRoot: string, accountId: string): string;
|
|
3
|
+
/** Task 2368 — is `slug` an ACTIVE agent on this account?
|
|
4
|
+
*
|
|
5
|
+
* Duplicated from platform/ui/app/lib/claude-agent/account.ts
|
|
6
|
+
* `isActiveAgentSlug`, which the plugin cannot import across the
|
|
7
|
+
* plugin/platform boundary. Same predicate: status must be exactly "active",
|
|
8
|
+
* and a missing agent dir or config resolves to no status, so this also
|
|
9
|
+
* rejects a nonexistent slug. telegram-agent-active.test.ts asserts the two
|
|
10
|
+
* agree. A bot may only be paired to an agent that can actually answer. */
|
|
11
|
+
export declare function isActiveAgentSlug(accountDir: string, slug: string): boolean;
|
|
12
|
+
//# sourceMappingURL=agent-active.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-active.d.ts","sourceRoot":"","sources":["../../src/lib/agent-active.ts"],"names":[],"mappings":"AAGA,2DAA2D;AAC3D,wBAAgB,aAAa,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAE7E;AAED;;;;;;;4EAO4E;AAC5E,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAQ3E"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { resolve, join } from "node:path";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
/** The account's own directory under the brand install. */
|
|
4
|
+
export function accountDirFor(platformRoot, accountId) {
|
|
5
|
+
return join(resolve(platformRoot, "..", "data", "accounts"), accountId);
|
|
6
|
+
}
|
|
7
|
+
/** Task 2368 — is `slug` an ACTIVE agent on this account?
|
|
8
|
+
*
|
|
9
|
+
* Duplicated from platform/ui/app/lib/claude-agent/account.ts
|
|
10
|
+
* `isActiveAgentSlug`, which the plugin cannot import across the
|
|
11
|
+
* plugin/platform boundary. Same predicate: status must be exactly "active",
|
|
12
|
+
* and a missing agent dir or config resolves to no status, so this also
|
|
13
|
+
* rejects a nonexistent slug. telegram-agent-active.test.ts asserts the two
|
|
14
|
+
* agree. A bot may only be paired to an agent that can actually answer. */
|
|
15
|
+
export function isActiveAgentSlug(accountDir, slug) {
|
|
16
|
+
const configPath = join(accountDir, "agents", slug, "config.json");
|
|
17
|
+
if (!existsSync(configPath))
|
|
18
|
+
return false;
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(readFileSync(configPath, "utf-8")).status === "active";
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=agent-active.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-active.js","sourceRoot":"","sources":["../../src/lib/agent-active.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEnD,2DAA2D;AAC3D,MAAM,UAAU,aAAa,CAAC,YAAoB,EAAE,SAAiB;IACnE,OAAO,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,SAAS,CAAC,CAAC;AAC1E,CAAC;AAED;;;;;;;4EAO4E;AAC5E,MAAM,UAAU,iBAAiB,CAAC,UAAkB,EAAE,IAAY;IAChE,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,EAAE,QAAQ,EAAE,IAAI,EAAE,aAAa,CAAC,CAAC;IACnE,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,OAAO,KAAK,CAAC;IAC1C,IAAI,CAAC;QACH,OAAQ,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAA0B,CAAC,MAAM,KAAK,QAAQ,CAAC;IACrG,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
export declare function configDirName(): string;
|
|
2
|
-
/**
|
|
3
|
-
*
|
|
2
|
+
/** The directory holding one secret file per registered bot. Created mode 700
|
|
3
|
+
* by the register tool; the secrets themselves are 600. */
|
|
4
|
+
export declare function telegramSecretsDir(): string;
|
|
5
|
+
/** Task 2368 — the webhook secret path, keyed on the bot's own Telegram id.
|
|
4
6
|
*
|
|
5
|
-
* Task 2365
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
|
|
7
|
+
* Task 2365 keyed on (account, botType), which cannot express N bots in one
|
|
8
|
+
* account: a second admin bot in one account would overwrite the first's
|
|
9
|
+
* secret and break it with no signal. The bot id can, and it is the same
|
|
10
|
+
* value the registered URL names, so the file and the URL cannot disagree.
|
|
11
|
+
* The reader (platform/ui/server/routes/telegram.ts secretPath) builds the
|
|
12
|
+
* same string; the duplication across the plugin/platform boundary is
|
|
13
|
+
* deliberate and ui/server/__tests__/telegram-secret-path.test.ts asserts the
|
|
14
|
+
* two agree.
|
|
15
|
+
*
|
|
16
|
+
* `botId` is digits by construction (botIdFromToken rejects anything else), so
|
|
17
|
+
* it cannot contribute a traversal. */
|
|
18
|
+
export declare function resolveSecretFilePath(botId: string): string;
|
|
16
19
|
//# sourceMappingURL=secret-path.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"secret-path.d.ts","sourceRoot":"","sources":["../../src/lib/secret-path.ts"],"names":[],"mappings":"AAYA,wBAAgB,aAAa,IAAI,MAAM,CAWtC;AAED
|
|
1
|
+
{"version":3,"file":"secret-path.d.ts","sourceRoot":"","sources":["../../src/lib/secret-path.ts"],"names":[],"mappings":"AAYA,wBAAgB,aAAa,IAAI,MAAM,CAWtC;AAED;4DAC4D;AAC5D,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED;;;;;;;;;;;;wCAYwC;AACxC,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE3D"}
|
|
@@ -22,29 +22,25 @@ export function configDirName() {
|
|
|
22
22
|
}
|
|
23
23
|
return ".maxy";
|
|
24
24
|
}
|
|
25
|
-
/**
|
|
26
|
-
*
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
* per bot type, so a second account registering an admin bot overwrote the
|
|
30
|
-
* first account's secret and broke it with no signal. The reader
|
|
31
|
-
* (platform/ui/server/routes/telegram.ts secretPath) builds the same string;
|
|
32
|
-
* the duplication across the plugin/platform boundary is deliberate and
|
|
33
|
-
* ui/server/__tests__/telegram-secret-path.test.ts asserts the two agree. */
|
|
34
|
-
export function resolveSecretFilePath(botType, accountId) {
|
|
35
|
-
const maxyDir = resolve(homedir(), configDirName());
|
|
36
|
-
const stem = botType === "admin"
|
|
37
|
-
? ".telegram-admin-webhook-secret"
|
|
38
|
-
: ".telegram-webhook-secret";
|
|
39
|
-
return resolve(maxyDir, `${stem}.${accountId}`);
|
|
25
|
+
/** The directory holding one secret file per registered bot. Created mode 700
|
|
26
|
+
* by the register tool; the secrets themselves are 600. */
|
|
27
|
+
export function telegramSecretsDir() {
|
|
28
|
+
return resolve(homedir(), configDirName(), "telegram-secrets");
|
|
40
29
|
}
|
|
41
|
-
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
30
|
+
/** Task 2368 — the webhook secret path, keyed on the bot's own Telegram id.
|
|
31
|
+
*
|
|
32
|
+
* Task 2365 keyed on (account, botType), which cannot express N bots in one
|
|
33
|
+
* account: a second admin bot in one account would overwrite the first's
|
|
34
|
+
* secret and break it with no signal. The bot id can, and it is the same
|
|
35
|
+
* value the registered URL names, so the file and the URL cannot disagree.
|
|
36
|
+
* The reader (platform/ui/server/routes/telegram.ts secretPath) builds the
|
|
37
|
+
* same string; the duplication across the plugin/platform boundary is
|
|
38
|
+
* deliberate and ui/server/__tests__/telegram-secret-path.test.ts asserts the
|
|
39
|
+
* two agree.
|
|
40
|
+
*
|
|
41
|
+
* `botId` is digits by construction (botIdFromToken rejects anything else), so
|
|
42
|
+
* it cannot contribute a traversal. */
|
|
43
|
+
export function resolveSecretFilePath(botId) {
|
|
44
|
+
return resolve(telegramSecretsDir(), botId);
|
|
49
45
|
}
|
|
50
46
|
//# sourceMappingURL=secret-path.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"secret-path.js","sourceRoot":"","sources":["../../src/lib/secret-path.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEnD,2EAA2E;AAC3E,uEAAuE;AACvE,mCAAmC;AACnC,EAAE;AACF,wDAAwD;AACxD,6EAA6E;AAC7E,0EAA0E;AAC1E,kEAAkE;AAClE,MAAM,UAAU,aAAa;IAC3B,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;IAC/C,IAAI,YAAY,EAAE,CAAC;QACjB,MAAM,SAAS,GAAG,IAAI,CAAC,YAAY,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAC;QAC7D,IAAI,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC1B,IAAI,CAAC;gBACH,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,IAAI,OAAO,CAAC;YAC3E,CAAC;YAAC,MAAM,CAAC,CAAC,wBAAwB,CAAC,CAAC;QACtC,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"secret-path.js","sourceRoot":"","sources":["../../src/lib/secret-path.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEnD,2EAA2E;AAC3E,uEAAuE;AACvE,mCAAmC;AACnC,EAAE;AACF,wDAAwD;AACxD,6EAA6E;AAC7E,0EAA0E;AAC1E,kEAAkE;AAClE,MAAM,UAAU,aAAa;IAC3B,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;IAC/C,IAAI,YAAY,EAAE,CAAC;QACjB,MAAM,SAAS,GAAG,IAAI,CAAC,YAAY,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAC;QAC7D,IAAI,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC1B,IAAI,CAAC;gBACH,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,IAAI,OAAO,CAAC;YAC3E,CAAC;YAAC,MAAM,CAAC,CAAC,wBAAwB,CAAC,CAAC;QACtC,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;4DAC4D;AAC5D,MAAM,UAAU,kBAAkB;IAChC,OAAO,OAAO,CAAC,OAAO,EAAE,EAAE,aAAa,EAAE,EAAE,kBAAkB,CAAC,CAAC;AACjE,CAAC;AAED;;;;;;;;;;;;wCAYwC;AACxC,MAAM,UAAU,qBAAqB,CAAC,KAAa;IACjD,OAAO,OAAO,CAAC,kBAAkB,EAAE,EAAE,KAAK,CAAC,CAAC;AAC9C,CAAC"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
/** Task
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* is overwritten
|
|
6
|
-
*
|
|
7
|
-
|
|
1
|
+
/** Task 2368 — stamp the bot's own id onto the webhook URL.
|
|
2
|
+
*
|
|
3
|
+
* It supersedes Task 2365's `account=`: a bot id already determines its
|
|
4
|
+
* account, and carrying both would be two authorities for one fact. Any `bot`
|
|
5
|
+
* the caller supplied is overwritten and any `account` is removed — the token
|
|
6
|
+
* this registration carries is the only authority on which bot it is for, and
|
|
7
|
+
* an operator transcribing either from a skill file is the failure mode this
|
|
8
|
+
* builder exists to remove. */
|
|
9
|
+
export declare function buildWebhookUrl(webhookUrl: string, botId: string): string;
|
|
8
10
|
//# sourceMappingURL=webhook-url.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhook-url.d.ts","sourceRoot":"","sources":["../../src/lib/webhook-url.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"webhook-url.d.ts","sourceRoot":"","sources":["../../src/lib/webhook-url.ts"],"names":[],"mappings":"AAAA;;;;;;;gCAOgC;AAChC,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAKzE"}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
/** Task
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* is overwritten
|
|
6
|
-
*
|
|
7
|
-
|
|
1
|
+
/** Task 2368 — stamp the bot's own id onto the webhook URL.
|
|
2
|
+
*
|
|
3
|
+
* It supersedes Task 2365's `account=`: a bot id already determines its
|
|
4
|
+
* account, and carrying both would be two authorities for one fact. Any `bot`
|
|
5
|
+
* the caller supplied is overwritten and any `account` is removed — the token
|
|
6
|
+
* this registration carries is the only authority on which bot it is for, and
|
|
7
|
+
* an operator transcribing either from a skill file is the failure mode this
|
|
8
|
+
* builder exists to remove. */
|
|
9
|
+
export function buildWebhookUrl(webhookUrl, botId) {
|
|
8
10
|
const u = new URL(webhookUrl);
|
|
9
|
-
u.searchParams.
|
|
11
|
+
u.searchParams.delete("account");
|
|
12
|
+
u.searchParams.set("bot", botId);
|
|
10
13
|
return u.toString();
|
|
11
14
|
}
|
|
12
15
|
//# sourceMappingURL=webhook-url.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhook-url.js","sourceRoot":"","sources":["../../src/lib/webhook-url.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"webhook-url.js","sourceRoot":"","sources":["../../src/lib/webhook-url.ts"],"names":[],"mappings":"AAAA;;;;;;;gCAOgC;AAChC,MAAM,UAAU,eAAe,CAAC,UAAkB,EAAE,KAAa;IAC/D,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC;IAC9B,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACjC,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IACjC,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC;AACtB,CAAC"}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
interface MessageParams {
|
|
2
2
|
channel: "telegram";
|
|
3
|
+
/** Task 2368 — which registered bot to send as. An account holds N bots, each
|
|
4
|
+
* with its own token, so "the account's token" is no longer a thing that can
|
|
5
|
+
* be resolved without naming one. */
|
|
6
|
+
botId: string;
|
|
3
7
|
chatId: string | number;
|
|
4
8
|
text: string;
|
|
5
9
|
parseMode?: "HTML" | "Markdown" | "MarkdownV2";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message.d.ts","sourceRoot":"","sources":["../../src/tools/message.ts"],"names":[],"mappings":"AAGA,UAAU,aAAa;IACrB,OAAO,EAAE,UAAU,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,YAAY,CAAC;IAC/C,cAAc,CAAC,EAAE,KAAK,CACpB,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAC7D,CAAC;CACH;AAED,UAAU,aAAa;IACrB,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,OAAO,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,
|
|
1
|
+
{"version":3,"file":"message.d.ts","sourceRoot":"","sources":["../../src/tools/message.ts"],"names":[],"mappings":"AAGA,UAAU,aAAa;IACrB,OAAO,EAAE,UAAU,CAAC;IACpB;;0CAEsC;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,YAAY,CAAC;IAC/C,cAAc,CAAC,EAAE,KAAK,CACpB,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAC7D,CAAC;CACH;AAED,UAAU,aAAa;IACrB,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,OAAO,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAiD3E"}
|
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
import { sendMessage } from "../lib/telegram.js";
|
|
2
|
-
import { resolveBotToken } from "../lib/account-token.js";
|
|
2
|
+
import { resolveBotToken, listRegisteredBots } from "../lib/account-token.js";
|
|
3
3
|
export async function message(params) {
|
|
4
4
|
const platformRoot = process.env.PLATFORM_ROOT;
|
|
5
5
|
const accountId = process.env.ACCOUNT_ID;
|
|
6
|
-
const botToken = platformRoot && accountId ? resolveBotToken(platformRoot, accountId) : null;
|
|
6
|
+
const botToken = platformRoot && accountId ? resolveBotToken(platformRoot, accountId, params.botId) : null;
|
|
7
7
|
if (!botToken) {
|
|
8
|
+
// Task 2368 — an account holds N bots and nothing else surfaces their ids to
|
|
9
|
+
// an agent, so the refusal enumerates them rather than failing opaquely. A
|
|
10
|
+
// caller that named the wrong bot, or guessed, gets the right answer here and
|
|
11
|
+
// succeeds on its next call. It never picks one: which bot a message goes out
|
|
12
|
+
// on is the caller's decision, and choosing would put a stranger's message on
|
|
13
|
+
// the operator's own bot.
|
|
14
|
+
const registered = platformRoot && accountId ? listRegisteredBots(platformRoot, accountId) : [];
|
|
15
|
+
const known = registered.length === 0
|
|
16
|
+
? "This account has no Telegram bot registered — run telegram:configure first."
|
|
17
|
+
: `This account's registered bots: ${registered.map((b) => `${b.id} (${b.role}${b.agent ? `, ${b.agent}` : ""})`).join("; ")}.`;
|
|
8
18
|
return {
|
|
9
19
|
sent: false,
|
|
10
20
|
channel: params.channel,
|
|
11
|
-
error:
|
|
21
|
+
error: `No Telegram bot "${params.botId}" is registered on this account. ${known}`,
|
|
12
22
|
};
|
|
13
23
|
}
|
|
14
24
|
let replyMarkup;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message.js","sourceRoot":"","sources":["../../src/tools/message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAA+B,MAAM,oBAAoB,CAAC;AAC9E,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"message.js","sourceRoot":"","sources":["../../src/tools/message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAA+B,MAAM,oBAAoB,CAAC;AAC9E,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAuB9E,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,MAAqB;IACjD,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;IAC/C,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC;IACzC,MAAM,QAAQ,GACZ,YAAY,IAAI,SAAS,CAAC,CAAC,CAAC,eAAe,CAAC,YAAY,EAAE,SAAS,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC5F,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,6EAA6E;QAC7E,2EAA2E;QAC3E,8EAA8E;QAC9E,8EAA8E;QAC9E,8EAA8E;QAC9E,0BAA0B;QAC1B,MAAM,UAAU,GAAG,YAAY,IAAI,SAAS,CAAC,CAAC,CAAC,kBAAkB,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAChG,MAAM,KAAK,GAAG,UAAU,CAAC,MAAM,KAAK,CAAC;YACnC,CAAC,CAAC,6EAA6E;YAC/E,CAAC,CAAC,mCAAmC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;QAClI,OAAO;YACL,IAAI,EAAE,KAAK;YACX,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,KAAK,EAAE,oBAAoB,MAAM,CAAC,KAAK,oCAAoC,KAAK,EAAE;SACnF,CAAC;IACJ,CAAC;IAED,IAAI,WAA+C,CAAC;IACpD,IAAI,MAAM,CAAC,cAAc,EAAE,CAAC;QAC1B,WAAW,GAAG;YACZ,eAAe,EAAE,MAAM,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CACjD,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;gBAChB,IAAI,EAAE,GAAG,CAAC,IAAI;gBACd,aAAa,EAAE,GAAG,CAAC,YAAY;gBAC/B,GAAG,EAAE,GAAG,CAAC,GAAG;aACb,CAAC,CAAC,CACJ;SACF,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,QAAQ,EAAE;QACzC,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,WAAW;KACZ,CAAC,CAAC;IAEH,OAAO;QACL,IAAI,EAAE,MAAM,CAAC,EAAE;QACf,OAAO,EAAE,UAAU;QACnB,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,KAAK,EAAE,MAAM,CAAC,KAAK;KACpB,CAAC;AACJ,CAAC"}
|
|
@@ -15,22 +15,30 @@ Setup happens entirely in the admin chat — there is no settings page to click
|
|
|
15
15
|
1. Tell the admin agent: "I want to connect a Telegram bot." The agent runs the `telegram:configure` skill.
|
|
16
16
|
2. Paste the bot token from BotFather when the agent asks.
|
|
17
17
|
3. Tell the agent whether this is an **admin bot** or a **public bot** (see below).
|
|
18
|
-
4. For an admin bot, give the agent the numeric Telegram user IDs that should have admin access. For a public bot, choose a DM policy (who can message it) and, for a whitelist, the allowed numeric IDs.
|
|
19
|
-
5. The agent verifies the token, registers the webhook on the brand's edge path, stores the
|
|
20
|
-
6. The agent confirms the bot is connected. Message the bot; the reply comes back from the agent.
|
|
18
|
+
4. For an admin bot, give the agent the numeric Telegram user IDs that should have admin access. For a public bot, choose which of your agents answers on it, a DM policy (who can message it), and, for a whitelist, the allowed numeric IDs.
|
|
19
|
+
5. The agent verifies the token, registers the webhook on the brand's edge path, and stores the bot under the brand install.
|
|
20
|
+
6. The agent confirms the bot is connected. Message the bot; the reply comes back from the agent you chose.
|
|
21
21
|
|
|
22
22
|
The token is stored in your account config under the brand install (never in a shared `~/.claude` directory), and the webhook is registered on the `/api/telegram/` path the brand's edge admits.
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
## How Many Bots You Can Have
|
|
25
|
+
|
|
26
|
+
As many as you like. Your account holds a list of bots, and each one is its own thing: its own token, its own role, its own list of who may message it, and — for a public bot — its own agent. Adding, re-pointing or removing one bot never touches another, in your account or anyone else's.
|
|
27
|
+
|
|
28
|
+
Each bot is identified by its own Telegram id, which is the part of the token before the colon. The agent reads it off the token you paste, puts it on the registered address for you, and gives that bot its own secret file. You never type it.
|
|
29
|
+
|
|
30
|
+
Two people talking to two of your bots are two separate conversations, and so are one person talking to two of them: each bot keeps its own thread with each person.
|
|
31
|
+
|
|
32
|
+
If you connected a bot before this changed, it stops delivering until you run the setup once more. The agent can tell you which ones from the standing check.
|
|
25
33
|
|
|
26
34
|
## Admin Bot vs Public Bot
|
|
27
35
|
|
|
28
|
-
|
|
36
|
+
Every bot is one or the other, and you say which when you set it up. It is not worked out from anything else.
|
|
29
37
|
|
|
30
|
-
- **Admin bot
|
|
31
|
-
- **Public bot
|
|
38
|
+
- **Admin bot**: only the numeric Telegram IDs you listed for THAT bot can message it. Everyone else is silently denied. It answers as your admin agent, with your account's tools.
|
|
39
|
+
- **Public bot**: customer-facing. A DM policy decides who may message it, and it answers as the agent you named when you set it up. That agent has to be one of your account's active agents; if you switch the agent off later, the bot stops answering and says so rather than falling back to another one.
|
|
32
40
|
|
|
33
|
-
A common setup is two bots: one admin bot for the business owner, one public bot for customers.
|
|
41
|
+
A common setup is two bots: one admin bot for the business owner, one public bot for customers. Nothing stops you having five.
|
|
34
42
|
|
|
35
43
|
## Finding Your Telegram User ID
|
|
36
44
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: configure
|
|
3
|
-
description: Guide the user through connecting a Telegram bot entirely via the admin chat — store the BotFather token under the brand install, register the webhook on the maxy edge-admitted path, set access policy, and
|
|
3
|
+
description: Guide the user through connecting a Telegram bot entirely via the admin chat — store the BotFather token under the brand install, register the webhook on the maxy edge-admitted path, set the bot's role and access policy, and pair a public bot to one of the account's agents. An account may hold any number of bots. Complete PTY setup, no UI clicks.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Configure Telegram
|
|
@@ -15,11 +15,14 @@ User says anything about connecting, linking, or setting up Telegram, BotFather,
|
|
|
15
15
|
|
|
16
16
|
A finished Telegram setup has all of these, and the skill is not done until each is true:
|
|
17
17
|
|
|
18
|
-
- The bot
|
|
19
|
-
- The webhook is registered against a URL whose path is `/api/telegram/` (the only path the maxy edge admits). The register tool refuses any other path and logs `op=webhook-register path=… account=… admitted=false` when it does.
|
|
20
|
-
- The registered URL carries `
|
|
21
|
-
-
|
|
22
|
-
-
|
|
18
|
+
- The bot is stored as one entry in `account.json`'s `telegram.bots` array (under the brand install, never `~/.claude/`). The `telegram-webhook-register` tool writes it there and logs `[telegram-setup] op=token-write dir=… brand=…`.
|
|
19
|
+
- The webhook is registered against a URL whose path is `/api/telegram/` (the only path the maxy edge admits). The register tool refuses any other path and logs `op=webhook-register path=… botId=… account=… admitted=false` when it does.
|
|
20
|
+
- The registered URL carries `bot=<botId>`, the bot's own Telegram id. The register tool derives it from the token and appends it, so you never type it. The receiver requires it and rejects `reason=unknown-bot`; the bot id is what determines the account, so no `account=` is carried.
|
|
21
|
+
- The bot's `role` is stated, not inferred: `admin` or `public`. It selects the spawn path, so it is a privilege decision.
|
|
22
|
+
- Access policy is set **on that entry**: an admin bot carries the operator's numeric Telegram user IDs (`adminUsers`); a public bot carries a DM policy (and an allowlist when the policy is `allowlist`). Nothing is shared with the account's other bots.
|
|
23
|
+
- A public bot names the `agent` it answers as, and that agent is active. An admin bot names none: it reads `agents/admin` by construction.
|
|
24
|
+
|
|
25
|
+
An account may hold any number of bots. Registering, re-pointing or removing one never disturbs another, in any account.
|
|
23
26
|
|
|
24
27
|
## Flow
|
|
25
28
|
|
|
@@ -29,54 +32,48 @@ Read `references/setup-guide.md` (cited throughout this flow) with the `plugin-r
|
|
|
29
32
|
|
|
30
33
|
Follow `references/setup-guide.md` "Creating a Bot": the operator creates the bot via @BotFather and gives you the token (e.g. `123456:ABC-DEF…`). Confirm you have it before continuing.
|
|
31
34
|
|
|
32
|
-
### Step 2 — Decide
|
|
35
|
+
### Step 2 — Decide the role, and for a public bot its agent
|
|
36
|
+
|
|
37
|
+
Ask which role this bot serves (see `references/setup-guide.md` "Admin Bot vs Public Bot"). The role is stated by the operator, never worked out from anything else: it decides which spawn path the bot's messages take.
|
|
33
38
|
|
|
34
|
-
|
|
39
|
+
- **Admin bot** — only the numeric Telegram user IDs the operator names for THIS bot may message it; everything else is denied. Collect those IDs (`references/setup-guide.md` "Finding Your Telegram User ID"). It answers as the admin agent.
|
|
40
|
+
- **Public bot** — customer-facing; choose a DM policy (`open`, `allowlist`, or `disabled`), for `allowlist` the allowed numeric IDs, and **which of this account's agents answers on it**. Call `account-manage` to list the account's agents and let the operator pick one. The agent must be active; the register tool refuses an inactive or unknown slug.
|
|
35
41
|
|
|
36
|
-
|
|
37
|
-
- **Public bot** — customer-facing; choose a DM policy (`open`, `allowlist`, or `disabled`) and, for `allowlist`, the allowed numeric IDs.
|
|
42
|
+
The account may already hold other bots. This one is added alongside them and shares nothing with them.
|
|
38
43
|
|
|
39
44
|
### Step 3 — Build the webhook URL
|
|
40
45
|
|
|
41
|
-
The path MUST be `/api/telegram
|
|
46
|
+
The path MUST be `/api/telegram/`. Use the account's domain for the host — the admin domain for an admin bot, the public domain for a public bot (read them from `brand-settings` / the account config). Example: `https://<admin-domain>/api/telegram/`.
|
|
42
47
|
|
|
43
|
-
Do NOT add `?account=`. The register tool
|
|
48
|
+
Do NOT add `?bot=` or `?account=`. The register tool derives the bot id from the token and stamps `?bot=<botId>` on for you; the token is the only authority on which bot it is, and a value typed by hand could name a different one.
|
|
44
49
|
|
|
45
50
|
The register tool aborts on any other path, so do not invent `/channels/telegram` or similar.
|
|
46
51
|
|
|
47
|
-
### Step 4 — Register (stores
|
|
52
|
+
### Step 4 — Register (stores the entry + webhook + secret in one call)
|
|
48
53
|
|
|
49
54
|
Call `telegram-webhook-register` with:
|
|
50
55
|
- `botToken` — the BotFather token
|
|
51
56
|
- `webhookUrl` — the URL from Step 3
|
|
52
|
-
- `
|
|
57
|
+
- `role` — `admin` or `public`
|
|
53
58
|
- admin bot: `adminUsers` — the numeric IDs from Step 2
|
|
54
|
-
- public bot: `dmPolicy` and, for `allowlist`, `allowFrom`
|
|
55
|
-
|
|
56
|
-
On success the tool has: verified the token, set the webhook with a generated secret (written to the brand configDir), persisted the token + policy into `account.json`'s `telegram` block, and logged `op=token-write` + `op=webhook-register admitted=true`. If it returns an error, read it back to the operator verbatim — the common ones are a wrong path (`admitted=false`) or a token write blocked outside the brand install (`op=token-write-rejected`).
|
|
57
|
-
|
|
58
|
-
### Step 5 — Select the agent (public bot)
|
|
59
|
-
|
|
60
|
-
Public inbound routes to the account's `defaultAgent`. Set it and confirm:
|
|
61
|
-
|
|
62
|
-
1. `account-manage` to list available public agents.
|
|
63
|
-
2. `account-update` with `field: "defaultAgent"`, `value: "<slug>"`.
|
|
64
|
-
3. Re-read by calling `account-manage` (or `account-update` reports the change) and confirm `defaultAgent` is the chosen slug. The post-condition is a non-empty `defaultAgent`, not the intent to set it.
|
|
59
|
+
- public bot: `agent` — the slug from Step 2, plus `dmPolicy` and, for `allowlist`, `allowFrom`
|
|
65
60
|
|
|
66
|
-
|
|
61
|
+
On success the tool has: verified the token, derived the bot id, set the webhook with a generated secret (written to `~/.<brand>/telegram-secrets/<botId>`), persisted the entry into `account.json`'s `telegram.bots` array, and logged `op=token-write` + `op=webhook-register admitted=true`. It replaces only the entry with this bot's id, so the account's other bots are untouched. If it returns an error, read it back to the operator verbatim — the common ones are a wrong path (`admitted=false`), a public bot with no or an inactive `agent` (`op=token-write-rejected reason=no-agent|agent-inactive`), or a token write blocked outside the brand install.
|
|
67
62
|
|
|
68
|
-
### Step
|
|
63
|
+
### Step 5 — Confirm
|
|
69
64
|
|
|
70
|
-
Tell the operator the bot is connected, which role it has, and that messaging the bot now reaches the agent and a reply returns. For a public bot, name the agent that will respond.
|
|
65
|
+
Tell the operator the bot is connected, which role it has, and that messaging the bot now reaches the agent and a reply returns. For a public bot, name the agent that will respond. If the account holds other bots, say that this one is separate from them: its own people, its own agent, its own conversations.
|
|
71
66
|
|
|
72
67
|
## Failure modes (state these loudly, do not paper over)
|
|
73
68
|
|
|
74
69
|
- **Wrong webhook path** — register returns an error and logs `op=webhook-register admitted=false`. Fix the URL to use `/api/telegram/`.
|
|
75
70
|
- **Token write outside the brand** — `op=token-write-rejected reason=non-brand-path`. The MCP env is misconfigured; do not retry blindly — report it.
|
|
76
|
-
-
|
|
77
|
-
- **Bad secret on inbound** — `[telegram-inbound] op=
|
|
78
|
-
- **Bot registered before
|
|
79
|
-
- **
|
|
71
|
+
- **Paired agent switched off** — `[telegram-inbound] op=agent … active=false` then `op=reject reason=agent-inactive`, and the standing `[telegram-audit] op=entry-agent-inactive` names the bot. Re-activate that agent or re-register the bot against another. There is no fall back to a different agent: answering as an agent the operator switched off is exactly what this refusal prevents.
|
|
72
|
+
- **Bad secret on inbound** — `[telegram-inbound] op=secret … result=mismatch` means the registered secret and the stored secret diverged; re-run register.
|
|
73
|
+
- **Bot registered before the bot registry** — `[telegram-inbound] op=reject reason=unknown-bot`, and `[telegram-audit] op=configured … secretFile=absent` names it. The registered URL predates `?bot=<botId>`; re-run register once for that bot. There is no compatibility branch, so it 401s until you do. The boot converter rewrites the stored config but cannot re-point a registered webhook: that is an outbound call, and the converter makes none.
|
|
74
|
+
- **One bot id claimed by two accounts** — inbound is refused `[telegram-inbound] op=reject reason=duplicate-bot-id`, and the standing `[telegram-audit] op=duplicate-bot-id` names it every tick. Two accounts hold the same token. Remove it from the one that should not have it; nothing picks between them.
|
|
75
|
+
- **Chat id claimed by two bots** — a scheduled dispatch logs `[schedule-inject] op=telegram-destination-ambiguous … dispatched=no`. Two bots, in one account or two, list the same numeric ID in their `adminUsers`. Remove it from the one that should not have it; the dispatch refuses rather than guessing which bot the person should hear from.
|
|
76
|
+
- **A secret file with no bot, or a bot with no secret file** — `[telegram-audit] op=secret-orphan botId=… side=file|entry`. Neither shows up any other way. `side=entry` means that bot cannot receive anything until it re-registers; `side=file` is left-over state from a bot that has been removed.
|
|
80
77
|
|
|
81
78
|
## Language
|
|
82
79
|
|
|
@@ -84,3 +81,4 @@ Use plain English:
|
|
|
84
81
|
- "bot token" not "Bot API credential"
|
|
85
82
|
- "your numeric Telegram ID" not "user_id integer"
|
|
86
83
|
- "the bot is connected" not "webhook registered with secret_token"
|
|
84
|
+
- "which of your agents should answer on this bot" not "the agentSlug for the entry"
|