@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
package/package.json
CHANGED
|
@@ -24,6 +24,18 @@ export type DestinationValidity = {
|
|
|
24
24
|
export declare function normalizeE164(value: string): string;
|
|
25
25
|
/** Normalized equality, non-empty. */
|
|
26
26
|
export declare function phonesMatch(a: string, b: string): boolean;
|
|
27
|
+
export interface TelegramAdmins {
|
|
28
|
+
/** Every numeric id admitted on ANY of this account's admin bots. */
|
|
29
|
+
adminUsers: number[];
|
|
30
|
+
/** The same set, split by the bot that admits it. Task 2368 — a destination
|
|
31
|
+
* in bot A's list is not dispatchable on bot B, so the picker and the
|
|
32
|
+
* dispatch both need to know which bot owns it, not just that the account
|
|
33
|
+
* does. */
|
|
34
|
+
byBot: Array<{
|
|
35
|
+
botId: string;
|
|
36
|
+
adminUsers: number[];
|
|
37
|
+
}>;
|
|
38
|
+
}
|
|
27
39
|
interface WhatsAppAdmins {
|
|
28
40
|
adminPhones: string[];
|
|
29
41
|
/** Manager phone to the sub-account it is bound to. */
|
|
@@ -33,9 +45,7 @@ interface WhatsAppAdmins {
|
|
|
33
45
|
* malformed config yields empty lists (a fail-closed default: no destination
|
|
34
46
|
* validates against empty config). */
|
|
35
47
|
export declare function readChannelAdmins(accountDir: string, channel: "whatsapp"): WhatsAppAdmins;
|
|
36
|
-
export declare function readChannelAdmins(accountDir: string, channel: "telegram"):
|
|
37
|
-
adminUsers: number[];
|
|
38
|
-
};
|
|
48
|
+
export declare function readChannelAdmins(accountDir: string, channel: "telegram"): TelegramAdmins;
|
|
39
49
|
/**
|
|
40
50
|
* The account a WhatsApp `destination` is registered to on the HOUSE's lists,
|
|
41
51
|
* or null if it is in neither list. A manager binding wins over an admin match:
|
|
@@ -63,11 +73,12 @@ export declare function boundSubAccountFor(accountDir: string, destination: stri
|
|
|
63
73
|
* owns the single line and may target any registered admin/manager) OR the
|
|
64
74
|
* registered account equals the creating account. Any other case is
|
|
65
75
|
* `cross-account`.
|
|
66
|
-
* - telegram: house-only. Reads the creating account's own
|
|
67
|
-
*
|
|
68
|
-
* cross-account concept — an integer chat id present in
|
|
69
|
-
* anything else is `not-registered`.
|
|
70
|
-
* branch, which is why a telegram caller
|
|
76
|
+
* - telegram: house-only. Reads the creating account's own admin bot entries,
|
|
77
|
+
* unioned across every one of them (Task 2368); there is no manager surface
|
|
78
|
+
* and thus no cross-account concept — an integer chat id present in ANY of
|
|
79
|
+
* that account's admin bots is valid, anything else is `not-registered`.
|
|
80
|
+
* `houseAccountId` is unused on this branch, which is why a telegram caller
|
|
81
|
+
* need not resolve one.
|
|
71
82
|
*/
|
|
72
83
|
export declare function validateAgentDestination(accountsDir: string, houseAccountId: string, creatingAccountId: string, channel: AgentChannel, destination: string): DestinationValidity;
|
|
73
84
|
/** Which authoritative list an offered destination came from. Not a name: no
|
|
@@ -78,6 +89,9 @@ export interface DispatchDestination {
|
|
|
78
89
|
channel: AgentChannel;
|
|
79
90
|
destination: string;
|
|
80
91
|
label: DispatchLabel;
|
|
92
|
+
/** Task 2368 — which of the account's bots admits this destination. Absent on
|
|
93
|
+
* WhatsApp, which has no bot concept. */
|
|
94
|
+
botId?: string;
|
|
81
95
|
/** Task 2204 — a display-only person name, attached by a CALLER that has a
|
|
82
96
|
* graph to read. `listDispatchDestinations` never sets it: this module reads
|
|
83
97
|
* account.json and nothing else, and `validateAgentDestination` never
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA0BA,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,UAAU,CAAC;AAEnD;;;;;;;;GAQG;AACH,MAAM,MAAM,mBAAmB,GAC3B;IAAE,KAAK,EAAE,IAAI,CAAA;CAAE,GACf;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,gBAAgB,CAAA;CAAE,GAC1C;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,eAAe,CAAC;IAAC,kBAAkB,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnG;wCACwC;AACxC,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOnD;AAED,sCAAsC;AACtC,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAIzD;AAED,UAAU,cAAc;IACtB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,uDAAuD;IACvD,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACzC;AAED;;uCAEuC;AACvC,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,cAAc,CAAC;AAC3F,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA0BA,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,UAAU,CAAC;AAEnD;;;;;;;;GAQG;AACH,MAAM,MAAM,mBAAmB,GAC3B;IAAE,KAAK,EAAE,IAAI,CAAA;CAAE,GACf;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,gBAAgB,CAAA;CAAE,GAC1C;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,eAAe,CAAC;IAAC,kBAAkB,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnG;wCACwC;AACxC,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOnD;AAED,sCAAsC;AACtC,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAIzD;AAED,MAAM,WAAW,cAAc;IAC7B,qEAAqE;IACrE,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB;;;gBAGY;IACZ,KAAK,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;CACvD;AAED,UAAU,cAAc;IACtB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,uDAAuD;IACvD,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACzC;AAED;;uCAEuC;AACvC,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,cAAc,CAAC;AAC3F,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,cAAc,CAAC;AA6E3F;;;;;;GAMG;AACH,wBAAgB,+BAA+B,CAC7C,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,MAAM,EACtB,WAAW,EAAE,MAAM,GAClB,MAAM,GAAG,IAAI,CAOf;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMzF;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,wBAAwB,CACtC,WAAW,EAAE,MAAM,EACnB,cAAc,EAAE,MAAM,EACtB,iBAAiB,EAAE,MAAM,EACzB,OAAO,EAAE,YAAY,EACrB,WAAW,EAAE,MAAM,GAClB,mBAAmB,CAkBrB;AAED;;uDAEuD;AACvD,MAAM,MAAM,aAAa,GAAG,aAAa,GAAG,iBAAiB,GAAG,gBAAgB,CAAC;AAEjF,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,YAAY,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,aAAa,CAAC;IACrB;8CAC0C;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;iFAG6E;IAC7E,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE;IAC9C,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,mBAAmB,EAAE,CA+CxB"}
|
|
@@ -98,11 +98,35 @@ function readChannelAdmins(accountDir, channel) {
|
|
|
98
98
|
}
|
|
99
99
|
return { adminPhones, accountManagers };
|
|
100
100
|
}
|
|
101
|
+
// Task 2368 — the bots array. The two-slot `telegram.adminUsers` is deleted,
|
|
102
|
+
// so it is not read here: an install still holding it reads as empty until the
|
|
103
|
+
// boot converter rewrites it, which is the fail-closed default this function
|
|
104
|
+
// already applies to a missing config. A public entry has no adminUsers
|
|
105
|
+
// surface at all, so only admin entries contribute.
|
|
101
106
|
const tg = config.telegram ?? {};
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
107
|
+
const byBot = [];
|
|
108
|
+
const seen = new Set();
|
|
109
|
+
const adminUsers = [];
|
|
110
|
+
if (Array.isArray(tg.bots)) {
|
|
111
|
+
for (const raw of tg.bots) {
|
|
112
|
+
if (!raw || typeof raw !== "object")
|
|
113
|
+
continue;
|
|
114
|
+
const b = raw;
|
|
115
|
+
if (b.role !== "admin" || typeof b.id !== "string" || b.id.length === 0)
|
|
116
|
+
continue;
|
|
117
|
+
const users = Array.isArray(b.adminUsers)
|
|
118
|
+
? b.adminUsers.filter((u) => typeof u === "number")
|
|
119
|
+
: [];
|
|
120
|
+
byBot.push({ botId: b.id, adminUsers: users });
|
|
121
|
+
for (const u of users) {
|
|
122
|
+
if (seen.has(u))
|
|
123
|
+
continue;
|
|
124
|
+
seen.add(u);
|
|
125
|
+
adminUsers.push(u);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return { adminUsers, byBot };
|
|
106
130
|
}
|
|
107
131
|
/**
|
|
108
132
|
* The account a WhatsApp `destination` is registered to on the HOUSE's lists,
|
|
@@ -147,11 +171,12 @@ function boundSubAccountFor(accountDir, destination) {
|
|
|
147
171
|
* owns the single line and may target any registered admin/manager) OR the
|
|
148
172
|
* registered account equals the creating account. Any other case is
|
|
149
173
|
* `cross-account`.
|
|
150
|
-
* - telegram: house-only. Reads the creating account's own
|
|
151
|
-
*
|
|
152
|
-
* cross-account concept — an integer chat id present in
|
|
153
|
-
* anything else is `not-registered`.
|
|
154
|
-
* branch, which is why a telegram caller
|
|
174
|
+
* - telegram: house-only. Reads the creating account's own admin bot entries,
|
|
175
|
+
* unioned across every one of them (Task 2368); there is no manager surface
|
|
176
|
+
* and thus no cross-account concept — an integer chat id present in ANY of
|
|
177
|
+
* that account's admin bots is valid, anything else is `not-registered`.
|
|
178
|
+
* `houseAccountId` is unused on this branch, which is why a telegram caller
|
|
179
|
+
* need not resolve one.
|
|
155
180
|
*/
|
|
156
181
|
function validateAgentDestination(accountsDir, houseAccountId, creatingAccountId, channel, destination) {
|
|
157
182
|
if (channel === "whatsapp") {
|
|
@@ -218,10 +243,19 @@ function listDispatchDestinations(input) {
|
|
|
218
243
|
admins.push({ channel: "whatsapp", destination: phone, label: "House admin" });
|
|
219
244
|
}
|
|
220
245
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
246
|
+
// Task 2368 — one offer per (bot, destination) pair. A destination in bot A's
|
|
247
|
+
// list cannot be dispatched on bot B, so the label alone no longer identifies
|
|
248
|
+
// where the message would go.
|
|
249
|
+
const { byBot } = readChannelAdmins((0, node_path_1.resolve)(accountsDir, accountId), "telegram");
|
|
250
|
+
const telegram = byBot
|
|
251
|
+
.flatMap((b) => b.adminUsers.map((id) => ({ botId: b.botId, id })))
|
|
252
|
+
.sort((a, b) => a.id - b.id || a.botId.localeCompare(b.botId))
|
|
253
|
+
.map(({ botId, id }) => ({
|
|
254
|
+
channel: "telegram",
|
|
255
|
+
destination: String(id),
|
|
256
|
+
label: "Telegram admin",
|
|
257
|
+
botId,
|
|
258
|
+
}));
|
|
225
259
|
const byDestination = (a, b) => a.destination.localeCompare(b.destination);
|
|
226
260
|
return [...managers.sort(byDestination), ...admins.sort(byDestination), ...telegram];
|
|
227
261
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;AA4CA,sCAOC;AAGD,kCAIC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;AA4CA,sCAOC;AAGD,kCAIC;AAuBD,8CA0EC;AASD,0EAWC;AAOD,gDAMC;AAsBD,4DAwBC;AAgCD,4DAmDC;AA7TD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qCAAuC;AACvC,yCAAoC;AAkBpC;wCACwC;AACxC,SAAgB,aAAa,CAAC,KAAa;IACzC,IAAI,SAAS,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;IACvD,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;IACjD,IAAI,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,SAAS,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;SACzD,IAAI,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,SAAS,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACpE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC;QAAE,OAAO,EAAE,CAAC;IACxC,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,sCAAsC;AACtC,SAAgB,WAAW,CAAC,CAAS,EAAE,CAAS;IAC9C,MAAM,EAAE,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;IAC5B,MAAM,EAAE,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;IAC5B,OAAO,EAAE,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC;AACpC,CAAC;AAuBD,SAAgB,iBAAiB,CAC/B,UAAkB,EAClB,OAAqB;IAErB,IAAI,MAAM,GAA4B,EAAE,CAAC;IACzC,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAA,sBAAY,EAAC,IAAA,mBAAO,EAAC,UAAU,EAAE,cAAc,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;IAClF,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,GAAG,EAAE,CAAC;IACd,CAAC;IACD,IAAI,OAAO,KAAK,UAAU,EAAE,CAAC;QAC3B,MAAM,EAAE,GAAI,MAAM,CAAC,QAAgD,IAAI,EAAE,CAAC;QAC1E,MAAM,WAAW,GAAG,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,WAAW,CAAC;YAC/C,CAAC,CAAC,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC;YAClE,CAAC,CAAC,EAAE,CAAC;QACP,MAAM,WAAW,GAAI,EAAE,CAAC,eAAuD,IAAI,EAAE,CAAC;QACtF,MAAM,eAAe,GAA2B,EAAE,CAAC;QACnD,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;YACjD,yEAAyE;YACzE,mDAAmD;YACnD,EAAE;YACF,wEAAwE;YACxE,mEAAmE;YACnE,oEAAoE;YACpE,uEAAuE;YACvE,yEAAyE;YACzE,uEAAuE;YACvE,oEAAoE;YACpE,uEAAuE;YACvE,UAAU;YACV,EAAE;YACF,sEAAsE;YACtE,uEAAuE;YACvE,wEAAwE;YACxE,uEAAuE;YACvE,sEAAsE;YACtE,wEAAwE;YACxE,0CAA0C;YAC1C,IAAI,OAAO,CAAC,KAAK,QAAQ;gBAAE,eAAe,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;iBAC7C,IAAI,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAQ,CAAkC,CAAC,cAAc,KAAK,QAAQ,EAAE,CAAC;gBAC9G,MAAM,IAAI,GAAI,CAAwB,CAAC,IAAI,CAAC;gBAC5C,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;oBAC5C,eAAe,CAAC,CAAC,CAAC,GAAI,CAAgC,CAAC,cAAc,CAAC;gBACxE,CAAC;YACH,CAAC;QACH,CAAC;QACD,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,CAAC;IAC1C,CAAC;IACD,6EAA6E;IAC7E,+EAA+E;IAC/E,6EAA6E;IAC7E,wEAAwE;IACxE,oDAAoD;IACpD,MAAM,EAAE,GAAI,MAAM,CAAC,QAA2C,IAAI,EAAE,CAAC;IACrE,MAAM,KAAK,GAAmD,EAAE,CAAC;IACjE,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,IAAI,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3B,KAAK,MAAM,GAAG,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC;YAC1B,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;gBAAE,SAAS;YAC9C,MAAM,CAAC,GAAG,GAA6D,CAAC;YACxE,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO,IAAI,OAAO,CAAC,CAAC,EAAE,KAAK,QAAQ,IAAI,CAAC,CAAC,EAAE,CAAC,MAAM,KAAK,CAAC;gBAAE,SAAS;YAClF,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC;gBACvC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC;gBAChE,CAAC,CAAC,EAAE,CAAC;YACP,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC;YAC/C,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;gBACtB,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;oBAAE,SAAS;gBAC1B,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;gBACZ,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACrB,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;AAC/B,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,+BAA+B,CAC7C,QAAgB,EAChB,cAAsB,EACtB,WAAmB;IAEnB,MAAM,EAAE,WAAW,EAAE,eAAe,EAAE,GAAG,iBAAiB,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IACjF,KAAK,MAAM,CAAC,YAAY,EAAE,YAAY,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,EAAE,CAAC;QAC3E,IAAI,YAAY,IAAI,WAAW,CAAC,YAAY,EAAE,WAAW,CAAC;YAAE,OAAO,YAAY,CAAC;IAClF,CAAC;IACD,IAAI,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;QAAE,OAAO,cAAc,CAAC;IAChF,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,SAAgB,kBAAkB,CAAC,UAAkB,EAAE,WAAmB;IACxE,MAAM,EAAE,eAAe,EAAE,GAAG,iBAAiB,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;IACtE,KAAK,MAAM,CAAC,YAAY,EAAE,YAAY,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,EAAE,CAAC;QAC3E,IAAI,YAAY,IAAI,WAAW,CAAC,YAAY,EAAE,WAAW,CAAC;YAAE,OAAO,YAAY,CAAC;IAClF,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,SAAgB,wBAAwB,CACtC,WAAmB,EACnB,cAAsB,EACtB,iBAAyB,EACzB,OAAqB,EACrB,WAAmB;IAEnB,IAAI,OAAO,KAAK,UAAU,EAAE,CAAC;QAC3B,MAAM,QAAQ,GAAG,IAAA,mBAAO,EAAC,WAAW,EAAE,cAAc,CAAC,CAAC;QACtD,MAAM,UAAU,GAAG,+BAA+B,CAAC,QAAQ,EAAE,cAAc,EAAE,WAAW,CAAC,CAAC;QAC1F,IAAI,UAAU,KAAK,IAAI;YAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,CAAC;QAC3E,IAAI,iBAAiB,KAAK,cAAc;YAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QACjE,IAAI,UAAU,KAAK,iBAAiB;YAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QAC7D,OAAO;YACL,KAAK,EAAE,KAAK;YACZ,MAAM,EAAE,eAAe;YACvB,kBAAkB,EAAE,UAAU;YAC9B,eAAe,EAAE,iBAAiB;SACnC,CAAC;IACJ,CAAC;IACD,MAAM,EAAE,UAAU,EAAE,GAAG,iBAAiB,CAAC,IAAA,mBAAO,EAAC,WAAW,EAAE,iBAAiB,CAAC,EAAE,UAAU,CAAC,CAAC;IAC9F,MAAM,QAAQ,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC;IACrC,MAAM,EAAE,GAAG,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACvE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,CAAC;AAC3E,CAAC;AAqBD;;;;;;;;;;GAUG;AACH,SAAgB,wBAAwB,CAAC,KAIxC;IACC,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,SAAS,EAAE,GAAG,KAAK,CAAC;IACzD,MAAM,QAAQ,GAA0B,EAAE,CAAC;IAC3C,MAAM,MAAM,GAA0B,EAAE,CAAC;IAEzC,IAAI,cAAc,KAAK,IAAI,EAAE,CAAC;QAC5B,MAAM,QAAQ,GAAG,IAAA,mBAAO,EAAC,WAAW,EAAE,cAAc,CAAC,CAAC;QACtD,MAAM,EAAE,WAAW,EAAE,eAAe,EAAE,GAAG,iBAAiB,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;QACjF,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;QAC/B,0EAA0E;QAC1E,4EAA4E;QAC5E,+DAA+D;QAC/D,KAAK,MAAM,CAAC,KAAK,EAAE,YAAY,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,EAAE,CAAC;YACpE,MAAM,GAAG,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;YACjC,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,SAAS;YAChD,IAAI,SAAS,KAAK,cAAc,IAAI,YAAY,KAAK,SAAS;gBAAE,SAAS;YACzE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACd,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,KAAK,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC,CAAC;QACvF,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,WAAW,EAAE,CAAC;YAChC,MAAM,GAAG,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;YACjC,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,SAAS;YAChD,wEAAwE;YACxE,2DAA2D;YAC3D,IAAI,SAAS,KAAK,cAAc;gBAAE,SAAS;YAC3C,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACd,MAAM,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,KAAK,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC,CAAC;QACjF,CAAC;IACH,CAAC;IAED,8EAA8E;IAC9E,8EAA8E;IAC9E,8BAA8B;IAC9B,MAAM,EAAE,KAAK,EAAE,GAAG,iBAAiB,CAAC,IAAA,mBAAO,EAAC,WAAW,EAAE,SAAS,CAAC,EAAE,UAAU,CAAC,CAAC;IACjF,MAAM,QAAQ,GAA0B,KAAK;SAC1C,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;SAClE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;SAC7D,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;QACvB,OAAO,EAAE,UAAmB;QAC5B,WAAW,EAAE,MAAM,CAAC,EAAE,CAAC;QACvB,KAAK,EAAE,gBAAyB;QAChC,KAAK;KACN,CAAC,CAAC,CAAC;IAEN,MAAM,aAAa,GAAG,CAAC,CAAsB,EAAE,CAAsB,EAAE,EAAE,CACvE,CAAC,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;IAC7C,OAAO,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,GAAG,QAAQ,CAAC,CAAC;AACvF,CAAC"}
|
|
@@ -170,7 +170,7 @@ describe("readChannelAdmins", () => {
|
|
|
170
170
|
|
|
171
171
|
it("keeps only numeric telegram adminUsers", () => {
|
|
172
172
|
const accountsDir = makeAccountsDir((d) => {
|
|
173
|
-
seedAccount(d, SUB_A, { telegram: { adminUsers: [123, "456", 789] } });
|
|
173
|
+
seedAccount(d, SUB_A, { telegram: { bots: [{ id: "111", token: "111:A", role: "admin", adminUsers: [123, "456", 789] }] } });
|
|
174
174
|
});
|
|
175
175
|
expect(readChannelAdmins(join(accountsDir, SUB_A), "telegram").adminUsers).toEqual([123, 789]);
|
|
176
176
|
});
|
|
@@ -239,7 +239,7 @@ describe("validateAgentDestination", () => {
|
|
|
239
239
|
function houseWith(managers: Record<string, unknown>, adminPhones: string[] = ["+447700900111"]): string {
|
|
240
240
|
return makeAccountsDir((d) => {
|
|
241
241
|
seedAccount(d, HOUSE, { role: "house", whatsapp: { adminPhones, accountManagers: managers } });
|
|
242
|
-
seedAccount(d, SUB_A, { telegram: { adminUsers: [555001] } });
|
|
242
|
+
seedAccount(d, SUB_A, { telegram: { bots: [{ id: "111", token: "111:A", role: "admin", adminUsers: [555001] }] } });
|
|
243
243
|
seedAccount(d, SUB_B, {});
|
|
244
244
|
});
|
|
245
245
|
}
|
|
@@ -317,9 +317,9 @@ describe("listDispatchDestinations", () => {
|
|
|
317
317
|
"+447700900444": { managesAccount: SUB_B, mode: "active" },
|
|
318
318
|
},
|
|
319
319
|
},
|
|
320
|
-
telegram: { adminUsers: [900001] },
|
|
320
|
+
telegram: { bots: [{ id: "900", token: "900:H", role: "admin", adminUsers: [900001] }] },
|
|
321
321
|
});
|
|
322
|
-
seedAccount(d, SUB_A, { telegram: { adminUsers: [555002, 555001] } });
|
|
322
|
+
seedAccount(d, SUB_A, { telegram: { bots: [{ id: "111", token: "111:A", role: "admin", adminUsers: [555002, 555001] }] } });
|
|
323
323
|
seedAccount(d, SUB_B, {});
|
|
324
324
|
});
|
|
325
325
|
}
|
|
@@ -330,7 +330,7 @@ describe("listDispatchDestinations", () => {
|
|
|
330
330
|
{ channel: "whatsapp", destination: "+447700900222", label: "Account manager" },
|
|
331
331
|
{ channel: "whatsapp", destination: "+447700900444", label: "Account manager" },
|
|
332
332
|
{ channel: "whatsapp", destination: "+447700900111", label: "House admin" },
|
|
333
|
-
{ channel: "telegram", destination: "900001", label: "Telegram admin" },
|
|
333
|
+
{ channel: "telegram", destination: "900001", label: "Telegram admin", botId: "900" },
|
|
334
334
|
]);
|
|
335
335
|
});
|
|
336
336
|
|
|
@@ -347,8 +347,8 @@ describe("listDispatchDestinations", () => {
|
|
|
347
347
|
const dir = seeded();
|
|
348
348
|
expect(listDispatchDestinations({ accountsDir: dir, houseAccountId: HOUSE, accountId: SUB_A })).toEqual([
|
|
349
349
|
{ channel: "whatsapp", destination: "+447700900222", label: "Account manager" },
|
|
350
|
-
{ channel: "telegram", destination: "555001", label: "Telegram admin" },
|
|
351
|
-
{ channel: "telegram", destination: "555002", label: "Telegram admin" },
|
|
350
|
+
{ channel: "telegram", destination: "555001", label: "Telegram admin", botId: "111" },
|
|
351
|
+
{ channel: "telegram", destination: "555002", label: "Telegram admin", botId: "111" },
|
|
352
352
|
]);
|
|
353
353
|
});
|
|
354
354
|
|
|
@@ -364,8 +364,8 @@ describe("listDispatchDestinations", () => {
|
|
|
364
364
|
it("drops every WhatsApp entry when the house cannot be resolved, keeping telegram", () => {
|
|
365
365
|
const dir = seeded();
|
|
366
366
|
expect(listDispatchDestinations({ accountsDir: dir, houseAccountId: null, accountId: SUB_A })).toEqual([
|
|
367
|
-
{ channel: "telegram", destination: "555001", label: "Telegram admin" },
|
|
368
|
-
{ channel: "telegram", destination: "555002", label: "Telegram admin" },
|
|
367
|
+
{ channel: "telegram", destination: "555001", label: "Telegram admin", botId: "111" },
|
|
368
|
+
{ channel: "telegram", destination: "555002", label: "Telegram admin", botId: "111" },
|
|
369
369
|
]);
|
|
370
370
|
});
|
|
371
371
|
|
|
@@ -382,3 +382,62 @@ describe("listDispatchDestinations", () => {
|
|
|
382
382
|
expect(listDispatchDestinations({ accountsDir: dir, houseAccountId: HOUSE, accountId: stranger })).toEqual([]);
|
|
383
383
|
});
|
|
384
384
|
});
|
|
385
|
+
|
|
386
|
+
describe("Task 2368 — telegram bots array", () => {
|
|
387
|
+
it("unions adminUsers across admin entries and ignores public entries", () => {
|
|
388
|
+
const accountsDir = makeAccountsDir((d) => {
|
|
389
|
+
seedAccount(d, SUB_A, { telegram: { bots: [
|
|
390
|
+
{ id: "111", token: "111:A", role: "admin", adminUsers: [1, 2] },
|
|
391
|
+
{ id: "222", token: "222:B", role: "admin", adminUsers: [2, 3] },
|
|
392
|
+
{ id: "333", token: "333:C", role: "public", agent: "beagle", allowFrom: [99] },
|
|
393
|
+
] } });
|
|
394
|
+
});
|
|
395
|
+
expect(readChannelAdmins(join(accountsDir, SUB_A), "telegram").adminUsers).toEqual([1, 2, 3]);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it("reports which bot each destination belongs to", () => {
|
|
399
|
+
const accountsDir = makeAccountsDir((d) => {
|
|
400
|
+
seedAccount(d, SUB_A, { telegram: { bots: [
|
|
401
|
+
{ id: "111", token: "111:A", role: "admin", adminUsers: [1] },
|
|
402
|
+
{ id: "222", token: "222:B", role: "admin", adminUsers: [2] },
|
|
403
|
+
] } });
|
|
404
|
+
});
|
|
405
|
+
expect(readChannelAdmins(join(accountsDir, SUB_A), "telegram").byBot).toEqual([
|
|
406
|
+
{ botId: "111", adminUsers: [1] },
|
|
407
|
+
{ botId: "222", adminUsers: [2] },
|
|
408
|
+
]);
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
it("labels each offered destination with its bot", () => {
|
|
412
|
+
const accountsDir = makeAccountsDir((d) => {
|
|
413
|
+
seedAccount(d, SUB_A, { telegram: { bots: [
|
|
414
|
+
{ id: "111", token: "111:A", role: "admin", adminUsers: [1] },
|
|
415
|
+
{ id: "222", token: "222:B", role: "admin", adminUsers: [2] },
|
|
416
|
+
] } });
|
|
417
|
+
});
|
|
418
|
+
expect(listDispatchDestinations({ accountsDir, houseAccountId: null, accountId: SUB_A })).toEqual([
|
|
419
|
+
{ channel: "telegram", destination: "1", label: "Telegram admin", botId: "111" },
|
|
420
|
+
{ channel: "telegram", destination: "2", label: "Telegram admin", botId: "222" },
|
|
421
|
+
]);
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
it("validates a destination held by ANY admin entry", () => {
|
|
425
|
+
const accountsDir = makeAccountsDir((d) => {
|
|
426
|
+
seedAccount(d, SUB_A, { telegram: { bots: [
|
|
427
|
+
{ id: "111", token: "111:A", role: "admin", adminUsers: [1] },
|
|
428
|
+
{ id: "222", token: "222:B", role: "admin", adminUsers: [2] },
|
|
429
|
+
] } });
|
|
430
|
+
});
|
|
431
|
+
expect(validateAgentDestination(accountsDir, HOUSE, SUB_A, "telegram", "2")).toEqual({ valid: true });
|
|
432
|
+
expect(validateAgentDestination(accountsDir, HOUSE, SUB_A, "telegram", "9")).toEqual({
|
|
433
|
+
valid: false, reason: "not-registered",
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
it("reads nothing from the deleted two-slot shape", () => {
|
|
438
|
+
const accountsDir = makeAccountsDir((d) => {
|
|
439
|
+
seedAccount(d, SUB_A, { telegram: { adminUsers: [1, 2] } });
|
|
440
|
+
});
|
|
441
|
+
expect(readChannelAdmins(join(accountsDir, SUB_A), "telegram").adminUsers).toEqual([]);
|
|
442
|
+
});
|
|
443
|
+
});
|
|
@@ -58,6 +58,16 @@ export function phonesMatch(a: string, b: string): boolean {
|
|
|
58
58
|
return na.length > 0 && na === nb;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
export interface TelegramAdmins {
|
|
62
|
+
/** Every numeric id admitted on ANY of this account's admin bots. */
|
|
63
|
+
adminUsers: number[];
|
|
64
|
+
/** The same set, split by the bot that admits it. Task 2368 — a destination
|
|
65
|
+
* in bot A's list is not dispatchable on bot B, so the picker and the
|
|
66
|
+
* dispatch both need to know which bot owns it, not just that the account
|
|
67
|
+
* does. */
|
|
68
|
+
byBot: Array<{ botId: string; adminUsers: number[] }>;
|
|
69
|
+
}
|
|
70
|
+
|
|
61
71
|
interface WhatsAppAdmins {
|
|
62
72
|
adminPhones: string[];
|
|
63
73
|
/** Manager phone to the sub-account it is bound to. */
|
|
@@ -68,11 +78,11 @@ interface WhatsAppAdmins {
|
|
|
68
78
|
* malformed config yields empty lists (a fail-closed default: no destination
|
|
69
79
|
* validates against empty config). */
|
|
70
80
|
export function readChannelAdmins(accountDir: string, channel: "whatsapp"): WhatsAppAdmins;
|
|
71
|
-
export function readChannelAdmins(accountDir: string, channel: "telegram"):
|
|
81
|
+
export function readChannelAdmins(accountDir: string, channel: "telegram"): TelegramAdmins;
|
|
72
82
|
export function readChannelAdmins(
|
|
73
83
|
accountDir: string,
|
|
74
84
|
channel: AgentChannel,
|
|
75
|
-
): WhatsAppAdmins |
|
|
85
|
+
): WhatsAppAdmins | TelegramAdmins {
|
|
76
86
|
let config: Record<string, unknown> = {};
|
|
77
87
|
try {
|
|
78
88
|
config = JSON.parse(readFileSync(resolve(accountDir, "account.json"), "utf-8"));
|
|
@@ -117,11 +127,32 @@ export function readChannelAdmins(
|
|
|
117
127
|
}
|
|
118
128
|
return { adminPhones, accountManagers };
|
|
119
129
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
130
|
+
// Task 2368 — the bots array. The two-slot `telegram.adminUsers` is deleted,
|
|
131
|
+
// so it is not read here: an install still holding it reads as empty until the
|
|
132
|
+
// boot converter rewrites it, which is the fail-closed default this function
|
|
133
|
+
// already applies to a missing config. A public entry has no adminUsers
|
|
134
|
+
// surface at all, so only admin entries contribute.
|
|
135
|
+
const tg = (config.telegram as { bots?: unknown } | undefined) ?? {};
|
|
136
|
+
const byBot: Array<{ botId: string; adminUsers: number[] }> = [];
|
|
137
|
+
const seen = new Set<number>();
|
|
138
|
+
const adminUsers: number[] = [];
|
|
139
|
+
if (Array.isArray(tg.bots)) {
|
|
140
|
+
for (const raw of tg.bots) {
|
|
141
|
+
if (!raw || typeof raw !== "object") continue;
|
|
142
|
+
const b = raw as { id?: unknown; role?: unknown; adminUsers?: unknown };
|
|
143
|
+
if (b.role !== "admin" || typeof b.id !== "string" || b.id.length === 0) continue;
|
|
144
|
+
const users = Array.isArray(b.adminUsers)
|
|
145
|
+
? b.adminUsers.filter((u): u is number => typeof u === "number")
|
|
146
|
+
: [];
|
|
147
|
+
byBot.push({ botId: b.id, adminUsers: users });
|
|
148
|
+
for (const u of users) {
|
|
149
|
+
if (seen.has(u)) continue;
|
|
150
|
+
seen.add(u);
|
|
151
|
+
adminUsers.push(u);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return { adminUsers, byBot };
|
|
125
156
|
}
|
|
126
157
|
|
|
127
158
|
/**
|
|
@@ -170,11 +201,12 @@ export function boundSubAccountFor(accountDir: string, destination: string): str
|
|
|
170
201
|
* owns the single line and may target any registered admin/manager) OR the
|
|
171
202
|
* registered account equals the creating account. Any other case is
|
|
172
203
|
* `cross-account`.
|
|
173
|
-
* - telegram: house-only. Reads the creating account's own
|
|
174
|
-
*
|
|
175
|
-
* cross-account concept — an integer chat id present in
|
|
176
|
-
* anything else is `not-registered`.
|
|
177
|
-
* branch, which is why a telegram caller
|
|
204
|
+
* - telegram: house-only. Reads the creating account's own admin bot entries,
|
|
205
|
+
* unioned across every one of them (Task 2368); there is no manager surface
|
|
206
|
+
* and thus no cross-account concept — an integer chat id present in ANY of
|
|
207
|
+
* that account's admin bots is valid, anything else is `not-registered`.
|
|
208
|
+
* `houseAccountId` is unused on this branch, which is why a telegram caller
|
|
209
|
+
* need not resolve one.
|
|
178
210
|
*/
|
|
179
211
|
export function validateAgentDestination(
|
|
180
212
|
accountsDir: string,
|
|
@@ -211,6 +243,9 @@ export interface DispatchDestination {
|
|
|
211
243
|
channel: AgentChannel;
|
|
212
244
|
destination: string;
|
|
213
245
|
label: DispatchLabel;
|
|
246
|
+
/** Task 2368 — which of the account's bots admits this destination. Absent on
|
|
247
|
+
* WhatsApp, which has no bot concept. */
|
|
248
|
+
botId?: string;
|
|
214
249
|
/** Task 2204 — a display-only person name, attached by a CALLER that has a
|
|
215
250
|
* graph to read. `listDispatchDestinations` never sets it: this module reads
|
|
216
251
|
* account.json and nothing else, and `validateAgentDestination` never
|
|
@@ -263,10 +298,19 @@ export function listDispatchDestinations(input: {
|
|
|
263
298
|
}
|
|
264
299
|
}
|
|
265
300
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
301
|
+
// Task 2368 — one offer per (bot, destination) pair. A destination in bot A's
|
|
302
|
+
// list cannot be dispatched on bot B, so the label alone no longer identifies
|
|
303
|
+
// where the message would go.
|
|
304
|
+
const { byBot } = readChannelAdmins(resolve(accountsDir, accountId), "telegram");
|
|
305
|
+
const telegram: DispatchDestination[] = byBot
|
|
306
|
+
.flatMap((b) => b.adminUsers.map((id) => ({ botId: b.botId, id })))
|
|
307
|
+
.sort((a, b) => a.id - b.id || a.botId.localeCompare(b.botId))
|
|
308
|
+
.map(({ botId, id }) => ({
|
|
309
|
+
channel: "telegram" as const,
|
|
310
|
+
destination: String(id),
|
|
311
|
+
label: "Telegram admin" as const,
|
|
312
|
+
botId,
|
|
313
|
+
}));
|
|
270
314
|
|
|
271
315
|
const byDestination = (a: DispatchDestination, b: DispatchDestination) =>
|
|
272
316
|
a.destination.localeCompare(b.destination);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: platform-architecture
|
|
3
3
|
description: Use when grounding any documented-surface claim about what Maxy ships — plugins, skills, specialists, install/deploy flows, internals. This is the install catalogue, not evidence of what is enabled on the current account. For install state on this account, call `capabilities-here`; for documented surface, cite the `Source:` URL inline.
|
|
4
|
-
content-hash: sha256:
|
|
4
|
+
content-hash: sha256:b20c0cc4943d42c84f0e324403b1da911481214c3834ff2c778daf1f5f6db086
|
|
5
5
|
brand: maxy-code
|
|
6
6
|
product-name: Maxy
|
|
7
7
|
---
|
|
@@ -3441,7 +3441,7 @@ either is a regression.
|
|
|
3441
3441
|
|---|---|---|
|
|
3442
3442
|
| `/session` | Admin cookie session: PIN-gated mint, validate, rotate. | `GET /`, `POST /` |
|
|
3443
3443
|
| `/sessions` | Legacy admin-server conversation routes. No UI consumer remains after the ConversationsModal was retired; the surviving handlers are deletion candidates and not described here. | (legacy, no live caller) |
|
|
3444
|
-
| `/sidebar-sessions` | Sole data path for the sidebar Sessions list. One JSONL on disk equals one row. The row's delete button is the only way a row disappears. Each row carries `sessionId`, `title`, `startedAt`, `live`, `isSubagent`, `pid: number \| null` (basename of the matched `sessions/<pid>.json`), and `projectDir` (the directory holding the JSONL — consumed by the delete route). The payload also carries top-level `accountId` so the pane renders the full UUID label whose first ~8 chars prefix-match the truncated Remote Control daemon entry in claude.ai/code. The legacy `rcUrl` field is gone — the row's external-link affordance now POSTs `/session-rc-spawn` to start a fresh local `claude --remote-control <name> --session-id <sid>` PTY on every click. **WhatsApp and Telegram sessions (admin + public) appear in this list**, colour-coded by channel (WhatsApp green `#25D366`, Telegram blue `#229ED9`, via a `conv-channel-*` left accent bar) and carrying the standard row kebab (Usage & cost, Reset/re-seat, Rename, Archive, Delete) so the operator can inspect and re-seat a channel session's params. Each row's `channel` is the authoritative `.meta.json` sidecar value (`whatsapp`/`telegram`/`webchat`/`browser`/null), not the JSONL-envelope channel. Each row also carries its sidecar `senderId`. For a WhatsApp/Telegram row with a `senderId` and no operator rename or ai-title (`titleSource:'prefix'`), the route resolves a contact `personName` and the sidebar renders the reader panel's `"<name> <number>"` label via the shared `operatorConvName`, degrading to the number alone then the stripped title on a miss. On a `whatsapp` row the number is printed in international form (`+44 7504 472444`, `+33 7 68 24 30 67`) by `formatSenderPhone` — `normalizeE164` then libphonenumber-js `formatInternational`, gated on `isPossible` so an unresolved LID prints raw; a `telegram` senderId is
|
|
3444
|
+
| `/sidebar-sessions` | Sole data path for the sidebar Sessions list. One JSONL on disk equals one row. The row's delete button is the only way a row disappears. Each row carries `sessionId`, `title`, `startedAt`, `live`, `isSubagent`, `pid: number \| null` (basename of the matched `sessions/<pid>.json`), and `projectDir` (the directory holding the JSONL — consumed by the delete route). The payload also carries top-level `accountId` so the pane renders the full UUID label whose first ~8 chars prefix-match the truncated Remote Control daemon entry in claude.ai/code. The legacy `rcUrl` field is gone — the row's external-link affordance now POSTs `/session-rc-spawn` to start a fresh local `claude --remote-control <name> --session-id <sid>` PTY on every click. **WhatsApp and Telegram sessions (admin + public) appear in this list**, colour-coded by channel (WhatsApp green `#25D366`, Telegram blue `#229ED9`, via a `conv-channel-*` left accent bar) and carrying the standard row kebab (Usage & cost, Reset/re-seat, Rename, Archive, Delete) so the operator can inspect and re-seat a channel session's params. Each row's `channel` is the authoritative `.meta.json` sidecar value (`whatsapp`/`telegram`/`webchat`/`browser`/null), not the JSONL-envelope channel. Each row also carries its sidecar `senderId`. For a WhatsApp/Telegram row with a `senderId` and no operator rename or ai-title (`titleSource:'prefix'`), the route resolves a contact `personName` and the sidebar renders the reader panel's `"<name> <number>"` label via the shared `operatorConvName`, degrading to the number alone then the stripped title on a miss. On a `whatsapp` row the number is printed in international form (`+44 7504 472444`, `+33 7 68 24 30 67`) by `formatSenderPhone` — `normalizeE164` then libphonenumber-js `formatInternational`, gated on `isPossible` so an unresolved LID prints raw; a `telegram` senderId is the sender half of that row's `<botId>:<senderId>` channel key (the route splits it; the sidecar stores the composite) and a `webchat` one is `session:<id>`, so neither is regrouped. The formatting is display-only: row identity, matching, and store keys stay on the normalized digits. Because the label function is shared, a Sessions row and that conversation's reader-panel row read identically. A channel row name has two sources, in precedence order. First the shared `resolveOperatorNamesBySender` helper the WhatsApp reader also calls, which matches the sender phone against the account's admins — that names an admin WhatsApp session and every Telegram session. Second, for a `whatsapp` row only, the WhatsApp pushName from the account's on-disk message store (`storePushNamesBySender` over `readConversationSummaries`, keyed by `normalizeE164` of the counterparty phone so a `+44 7…` sender matches a `447…` store key). The store is consulted only when the resolver reports **no binding** for that sender. That is typically a public WhatsApp customer, the case this exists for — such a sender has no admin record and so can only be named by the store, and without it those rows degraded to the bare number while the reader panel named them — though an admin missing from `users.json` qualifies too. A resolver miss caused by Neo4j being unreachable deliberately does NOT qualify: that is an outage rather than an absent binding, so the row degrades to the number and still logs `reason=neo4j-down`, keeping the outage signal intact. Telegram has no message store and is never looked up. The store read is guarded: a corrupt or absent store degrades to no pushName and logs `op=store-unreadable`, never 500s the list. Both sources are the ones the reader itself uses, so the two surfaces cannot drift; a renamed or ai-titled channel row keeps its own title. The Sessions list reads the viewed account's own store only. An account-manager's thread persists under the house account rather than the sub-account it manages, so on a multi-account install viewing a sub-account that row is not named here even though the reader panel names it — the reader additionally consults the house store, filtered to manager phones bound to that sub. The only role/channel excluded from the list is `public`+`webchat` (its dedicated reader is the sole surface); the exclusion predicate is `isSessionListExcluded`, decoupled from the reader's inclusion predicate `isReaderChannelSession` (unchanged), so the WhatsApp/Telegram reader panels are unaffected — a store-only WhatsApp conversation with no session JSONL stays reader-only. Observability rides `[admin-sessions-list] … channelInList=whatsapp:<n>,telegram:<n>,webchat:<n> channelNameCandidates=<n> channelNamesResolved=<n> (operator=<n> store=<n>) unknownChannel=<n> excludedPublicWebchat=<n>`, and each unresolved channel name logs one `[admin-sessions-list] op=channel-name-unresolved sessionId=<id8> channel=<whatsapp\|telegram> senderId=<id> reason=<no-senderId\|classify-miss\|neo4j-down>` line — emitted only when **both** sources miss, so a residual shortfall means a genuinely nameless sender rather than the expected public-customer floor, and the `operator=`/`store=` split says which source is carrying the names. | `GET /` |
|
|
3445
3445
|
| `/session-delete` | POST `{ sessionId }`. Thin proxy over the manager: POST `/:id/stop` (idempotent) then DELETE `/:id`. Sends **no** accountId. The manager resolves the session by scanning every slug under `projects/` for its JSONL (`slugForExistingJsonl`, covering `archive/`), exactly as the sidebar list enumerates rows, and gates on file existence alone — so any session the operator can see is deletable. This replaces the earlier accountId-derived slug and the `!row` term, which narrowed resolution below the list and left a session under a non-house slug with no watcher row (e.g. a legacy `-home-admin` session on a single-account install) listed yet un-deletable. Account scope is a view filter, not a delete gate — admin access is install-wide, so there is nothing to scope against. The sibling `/session-stop` (the standalone End control) resolves identically and also forwards no accountId. | `POST /` |
|
|
3446
3446
|
| `/session-rc-spawn` | POST `{ sessionId?, name?, title?, model?, effort? }`. Fire-and-forget `claude --remote-control [name] [--session-id <sid>]`. Present `sessionId` resumes; absent starts a fresh session (also used by the sidebar's "New session" button — it no longer opens claude.ai/code directly). Proxies to the manager's `/rc-spawn`, which waits up to **60 s** (raised from 12 s) for the spawned PTY to bind and returns `{ spawnedPid, sessionId, bridgeSessionId, slug, outcome, reason }`. For a webchat-bound spawn (every admin-gated host's "New session", returning a same-origin `/chat?session=<id>` target — `resolveRcSpawnOutcome` → `sameOrigin:true`) the Sidebar navigates the **current** tab via `window.location.assign`, replacing the dashboard in place (back returns to it); only a claude.ai/code slug (`sameOrigin:false`, the bare-admin resume bridge, never a new-session outcome) navigates a separately-opened tab. On `timeout` or `spawn-failed` it shows an error modal (reason + sessionId) and **never** opens a bare claude.ai/code tab. The new process registers itself as its own Remote Control entry in claude.ai/code. **Naming.** `title` is the operator's name for a NEW session, typed in the naming modal that gates all four New-session controls (sidebar CTA, composer popover, zero-sessions splash, Conversations flyout); nothing is POSTed until the operator confirms it, and the modal prefills `New session: <date, time>`. `resolveSpawnPlan` trims it, it never rides the manager spawn payload, and this route writes it to the user-title store through the manager's `POST /:id/rename` **before it answers**, so the client cannot navigate ahead of the write; the response gains `titleSource: 'user' \| 'none'`. A resume carries no title. A rejected or unreachable rename leaves the spawn intact and returns `'none'`. Lifeline: `[chat-spawn] op=title spawnReqId=… sessionId=… titleLen=… source=modal\|absent outcome=applied\|rejected\|failed`, where `source=absent` on a new spawn is the standing signal that a control bypassed the modal. | `POST /` |
|
|
3447
3447
|
| `/claude-sessions` | **Spawn surface only**. `POST /` is the Sidebar new-session-with-prompt path, cookie-auth only (the recorder loopback caller was removed; LinkedIn ingest moved to `/rc-spawn`). The former UI-facing handlers (SSE row feed, list, resume, stop, rename, archive, delete, `/:id/meta`, `/:id/input`, `/:id/log`) were removed — the maxy dashboard no longer manages or displays sessions. | `POST /` |
|
|
@@ -3691,7 +3691,7 @@ destination is separate and equally required: `chooseDispatchMode` returns
|
|
|
3691
3691
|
`{kind:'none'}` unless `agentChannel`, `agentDestination` and `agentPrompt` are
|
|
3692
3692
|
all present, and a destination cannot be seeded because
|
|
3693
3693
|
`validateAgentDestination` is fail-closed against the account's
|
|
3694
|
-
`whatsapp.adminPhones`
|
|
3694
|
+
`whatsapp.adminPhones` and the `adminUsers` of each entry in `telegram.bots[]`, all empty on a fresh install.
|
|
3695
3695
|
|
|
3696
3696
|
The seed re-runs on every upgrade and never writes `eventStatus`,
|
|
3697
3697
|
`agentChannel`, `agentDestination`, `recurrence` or `timezone` on a row that
|
|
@@ -9,6 +9,10 @@ Invoked by the admin agent directly.
|
|
|
9
9
|
|
|
10
10
|
This is the platform's release timeline, newest first. Each entry shows the date it shipped and the version it shipped in, so you can tell the operator how current their install is. To compare, read the installed version from `capabilities-here` and match it against the versions below. Keep answers high level and in plain English; this is a summary, not a full commit log.
|
|
11
11
|
|
|
12
|
+
## 2026-08-02 (0.1.551)
|
|
13
|
+
|
|
14
|
+
- You can now run as many Telegram bots as you like, each paired to its own agent, with its own access list, secret and conversations kept apart. Existing setups are converted automatically, the sidebar splits by bot, and a standing check reports each one.
|
|
15
|
+
|
|
12
16
|
## 2026-08-02 (0.1.550)
|
|
13
17
|
|
|
14
18
|
- A Telegram conversation now starts with its channel attached, so the assistant can reply on it rather than the session coming up unable to answer.
|
|
@@ -56,7 +56,7 @@ either is a regression.
|
|
|
56
56
|
|---|---|---|
|
|
57
57
|
| `/session` | Admin cookie session: PIN-gated mint, validate, rotate. | `GET /`, `POST /` |
|
|
58
58
|
| `/sessions` | Legacy admin-server conversation routes. No UI consumer remains after the ConversationsModal was retired; the surviving handlers are deletion candidates and not described here. | (legacy, no live caller) |
|
|
59
|
-
| `/sidebar-sessions` | Sole data path for the sidebar Sessions list. One JSONL on disk equals one row. The row's delete button is the only way a row disappears. Each row carries `sessionId`, `title`, `startedAt`, `live`, `isSubagent`, `pid: number \| null` (basename of the matched `sessions/<pid>.json`), and `projectDir` (the directory holding the JSONL — consumed by the delete route). The payload also carries top-level `accountId` so the pane renders the full UUID label whose first ~8 chars prefix-match the truncated Remote Control daemon entry in claude.ai/code. The legacy `rcUrl` field is gone — the row's external-link affordance now POSTs `/session-rc-spawn` to start a fresh local `claude --remote-control <name> --session-id <sid>` PTY on every click. **WhatsApp and Telegram sessions (admin + public) appear in this list**, colour-coded by channel (WhatsApp green `#25D366`, Telegram blue `#229ED9`, via a `conv-channel-*` left accent bar) and carrying the standard row kebab (Usage & cost, Reset/re-seat, Rename, Archive, Delete) so the operator can inspect and re-seat a channel session's params. Each row's `channel` is the authoritative `.meta.json` sidecar value (`whatsapp`/`telegram`/`webchat`/`browser`/null), not the JSONL-envelope channel. Each row also carries its sidecar `senderId`. For a WhatsApp/Telegram row with a `senderId` and no operator rename or ai-title (`titleSource:'prefix'`), the route resolves a contact `personName` and the sidebar renders the reader panel's `"<name> <number>"` label via the shared `operatorConvName`, degrading to the number alone then the stripped title on a miss. On a `whatsapp` row the number is printed in international form (`+44 7504 472444`, `+33 7 68 24 30 67`) by `formatSenderPhone` — `normalizeE164` then libphonenumber-js `formatInternational`, gated on `isPossible` so an unresolved LID prints raw; a `telegram` senderId is
|
|
59
|
+
| `/sidebar-sessions` | Sole data path for the sidebar Sessions list. One JSONL on disk equals one row. The row's delete button is the only way a row disappears. Each row carries `sessionId`, `title`, `startedAt`, `live`, `isSubagent`, `pid: number \| null` (basename of the matched `sessions/<pid>.json`), and `projectDir` (the directory holding the JSONL — consumed by the delete route). The payload also carries top-level `accountId` so the pane renders the full UUID label whose first ~8 chars prefix-match the truncated Remote Control daemon entry in claude.ai/code. The legacy `rcUrl` field is gone — the row's external-link affordance now POSTs `/session-rc-spawn` to start a fresh local `claude --remote-control <name> --session-id <sid>` PTY on every click. **WhatsApp and Telegram sessions (admin + public) appear in this list**, colour-coded by channel (WhatsApp green `#25D366`, Telegram blue `#229ED9`, via a `conv-channel-*` left accent bar) and carrying the standard row kebab (Usage & cost, Reset/re-seat, Rename, Archive, Delete) so the operator can inspect and re-seat a channel session's params. Each row's `channel` is the authoritative `.meta.json` sidecar value (`whatsapp`/`telegram`/`webchat`/`browser`/null), not the JSONL-envelope channel. Each row also carries its sidecar `senderId`. For a WhatsApp/Telegram row with a `senderId` and no operator rename or ai-title (`titleSource:'prefix'`), the route resolves a contact `personName` and the sidebar renders the reader panel's `"<name> <number>"` label via the shared `operatorConvName`, degrading to the number alone then the stripped title on a miss. On a `whatsapp` row the number is printed in international form (`+44 7504 472444`, `+33 7 68 24 30 67`) by `formatSenderPhone` — `normalizeE164` then libphonenumber-js `formatInternational`, gated on `isPossible` so an unresolved LID prints raw; a `telegram` senderId is the sender half of that row's `<botId>:<senderId>` channel key (the route splits it; the sidecar stores the composite) and a `webchat` one is `session:<id>`, so neither is regrouped. The formatting is display-only: row identity, matching, and store keys stay on the normalized digits. Because the label function is shared, a Sessions row and that conversation's reader-panel row read identically. A channel row name has two sources, in precedence order. First the shared `resolveOperatorNamesBySender` helper the WhatsApp reader also calls, which matches the sender phone against the account's admins — that names an admin WhatsApp session and every Telegram session. Second, for a `whatsapp` row only, the WhatsApp pushName from the account's on-disk message store (`storePushNamesBySender` over `readConversationSummaries`, keyed by `normalizeE164` of the counterparty phone so a `+44 7…` sender matches a `447…` store key). The store is consulted only when the resolver reports **no binding** for that sender. That is typically a public WhatsApp customer, the case this exists for — such a sender has no admin record and so can only be named by the store, and without it those rows degraded to the bare number while the reader panel named them — though an admin missing from `users.json` qualifies too. A resolver miss caused by Neo4j being unreachable deliberately does NOT qualify: that is an outage rather than an absent binding, so the row degrades to the number and still logs `reason=neo4j-down`, keeping the outage signal intact. Telegram has no message store and is never looked up. The store read is guarded: a corrupt or absent store degrades to no pushName and logs `op=store-unreadable`, never 500s the list. Both sources are the ones the reader itself uses, so the two surfaces cannot drift; a renamed or ai-titled channel row keeps its own title. The Sessions list reads the viewed account's own store only. An account-manager's thread persists under the house account rather than the sub-account it manages, so on a multi-account install viewing a sub-account that row is not named here even though the reader panel names it — the reader additionally consults the house store, filtered to manager phones bound to that sub. The only role/channel excluded from the list is `public`+`webchat` (its dedicated reader is the sole surface); the exclusion predicate is `isSessionListExcluded`, decoupled from the reader's inclusion predicate `isReaderChannelSession` (unchanged), so the WhatsApp/Telegram reader panels are unaffected — a store-only WhatsApp conversation with no session JSONL stays reader-only. Observability rides `[admin-sessions-list] … channelInList=whatsapp:<n>,telegram:<n>,webchat:<n> channelNameCandidates=<n> channelNamesResolved=<n> (operator=<n> store=<n>) unknownChannel=<n> excludedPublicWebchat=<n>`, and each unresolved channel name logs one `[admin-sessions-list] op=channel-name-unresolved sessionId=<id8> channel=<whatsapp\|telegram> senderId=<id> reason=<no-senderId\|classify-miss\|neo4j-down>` line — emitted only when **both** sources miss, so a residual shortfall means a genuinely nameless sender rather than the expected public-customer floor, and the `operator=`/`store=` split says which source is carrying the names. | `GET /` |
|
|
60
60
|
| `/session-delete` | POST `{ sessionId }`. Thin proxy over the manager: POST `/:id/stop` (idempotent) then DELETE `/:id`. Sends **no** accountId. The manager resolves the session by scanning every slug under `projects/` for its JSONL (`slugForExistingJsonl`, covering `archive/`), exactly as the sidebar list enumerates rows, and gates on file existence alone — so any session the operator can see is deletable. This replaces the earlier accountId-derived slug and the `!row` term, which narrowed resolution below the list and left a session under a non-house slug with no watcher row (e.g. a legacy `-home-admin` session on a single-account install) listed yet un-deletable. Account scope is a view filter, not a delete gate — admin access is install-wide, so there is nothing to scope against. The sibling `/session-stop` (the standalone End control) resolves identically and also forwards no accountId. | `POST /` |
|
|
61
61
|
| `/session-rc-spawn` | POST `{ sessionId?, name?, title?, model?, effort? }`. Fire-and-forget `claude --remote-control [name] [--session-id <sid>]`. Present `sessionId` resumes; absent starts a fresh session (also used by the sidebar's "New session" button — it no longer opens claude.ai/code directly). Proxies to the manager's `/rc-spawn`, which waits up to **60 s** (raised from 12 s) for the spawned PTY to bind and returns `{ spawnedPid, sessionId, bridgeSessionId, slug, outcome, reason }`. For a webchat-bound spawn (every admin-gated host's "New session", returning a same-origin `/chat?session=<id>` target — `resolveRcSpawnOutcome` → `sameOrigin:true`) the Sidebar navigates the **current** tab via `window.location.assign`, replacing the dashboard in place (back returns to it); only a claude.ai/code slug (`sameOrigin:false`, the bare-admin resume bridge, never a new-session outcome) navigates a separately-opened tab. On `timeout` or `spawn-failed` it shows an error modal (reason + sessionId) and **never** opens a bare claude.ai/code tab. The new process registers itself as its own Remote Control entry in claude.ai/code. **Naming.** `title` is the operator's name for a NEW session, typed in the naming modal that gates all four New-session controls (sidebar CTA, composer popover, zero-sessions splash, Conversations flyout); nothing is POSTed until the operator confirms it, and the modal prefills `New session: <date, time>`. `resolveSpawnPlan` trims it, it never rides the manager spawn payload, and this route writes it to the user-title store through the manager's `POST /:id/rename` **before it answers**, so the client cannot navigate ahead of the write; the response gains `titleSource: 'user' \| 'none'`. A resume carries no title. A rejected or unreachable rename leaves the spawn intact and returns `'none'`. Lifeline: `[chat-spawn] op=title spawnReqId=… sessionId=… titleLen=… source=modal\|absent outcome=applied\|rejected\|failed`, where `source=absent` on a new spawn is the standing signal that a control bypassed the modal. | `POST /` |
|
|
62
62
|
| `/claude-sessions` | **Spawn surface only**. `POST /` is the Sidebar new-session-with-prompt path, cookie-auth only (the recorder loopback caller was removed; LinkedIn ingest moved to `/rc-spawn`). The former UI-facing handlers (SSE row feed, list, resume, stop, rename, archive, delete, `/:id/meta`, `/:id/input`, `/:id/log`) were removed — the maxy dashboard no longer manages or displays sessions. | `POST /` |
|
|
@@ -306,7 +306,7 @@ destination is separate and equally required: `chooseDispatchMode` returns
|
|
|
306
306
|
`{kind:'none'}` unless `agentChannel`, `agentDestination` and `agentPrompt` are
|
|
307
307
|
all present, and a destination cannot be seeded because
|
|
308
308
|
`validateAgentDestination` is fail-closed against the account's
|
|
309
|
-
`whatsapp.adminPhones`
|
|
309
|
+
`whatsapp.adminPhones` and the `adminUsers` of each entry in `telegram.bots[]`, all empty on a fresh install.
|
|
310
310
|
|
|
311
311
|
The seed re-runs on every upgrade and never writes `eventStatus`,
|
|
312
312
|
`agentChannel`, `agentDestination`, `recurrence` or `timezone` on a row that
|
|
@@ -126,12 +126,12 @@ describe("validateAgentDestination — telegram unchanged (creating-account dir,
|
|
|
126
126
|
dirs = [];
|
|
127
127
|
});
|
|
128
128
|
it("accepts a chat id in the creating account's telegram.adminUsers", () => {
|
|
129
|
-
const acc = makeAccountsDir((a) => seedAccount(a, HOUSE, { role: "house", telegram: { adminUsers: [12345678] } }));
|
|
129
|
+
const acc = makeAccountsDir((a) => seedAccount(a, HOUSE, { role: "house", telegram: { bots: [{ id: "111", token: "111:A", role: "admin", adminUsers: [12345678] }] } }));
|
|
130
130
|
dirs.push(acc);
|
|
131
131
|
expect(validateAgentDestination(acc, HOUSE, "telegram", "12345678")).toEqual({ valid: true });
|
|
132
132
|
});
|
|
133
133
|
it("rejects a chat id absent from adminUsers as not-registered", () => {
|
|
134
|
-
const acc = makeAccountsDir((a) => seedAccount(a, HOUSE, { role: "house", telegram: { adminUsers: [12345678] } }));
|
|
134
|
+
const acc = makeAccountsDir((a) => seedAccount(a, HOUSE, { role: "house", telegram: { bots: [{ id: "111", token: "111:A", role: "admin", adminUsers: [12345678] }] } }));
|
|
135
135
|
dirs.push(acc);
|
|
136
136
|
expect(validateAgentDestination(acc, HOUSE, "telegram", "99999999")).toEqual({
|
|
137
137
|
valid: false,
|
package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/agent-dispatch.test.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-dispatch.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/agent-dispatch.test.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"agent-dispatch.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/agent-dispatch.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACxE,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EACL,wBAAwB,EACxB,0BAA0B,EAC1B,uBAAuB,GACxB,MAAM,sBAAsB,CAAC;AAE9B,uEAAuE;AACvE,gFAAgF;AAChF,4EAA4E;AAC5E,6EAA6E;AAC7E,sEAAsE;AACtE,2EAA2E;AAC3E,kEAAkE;AAElE,0EAA0E;AAC1E,sEAAsE;AACtE,2EAA2E;AAC3E,MAAM,KAAK,GAAG,sCAAsC,CAAC;AACrD,MAAM,KAAK,GAAG,sCAAsC,CAAC;AACrD,MAAM,KAAK,GAAG,sCAAsC,CAAC;AAErD,SAAS,eAAe,CAAC,IAAmC;IAC1D,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,iBAAiB,CAAC,CAAC,CAAC;IAC3D,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,WAAW,CAAC,WAAmB,EAAE,EAAU,EAAE,GAA4B;IAChF,MAAM,CAAC,GAAG,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;IAChC,SAAS,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAClC,aAAa,CAAC,IAAI,CAAC,CAAC,EAAE,cAAc,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC;AACvE,CAAC;AAED,+EAA+E;AAC/E,SAAS,cAAc,CAAC,MAA+B;IACrD,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,qBAAqB,CAAC,CAAC,CAAC;IAC/D,aAAa,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC;IAC1E,OAAO,GAAG,CAAC;AACb,CAAC;AAED,QAAQ,CAAC,8EAA8E,EAAE,GAAG,EAAE;IAC5F,IAAI,IAAI,GAAa,EAAE,CAAC;IACxB,SAAS,CAAC,GAAG,EAAE;QACb,KAAK,MAAM,CAAC,IAAI,IAAI;YAAE,MAAM,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAClE,IAAI,GAAG,EAAE,CAAC;IACZ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yFAAyF,EAAE,GAAG,EAAE;QACjG,MAAM,GAAG,GAAG,eAAe,CAAC,CAAC,CAAC,EAAE,EAAE,CAChC,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,WAAW,EAAE,CAAC,eAAe,CAAC,EAAE,EAAE,CAAC,CACvF,CAAC;QACF,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACf,MAAM,CAAC,wBAAwB,CAAC,GAAG,EAAE,KAAK,EAAE,UAAU,EAAE,iBAAiB,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACvG,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;QAClE,MAAM,GAAG,GAAG,eAAe,CAAC,CAAC,CAAC,EAAE,EAAE;YAChC,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,eAAe,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;YACnG,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;QAC5B,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACf,MAAM,CAAC,wBAAwB,CAAC,GAAG,EAAE,KAAK,EAAE,UAAU,EAAE,iBAAiB,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACvG,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gFAAgF,EAAE,GAAG,EAAE;QACxF,MAAM,GAAG,GAAG,eAAe,CAAC,CAAC,CAAC,EAAE,EAAE;YAChC,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,eAAe,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;YACnG,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;YAC1B,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;QAC5B,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACf,MAAM,CAAC,wBAAwB,CAAC,GAAG,EAAE,KAAK,EAAE,UAAU,EAAE,eAAe,CAAC,CAAC,CAAC,OAAO,CAAC;YAChF,KAAK,EAAE,KAAK;YACZ,MAAM,EAAE,eAAe;YACvB,kBAAkB,EAAE,KAAK;YACzB,eAAe,EAAE,KAAK;SACvB,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6FAA6F,EAAE,GAAG,EAAE;QACrG,MAAM,GAAG,GAAG,eAAe,CAAC,CAAC,CAAC,EAAE,EAAE;YAChC,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,WAAW,EAAE,CAAC,eAAe,CAAC,EAAE,EAAE,CAAC,CAAC;YACvF,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;QAC5B,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACf,MAAM,CAAC,wBAAwB,CAAC,GAAG,EAAE,KAAK,EAAE,UAAU,EAAE,eAAe,CAAC,CAAC,CAAC,OAAO,CAAC;YAChF,KAAK,EAAE,KAAK;YACZ,MAAM,EAAE,eAAe;YACvB,kBAAkB,EAAE,KAAK;YACzB,eAAe,EAAE,KAAK;SACvB,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8EAA8E,EAAE,GAAG,EAAE;QACtF,MAAM,GAAG,GAAG,eAAe,CAAC,CAAC,CAAC,EAAE,EAAE;YAChC,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,eAAe,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;YACnG,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;QAC5B,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACf,MAAM,CAAC,wBAAwB,CAAC,GAAG,EAAE,KAAK,EAAE,UAAU,EAAE,eAAe,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACrG,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,GAAG,GAAG,eAAe,CAAC,CAAC,CAAC,EAAE,EAAE,CAChC,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,WAAW,EAAE,CAAC,eAAe,CAAC,EAAE,EAAE,CAAC,CACvF,CAAC;QACF,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACf,MAAM,CAAC,wBAAwB,CAAC,GAAG,EAAE,KAAK,EAAE,UAAU,EAAE,eAAe,CAAC,CAAC,CAAC,OAAO,CAAC;YAChF,KAAK,EAAE,KAAK;YACZ,MAAM,EAAE,gBAAgB;SACzB,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sFAAsF,EAAE,GAAG,EAAE;QAC9F,yEAAyE;QACzE,iEAAiE;QACjE,MAAM,GAAG,GAAG,eAAe,CAAC,CAAC,CAAC,EAAE,EAAE;YAChC,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,eAAe,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;YACnG,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC;QAC1C,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACf,MAAM,CAAC,wBAAwB,CAAC,GAAG,EAAE,KAAK,EAAE,UAAU,EAAE,eAAe,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACrG,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yGAAyG,EAAE,GAAG,EAAE;QACjH,MAAM,GAAG,GAAG,eAAe,CAAC,CAAC,CAAC,EAAE,EAAE;YAChC,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE;gBACpB,IAAI,EAAE,OAAO;gBACb,QAAQ,EAAE,EAAE,WAAW,EAAE,CAAC,eAAe,CAAC,EAAE,eAAe,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,EAAE;aACzF,CAAC,CAAC;YACH,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;QAC5B,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACf,6CAA6C;QAC7C,MAAM,CAAC,wBAAwB,CAAC,GAAG,EAAE,KAAK,EAAE,UAAU,EAAE,eAAe,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACrG,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,kFAAkF,EAAE,GAAG,EAAE;IAChG,IAAI,IAAI,GAAa,EAAE,CAAC;IACxB,SAAS,CAAC,GAAG,EAAE;QACb,KAAK,MAAM,CAAC,IAAI,IAAI;YAAE,MAAM,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAClE,IAAI,GAAG,EAAE,CAAC;IACZ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iEAAiE,EAAE,GAAG,EAAE;QACzE,MAAM,GAAG,GAAG,eAAe,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;QACzK,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACf,MAAM,CAAC,wBAAwB,CAAC,GAAG,EAAE,KAAK,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAChG,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4DAA4D,EAAE,GAAG,EAAE;QACpE,MAAM,GAAG,GAAG,eAAe,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;QACzK,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACf,MAAM,CAAC,wBAAwB,CAAC,GAAG,EAAE,KAAK,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC;YAC3E,KAAK,EAAE,KAAK;YACZ,MAAM,EAAE,gBAAgB;SACzB,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,oFAAoF,EAAE,GAAG,EAAE;IAClG,IAAI,IAAI,GAAa,EAAE,CAAC;IACxB,SAAS,CAAC,GAAG,EAAE;QACb,KAAK,MAAM,CAAC,IAAI,IAAI;YAAE,MAAM,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAClE,IAAI,GAAG,EAAE,CAAC;IACZ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,GAAG,GAAG,eAAe,CAAC,CAAC,WAAW,EAAE,EAAE,CAC1C,WAAW,CAAC,WAAW,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CACtH,CAAC;QACF,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACf,MAAM,CAAC,0BAA0B,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;IAC7E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACrD,MAAM,GAAG,GAAG,eAAe,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACtC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACf,MAAM,CAAC,0BAA0B,CAAC,GAAG,EAAE,iBAAiB,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,MAAM,GAAG,GAAG,eAAe,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,WAAW,CAAC,WAAW,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC;QAC1G,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACf,MAAM,CAAC,0BAA0B,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,kDAAkD,EAAE,GAAG,EAAE;IAChE,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,CACJ,uBAAuB,CAAC;YACtB,SAAS,EAAE,IAAI;YACf,OAAO,EAAE,IAAI;YACb,UAAU,EAAE,CAAC,KAAK,CAAC;YACnB,aAAa,EAAE,gBAAgB;YAC/B,YAAY,EAAE,sBAAsB;SACrC,CAAC,CACH,CAAC,OAAO,CAAC;YACR,SAAS,EAAE,IAAI;YACf,OAAO,EAAE,IAAI;YACb,UAAU,EAAE,CAAC,KAAK,CAAC;YACnB,aAAa,EAAE,gBAAgB;YAC/B,YAAY,EAAE,sBAAsB;SACrC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qEAAqE,EAAE,GAAG,EAAE;QAC7E,MAAM,CACJ,uBAAuB,CAAC;YACtB,SAAS,EAAE,IAAI;YACf,OAAO,EAAE,IAAI;YACb,UAAU,EAAE,CAAC,KAAK,EAAE,EAAE,EAAE,CAAsB,CAAC;YAC/C,aAAa,EAAE,EAAE;YACjB,YAAY,EAAE,sBAAsB;SACrC,CAAC,CACH,CAAC,OAAO,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,KAAK,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,YAAY,EAAE,sBAAsB,EAAE,CAAC,CAAC;IAChI,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|