@rubytech/create-realagent-code 0.1.592 → 0.1.593
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/plugins/admin/skills/platform-architecture/SKILL.md +7 -3
- package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +6 -0
- package/payload/platform/plugins/docs/references/internals.md +4 -2
- package/payload/platform/plugins/docs/references/telegram-guide.md +2 -0
- package/payload/platform/plugins/scheduling/PLUGIN.md +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/agent-turn-dispatch.test.js +15 -2
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/agent-turn-dispatch.test.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js +7 -2
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/scheduling-gate.test.js +125 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/scheduling-gate.test.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.d.ts +28 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.js +72 -12
- package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts +37 -0
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +178 -13
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-public-lockdown.d.ts +13 -5
- package/payload/platform/services/claude-session-manager/dist/rc-public-lockdown.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-public-lockdown.js +23 -9
- package/payload/platform/services/claude-session-manager/dist/rc-public-lockdown.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/telegram-channel-mcp.d.ts +7 -4
- package/payload/platform/services/claude-session-manager/dist/telegram-channel-mcp.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/telegram-channel-mcp.js +5 -3
- package/payload/platform/services/claude-session-manager/dist/telegram-channel-mcp.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/telegram-channel-store.d.ts +15 -1
- package/payload/platform/services/claude-session-manager/dist/telegram-channel-store.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/telegram-channel-store.js +16 -7
- package/payload/platform/services/claude-session-manager/dist/telegram-channel-store.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/telegram-plugin-mcp.d.ts +28 -0
- package/payload/platform/services/claude-session-manager/dist/telegram-plugin-mcp.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/telegram-plugin-mcp.js +53 -0
- package/payload/platform/services/claude-session-manager/dist/telegram-plugin-mcp.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/wa-channel-mcp.d.ts +9 -5
- package/payload/platform/services/claude-session-manager/dist/wa-channel-mcp.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/wa-channel-mcp.js +12 -6
- package/payload/platform/services/claude-session-manager/dist/wa-channel-mcp.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/webchat-channel-mcp.d.ts +7 -3
- package/payload/platform/services/claude-session-manager/dist/webchat-channel-mcp.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/webchat-channel-mcp.js +5 -1
- package/payload/platform/services/claude-session-manager/dist/webchat-channel-mcp.js.map +1 -1
- package/payload/platform/services/telegram-channel/dist/instructions.d.ts +14 -2
- package/payload/platform/services/telegram-channel/dist/instructions.d.ts.map +1 -1
- package/payload/platform/services/telegram-channel/dist/instructions.js +34 -3
- package/payload/platform/services/telegram-channel/dist/instructions.js.map +1 -1
- package/payload/server/server.js +74 -9
|
@@ -23,6 +23,43 @@ import { TelegramChannelStore } from './telegram-channel-store.js';
|
|
|
23
23
|
* named agent's IDENTITY/SOUL/KNOWLEDGE via composeAppendSystemPrompt, the same
|
|
24
24
|
* composer /public-spawn uses. An admin spawn is unchanged and still composes
|
|
25
25
|
* only reseat / voice / specialist-domains / authenticated-identity fragments. */
|
|
26
|
+
/** Task 2666 — the framing a channel server may be given, or null when the
|
|
27
|
+
* spawn does not carry one.
|
|
28
|
+
*
|
|
29
|
+
* A channel server has exactly two framings, and `admin` is the
|
|
30
|
+
* higher-privilege one: its instruction block tells the agent the message "is
|
|
31
|
+
* a message the operator has typed to you directly" and to act on it as their
|
|
32
|
+
* instruction. Every site that reached it by exclusion — anything not
|
|
33
|
+
* `'public'` becomes admin — turned each way of getting the role wrong into a
|
|
34
|
+
* privilege elevation that emitted nothing, because a spawn that silently
|
|
35
|
+
* became admin is byte-identical to a correct admin spawn.
|
|
36
|
+
*
|
|
37
|
+
* So this answers the question and never invents an answer. `passive` is not a
|
|
38
|
+
* channel framing and null means the role was never resolved; both return null
|
|
39
|
+
* and the caller refuses the spawn. */
|
|
40
|
+
export declare function channelFramingRole(role: AgentRole | null): 'admin' | 'public' | null;
|
|
41
|
+
/** Task 2666 — the same value at the point of use, where null is unreachable
|
|
42
|
+
* because the caller refused the spawn already.
|
|
43
|
+
*
|
|
44
|
+
* A throw rather than a `!` assertion or a default. The compiler cannot narrow
|
|
45
|
+
* through the `channelBound && framing === null` conjunction that guards the
|
|
46
|
+
* attach blocks, and the two ways of silencing it are both worse than a crash:
|
|
47
|
+
* `!` asserts an invariant it cannot check, and a default reintroduces exactly
|
|
48
|
+
* the admin-by-exclusion this task removed. If the guard is ever conditioned
|
|
49
|
+
* away, this is loud on the first spawn instead of quiet for ever. */
|
|
50
|
+
export declare function requireChannelFraming(role: 'admin' | 'public' | null): 'admin' | 'public';
|
|
51
|
+
/** Task 2666 — the telegram plugin's own public tools, out of the install-wide
|
|
52
|
+
* public surface `loadToolSurface` built from every PLUGIN.md at boot.
|
|
53
|
+
*
|
|
54
|
+
* Filtered to this one plugin rather than passed whole: a pinned public
|
|
55
|
+
* telegram spawn registers only the telegram servers, so a public tool
|
|
56
|
+
* declared on any other plugin would name a server that is not there — the
|
|
57
|
+
* granted-but-uncallable failure this task's `op=mcp-registered` line exists
|
|
58
|
+
* to make visible. Filtering states that boundary instead of relying on no
|
|
59
|
+
* other plugin ever declaring one.
|
|
60
|
+
*
|
|
61
|
+
* Sorted so the emitted argv is stable and the audit test can compare. */
|
|
62
|
+
export declare function telegramPublicToolsFrom(publicSurface: ReadonlySet<string>): string[];
|
|
26
63
|
export declare function parseRcSpawnRole(body: {
|
|
27
64
|
role?: unknown;
|
|
28
65
|
}): Extract<AgentRole, 'admin' | 'public'> | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-server.d.ts","sourceRoot":"","sources":["../src/http-server.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"http-server.d.ts","sourceRoot":"","sources":["../src/http-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -27,6 +27,7 @@ import { requirePlatformPort } from './channel-spawn-tee.js';
|
|
|
27
27
|
import { parseWaChannelBody, sessionIdFieldsFor } from './public-spawn-session.js';
|
|
28
28
|
import { buildWebchatChannelMcpServers, buildWebchatChannelArgv, webchatChannelMcpConfigPath, WEBCHAT_CHANNEL_SERVER_NAME } from './webchat-channel-mcp.js';
|
|
29
29
|
import { buildTelegramChannelMcpServers, buildTelegramChannelArgv, telegramChannelMcpConfigPath, TELEGRAM_CHANNEL_SERVER_NAME } from './telegram-channel-mcp.js';
|
|
30
|
+
import { buildTelegramPluginMcpServers, TELEGRAM_PLUGIN_SERVER_NAME } from './telegram-plugin-mcp.js';
|
|
30
31
|
import { buildChannelMcpServers, buildChannelArgv, channelMcpConfigPath, CHANNEL_SERVER_NAME } from './channel-mcp.js';
|
|
31
32
|
import { buildRcPublicLockdownArgv, formatToolAllowlistLine, PUBLIC_ALLOWED_TOOLS_ENV, allowedToolsFromArgv, formatRoleResolutionLine, } from './rc-public-lockdown.js';
|
|
32
33
|
import { resolvePublicSpawnModel } from './public-spawn-model.js';
|
|
@@ -78,6 +79,53 @@ const ROLES = ['admin', 'public'];
|
|
|
78
79
|
* named agent's IDENTITY/SOUL/KNOWLEDGE via composeAppendSystemPrompt, the same
|
|
79
80
|
* composer /public-spawn uses. An admin spawn is unchanged and still composes
|
|
80
81
|
* only reseat / voice / specialist-domains / authenticated-identity fragments. */
|
|
82
|
+
/** Task 2666 — the framing a channel server may be given, or null when the
|
|
83
|
+
* spawn does not carry one.
|
|
84
|
+
*
|
|
85
|
+
* A channel server has exactly two framings, and `admin` is the
|
|
86
|
+
* higher-privilege one: its instruction block tells the agent the message "is
|
|
87
|
+
* a message the operator has typed to you directly" and to act on it as their
|
|
88
|
+
* instruction. Every site that reached it by exclusion — anything not
|
|
89
|
+
* `'public'` becomes admin — turned each way of getting the role wrong into a
|
|
90
|
+
* privilege elevation that emitted nothing, because a spawn that silently
|
|
91
|
+
* became admin is byte-identical to a correct admin spawn.
|
|
92
|
+
*
|
|
93
|
+
* So this answers the question and never invents an answer. `passive` is not a
|
|
94
|
+
* channel framing and null means the role was never resolved; both return null
|
|
95
|
+
* and the caller refuses the spawn. */
|
|
96
|
+
export function channelFramingRole(role) {
|
|
97
|
+
return role === 'admin' || role === 'public' ? role : null;
|
|
98
|
+
}
|
|
99
|
+
/** Task 2666 — the same value at the point of use, where null is unreachable
|
|
100
|
+
* because the caller refused the spawn already.
|
|
101
|
+
*
|
|
102
|
+
* A throw rather than a `!` assertion or a default. The compiler cannot narrow
|
|
103
|
+
* through the `channelBound && framing === null` conjunction that guards the
|
|
104
|
+
* attach blocks, and the two ways of silencing it are both worse than a crash:
|
|
105
|
+
* `!` asserts an invariant it cannot check, and a default reintroduces exactly
|
|
106
|
+
* the admin-by-exclusion this task removed. If the guard is ever conditioned
|
|
107
|
+
* away, this is loud on the first spawn instead of quiet for ever. */
|
|
108
|
+
export function requireChannelFraming(role) {
|
|
109
|
+
if (role === null) {
|
|
110
|
+
throw new Error('rc-spawn: channel framing unresolved at attach — the guard above must refuse first');
|
|
111
|
+
}
|
|
112
|
+
return role;
|
|
113
|
+
}
|
|
114
|
+
/** Task 2666 — the telegram plugin's own public tools, out of the install-wide
|
|
115
|
+
* public surface `loadToolSurface` built from every PLUGIN.md at boot.
|
|
116
|
+
*
|
|
117
|
+
* Filtered to this one plugin rather than passed whole: a pinned public
|
|
118
|
+
* telegram spawn registers only the telegram servers, so a public tool
|
|
119
|
+
* declared on any other plugin would name a server that is not there — the
|
|
120
|
+
* granted-but-uncallable failure this task's `op=mcp-registered` line exists
|
|
121
|
+
* to make visible. Filtering states that boundary instead of relying on no
|
|
122
|
+
* other plugin ever declaring one.
|
|
123
|
+
*
|
|
124
|
+
* Sorted so the emitted argv is stable and the audit test can compare. */
|
|
125
|
+
export function telegramPublicToolsFrom(publicSurface) {
|
|
126
|
+
const prefix = `mcp__${TELEGRAM_PLUGIN_SERVER_NAME}__`;
|
|
127
|
+
return [...publicSurface].filter((n) => n.startsWith(prefix)).sort();
|
|
128
|
+
}
|
|
81
129
|
export function parseRcSpawnRole(body) {
|
|
82
130
|
return body.role === 'admin' || body.role === 'public' ? body.role : null;
|
|
83
131
|
}
|
|
@@ -2859,7 +2907,23 @@ export function buildHttpApp(deps) {
|
|
|
2859
2907
|
typeof telegramChannelRaw.senderId === 'string' &&
|
|
2860
2908
|
typeof telegramChannelRaw.gatewayUrl === 'string' &&
|
|
2861
2909
|
typeof telegramChannelRaw.serverPath === 'string'
|
|
2862
|
-
?
|
|
2910
|
+
? (() => {
|
|
2911
|
+
const o = telegramChannelRaw;
|
|
2912
|
+
// Task 2666 — the pin is admitted only as a matched PAIR of
|
|
2913
|
+
// non-empty strings, and is OPTIONAL so the admin and specialist
|
|
2914
|
+
// telegram spawns that come through this same door still validate.
|
|
2915
|
+
// A half pin would confine one axis and leave the other free,
|
|
2916
|
+
// which reads as protection while granting exactly what the pin
|
|
2917
|
+
// exists to prevent.
|
|
2918
|
+
const pinned = typeof o.botId === 'string' && o.botId.length > 0 &&
|
|
2919
|
+
typeof o.chatId === 'string' && o.chatId.length > 0;
|
|
2920
|
+
return {
|
|
2921
|
+
senderId: o.senderId,
|
|
2922
|
+
gatewayUrl: o.gatewayUrl,
|
|
2923
|
+
serverPath: o.serverPath,
|
|
2924
|
+
...(pinned ? { botId: o.botId, chatId: o.chatId } : {}),
|
|
2925
|
+
};
|
|
2926
|
+
})()
|
|
2863
2927
|
: undefined;
|
|
2864
2928
|
// Task-962 — recover an already-live but unresponsive webchat session. When
|
|
2865
2929
|
// set, the already-live branch classifies the child busy vs wedged and
|
|
@@ -3308,8 +3372,17 @@ export function buildHttpApp(deps) {
|
|
|
3308
3372
|
else {
|
|
3309
3373
|
const stored = deps.telegramChannelStore?.get(sessionId);
|
|
3310
3374
|
if (stored) {
|
|
3311
|
-
|
|
3312
|
-
|
|
3375
|
+
// Task 2666 — the pin comes back with the binding, so the second and
|
|
3376
|
+
// every later message of a public conversation keeps the rich
|
|
3377
|
+
// surface. Spread as a pair: the store admits both or neither.
|
|
3378
|
+
effectiveTelegramChannel = {
|
|
3379
|
+
senderId: stored.senderId,
|
|
3380
|
+
gatewayUrl: stored.gatewayUrl,
|
|
3381
|
+
serverPath: stored.serverPath,
|
|
3382
|
+
...(stored.botId && stored.chatId ? { botId: stored.botId, chatId: stored.chatId } : {}),
|
|
3383
|
+
};
|
|
3384
|
+
deps.logger(`[rc-spawn] op=telegram-channel-rebind sessionId=${sessionId.slice(0, 8)} senderId=${stored.senderId} ` +
|
|
3385
|
+
`pinned=${stored.botId && stored.chatId ? 'yes' : 'no'} source=store`);
|
|
3313
3386
|
}
|
|
3314
3387
|
}
|
|
3315
3388
|
}
|
|
@@ -3395,9 +3468,35 @@ export function buildHttpApp(deps) {
|
|
|
3395
3468
|
targetSet = mergeTarget(targetSet, { kind: 'whatsapp', ...effectiveWaChannel, accountId: effectiveAccountId });
|
|
3396
3469
|
if (effectiveWebchatChannel)
|
|
3397
3470
|
targetSet = mergeTarget(targetSet, { kind: 'webchat', ...effectiveWebchatChannel });
|
|
3471
|
+
// Task 2666 — resolved ONCE for all three channel builders, and refused
|
|
3472
|
+
// rather than defaulted. Before this, each builder took an optional role
|
|
3473
|
+
// and filled an absent one in as 'admin' at the stamp, so a resume that
|
|
3474
|
+
// carried no role framed a stranger's conversation as the operator's and
|
|
3475
|
+
// logged nothing. Two of the three channel servers already threw on an
|
|
3476
|
+
// absent value; the builders' own defaults meant they never saw one, so
|
|
3477
|
+
// that strictness was unreachable.
|
|
3478
|
+
//
|
|
3479
|
+
// The cost is stated rather than hidden, as Task 2267 stated its own. That
|
|
3480
|
+
// task chose 200-plus-lockdown for a channel-bound resume whose role will
|
|
3481
|
+
// not resolve, which fails closed on TOOLS. It could not fail closed on
|
|
3482
|
+
// framing, because the framing default was admin. Refusing covers both, and
|
|
3483
|
+
// an admin whose sidecar is damaged now gets a named error instead of a
|
|
3484
|
+
// session that silently comes back holding one tool and never repairs
|
|
3485
|
+
// itself.
|
|
3486
|
+
const channelFraming = channelFramingRole(effectiveRcRole);
|
|
3487
|
+
if (channelBound && channelFraming === null) {
|
|
3488
|
+
deps.logger(`[rc-spawn] op=channel-role-unresolved sessionId=${sessionId?.slice(0, 8) ?? 'none'} ` +
|
|
3489
|
+
`role=${effectiveRcRole ?? 'none'} outcome=${priorSidecarRead?.outcome ?? 'missing'}`);
|
|
3490
|
+
return c.json({ error: 'spawn-failed', reason: 'channel-role-unresolved' }, 503);
|
|
3491
|
+
}
|
|
3398
3492
|
let waPath = null;
|
|
3399
3493
|
let webchatPath = null;
|
|
3400
3494
|
let telegramPath = null;
|
|
3495
|
+
// Task 2666 — the telegram tools this spawn is granted. Non-empty only when
|
|
3496
|
+
// a pinned public telegram channel attached AND its config write succeeded,
|
|
3497
|
+
// so a failed write cannot leave the allowlist naming a server that was
|
|
3498
|
+
// never registered.
|
|
3499
|
+
let telegramPublicExtraTools = [];
|
|
3401
3500
|
// Task 2260 — the channel server this spawn actually registered, set inside
|
|
3402
3501
|
// each branch below that succeeded in writing its --mcp-config (a write
|
|
3403
3502
|
// failure leaves it null). The public lockdown reads it to name the one
|
|
@@ -3439,11 +3538,15 @@ export function buildHttpApp(deps) {
|
|
|
3439
3538
|
nodeBin: deps.nodeBin,
|
|
3440
3539
|
// Task 2290 — the account the gateway keys this session's hub entry on.
|
|
3441
3540
|
accountId: effectiveAccountId,
|
|
3442
|
-
// Task 2458 — the
|
|
3443
|
-
//
|
|
3444
|
-
//
|
|
3445
|
-
//
|
|
3446
|
-
role
|
|
3541
|
+
// Task 2458 — the gateway branches this session's turn-end delivery
|
|
3542
|
+
// on it, so a public visitor's answer goes back down the posting key
|
|
3543
|
+
// instead of into the admin identity fan-out.
|
|
3544
|
+
//
|
|
3545
|
+
// Task 2666 — the RESOLVED framing, not the body role. The body role
|
|
3546
|
+
// is null on a bare resume, which used to fall through the builder's
|
|
3547
|
+
// default to admin. Refused above when it will not resolve, so this
|
|
3548
|
+
// is non-null here.
|
|
3549
|
+
role: requireChannelFraming(channelFraming),
|
|
3447
3550
|
// Task 2356 — the spawn-tee inputs, so this server's stderr lands in
|
|
3448
3551
|
// logs/mcp-whatsapp-channel-<sessionId>.log.
|
|
3449
3552
|
platformRoot: deps.platformRoot,
|
|
@@ -3470,6 +3573,10 @@ export function buildHttpApp(deps) {
|
|
|
3470
3573
|
gatewayUrl: effectiveWebchatChannel.gatewayUrl,
|
|
3471
3574
|
serverPath: effectiveWebchatChannel.serverPath,
|
|
3472
3575
|
nodeBin: deps.nodeBin,
|
|
3576
|
+
// Task 2666 — this call site named NO role at all, so every webchat
|
|
3577
|
+
// spawn through this door took the builder's 'admin' default,
|
|
3578
|
+
// including a public one. The resolved framing is passed now.
|
|
3579
|
+
role: requireChannelFraming(channelFraming),
|
|
3473
3580
|
// Task 2356 — the spawn-tee inputs, so this server's stderr lands in
|
|
3474
3581
|
// logs/mcp-webchat-channel-<sessionId>.log.
|
|
3475
3582
|
platformRoot: deps.platformRoot,
|
|
@@ -3500,25 +3607,83 @@ export function buildHttpApp(deps) {
|
|
|
3500
3607
|
gatewayUrl: effectiveTelegramChannel.gatewayUrl,
|
|
3501
3608
|
serverPath: effectiveTelegramChannel.serverPath,
|
|
3502
3609
|
nodeBin: deps.nodeBin,
|
|
3503
|
-
// Task 2325 —
|
|
3504
|
-
//
|
|
3505
|
-
|
|
3610
|
+
// Task 2325 — `?? undefined` only bridges null to the optional
|
|
3611
|
+
// parameter this builder declares.
|
|
3612
|
+
//
|
|
3613
|
+
// Task 2666 — `effectiveRcRole`, not the body role. A resume omits
|
|
3614
|
+
// `role` (the wedged-session recovery and the re-seat send both post
|
|
3615
|
+
// `{ sessionId }` alone), so the body value resolved null and this
|
|
3616
|
+
// server booted with ADMIN framing on a public conversation: the
|
|
3617
|
+
// admin block tells the agent the message "is a message the operator
|
|
3618
|
+
// has typed to you directly" and to act on it as their instruction,
|
|
3619
|
+
// which handed a stranger's message operator authority. The lockdown
|
|
3620
|
+
// below and this task's pin gate already read `effectiveRcRole`, so
|
|
3621
|
+
// the framing was also the one axis that disagreed with the tool
|
|
3622
|
+
// grant on the same spawn. Resolved once above, and refused there
|
|
3623
|
+
// when it will not resolve, rather than mapped to a default here.
|
|
3624
|
+
role: requireChannelFraming(channelFraming),
|
|
3506
3625
|
// Task 2356 — the spawn-tee inputs, so this server's stderr lands in
|
|
3507
3626
|
// logs/mcp-telegram-channel-<sessionId>.log.
|
|
3508
3627
|
platformRoot: deps.platformRoot,
|
|
3509
3628
|
logDir: deps.logDir,
|
|
3510
3629
|
platformPort: requirePlatformPort(),
|
|
3511
3630
|
});
|
|
3631
|
+
// Task 2666 — the pinned rich surface. Gated on the PAIR: without both
|
|
3632
|
+
// a bot and a chat there is nothing to pin the tools to, and an
|
|
3633
|
+
// unpinned grant would let a visitor's turn name any chat. Gated on
|
|
3634
|
+
// role:'public' too, because an admin telegram spawn already holds this
|
|
3635
|
+
// same plugin under the long prefix from the installed-plugin channel;
|
|
3636
|
+
// registering it again here would give one session two live copies of
|
|
3637
|
+
// the same MCP server.
|
|
3638
|
+
const telegramPin = effectiveRcRole === 'public' && effectiveTelegramChannel.botId && effectiveTelegramChannel.chatId
|
|
3639
|
+
? { botId: effectiveTelegramChannel.botId, chatId: effectiveTelegramChannel.chatId }
|
|
3640
|
+
: null;
|
|
3641
|
+
const telegramPluginServers = telegramPin
|
|
3642
|
+
? buildTelegramPluginMcpServers({
|
|
3643
|
+
sessionId,
|
|
3644
|
+
botId: telegramPin.botId,
|
|
3645
|
+
chatId: telegramPin.chatId,
|
|
3646
|
+
accountId: effectiveAccountId,
|
|
3647
|
+
nodeBin: deps.nodeBin,
|
|
3648
|
+
platformRoot: deps.platformRoot,
|
|
3649
|
+
logDir: deps.logDir,
|
|
3650
|
+
platformPort: requirePlatformPort(),
|
|
3651
|
+
})
|
|
3652
|
+
: {};
|
|
3653
|
+
if (!telegramPin && effectiveRcRole === 'public') {
|
|
3654
|
+
// A public telegram spawn that reached here with no pin. Emitted
|
|
3655
|
+
// rather than left silent: the surface it gets is the pre-2666 single
|
|
3656
|
+
// entry, and without this line that is indistinguishable from a spawn
|
|
3657
|
+
// this feature was never meant to widen.
|
|
3658
|
+
deps.logger(`[telegram-public] op=pin-absent sessionId=${sessionId.slice(0, 8)} ` +
|
|
3659
|
+
`botId=${effectiveTelegramChannel.botId ?? 'none'} chatId=${effectiveTelegramChannel.chatId ?? 'none'}`);
|
|
3660
|
+
}
|
|
3512
3661
|
telegramPath = telegramChannelMcpConfigPath(sessionId, tmpdir());
|
|
3513
3662
|
try {
|
|
3514
|
-
|
|
3663
|
+
// Both servers in ONE config file, so the existing single
|
|
3664
|
+
// `--mcp-config` argv carries them. The dev-channels flag still names
|
|
3665
|
+
// `telegram-channel` alone: the plugin server is an ordinary MCP
|
|
3666
|
+
// server, not a development channel.
|
|
3667
|
+
writeFileSync(telegramPath, JSON.stringify({ mcpServers: { ...telegramServers, ...telegramPluginServers } }, null, 2), 'utf8');
|
|
3515
3668
|
argv.push(...buildTelegramChannelArgv(telegramPath));
|
|
3516
3669
|
channelServerName = TELEGRAM_CHANNEL_SERVER_NAME;
|
|
3670
|
+
if (telegramPin) {
|
|
3671
|
+
telegramPublicExtraTools = telegramPublicToolsFrom(deps.toolSurface.public);
|
|
3672
|
+
// The config write, with the count actually in the file. `count=N`
|
|
3673
|
+
// on the tool-allowlist line with no line here is the
|
|
3674
|
+
// argv-names-an-unregistered-server signature, and it is visible at
|
|
3675
|
+
// spawn with no visitor turn.
|
|
3676
|
+
deps.logger(`[telegram-public] op=mcp-registered sessionId=${sessionId.slice(0, 8)} ` +
|
|
3677
|
+
`server=${TELEGRAM_PLUGIN_SERVER_NAME} path=${telegramPath} tools=${telegramPublicExtraTools.length}`);
|
|
3678
|
+
}
|
|
3517
3679
|
deps.logger(`[rc-spawn] op=telegram-channel senderId=${effectiveTelegramChannel.senderId} gateway=${effectiveTelegramChannel.gatewayUrl} path=${telegramPath}`);
|
|
3518
3680
|
deps.logger(`[rc-spawn] op=channel-mcp-spawn channel=telegram node=${deps.nodeBin} exists=${existsSync(deps.nodeBin)}`);
|
|
3519
3681
|
}
|
|
3520
3682
|
catch (err) {
|
|
3521
3683
|
telegramPath = null;
|
|
3684
|
+
// Reset with it: granting tools whose server was never registered is
|
|
3685
|
+
// the exact failure the op=mcp-registered line exists to catch.
|
|
3686
|
+
telegramPublicExtraTools = [];
|
|
3522
3687
|
deps.logger(`[rc-spawn] op=telegram-channel-write-failed err=${JSON.stringify(err instanceof Error ? err.message : String(err))}`);
|
|
3523
3688
|
}
|
|
3524
3689
|
}
|
|
@@ -3549,7 +3714,7 @@ export function buildHttpApp(deps) {
|
|
|
3549
3714
|
// itself gated on a non-null role and so never repairs the file. The
|
|
3550
3715
|
// `op=role-resolution` line above is what makes that state greppable.
|
|
3551
3716
|
const publicLockdown = effectiveRcRole === 'public' || (effectiveRcRole === null && channelBound)
|
|
3552
|
-
? buildRcPublicLockdownArgv(channelServerName)
|
|
3717
|
+
? buildRcPublicLockdownArgv(channelServerName, telegramPublicExtraTools)
|
|
3553
3718
|
: null;
|
|
3554
3719
|
if (publicLockdown) {
|
|
3555
3720
|
// A re-seat fork pushes its own caller-picked --permission-mode above,
|