@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/payload/server/server.js
CHANGED
|
@@ -1836,10 +1836,10 @@ var require_dist6 = __commonJS({
|
|
|
1836
1836
|
});
|
|
1837
1837
|
|
|
1838
1838
|
// server/index.ts
|
|
1839
|
-
import { readFileSync as
|
|
1839
|
+
import { readFileSync as readFileSync48, existsSync as existsSync52, watchFile, readdirSync as readdirSync30, statSync as statSync27 } from "fs";
|
|
1840
1840
|
import { spawn as spawn3 } from "child_process";
|
|
1841
1841
|
import { createHash as createHash7 } from "crypto";
|
|
1842
|
-
import { resolve as resolve45, join as
|
|
1842
|
+
import { resolve as resolve45, join as join56, basename as basename18 } from "path";
|
|
1843
1843
|
import { homedir as homedir4 } from "os";
|
|
1844
1844
|
|
|
1845
1845
|
// server/lib/route-cache-watchers.ts
|
|
@@ -3847,11 +3847,22 @@ ${result.result.text}` : result.result.text;
|
|
|
3847
3847
|
import { statSync as statSync5 } from "fs";
|
|
3848
3848
|
import { join as join10 } from "path";
|
|
3849
3849
|
|
|
3850
|
+
// app/lib/telegram/channel-key.ts
|
|
3851
|
+
function telegramChannelKey(botId, senderId) {
|
|
3852
|
+
return `${botId}:${senderId}`;
|
|
3853
|
+
}
|
|
3854
|
+
function parseTelegramChannelKey(key) {
|
|
3855
|
+
const i = key.indexOf(":");
|
|
3856
|
+
if (i <= 0 || i === key.length - 1) return null;
|
|
3857
|
+
return { botId: key.slice(0, i), senderId: key.slice(i + 1) };
|
|
3858
|
+
}
|
|
3859
|
+
|
|
3850
3860
|
// app/lib/channel-pty-bridge/admin-session-id.ts
|
|
3851
3861
|
import { createHash } from "crypto";
|
|
3852
|
-
function adminSessionIdFor(accountId, senderId, personId) {
|
|
3862
|
+
function adminSessionIdFor(accountId, senderId, personId, botId) {
|
|
3853
3863
|
const subject = personId && personId.length > 0 ? `person:${personId}` : senderId;
|
|
3854
|
-
const
|
|
3864
|
+
const scope = botId && botId.length > 0 ? `admin:${botId}` : "admin";
|
|
3865
|
+
const b = createHash("sha256").update(`${accountId}:${scope}:${subject}`).digest().subarray(0, 16);
|
|
3855
3866
|
const v = Buffer.from(b);
|
|
3856
3867
|
v[6] = v[6] & 15 | 64;
|
|
3857
3868
|
v[8] = v[8] & 63 | 128;
|
|
@@ -4598,7 +4609,8 @@ async function computeSessionList(scopeAccountId) {
|
|
|
4598
4609
|
continue;
|
|
4599
4610
|
}
|
|
4600
4611
|
const metaPath = join9(projectDir, `${sessionId}.meta.json`);
|
|
4601
|
-
const { bridgeIds, role, channel: sidecarChannel, senderId, personId, adminUserId, accountId: rowAccountId } = await readSidecarMetaAsync(metaPath);
|
|
4612
|
+
const { bridgeIds, role, channel: sidecarChannel, senderId: rawSenderId, personId, adminUserId, accountId: rowAccountId } = await readSidecarMetaAsync(metaPath);
|
|
4613
|
+
const senderId = sidecarChannel === "telegram" ? parseTelegramChannelKey(rawSenderId ?? "")?.senderId ?? rawSenderId : rawSenderId;
|
|
4602
4614
|
if (multiAccount) {
|
|
4603
4615
|
if (rowAccountId === null) untaggedCount += 1;
|
|
4604
4616
|
if (rowAccountId !== scopeAccountId) continue;
|
|
@@ -4872,19 +4884,15 @@ function createScheduleInjectRoutes(deps) {
|
|
|
4872
4884
|
console.error(`${TAG5} op=telegram-destination-ambiguous eventId=${eventId} destination=${destination} accounts=${resolution.count} dispatched=no`);
|
|
4873
4885
|
return c.json({ ok: false, error: "telegram-destination-ambiguous" }, 403);
|
|
4874
4886
|
}
|
|
4875
|
-
const effectiveAccount = resolution
|
|
4876
|
-
console.error(`${TAG5} op=effective-account eventId=${eventId} effectiveAccount=${effectiveAccount}`);
|
|
4887
|
+
const { accountId: effectiveAccount, botId, botToken } = resolution;
|
|
4888
|
+
console.error(`${TAG5} op=effective-account eventId=${eventId} effectiveAccount=${effectiveAccount} botId=${botId}`);
|
|
4877
4889
|
if (eventAccountId === null || eventAccountId !== effectiveAccount) {
|
|
4878
4890
|
console.error(`${TAG5} op=event-account-scope-mismatch eventId=${eventId} eventAccount=${eventAccountId ?? "-"} effectiveAccount=${effectiveAccount} destination=${destination} dispatched=no`);
|
|
4879
4891
|
return c.json({ ok: false, error: "event-account-scope-mismatch" }, 403);
|
|
4880
4892
|
}
|
|
4881
|
-
const botToken = resolution.botToken;
|
|
4882
|
-
if (!botToken) {
|
|
4883
|
-
console.error(`${TAG5} reject reason=no-admin-bot-token eventId=${eventId} accountId=${effectiveAccount}`);
|
|
4884
|
-
return c.json({ ok: false, error: "no-admin-bot-token" }, 400);
|
|
4885
|
-
}
|
|
4886
4893
|
const chatId = Number(destination);
|
|
4887
|
-
const
|
|
4894
|
+
const channelKey = telegramChannelKey(botId, destination);
|
|
4895
|
+
const sessionId = deps.resolveSessionOverride?.(effectiveAccount, "telegram", channelKey) ?? adminSessionIdFor(effectiveAccount, destination, void 0, botId);
|
|
4888
4896
|
const reply = (text) => deps.sendTelegramText(botToken, chatId, text);
|
|
4889
4897
|
try {
|
|
4890
4898
|
await deps.tgHandleInbound({
|
|
@@ -4893,6 +4901,14 @@ function createScheduleInjectRoutes(deps) {
|
|
|
4893
4901
|
// session scope are one account and Telegram needs no
|
|
4894
4902
|
// effectiveAccountId companion field the way WhatsApp does above.
|
|
4895
4903
|
accountId: effectiveAccount,
|
|
4904
|
+
// A scheduled dispatch is always role:'admin', and the admin branch of
|
|
4905
|
+
// the spawn never reads the account dir (only the public branch
|
|
4906
|
+
// re-checks its paired agent against it), so there is nothing to
|
|
4907
|
+
// resolve here.
|
|
4908
|
+
accountDir: "",
|
|
4909
|
+
botId,
|
|
4910
|
+
botToken,
|
|
4911
|
+
agentSlug: "admin",
|
|
4896
4912
|
senderId: destination,
|
|
4897
4913
|
chatId: destination,
|
|
4898
4914
|
role: "admin",
|
|
@@ -12907,6 +12923,44 @@ import { homedir as homedir2 } from "os";
|
|
|
12907
12923
|
import { resolve as resolve17, join as join29 } from "path";
|
|
12908
12924
|
import { existsSync as existsSync21, readFileSync as readFileSync23 } from "fs";
|
|
12909
12925
|
|
|
12926
|
+
// app/lib/telegram/bot-registry.ts
|
|
12927
|
+
function botIdFromToken(token) {
|
|
12928
|
+
const i = token.indexOf(":");
|
|
12929
|
+
if (i <= 0) return null;
|
|
12930
|
+
const id = token.slice(0, i);
|
|
12931
|
+
return /^\d+$/.test(id) ? id : null;
|
|
12932
|
+
}
|
|
12933
|
+
function listBotEntries(config) {
|
|
12934
|
+
const raw = config?.bots;
|
|
12935
|
+
if (!Array.isArray(raw)) return [];
|
|
12936
|
+
const out = [];
|
|
12937
|
+
for (const e of raw) {
|
|
12938
|
+
if (!e || typeof e !== "object") continue;
|
|
12939
|
+
const { id, token, role } = e;
|
|
12940
|
+
if (typeof id !== "string" || id.length === 0) continue;
|
|
12941
|
+
if (typeof token !== "string" || token.length === 0) continue;
|
|
12942
|
+
if (role === "admin") {
|
|
12943
|
+
out.push(e);
|
|
12944
|
+
} else if (role === "public") {
|
|
12945
|
+
const agent = e.agent;
|
|
12946
|
+
if (typeof agent !== "string" || agent.length === 0) continue;
|
|
12947
|
+
out.push(e);
|
|
12948
|
+
}
|
|
12949
|
+
}
|
|
12950
|
+
return out;
|
|
12951
|
+
}
|
|
12952
|
+
function resolveBotEntry(accounts, botId) {
|
|
12953
|
+
const hits = [];
|
|
12954
|
+
for (const a of accounts) {
|
|
12955
|
+
for (const entry of listBotEntries(a.config.telegram)) {
|
|
12956
|
+
if (entry.id === botId) hits.push({ accountId: a.accountId, accountDir: a.accountDir, entry });
|
|
12957
|
+
}
|
|
12958
|
+
}
|
|
12959
|
+
if (hits.length === 0) return { kind: "none" };
|
|
12960
|
+
if (hits.length > 1) return { kind: "duplicate", count: hits.length };
|
|
12961
|
+
return { kind: "one", ...hits[0] };
|
|
12962
|
+
}
|
|
12963
|
+
|
|
12910
12964
|
// app/lib/telegram/gateway/instance.ts
|
|
12911
12965
|
var instance = null;
|
|
12912
12966
|
function setTelegramGateway(gw) {
|
|
@@ -12918,20 +12972,20 @@ function getTelegramGateway() {
|
|
|
12918
12972
|
|
|
12919
12973
|
// app/lib/telegram/access-control.ts
|
|
12920
12974
|
function checkTelegramAccess(params) {
|
|
12921
|
-
const { senderId,
|
|
12922
|
-
if (
|
|
12923
|
-
const adminUsers =
|
|
12975
|
+
const { senderId, entry } = params;
|
|
12976
|
+
if (entry.role === "admin") {
|
|
12977
|
+
const adminUsers = entry.adminUsers ?? [];
|
|
12924
12978
|
if (adminUsers.includes(senderId)) {
|
|
12925
12979
|
return { allowed: true, reason: "admin-binding", agentType: "admin" };
|
|
12926
12980
|
}
|
|
12927
12981
|
return { allowed: false, reason: "not-admin-user", agentType: "admin" };
|
|
12928
12982
|
}
|
|
12929
|
-
const policy =
|
|
12983
|
+
const policy = entry.dmPolicy ?? "disabled";
|
|
12930
12984
|
switch (policy) {
|
|
12931
12985
|
case "open":
|
|
12932
12986
|
return { allowed: true, reason: "dm-policy-open", agentType: "public" };
|
|
12933
12987
|
case "allowlist": {
|
|
12934
|
-
const allowFrom =
|
|
12988
|
+
const allowFrom = entry.allowFrom ?? [];
|
|
12935
12989
|
if (allowFrom.includes(senderId)) {
|
|
12936
12990
|
return { allowed: true, reason: "allowlist-match", agentType: "public" };
|
|
12937
12991
|
}
|
|
@@ -12953,11 +13007,11 @@ function routeTelegramUpdate(input) {
|
|
|
12953
13007
|
if (typeof m.text !== "string" || m.text.length === 0) {
|
|
12954
13008
|
return { kind: "ignore", reason: "no-text" };
|
|
12955
13009
|
}
|
|
12956
|
-
const access = checkTelegramAccess({ senderId: m.from.id,
|
|
13010
|
+
const access = checkTelegramAccess({ senderId: m.from.id, entry: input.entry });
|
|
12957
13011
|
if (!access.allowed) {
|
|
12958
13012
|
return { kind: "denied", reason: access.reason, agentType: access.agentType };
|
|
12959
13013
|
}
|
|
12960
|
-
return { kind: "dispatch", senderId: String(m.from.id), chatId: m.chat.id, text: m.text, agentType: access.agentType };
|
|
13014
|
+
return { kind: "dispatch", senderId: String(m.from.id), chatId: m.chat.id, text: m.text, agentType: access.agentType, reason: access.reason };
|
|
12961
13015
|
}
|
|
12962
13016
|
|
|
12963
13017
|
// server/routes/telegram.ts
|
|
@@ -12973,81 +13027,85 @@ function configDirName() {
|
|
|
12973
13027
|
}
|
|
12974
13028
|
return ".maxy";
|
|
12975
13029
|
}
|
|
12976
|
-
function
|
|
12977
|
-
|
|
12978
|
-
|
|
13030
|
+
function telegramSecretsDir() {
|
|
13031
|
+
return resolve17(homedir2(), configDirName(), "telegram-secrets");
|
|
13032
|
+
}
|
|
13033
|
+
function secretPath(botId) {
|
|
13034
|
+
return resolve17(telegramSecretsDir(), botId);
|
|
12979
13035
|
}
|
|
12980
13036
|
var app10 = new Hono();
|
|
12981
13037
|
app10.post("/", async (c) => {
|
|
12982
|
-
const
|
|
12983
|
-
|
|
12984
|
-
if (!botType) {
|
|
13038
|
+
const botId = c.req.query("bot");
|
|
13039
|
+
if (!botId) {
|
|
12985
13040
|
console.error(`${TAG19} op=reject reason=missing-bot-param`);
|
|
12986
13041
|
return c.json({ ok: false }, 400);
|
|
12987
13042
|
}
|
|
12988
|
-
|
|
12989
|
-
|
|
12990
|
-
|
|
13043
|
+
console.error(`${TAG19} op=received botId=${botId} path=/api/telegram/`);
|
|
13044
|
+
const resolved = resolveBotEntry(listValidAccounts(), botId);
|
|
13045
|
+
if (resolved.kind === "none") {
|
|
13046
|
+
console.error(`${TAG19} op=reject reason=unknown-bot botId=${botId}`);
|
|
12991
13047
|
return c.json({ ok: false }, 401);
|
|
12992
13048
|
}
|
|
12993
|
-
|
|
12994
|
-
|
|
12995
|
-
console.error(`${TAG19} op=reject reason=bad-account botType=${botType}`);
|
|
13049
|
+
if (resolved.kind === "duplicate") {
|
|
13050
|
+
console.error(`${TAG19} op=reject reason=duplicate-bot-id botId=${botId} accounts=${resolved.count}`);
|
|
12996
13051
|
return c.json({ ok: false }, 401);
|
|
12997
13052
|
}
|
|
12998
|
-
const
|
|
13053
|
+
const { accountId, accountDir, entry } = resolved;
|
|
13054
|
+
const agentSlug = entry.role === "admin" ? "admin" : entry.agent;
|
|
13055
|
+
console.error(`${TAG19} op=entry botId=${botId} accountId=${accountId} role=${entry.role} agent=${agentSlug}`);
|
|
13056
|
+
const sp = secretPath(botId);
|
|
12999
13057
|
if (!existsSync21(sp)) {
|
|
13000
|
-
console.error(`${TAG19} op=
|
|
13058
|
+
console.error(`${TAG19} op=secret botId=${botId} result=missing-file`);
|
|
13001
13059
|
return c.json({ ok: false }, 401);
|
|
13002
13060
|
}
|
|
13003
13061
|
const expected = readFileSync23(sp, "utf-8").trim();
|
|
13004
13062
|
const got = c.req.header("x-telegram-bot-api-secret-token") ?? "";
|
|
13005
13063
|
if (got !== expected) {
|
|
13006
|
-
console.error(`${TAG19} op=
|
|
13064
|
+
console.error(`${TAG19} op=secret botId=${botId} result=mismatch`);
|
|
13007
13065
|
return c.json({ ok: false }, 401);
|
|
13008
13066
|
}
|
|
13067
|
+
console.error(`${TAG19} op=secret botId=${botId} result=ok`);
|
|
13009
13068
|
let update;
|
|
13010
13069
|
try {
|
|
13011
13070
|
update = await c.req.json();
|
|
13012
13071
|
} catch {
|
|
13013
13072
|
return c.json({ ok: true }, 200);
|
|
13014
13073
|
}
|
|
13015
|
-
const decision = routeTelegramUpdate({ update,
|
|
13074
|
+
const decision = routeTelegramUpdate({ update, entry });
|
|
13016
13075
|
if (decision.kind === "ignore") {
|
|
13017
|
-
console.error(`${TAG19} op=ignore reason=${decision.reason}`);
|
|
13076
|
+
console.error(`${TAG19} op=ignore botId=${botId} reason=${decision.reason}`);
|
|
13018
13077
|
return c.json({ ok: true }, 200);
|
|
13019
13078
|
}
|
|
13020
13079
|
if (decision.kind === "denied") {
|
|
13021
|
-
console.error(`${TAG19} op=
|
|
13080
|
+
console.error(`${TAG19} op=access botId=${botId} senderId=- allowed=false reason=${decision.reason}`);
|
|
13022
13081
|
return c.json({ ok: true }, 200);
|
|
13023
13082
|
}
|
|
13024
|
-
const
|
|
13025
|
-
|
|
13026
|
-
|
|
13027
|
-
|
|
13083
|
+
const { senderId, chatId, text } = decision;
|
|
13084
|
+
console.error(`${TAG19} op=access botId=${botId} senderId=${senderId} allowed=true reason=${decision.reason}`);
|
|
13085
|
+
const active = entry.role === "admin" ? true : isActiveAgentSlug(accountDir, entry.agent);
|
|
13086
|
+
console.error(`${TAG19} op=agent botId=${botId} slug=${agentSlug} active=${active}`);
|
|
13087
|
+
if (!active) {
|
|
13088
|
+
console.error(`${TAG19} op=reject reason=agent-inactive botId=${botId} agent=${agentSlug}`);
|
|
13028
13089
|
return c.json({ ok: true }, 200);
|
|
13029
13090
|
}
|
|
13030
|
-
console.error(`${TAG19} op=update accountId=${account.accountId} from=${decision.senderId}`);
|
|
13031
|
-
const botToken = botType === "admin" ? account.config.telegram?.adminBotToken : account.config.telegram?.publicBotToken;
|
|
13032
|
-
const { senderId, chatId, text } = decision;
|
|
13033
13091
|
const gateway = getTelegramGateway();
|
|
13034
13092
|
if (!gateway) {
|
|
13035
|
-
console.error(`${TAG19} op=
|
|
13093
|
+
console.error(`${TAG19} op=reply-dropped botId=${botId} reason=gateway-not-ready`);
|
|
13036
13094
|
return c.json({ ok: true }, 200);
|
|
13037
13095
|
}
|
|
13038
13096
|
const reply = async (replyText) => {
|
|
13039
|
-
|
|
13040
|
-
|
|
13041
|
-
|
|
13042
|
-
return;
|
|
13043
|
-
}
|
|
13044
|
-
const sent = await sendTelegramText(botToken, chatId, replyText);
|
|
13045
|
-
console.error(`${TAG19} op=reply-sent from=${senderId} ok=${sent.ok}${sent.ok ? "" : ` error=${sent.error}`}`);
|
|
13097
|
+
const sent = await sendTelegramText(entry.token, chatId, replyText);
|
|
13098
|
+
if (sent.ok) console.error(`${TAG19} op=reply-sent botId=${botId} ok=true`);
|
|
13099
|
+
else console.error(`${TAG19} op=reply-dropped botId=${botId} reason=${sent.error}`);
|
|
13046
13100
|
};
|
|
13047
13101
|
void gateway.handleInbound({
|
|
13048
|
-
accountId
|
|
13102
|
+
accountId,
|
|
13103
|
+
accountDir,
|
|
13104
|
+
botId,
|
|
13105
|
+
botToken: entry.token,
|
|
13106
|
+
agentSlug,
|
|
13049
13107
|
senderId,
|
|
13050
|
-
role,
|
|
13108
|
+
role: entry.role,
|
|
13051
13109
|
personId: null,
|
|
13052
13110
|
chatId: String(chatId),
|
|
13053
13111
|
text,
|
|
@@ -16927,18 +16985,18 @@ var ALLOWED_EVENTS = /* @__PURE__ */ new Set([
|
|
|
16927
16985
|
]);
|
|
16928
16986
|
var app24 = new Hono();
|
|
16929
16987
|
app24.post("/", async (c) => {
|
|
16930
|
-
const
|
|
16988
|
+
const TAG54 = "[admin:events]";
|
|
16931
16989
|
let body;
|
|
16932
16990
|
try {
|
|
16933
16991
|
body = await c.req.json();
|
|
16934
16992
|
} catch (err) {
|
|
16935
16993
|
const detail = err instanceof Error ? err.message : String(err);
|
|
16936
|
-
console.error(`${
|
|
16994
|
+
console.error(`${TAG54} reject reason=body-not-json detail=${detail}`);
|
|
16937
16995
|
return c.json({ ok: false, detail: "Request body was not valid JSON" }, 400);
|
|
16938
16996
|
}
|
|
16939
16997
|
const event = typeof body.event === "string" ? body.event : "";
|
|
16940
16998
|
if (!ALLOWED_EVENTS.has(event)) {
|
|
16941
|
-
console.error(`${
|
|
16999
|
+
console.error(`${TAG54} reject reason=event-not-allowed event=${JSON.stringify(event)}`);
|
|
16942
17000
|
return c.json({ ok: false, detail: `Event "${event}" is not allowed` }, 400);
|
|
16943
17001
|
}
|
|
16944
17002
|
const rawFields = body.fields && typeof body.fields === "object" ? body.fields : {};
|
|
@@ -22405,6 +22463,7 @@ async function managerSpawn(opts) {
|
|
|
22405
22463
|
name: opts.name,
|
|
22406
22464
|
webchatChannel: opts.webchatChannel,
|
|
22407
22465
|
waChannel: opts.waChannel,
|
|
22466
|
+
telegramChannel: opts.telegramChannel,
|
|
22408
22467
|
sessionId: opts.sessionId,
|
|
22409
22468
|
previousContext: opts.previousContext,
|
|
22410
22469
|
initialMessage: opts.initialMessage,
|
|
@@ -23935,8 +23994,24 @@ function readChannelAdmins(accountDir, channel) {
|
|
|
23935
23994
|
return { adminPhones, accountManagers };
|
|
23936
23995
|
}
|
|
23937
23996
|
const tg = config.telegram ?? {};
|
|
23938
|
-
const
|
|
23939
|
-
|
|
23997
|
+
const byBot = [];
|
|
23998
|
+
const seen = /* @__PURE__ */ new Set();
|
|
23999
|
+
const adminUsers = [];
|
|
24000
|
+
if (Array.isArray(tg.bots)) {
|
|
24001
|
+
for (const raw of tg.bots) {
|
|
24002
|
+
if (!raw || typeof raw !== "object") continue;
|
|
24003
|
+
const b = raw;
|
|
24004
|
+
if (b.role !== "admin" || typeof b.id !== "string" || b.id.length === 0) continue;
|
|
24005
|
+
const users = Array.isArray(b.adminUsers) ? b.adminUsers.filter((u) => typeof u === "number") : [];
|
|
24006
|
+
byBot.push({ botId: b.id, adminUsers: users });
|
|
24007
|
+
for (const u of users) {
|
|
24008
|
+
if (seen.has(u)) continue;
|
|
24009
|
+
seen.add(u);
|
|
24010
|
+
adminUsers.push(u);
|
|
24011
|
+
}
|
|
24012
|
+
}
|
|
24013
|
+
}
|
|
24014
|
+
return { adminUsers, byBot };
|
|
23940
24015
|
}
|
|
23941
24016
|
function registeredAccountForDestination(houseDir, houseAccountId, destination) {
|
|
23942
24017
|
const { adminPhones, accountManagers } = readChannelAdmins(houseDir, "whatsapp");
|
|
@@ -23995,8 +24070,13 @@ function listDispatchDestinations(input) {
|
|
|
23995
24070
|
admins.push({ channel: "whatsapp", destination: phone, label: "House admin" });
|
|
23996
24071
|
}
|
|
23997
24072
|
}
|
|
23998
|
-
const {
|
|
23999
|
-
const telegram =
|
|
24073
|
+
const { byBot } = readChannelAdmins(resolve31(accountsDir, accountId), "telegram");
|
|
24074
|
+
const telegram = byBot.flatMap((b) => b.adminUsers.map((id) => ({ botId: b.botId, id }))).sort((a, b) => a.id - b.id || a.botId.localeCompare(b.botId)).map(({ botId, id }) => ({
|
|
24075
|
+
channel: "telegram",
|
|
24076
|
+
destination: String(id),
|
|
24077
|
+
label: "Telegram admin",
|
|
24078
|
+
botId
|
|
24079
|
+
}));
|
|
24000
24080
|
const byDestination = (a, b) => a.destination.localeCompare(b.destination);
|
|
24001
24081
|
return [...managers.sort(byDestination), ...admins.sort(byDestination), ...telegram];
|
|
24002
24082
|
}
|
|
@@ -31103,19 +31183,22 @@ var TelegramGateway = class {
|
|
|
31103
31183
|
* its own `sessionIdFor`.
|
|
31104
31184
|
*/
|
|
31105
31185
|
sessionIdFor(input) {
|
|
31106
|
-
|
|
31186
|
+
const key = telegramChannelKey(input.botId, input.senderId);
|
|
31187
|
+
return (input.role === "admin" ? this.deps.resolveSessionOverride?.(input.accountId, key) : null) || adminSessionIdFor(
|
|
31107
31188
|
input.accountId,
|
|
31108
31189
|
input.senderId,
|
|
31109
|
-
input.role === "public" && input.personId ? input.personId : void 0
|
|
31190
|
+
input.role === "public" && input.personId ? input.personId : void 0,
|
|
31191
|
+
input.botId
|
|
31110
31192
|
);
|
|
31111
31193
|
}
|
|
31112
31194
|
/** Handle one inbound telegram message. */
|
|
31113
31195
|
async handleInbound(input) {
|
|
31114
31196
|
const bytes = Buffer.byteLength(input.text, "utf8");
|
|
31115
|
-
const
|
|
31116
|
-
const
|
|
31197
|
+
const key = telegramChannelKey(input.botId, input.senderId);
|
|
31198
|
+
const hadSubscriber = this.hub.hasSubscriber(key);
|
|
31199
|
+
const willDeliverNow = this.hub.isReady(key);
|
|
31117
31200
|
const source = input.source ?? "user";
|
|
31118
|
-
this.replies.set(
|
|
31201
|
+
this.replies.set(key, input.reply);
|
|
31119
31202
|
let standingRules;
|
|
31120
31203
|
if (input.role === "admin" && this.deps.fetchStandingRules) {
|
|
31121
31204
|
const res = await this.deps.fetchStandingRules(input.accountId);
|
|
@@ -31127,19 +31210,24 @@ var TelegramGateway = class {
|
|
|
31127
31210
|
);
|
|
31128
31211
|
}
|
|
31129
31212
|
this.hub.deliver(
|
|
31130
|
-
{ key
|
|
31213
|
+
{ key, text: input.text, messageId: `tg-${++this.seq}`, source, standingRules, scheduleProvenance: input.scheduleProvenance },
|
|
31131
31214
|
Date.now()
|
|
31132
31215
|
);
|
|
31133
31216
|
const resolvedSessionId = this.sessionIdFor(input);
|
|
31134
31217
|
console.error(
|
|
31135
|
-
`[telegram-native] op=inbound key=${input.
|
|
31218
|
+
`[telegram-native] op=inbound key=${key} botId=${input.botId} accountId=${input.accountId} role=${input.role} agent=${input.agentSlug} bytes=${bytes} source=${source} delivery=${willDeliverNow ? "immediate" : "queued"} sessionId=${resolvedSessionId}`
|
|
31136
31219
|
);
|
|
31137
|
-
if (!hadSubscriber && !this.spawning.has(
|
|
31138
|
-
this.spawning.add(
|
|
31139
|
-
console.error(`[telegram-native] op=spawn-trigger key=${input.
|
|
31220
|
+
if (!hadSubscriber && !this.spawning.has(key)) {
|
|
31221
|
+
this.spawning.add(key);
|
|
31222
|
+
console.error(`[telegram-native] op=spawn-trigger key=${key} botId=${input.botId} role=${input.role}`);
|
|
31140
31223
|
try {
|
|
31141
31224
|
await this.deps.ensureChannelSession({
|
|
31142
31225
|
accountId: input.accountId,
|
|
31226
|
+
accountDir: input.accountDir,
|
|
31227
|
+
botId: input.botId,
|
|
31228
|
+
botToken: input.botToken,
|
|
31229
|
+
agentSlug: input.agentSlug,
|
|
31230
|
+
channelKey: key,
|
|
31143
31231
|
senderId: input.senderId,
|
|
31144
31232
|
role: input.role,
|
|
31145
31233
|
personId: input.personId ?? null,
|
|
@@ -31148,7 +31236,7 @@ var TelegramGateway = class {
|
|
|
31148
31236
|
serverPath: this.deps.serverPath
|
|
31149
31237
|
});
|
|
31150
31238
|
} finally {
|
|
31151
|
-
this.spawning.delete(
|
|
31239
|
+
this.spawning.delete(key);
|
|
31152
31240
|
}
|
|
31153
31241
|
}
|
|
31154
31242
|
}
|
|
@@ -31162,12 +31250,17 @@ var TelegramGateway = class {
|
|
|
31162
31250
|
// app/lib/telegram/gateway/spawn-request.ts
|
|
31163
31251
|
function buildTelegramSpawnRequest(input) {
|
|
31164
31252
|
const personId = input.role === "public" && input.personId ? input.personId : void 0;
|
|
31253
|
+
const channelKey = telegramChannelKey(input.botId, input.senderId);
|
|
31165
31254
|
return {
|
|
31166
31255
|
// Task 1746 / 1789 — the deterministic per-sender id is the DEFAULT; an
|
|
31167
31256
|
// operator re-seat sets a stable per-sender forward pointer that wins here.
|
|
31168
31257
|
// Mirror of the WhatsApp builder; see its comment for the full distinction
|
|
31169
31258
|
// between this pointer and Task 1526's removed per-firing seat.
|
|
31170
|
-
|
|
31259
|
+
// Task 2368 — the bot id is a hash component of its own, not part of the
|
|
31260
|
+
// subject: with a resolved personId the subject is `person:<personId>` and
|
|
31261
|
+
// the sender never enters the hash at all.
|
|
31262
|
+
sessionId: input.overrideSessionId || adminSessionIdFor(input.accountId, input.senderId, personId, input.botId),
|
|
31263
|
+
channelKey,
|
|
31171
31264
|
role: input.role,
|
|
31172
31265
|
channel: "telegram",
|
|
31173
31266
|
personId,
|
|
@@ -31177,8 +31270,78 @@ function buildTelegramSpawnRequest(input) {
|
|
|
31177
31270
|
// session resolve under that account, not the manager's boot-time house
|
|
31178
31271
|
// default. Mirror of the WhatsApp builder.
|
|
31179
31272
|
targetAccountId: input.accountId,
|
|
31180
|
-
telegramChannel: { senderId:
|
|
31181
|
-
logContext: `channel=telegram-native role=${input.role} senderId=${input.senderId}`
|
|
31273
|
+
telegramChannel: { senderId: channelKey, gatewayUrl: input.gatewayUrl, serverPath: input.serverPath },
|
|
31274
|
+
logContext: `channel=telegram-native role=${input.role} botId=${input.botId} senderId=${input.senderId}`
|
|
31275
|
+
};
|
|
31276
|
+
}
|
|
31277
|
+
|
|
31278
|
+
// server/telegram-ensure-session.ts
|
|
31279
|
+
var TAG50 = "[telegram-inbound]";
|
|
31280
|
+
function buildTelegramEnsureSession(deps) {
|
|
31281
|
+
return async (input) => {
|
|
31282
|
+
const req = buildTelegramSpawnRequest({
|
|
31283
|
+
accountId: input.accountId,
|
|
31284
|
+
senderId: input.senderId,
|
|
31285
|
+
role: input.role,
|
|
31286
|
+
personId: input.personId,
|
|
31287
|
+
gatewayUrl: input.gatewayUrl,
|
|
31288
|
+
serverPath: input.serverPath,
|
|
31289
|
+
botId: input.botId,
|
|
31290
|
+
// Task 1789 — admin rows only; see the WhatsApp twin.
|
|
31291
|
+
overrideSessionId: input.role === "admin" ? deps.resolveSessionOverride(input.accountId, input.channelKey) : null
|
|
31292
|
+
});
|
|
31293
|
+
if (input.role === "public") {
|
|
31294
|
+
const result2 = await deps.managerSpawn({
|
|
31295
|
+
senderId: req.channelKey,
|
|
31296
|
+
role: "public",
|
|
31297
|
+
channel: "telegram",
|
|
31298
|
+
accountId: input.accountId,
|
|
31299
|
+
targetAccountId: input.accountId,
|
|
31300
|
+
// The slug came from THIS account's own bot entry, so the spawn reads
|
|
31301
|
+
// that account's agents directory, runs in its directory and files its
|
|
31302
|
+
// session under it. Mirror of the WhatsApp public-agent path.
|
|
31303
|
+
agentAccountId: input.accountId,
|
|
31304
|
+
agentSlug: input.agentSlug,
|
|
31305
|
+
sessionId: req.sessionId,
|
|
31306
|
+
personId: input.personId ?? void 0,
|
|
31307
|
+
telegramChannel: req.telegramChannel
|
|
31308
|
+
});
|
|
31309
|
+
if ("error" in result2) {
|
|
31310
|
+
console.error(
|
|
31311
|
+
`[telegram-native] op=spawn-failed botId=${input.botId} key=${req.channelKey} role=public agent=${input.agentSlug} error=${result2.error} status=${result2.status}`
|
|
31312
|
+
);
|
|
31313
|
+
return;
|
|
31314
|
+
}
|
|
31315
|
+
console.error(
|
|
31316
|
+
`${TAG50} op=spawn botId=${input.botId} sessionId=${result2.sessionId} targetAccountId=${input.accountId} role=public`
|
|
31317
|
+
);
|
|
31318
|
+
return;
|
|
31319
|
+
}
|
|
31320
|
+
const result = await deps.managerRcSpawn(req);
|
|
31321
|
+
if ("error" in result) {
|
|
31322
|
+
console.error(
|
|
31323
|
+
`[telegram-native] op=spawn-failed botId=${input.botId} key=${req.channelKey} role=admin error=${result.error} status=${result.status}`
|
|
31324
|
+
);
|
|
31325
|
+
return;
|
|
31326
|
+
}
|
|
31327
|
+
console.error(
|
|
31328
|
+
`${TAG50} op=spawn botId=${input.botId} sessionId=${req.sessionId} targetAccountId=${input.accountId} role=admin`
|
|
31329
|
+
);
|
|
31330
|
+
deps.followers.get(req.channelKey)?.abort();
|
|
31331
|
+
let ac;
|
|
31332
|
+
ac = deps.startFileFollower({
|
|
31333
|
+
sessionId: req.sessionId,
|
|
31334
|
+
senderId: req.channelKey,
|
|
31335
|
+
accountId: input.accountId,
|
|
31336
|
+
chatId: input.chatId,
|
|
31337
|
+
botToken: input.botToken,
|
|
31338
|
+
// Identity-guarded eviction: only delete if this controller is still the
|
|
31339
|
+
// current one, so a stale self-close never removes a live follower.
|
|
31340
|
+
onClose: () => {
|
|
31341
|
+
if (deps.followers.get(req.channelKey) === ac) deps.followers.delete(req.channelKey);
|
|
31342
|
+
}
|
|
31343
|
+
});
|
|
31344
|
+
deps.followers.set(req.channelKey, ac);
|
|
31182
31345
|
};
|
|
31183
31346
|
}
|
|
31184
31347
|
|
|
@@ -31186,7 +31349,7 @@ function buildTelegramSpawnRequest(input) {
|
|
|
31186
31349
|
import { realpathSync as realpathSync10 } from "fs";
|
|
31187
31350
|
import { readFile as readFile14, stat as stat10 } from "fs/promises";
|
|
31188
31351
|
import { resolve as resolve42, basename as basename16 } from "path";
|
|
31189
|
-
var
|
|
31352
|
+
var TAG51 = "[telegram:outbound]";
|
|
31190
31353
|
var TELEGRAM_DOCUMENT_MAX_BYTES = 50 * 1024 * 1024;
|
|
31191
31354
|
async function sendTelegramDocument(input) {
|
|
31192
31355
|
const { botToken, chatId, filePath, caption, maxyAccountId, platformRoot: platformRoot5 } = input;
|
|
@@ -31203,10 +31366,10 @@ async function sendTelegramDocument(input) {
|
|
|
31203
31366
|
} catch (err) {
|
|
31204
31367
|
const code = err.code;
|
|
31205
31368
|
if (code === "ENOENT") {
|
|
31206
|
-
console.error(`${
|
|
31369
|
+
console.error(`${TAG51} document ENOENT scope=account-dir accountDir=${accountDir} maxyAccountId=${maxyAccountId}`);
|
|
31207
31370
|
return { ok: false, status: 500, error: `Account directory not resolvable for ${maxyAccountId}` };
|
|
31208
31371
|
}
|
|
31209
|
-
console.error(`${
|
|
31372
|
+
console.error(`${TAG51} document path error scope=account-dir maxyAccountId=${maxyAccountId}: ${String(err)}`);
|
|
31210
31373
|
return { ok: false, status: 500, error: String(err) };
|
|
31211
31374
|
}
|
|
31212
31375
|
let resolvedPath;
|
|
@@ -31215,14 +31378,14 @@ async function sendTelegramDocument(input) {
|
|
|
31215
31378
|
} catch (err) {
|
|
31216
31379
|
const code = err.code;
|
|
31217
31380
|
if (code === "ENOENT") {
|
|
31218
|
-
console.error(`${
|
|
31381
|
+
console.error(`${TAG51} document ENOENT scope=file path=${filePath} maxyAccountId=${maxyAccountId}`);
|
|
31219
31382
|
return { ok: false, status: 404, error: `File not found: ${filePath}` };
|
|
31220
31383
|
}
|
|
31221
|
-
console.error(`${
|
|
31384
|
+
console.error(`${TAG51} document path error scope=file maxyAccountId=${maxyAccountId}: ${String(err)}`);
|
|
31222
31385
|
return { ok: false, status: 500, error: String(err) };
|
|
31223
31386
|
}
|
|
31224
31387
|
if (!resolvedPath.startsWith(accountResolved + "/")) {
|
|
31225
|
-
console.error(`${
|
|
31388
|
+
console.error(`${TAG51} document REJECTED reason=outside_account_directory maxyAccountId=${maxyAccountId}`);
|
|
31226
31389
|
return { ok: false, status: 403, error: "Access denied: file is outside the account directory" };
|
|
31227
31390
|
}
|
|
31228
31391
|
const fileStat = await stat10(resolvedPath);
|
|
@@ -31255,29 +31418,24 @@ async function sendTelegramDocument(input) {
|
|
|
31255
31418
|
error = e instanceof Error ? e.message : String(e);
|
|
31256
31419
|
}
|
|
31257
31420
|
console.error(
|
|
31258
|
-
`${
|
|
31421
|
+
`${TAG51} sent document to=${chatId} file=${filename} bytes=${fileStat.size} ok=${ok2}` + (messageId !== void 0 ? ` id=${messageId}` : "")
|
|
31259
31422
|
);
|
|
31260
31423
|
return ok2 ? { ok: true, messageId } : { ok: false, status: 500, error };
|
|
31261
31424
|
}
|
|
31262
31425
|
|
|
31263
31426
|
// app/lib/telegram/outbound/file-delivery.ts
|
|
31264
|
-
var
|
|
31427
|
+
var TAG52 = "[telegram:outbound]";
|
|
31265
31428
|
function platformRoot4() {
|
|
31266
31429
|
return process.env.MAXY_PLATFORM_ROOT || "";
|
|
31267
31430
|
}
|
|
31268
|
-
function makeTelegramSendFile(entry) {
|
|
31269
|
-
let botToken;
|
|
31431
|
+
function makeTelegramSendFile(entry, botToken) {
|
|
31270
31432
|
return async (filePath, caption) => {
|
|
31271
|
-
if (botToken === void 0) {
|
|
31272
|
-
const tg = resolveAccount()?.config.telegram;
|
|
31273
|
-
botToken = (entry.role === "admin" ? tg?.adminBotToken : tg?.publicBotToken) ?? null;
|
|
31274
|
-
}
|
|
31275
31433
|
if (!botToken) {
|
|
31276
|
-
console.error(`${
|
|
31434
|
+
console.error(`${TAG52} file-delivery reject reason=no-bot-token sender=${entry.senderId} role=${entry.role}`);
|
|
31277
31435
|
return { ok: false, error: "no-bot-token" };
|
|
31278
31436
|
}
|
|
31279
31437
|
if (entry.replyTarget == null) {
|
|
31280
|
-
console.error(`${
|
|
31438
|
+
console.error(`${TAG52} file-delivery reject reason=no-reply-target sender=${entry.senderId} role=${entry.role}`);
|
|
31281
31439
|
return { ok: false, error: "no-reply-target" };
|
|
31282
31440
|
}
|
|
31283
31441
|
const result = await sendTelegramDocument({
|
|
@@ -31291,12 +31449,12 @@ function makeTelegramSendFile(entry) {
|
|
|
31291
31449
|
return result.ok ? { ok: true } : { ok: false, error: result.error };
|
|
31292
31450
|
};
|
|
31293
31451
|
}
|
|
31294
|
-
function makeTelegramFileDelivery(entry) {
|
|
31452
|
+
function makeTelegramFileDelivery(entry, botToken) {
|
|
31295
31453
|
return makeFileDelivery({
|
|
31296
31454
|
entry,
|
|
31297
|
-
tag:
|
|
31455
|
+
tag: TAG52,
|
|
31298
31456
|
channel: "telegram",
|
|
31299
|
-
sendFile: makeTelegramSendFile(entry)
|
|
31457
|
+
sendFile: makeTelegramSendFile(entry, botToken)
|
|
31300
31458
|
});
|
|
31301
31459
|
}
|
|
31302
31460
|
|
|
@@ -31324,7 +31482,7 @@ function startTelegramNativeFileFollower(input) {
|
|
|
31324
31482
|
return startFollower({
|
|
31325
31483
|
entry,
|
|
31326
31484
|
tag: "[telegram-adaptor]",
|
|
31327
|
-
fileDelivery: makeTelegramFileDelivery(entry),
|
|
31485
|
+
fileDelivery: makeTelegramFileDelivery(entry, input.botToken),
|
|
31328
31486
|
// A resumed session's JSONL already holds prior SendUserFile tool_uses;
|
|
31329
31487
|
// suppress replay so historical files are not re-sent on attach.
|
|
31330
31488
|
suppressResumeReplay: true,
|
|
@@ -31335,7 +31493,7 @@ function startTelegramNativeFileFollower(input) {
|
|
|
31335
31493
|
|
|
31336
31494
|
// app/lib/channel-pty-bridge/public-session-end-review.ts
|
|
31337
31495
|
import { randomUUID as randomUUID14 } from "crypto";
|
|
31338
|
-
var
|
|
31496
|
+
var TAG53 = "[public-session-review]";
|
|
31339
31497
|
var CONSUMED_CAP = 500;
|
|
31340
31498
|
var consumed = /* @__PURE__ */ new Set();
|
|
31341
31499
|
function consumeOnce(sessionId) {
|
|
@@ -31362,7 +31520,7 @@ async function fetchJsonl(sessionId) {
|
|
|
31362
31520
|
return await res.text();
|
|
31363
31521
|
} catch (err) {
|
|
31364
31522
|
console.error(
|
|
31365
|
-
`${
|
|
31523
|
+
`${TAG53} jsonl-fetch-failed sessionId=${sessionId} err="${err instanceof Error ? err.message : String(err)}"`
|
|
31366
31524
|
);
|
|
31367
31525
|
return null;
|
|
31368
31526
|
}
|
|
@@ -31386,7 +31544,7 @@ async function fetchPriorWrites(sliceToken, accountId) {
|
|
|
31386
31544
|
const res = await fetch(url);
|
|
31387
31545
|
if (!res.ok) {
|
|
31388
31546
|
console.error(
|
|
31389
|
-
`${
|
|
31547
|
+
`${TAG53} prior-writes-fetch-failed sliceToken=${sliceToken.slice(0, 8)} status=${res.status}`
|
|
31390
31548
|
);
|
|
31391
31549
|
return [];
|
|
31392
31550
|
}
|
|
@@ -31394,7 +31552,7 @@ async function fetchPriorWrites(sliceToken, accountId) {
|
|
|
31394
31552
|
return Array.isArray(body.writes) ? body.writes : [];
|
|
31395
31553
|
} catch (err) {
|
|
31396
31554
|
console.error(
|
|
31397
|
-
`${
|
|
31555
|
+
`${TAG53} prior-writes-fetch-threw sliceToken=${sliceToken.slice(0, 8)} err="${err instanceof Error ? err.message : String(err)}"`
|
|
31398
31556
|
);
|
|
31399
31557
|
return [];
|
|
31400
31558
|
}
|
|
@@ -31423,7 +31581,7 @@ function composeInitialMessage(input) {
|
|
|
31423
31581
|
}
|
|
31424
31582
|
async function dispatchReviewer(input, initialMessage) {
|
|
31425
31583
|
const sessionId = randomUUID14();
|
|
31426
|
-
console.log(`${
|
|
31584
|
+
console.log(`${TAG53} route target=rc-spawn sessionId=${sessionId.slice(0, 8)} sourceSession=${input.sessionId.slice(0, 8)}`);
|
|
31427
31585
|
const spawned = await managerRcSpawn({
|
|
31428
31586
|
sessionId,
|
|
31429
31587
|
initialMessage,
|
|
@@ -31447,21 +31605,21 @@ async function firePublicSessionEndReview(input) {
|
|
|
31447
31605
|
const dispatchedAt = Date.now();
|
|
31448
31606
|
if (consumed.has(input.sessionId)) {
|
|
31449
31607
|
console.log(
|
|
31450
|
-
`${
|
|
31608
|
+
`${TAG53} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-already-reviewed`
|
|
31451
31609
|
);
|
|
31452
31610
|
return;
|
|
31453
31611
|
}
|
|
31454
31612
|
const jsonl = await fetchJsonl(input.sessionId);
|
|
31455
31613
|
if (!jsonl) {
|
|
31456
31614
|
console.log(
|
|
31457
|
-
`${
|
|
31615
|
+
`${TAG53} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-jsonl-missing`
|
|
31458
31616
|
);
|
|
31459
31617
|
return;
|
|
31460
31618
|
}
|
|
31461
31619
|
const operatorTurns = countOperatorTurns(jsonl);
|
|
31462
31620
|
if (operatorTurns === 0) {
|
|
31463
31621
|
console.log(
|
|
31464
|
-
`${
|
|
31622
|
+
`${TAG53} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-no-turns`
|
|
31465
31623
|
);
|
|
31466
31624
|
return;
|
|
31467
31625
|
}
|
|
@@ -31475,19 +31633,19 @@ async function firePublicSessionEndReview(input) {
|
|
|
31475
31633
|
});
|
|
31476
31634
|
if (!consumeOnce(input.sessionId)) {
|
|
31477
31635
|
console.log(
|
|
31478
|
-
`${
|
|
31636
|
+
`${TAG53} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-already-reviewed`
|
|
31479
31637
|
);
|
|
31480
31638
|
return;
|
|
31481
31639
|
}
|
|
31482
31640
|
const dispatched = await dispatchReviewer(input, initialMessage);
|
|
31483
31641
|
if (!dispatched.ok) {
|
|
31484
31642
|
console.error(
|
|
31485
|
-
`${
|
|
31643
|
+
`${TAG53} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-spawn-failed reason=${dispatched.reason}`
|
|
31486
31644
|
);
|
|
31487
31645
|
return;
|
|
31488
31646
|
}
|
|
31489
31647
|
console.log(
|
|
31490
|
-
`${
|
|
31648
|
+
`${TAG53} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=dispatched managerSessionId=${dispatched.managerSessionId} operatorTurns=${operatorTurns} priorWrites=${priorWrites.length} ms=${Date.now() - dispatchedAt}`
|
|
31491
31649
|
);
|
|
31492
31650
|
}
|
|
31493
31651
|
|
|
@@ -31625,28 +31783,59 @@ import { statSync as statSync25 } from "fs";
|
|
|
31625
31783
|
import { basename as basename17, dirname as dirname17, join as join54 } from "path";
|
|
31626
31784
|
function buildTelegramAuditLines(input) {
|
|
31627
31785
|
const lines = [];
|
|
31628
|
-
|
|
31629
|
-
|
|
31630
|
-
|
|
31631
|
-
|
|
31632
|
-
|
|
31633
|
-
|
|
31634
|
-
|
|
31635
|
-
|
|
31636
|
-
|
|
31637
|
-
|
|
31638
|
-
|
|
31786
|
+
const entries2 = input.accounts.flatMap(
|
|
31787
|
+
(a) => listBotEntries(a.telegram).map((entry) => ({ accountId: a.accountId, accountDir: a.accountDir, entry }))
|
|
31788
|
+
);
|
|
31789
|
+
if (entries2.length === 0) return lines;
|
|
31790
|
+
const byId = /* @__PURE__ */ new Map();
|
|
31791
|
+
for (const { entry } of entries2) byId.set(entry.id, (byId.get(entry.id) ?? 0) + 1);
|
|
31792
|
+
for (const [botId, count] of byId) {
|
|
31793
|
+
if (count > 1) lines.push(`[telegram-audit] op=duplicate-bot-id botId=${botId} accounts=${count}`);
|
|
31794
|
+
}
|
|
31795
|
+
for (const { accountId, accountDir, entry } of entries2) {
|
|
31796
|
+
if (entry.role === "public" && !input.agentActive(accountDir, entry.agent)) {
|
|
31639
31797
|
lines.push(
|
|
31640
|
-
`[telegram-audit] op=
|
|
31798
|
+
`[telegram-audit] op=entry-agent-inactive accountId=${accountId} botId=${entry.id} agent=${entry.agent}`
|
|
31641
31799
|
);
|
|
31642
31800
|
}
|
|
31801
|
+
if (!input.secretFileExists(entry.id)) {
|
|
31802
|
+
lines.push(`[telegram-audit] op=secret-orphan botId=${entry.id} side=entry`);
|
|
31803
|
+
}
|
|
31804
|
+
}
|
|
31805
|
+
const known = new Set(entries2.map((e) => e.entry.id));
|
|
31806
|
+
for (const botId of input.secretFilesOnDisk) {
|
|
31807
|
+
if (!known.has(botId)) lines.push(`[telegram-audit] op=secret-orphan botId=${botId} side=file`);
|
|
31808
|
+
}
|
|
31809
|
+
for (const { accountId, entry } of entries2) {
|
|
31810
|
+
const last = input.lastInboundByBot(entry.id);
|
|
31811
|
+
const age = last === null ? "never" : String(input.now - last);
|
|
31812
|
+
const agent = entry.role === "admin" ? "admin" : entry.agent;
|
|
31813
|
+
const secretFile = input.secretFileExists(entry.id) ? "present" : "absent";
|
|
31814
|
+
lines.push(
|
|
31815
|
+
`[telegram-audit] op=configured botId=${entry.id} accountId=${accountId} agent=${agent} secretFile=${secretFile} lastInboundAgeMs=${age}`
|
|
31816
|
+
);
|
|
31643
31817
|
}
|
|
31644
31818
|
return lines;
|
|
31645
31819
|
}
|
|
31646
|
-
function
|
|
31820
|
+
function newestTelegramSessionByBot() {
|
|
31647
31821
|
const out = /* @__PURE__ */ new Map();
|
|
31822
|
+
for (const { botId, mtime } of walkTelegramSidecars()) {
|
|
31823
|
+
if (botId === null) continue;
|
|
31824
|
+
const prev = out.get(botId);
|
|
31825
|
+
if (prev === void 0 || mtime > prev) out.set(botId, mtime);
|
|
31826
|
+
}
|
|
31827
|
+
return out;
|
|
31828
|
+
}
|
|
31829
|
+
function countTelegramSessionsForAccount(accountId) {
|
|
31830
|
+
let n = 0;
|
|
31831
|
+
for (const { accountId: id } of walkTelegramSidecars()) {
|
|
31832
|
+
if (id === accountId) n += 1;
|
|
31833
|
+
}
|
|
31834
|
+
return n;
|
|
31835
|
+
}
|
|
31836
|
+
function* walkTelegramSidecars() {
|
|
31648
31837
|
const cfg = claudeConfigDir();
|
|
31649
|
-
if (!cfg) return
|
|
31838
|
+
if (!cfg) return;
|
|
31650
31839
|
const projectsRoot = join54(cfg, "projects");
|
|
31651
31840
|
for (const { path } of enumerateJsonls(projectsRoot)) {
|
|
31652
31841
|
const sessionId = basename17(path).replace(/\.jsonl$/, "");
|
|
@@ -31658,11 +31847,80 @@ function newestTelegramSessionByAccount() {
|
|
|
31658
31847
|
} catch {
|
|
31659
31848
|
continue;
|
|
31660
31849
|
}
|
|
31661
|
-
|
|
31662
|
-
|
|
31850
|
+
yield {
|
|
31851
|
+
accountId: meta.accountId,
|
|
31852
|
+
botId: parseTelegramChannelKey(meta.senderId ?? "")?.botId ?? null,
|
|
31853
|
+
mtime
|
|
31854
|
+
};
|
|
31855
|
+
}
|
|
31856
|
+
}
|
|
31857
|
+
|
|
31858
|
+
// server/telegram-migrate.ts
|
|
31859
|
+
import { readFileSync as readFileSync45, writeFileSync as writeFileSync17, existsSync as existsSync49, renameSync as renameSync11, mkdirSync as mkdirSync10 } from "fs";
|
|
31860
|
+
import { dirname as dirname18, join as join55 } from "path";
|
|
31861
|
+
function migrateTelegramTwoSlot(input) {
|
|
31862
|
+
const out = [];
|
|
31863
|
+
for (const account of input.accounts) {
|
|
31864
|
+
const configPath3 = join55(account.accountDir, "account.json");
|
|
31865
|
+
let config;
|
|
31866
|
+
try {
|
|
31867
|
+
config = JSON.parse(readFileSync45(configPath3, "utf-8"));
|
|
31868
|
+
} catch {
|
|
31869
|
+
console.error(`[telegram-audit] op=migrate-unreadable accountId=${account.accountId}`);
|
|
31870
|
+
continue;
|
|
31871
|
+
}
|
|
31872
|
+
const tg = config.telegram;
|
|
31873
|
+
if (!tg) continue;
|
|
31874
|
+
const adminToken = typeof tg.adminBotToken === "string" ? tg.adminBotToken : null;
|
|
31875
|
+
const publicToken = typeof tg.publicBotToken === "string" ? tg.publicBotToken : null;
|
|
31876
|
+
if (!adminToken && !publicToken) continue;
|
|
31877
|
+
const outcome = { accountId: account.accountId, converted: 0, orphanSessions: 0 };
|
|
31878
|
+
const bots = Array.isArray(tg.bots) ? [...tg.bots] : [];
|
|
31879
|
+
if (adminToken) {
|
|
31880
|
+
const id = botIdFromToken(adminToken);
|
|
31881
|
+
if (!id) {
|
|
31882
|
+
outcome.skippedAdmin = "malformed-token";
|
|
31883
|
+
} else {
|
|
31884
|
+
const adminUsers = Array.isArray(tg.adminUsers) ? tg.adminUsers.filter((u) => typeof u === "number") : void 0;
|
|
31885
|
+
bots.push({ id, token: adminToken, role: "admin", ...adminUsers ? { adminUsers } : {} });
|
|
31886
|
+
moveSecret(input.legacySecretPathFor("admin", account.accountId), input.secretPathFor(id));
|
|
31887
|
+
outcome.converted += 1;
|
|
31888
|
+
}
|
|
31889
|
+
}
|
|
31890
|
+
if (publicToken) {
|
|
31891
|
+
const id = botIdFromToken(publicToken);
|
|
31892
|
+
const agent = typeof config.defaultAgent === "string" && config.defaultAgent.length > 0 ? config.defaultAgent : null;
|
|
31893
|
+
if (!id) {
|
|
31894
|
+
outcome.skippedPublic = "malformed-token";
|
|
31895
|
+
} else if (!agent) {
|
|
31896
|
+
outcome.skippedPublic = "no-default-agent";
|
|
31897
|
+
} else {
|
|
31898
|
+
const dmPolicy = typeof tg.dmPolicy === "string" ? tg.dmPolicy : void 0;
|
|
31899
|
+
const allowFrom = Array.isArray(tg.allowFrom) ? tg.allowFrom.filter((u) => typeof u === "number") : void 0;
|
|
31900
|
+
bots.push({
|
|
31901
|
+
id,
|
|
31902
|
+
token: publicToken,
|
|
31903
|
+
role: "public",
|
|
31904
|
+
agent,
|
|
31905
|
+
...dmPolicy ? { dmPolicy } : {},
|
|
31906
|
+
...allowFrom ? { allowFrom } : {}
|
|
31907
|
+
});
|
|
31908
|
+
moveSecret(input.legacySecretPathFor("public", account.accountId), input.secretPathFor(id));
|
|
31909
|
+
outcome.converted += 1;
|
|
31910
|
+
}
|
|
31911
|
+
}
|
|
31912
|
+
config.telegram = { bots };
|
|
31913
|
+
writeFileSync17(configPath3, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
31914
|
+
outcome.orphanSessions = input.countOrphanSessions(account.accountId);
|
|
31915
|
+
out.push(outcome);
|
|
31663
31916
|
}
|
|
31664
31917
|
return out;
|
|
31665
31918
|
}
|
|
31919
|
+
function moveSecret(from, to) {
|
|
31920
|
+
if (!existsSync49(from)) return;
|
|
31921
|
+
mkdirSync10(dirname18(to), { recursive: true, mode: 448 });
|
|
31922
|
+
renameSync11(from, to);
|
|
31923
|
+
}
|
|
31666
31924
|
|
|
31667
31925
|
// app/lib/whatsapp/recall-store-backfill.ts
|
|
31668
31926
|
function backfillMisfiledRecords(params) {
|
|
@@ -31700,14 +31958,14 @@ function backfillMisfiledRecords(params) {
|
|
|
31700
31958
|
}
|
|
31701
31959
|
|
|
31702
31960
|
// server/lib/booking-site-accounts.ts
|
|
31703
|
-
import { existsSync as
|
|
31961
|
+
import { existsSync as existsSync50, readFileSync as readFileSync46 } from "fs";
|
|
31704
31962
|
import { resolve as resolve43 } from "path";
|
|
31705
31963
|
function listBookingSiteAccounts(accounts) {
|
|
31706
31964
|
return accounts.filter((a) => {
|
|
31707
31965
|
const availPath = resolve43(a.accountDir, "calendar-availability.json");
|
|
31708
|
-
if (!
|
|
31966
|
+
if (!existsSync50(availPath)) return false;
|
|
31709
31967
|
try {
|
|
31710
|
-
const cfg = JSON.parse(
|
|
31968
|
+
const cfg = JSON.parse(readFileSync46(availPath, "utf-8"));
|
|
31711
31969
|
return typeof cfg.bookingDbName === "string" && cfg.bookingDbName.length > 0;
|
|
31712
31970
|
} catch {
|
|
31713
31971
|
return false;
|
|
@@ -31745,7 +32003,7 @@ function broadcastAdminShutdown(reason) {
|
|
|
31745
32003
|
|
|
31746
32004
|
// ../lib/entitlement/src/index.ts
|
|
31747
32005
|
import { createPublicKey, createHash as createHash6, verify as cryptoVerify } from "crypto";
|
|
31748
|
-
import { existsSync as
|
|
32006
|
+
import { existsSync as existsSync51, readFileSync as readFileSync47, statSync as statSync26 } from "fs";
|
|
31749
32007
|
import { resolve as resolve44 } from "path";
|
|
31750
32008
|
|
|
31751
32009
|
// ../lib/entitlement/src/canonicalize.ts
|
|
@@ -31794,7 +32052,7 @@ function resolveEntitlement(brand, account) {
|
|
|
31794
32052
|
return logResolved(implicitTrust(account), null);
|
|
31795
32053
|
}
|
|
31796
32054
|
const entitlementPath = resolve44(brand.configDir, "entitlement.json");
|
|
31797
|
-
if (!
|
|
32055
|
+
if (!existsSync51(entitlementPath)) {
|
|
31798
32056
|
return logResolved(anonymousFallback("missing"), { reason: "missing" });
|
|
31799
32057
|
}
|
|
31800
32058
|
const stat11 = statSync26(entitlementPath);
|
|
@@ -31809,7 +32067,7 @@ function resolveEntitlement(brand, account) {
|
|
|
31809
32067
|
function verifyAndResolve(brand, entitlementPath, account) {
|
|
31810
32068
|
let pubkeyPem;
|
|
31811
32069
|
try {
|
|
31812
|
-
pubkeyPem =
|
|
32070
|
+
pubkeyPem = readFileSync47(pubkeyPath(brand), "utf-8");
|
|
31813
32071
|
} catch (err) {
|
|
31814
32072
|
return logResolved(anonymousFallback("pubkey-missing"), {
|
|
31815
32073
|
reason: "pubkey-missing"
|
|
@@ -31823,7 +32081,7 @@ function verifyAndResolve(brand, entitlementPath, account) {
|
|
|
31823
32081
|
}
|
|
31824
32082
|
let envelope;
|
|
31825
32083
|
try {
|
|
31826
|
-
envelope = JSON.parse(
|
|
32084
|
+
envelope = JSON.parse(readFileSync47(entitlementPath, "utf-8"));
|
|
31827
32085
|
} catch {
|
|
31828
32086
|
return logResolved(anonymousFallback("malformed"), { reason: "malformed" });
|
|
31829
32087
|
}
|
|
@@ -31985,14 +32243,14 @@ function clientFrom(c) {
|
|
|
31985
32243
|
}
|
|
31986
32244
|
installMediaDownloadGuard();
|
|
31987
32245
|
var PLATFORM_ROOT6 = process.env.MAXY_PLATFORM_ROOT || "";
|
|
31988
|
-
var BRAND_JSON_PATH = PLATFORM_ROOT6 ?
|
|
32246
|
+
var BRAND_JSON_PATH = PLATFORM_ROOT6 ? join56(PLATFORM_ROOT6, "config", "brand.json") : "";
|
|
31989
32247
|
var BRAND = { productName: "Maxy", hostname: "maxy", configDir: ".maxy", marketingUrl: "https://getmaxy.com" };
|
|
31990
|
-
if (BRAND_JSON_PATH && !
|
|
32248
|
+
if (BRAND_JSON_PATH && !existsSync52(BRAND_JSON_PATH)) {
|
|
31991
32249
|
console.error(`[brand] WARNING: brand.json not found at ${BRAND_JSON_PATH} \u2014 using Maxy defaults`);
|
|
31992
32250
|
}
|
|
31993
|
-
if (BRAND_JSON_PATH &&
|
|
32251
|
+
if (BRAND_JSON_PATH && existsSync52(BRAND_JSON_PATH)) {
|
|
31994
32252
|
try {
|
|
31995
|
-
const parsed = JSON.parse(
|
|
32253
|
+
const parsed = JSON.parse(readFileSync48(BRAND_JSON_PATH, "utf-8"));
|
|
31996
32254
|
BRAND = { ...BRAND, ...parsed };
|
|
31997
32255
|
} catch (err) {
|
|
31998
32256
|
console.error(`[brand] Failed to parse brand.json: ${err.message}`);
|
|
@@ -32024,11 +32282,11 @@ var brandLoginOpts = {
|
|
|
32024
32282
|
appleTouchIconPath: brandAppIcon512Path,
|
|
32025
32283
|
themeColor: BRAND.defaultColors?.primary
|
|
32026
32284
|
};
|
|
32027
|
-
var ALIAS_DOMAINS_PATH =
|
|
32285
|
+
var ALIAS_DOMAINS_PATH = join56(homedir4(), BRAND.configDir, "alias-domains.json");
|
|
32028
32286
|
function loadAliasDomains() {
|
|
32029
32287
|
try {
|
|
32030
|
-
if (!
|
|
32031
|
-
const parsed = JSON.parse(
|
|
32288
|
+
if (!existsSync52(ALIAS_DOMAINS_PATH)) return null;
|
|
32289
|
+
const parsed = JSON.parse(readFileSync48(ALIAS_DOMAINS_PATH, "utf-8"));
|
|
32032
32290
|
if (!Array.isArray(parsed)) {
|
|
32033
32291
|
console.error("[alias-domains] malformed alias-domains.json \u2014 expected array");
|
|
32034
32292
|
return null;
|
|
@@ -32119,7 +32377,7 @@ var waGateway = new WaGateway({
|
|
|
32119
32377
|
sendToParty: (accountId, phone, text) => sendWhatsAppTextForRelay(accountId, phone, text),
|
|
32120
32378
|
resolveSessionOverride: (accountId, senderId) => resolveChannelOverride(accountId, "whatsapp", senderId),
|
|
32121
32379
|
gatewayUrl: `http://127.0.0.1:${process.env.MAXY_UI_INTERNAL_PORT ?? ""}`,
|
|
32122
|
-
serverPath: process.env.MAXY_WA_CHANNEL_SERVER_PATH ?? resolve45(process.env.MAXY_PLATFORM_ROOT ??
|
|
32380
|
+
serverPath: process.env.MAXY_WA_CHANNEL_SERVER_PATH ?? resolve45(process.env.MAXY_PLATFORM_ROOT ?? join56(__dirname, ".."), "services/whatsapp-channel/dist/server.js"),
|
|
32123
32381
|
// Task 751 / 1390 — file delivery on the native channel. `maxyAccountId` (the
|
|
32124
32382
|
// path-validation scope) is the sender's effective SESSION account, resolved
|
|
32125
32383
|
// once at the inbound gate and threaded here via the gateway's per-sender doc
|
|
@@ -32134,7 +32392,7 @@ var waGateway = new WaGateway({
|
|
|
32134
32392
|
caption,
|
|
32135
32393
|
accountId,
|
|
32136
32394
|
maxyAccountId,
|
|
32137
|
-
platformRoot: resolve45(process.env.MAXY_PLATFORM_ROOT ??
|
|
32395
|
+
platformRoot: resolve45(process.env.MAXY_PLATFORM_ROOT ?? join56(__dirname, ".."))
|
|
32138
32396
|
});
|
|
32139
32397
|
return result.ok ? { ok: true, messageId: result.messageId } : { ok: false, error: result.error };
|
|
32140
32398
|
},
|
|
@@ -32301,7 +32559,7 @@ function runDuplicateSenderAudit() {
|
|
|
32301
32559
|
try {
|
|
32302
32560
|
const configDir = process.env.CLAUDE_CONFIG_DIR;
|
|
32303
32561
|
if (!configDir) return;
|
|
32304
|
-
const projectsRoot =
|
|
32562
|
+
const projectsRoot = join56(configDir, "projects");
|
|
32305
32563
|
const records = [];
|
|
32306
32564
|
for (const { path: jsonlPath, isSubagent, archived } of enumerateJsonls(projectsRoot)) {
|
|
32307
32565
|
if (isSubagent || archived) continue;
|
|
@@ -32442,42 +32700,22 @@ var telegramGateway = new TelegramGateway({
|
|
|
32442
32700
|
// Task 2353 — no turn-instruction resolver on this door. See the dep comment in
|
|
32443
32701
|
// telegram-gateway.ts: the account file governs the relay turn, which only the
|
|
32444
32702
|
// WhatsApp door raises.
|
|
32445
|
-
|
|
32703
|
+
// Task 2368 — the pointer keys on the `<botId>:<senderId>` channel key, which
|
|
32704
|
+
// is what the classification sidecar records (the manager reads
|
|
32705
|
+
// telegramChannel.senderId) and therefore what session-reseat.ts writes.
|
|
32706
|
+
resolveSessionOverride: (accountId, channelKey) => resolveChannelOverride(accountId, "telegram", channelKey),
|
|
32446
32707
|
gatewayUrl: telegramGatewayUrl(),
|
|
32447
32708
|
serverPath: telegramServerPath(),
|
|
32448
|
-
|
|
32449
|
-
|
|
32450
|
-
|
|
32451
|
-
|
|
32452
|
-
|
|
32453
|
-
|
|
32454
|
-
|
|
32455
|
-
|
|
32456
|
-
|
|
32457
|
-
|
|
32458
|
-
});
|
|
32459
|
-
const result = await managerRcSpawn(req);
|
|
32460
|
-
if ("error" in result) {
|
|
32461
|
-
console.error(`[telegram-native] spawn-failed senderId=${senderId} role=${role} error=${result.error} status=${result.status}`);
|
|
32462
|
-
return;
|
|
32463
|
-
}
|
|
32464
|
-
if (role !== "admin") return;
|
|
32465
|
-
const prior = telegramFileFollowers.get(senderId);
|
|
32466
|
-
if (prior) prior.abort();
|
|
32467
|
-
let ac;
|
|
32468
|
-
ac = startTelegramNativeFileFollower({
|
|
32469
|
-
sessionId: req.sessionId,
|
|
32470
|
-
senderId,
|
|
32471
|
-
accountId,
|
|
32472
|
-
chatId,
|
|
32473
|
-
// Identity-guarded eviction: only delete if this controller is still the
|
|
32474
|
-
// current one, so a stale self-close never removes a live follower.
|
|
32475
|
-
onClose: () => {
|
|
32476
|
-
if (telegramFileFollowers.get(senderId) === ac) telegramFileFollowers.delete(senderId);
|
|
32477
|
-
}
|
|
32478
|
-
});
|
|
32479
|
-
telegramFileFollowers.set(senderId, ac);
|
|
32480
|
-
}
|
|
32709
|
+
// Task 2368 — the role branch lives in its own module so it has a test that
|
|
32710
|
+
// needs no live manager. An admin bot keeps /rc-spawn; a public bot goes to
|
|
32711
|
+
// /public-spawn, the only door that accepts an agent slug.
|
|
32712
|
+
ensureChannelSession: buildTelegramEnsureSession({
|
|
32713
|
+
managerRcSpawn,
|
|
32714
|
+
managerSpawn,
|
|
32715
|
+
resolveSessionOverride: (accountId, channelKey) => resolveChannelOverride(accountId, "telegram", channelKey),
|
|
32716
|
+
startFileFollower: startTelegramNativeFileFollower,
|
|
32717
|
+
followers: telegramFileFollowers
|
|
32718
|
+
})
|
|
32481
32719
|
});
|
|
32482
32720
|
setTelegramGateway(telegramGateway);
|
|
32483
32721
|
app76.route("/", telegramGateway.routes());
|
|
@@ -32519,24 +32757,29 @@ var scheduleInjectRoutes = createScheduleInjectRoutes({
|
|
|
32519
32757
|
// effective-account.ts for why `resolveAccount()` was the wrong side to move,
|
|
32520
32758
|
// and why the real dependencies are bound there rather than assembled here.
|
|
32521
32759
|
effectiveAccountFor: resolveScheduledEffectiveAccountForInstall,
|
|
32522
|
-
// Task
|
|
32523
|
-
//
|
|
32760
|
+
// Task 2368 — which BOT claims this chat id, across every account. Reads the
|
|
32761
|
+
// same per-bot split, through the same readChannelAdmins call, that
|
|
32524
32762
|
// validateAgentDestination uses (agent-dispatch-rule), so the routine picker,
|
|
32525
|
-
// the save and this dispatch cannot disagree. Two claimants is reported to
|
|
32526
|
-
//
|
|
32763
|
+
// the save and this dispatch cannot disagree. Two claimants is reported to the
|
|
32764
|
+
// route and refused there, never picked between — and two bots in ONE account
|
|
32765
|
+
// is as ambiguous as two accounts, because the dispatch has no basis to choose
|
|
32766
|
+
// which bot the person hears from.
|
|
32527
32767
|
telegramAccountForDestination: (destination) => {
|
|
32528
32768
|
const asNumber = Number(destination);
|
|
32529
32769
|
if (!Number.isInteger(asNumber)) return { kind: "none" };
|
|
32530
|
-
const
|
|
32531
|
-
|
|
32532
|
-
|
|
32533
|
-
|
|
32534
|
-
|
|
32535
|
-
|
|
32536
|
-
|
|
32537
|
-
|
|
32538
|
-
|
|
32539
|
-
}
|
|
32770
|
+
const hits = [];
|
|
32771
|
+
for (const a of listValidAccounts()) {
|
|
32772
|
+
const entries2 = listBotEntries(a.config.telegram);
|
|
32773
|
+
for (const bot of readChannelAdmins(a.accountDir, "telegram").byBot) {
|
|
32774
|
+
if (!bot.adminUsers.includes(asNumber)) continue;
|
|
32775
|
+
const entry = entries2.find((e) => e.id === bot.botId);
|
|
32776
|
+
if (!entry) continue;
|
|
32777
|
+
hits.push({ accountId: a.accountId, botId: bot.botId, botToken: entry.token });
|
|
32778
|
+
}
|
|
32779
|
+
}
|
|
32780
|
+
if (hits.length === 0) return { kind: "none" };
|
|
32781
|
+
if (hits.length > 1) return { kind: "ambiguous", count: hits.length };
|
|
32782
|
+
return { kind: "one", ...hits[0] };
|
|
32540
32783
|
}
|
|
32541
32784
|
});
|
|
32542
32785
|
app76.route("/api/channel/schedule-inject", scheduleInjectRoutes);
|
|
@@ -32909,14 +33152,14 @@ app76.get("/agent-assets/:slug/:filename", (c) => {
|
|
|
32909
33152
|
console.error(`[agent-assets] path-traversal-rejected slug=${slug} file=${filename}`);
|
|
32910
33153
|
return c.text("Forbidden", 403);
|
|
32911
33154
|
}
|
|
32912
|
-
if (!
|
|
33155
|
+
if (!existsSync52(filePath)) {
|
|
32913
33156
|
console.error(`[agent-assets] serve slug=${slug} file=${filename} status=404`);
|
|
32914
33157
|
return c.text("Not found", 404);
|
|
32915
33158
|
}
|
|
32916
33159
|
const ext = "." + filename.split(".").pop()?.toLowerCase();
|
|
32917
33160
|
const contentType = IMAGE_MIME[ext] || "application/octet-stream";
|
|
32918
33161
|
console.log(`[agent-assets] serve slug=${slug} file=${filename} status=200`);
|
|
32919
|
-
const body =
|
|
33162
|
+
const body = readFileSync48(filePath);
|
|
32920
33163
|
return c.body(body, 200, {
|
|
32921
33164
|
"Content-Type": contentType,
|
|
32922
33165
|
"Cache-Control": "public, max-age=3600"
|
|
@@ -32939,14 +33182,14 @@ app76.get("/generated/:filename", (c) => {
|
|
|
32939
33182
|
console.error(`[generated] serve file=${filename} status=403`);
|
|
32940
33183
|
return c.text("Forbidden", 403);
|
|
32941
33184
|
}
|
|
32942
|
-
if (!
|
|
33185
|
+
if (!existsSync52(filePath)) {
|
|
32943
33186
|
console.error(`[generated] serve file=${filename} status=404`);
|
|
32944
33187
|
return c.text("Not found", 404);
|
|
32945
33188
|
}
|
|
32946
33189
|
const ext = "." + filename.split(".").pop()?.toLowerCase();
|
|
32947
33190
|
const contentType = IMAGE_MIME[ext] || "application/octet-stream";
|
|
32948
33191
|
console.log(`[generated] serve file=${filename} status=200`);
|
|
32949
|
-
const body =
|
|
33192
|
+
const body = readFileSync48(filePath);
|
|
32950
33193
|
return c.body(body, 200, {
|
|
32951
33194
|
"Content-Type": contentType,
|
|
32952
33195
|
"Cache-Control": "public, max-age=86400"
|
|
@@ -32959,9 +33202,9 @@ app76.route("/v", visitor_consent_default);
|
|
|
32959
33202
|
var htmlCache = /* @__PURE__ */ new Map();
|
|
32960
33203
|
var brandLogoPath = "/brand/maxy-monochrome.png";
|
|
32961
33204
|
var brandIconPath = "/brand/maxy-monochrome.png";
|
|
32962
|
-
if (BRAND_JSON_PATH &&
|
|
33205
|
+
if (BRAND_JSON_PATH && existsSync52(BRAND_JSON_PATH)) {
|
|
32963
33206
|
try {
|
|
32964
|
-
const fullBrand = JSON.parse(
|
|
33207
|
+
const fullBrand = JSON.parse(readFileSync48(BRAND_JSON_PATH, "utf-8"));
|
|
32965
33208
|
if (fullBrand.assets?.logo) brandLogoPath = `/brand/${fullBrand.assets.logo}`;
|
|
32966
33209
|
brandIconPath = fullBrand.assets?.icon ? `/brand/${fullBrand.assets.icon}` : brandLogoPath;
|
|
32967
33210
|
} catch {
|
|
@@ -32988,11 +33231,11 @@ var brandScript = `<script>window.__BRAND__=${JSON.stringify({
|
|
|
32988
33231
|
var brandThemeColor = BRAND.defaultColors?.primary ?? "#000000";
|
|
32989
33232
|
var brandBackgroundColor = BRAND.defaultColors?.background ?? "#ffffff";
|
|
32990
33233
|
var brandAppIconsExist = [brandAppIcon192Path, brandAppIcon512Path, brandMaskableIconPath].every(
|
|
32991
|
-
(p) =>
|
|
33234
|
+
(p) => existsSync52(resolve45(process.cwd(), "public", p.replace(/^\//, "")))
|
|
32992
33235
|
);
|
|
32993
33236
|
var SW_SOURCE = (() => {
|
|
32994
33237
|
try {
|
|
32995
|
-
return
|
|
33238
|
+
return readFileSync48(resolve45(process.cwd(), "public", "sw.js"), "utf-8");
|
|
32996
33239
|
} catch {
|
|
32997
33240
|
return null;
|
|
32998
33241
|
}
|
|
@@ -33000,9 +33243,9 @@ var SW_SOURCE = (() => {
|
|
|
33000
33243
|
function readInstalledVersion() {
|
|
33001
33244
|
try {
|
|
33002
33245
|
if (!PLATFORM_ROOT6) return "unknown";
|
|
33003
|
-
const versionFile =
|
|
33004
|
-
if (!
|
|
33005
|
-
const content =
|
|
33246
|
+
const versionFile = join56(PLATFORM_ROOT6, "config", `.${BRAND.hostname}-version`);
|
|
33247
|
+
if (!existsSync52(versionFile)) return "unknown";
|
|
33248
|
+
const content = readFileSync48(versionFile, "utf-8").trim();
|
|
33006
33249
|
return content || "unknown";
|
|
33007
33250
|
} catch {
|
|
33008
33251
|
return "unknown";
|
|
@@ -33013,7 +33256,7 @@ var versionScript = `<script>window.__MAXY_VERSION__=${JSON.stringify(INSTALLED_
|
|
|
33013
33256
|
function cachedHtml(file) {
|
|
33014
33257
|
let html = htmlCache.get(file);
|
|
33015
33258
|
if (!html) {
|
|
33016
|
-
html =
|
|
33259
|
+
html = readFileSync48(resolve45(process.cwd(), "public", file), "utf-8");
|
|
33017
33260
|
const productNameEsc = escapeHtml(BRAND.productName);
|
|
33018
33261
|
html = html.replace(/<title>([^<]*)<\/title>/, (_match, inner) => `<title>${escapeHtml(inner).replace(/Maxy/g, productNameEsc)}</title>`);
|
|
33019
33262
|
html = html.replace('href="/favicon.ico"', `href="${escapeHtml(brandFaviconPath)}"`);
|
|
@@ -33030,14 +33273,14 @@ ${clientErrorReporterScript}
|
|
|
33030
33273
|
return html;
|
|
33031
33274
|
}
|
|
33032
33275
|
function loadBrandingCache(agentSlug) {
|
|
33033
|
-
const configDir =
|
|
33276
|
+
const configDir = join56(homedir4(), BRAND.configDir);
|
|
33034
33277
|
try {
|
|
33035
33278
|
const verdict = resolvePublicAddressFromDisk(agentSlug);
|
|
33036
33279
|
const accountId = verdict.kind === "served" ? verdict.owner.accountId : getDefaultAccountId();
|
|
33037
33280
|
if (!accountId) return null;
|
|
33038
|
-
const cachePath =
|
|
33039
|
-
if (!
|
|
33040
|
-
return JSON.parse(
|
|
33281
|
+
const cachePath = join56(configDir, "branding-cache", accountId, `${agentSlug}.json`);
|
|
33282
|
+
if (!existsSync52(cachePath)) return null;
|
|
33283
|
+
return JSON.parse(readFileSync48(cachePath, "utf-8"));
|
|
33041
33284
|
} catch {
|
|
33042
33285
|
return null;
|
|
33043
33286
|
}
|
|
@@ -33132,7 +33375,7 @@ app76.use("/vnc-popout.html", logViewerFetch);
|
|
|
33132
33375
|
app76.get("/vnc-popout.html", (c) => {
|
|
33133
33376
|
let html = htmlCache.get("vnc-popout.html");
|
|
33134
33377
|
if (!html) {
|
|
33135
|
-
html =
|
|
33378
|
+
html = readFileSync48(resolve45(process.cwd(), "public", "vnc-popout.html"), "utf-8");
|
|
33136
33379
|
const name = escapeHtml(BRAND.productName);
|
|
33137
33380
|
html = html.replace("<title>Browser \u2014 Maxy</title>", `<title>${name}</title>`);
|
|
33138
33381
|
html = html.replace("</head>", ` ${brandScript}
|
|
@@ -33285,10 +33528,10 @@ var hostname = process.env.HOSTNAME ?? "127.0.0.1";
|
|
|
33285
33528
|
var httpServer = serve({ fetch: app76.fetch, port, hostname });
|
|
33286
33529
|
console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
33287
33530
|
{
|
|
33288
|
-
const reconcilePlatformRoot = process.env.MAXY_PLATFORM_ROOT ??
|
|
33531
|
+
const reconcilePlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join56(__dirname, "..");
|
|
33289
33532
|
const reconcileScript = resolve45(reconcilePlatformRoot, "plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js");
|
|
33290
33533
|
const runReconcile = (ctx) => {
|
|
33291
|
-
if (!
|
|
33534
|
+
if (!existsSync52(reconcileScript)) return;
|
|
33292
33535
|
try {
|
|
33293
33536
|
const child = spawn3(process.execPath, [reconcileScript], {
|
|
33294
33537
|
env: { ...process.env, PLATFORM_ROOT: reconcilePlatformRoot },
|
|
@@ -33308,7 +33551,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
33308
33551
|
detached: true,
|
|
33309
33552
|
run: runReconcile
|
|
33310
33553
|
});
|
|
33311
|
-
const reconcileStatePath =
|
|
33554
|
+
const reconcileStatePath = join56(MAXY_DIR, "booking-reconcile-state.json");
|
|
33312
33555
|
registerLoop({
|
|
33313
33556
|
name: "calendar-reconcile-liveness",
|
|
33314
33557
|
intervalMs: 6e5,
|
|
@@ -33326,7 +33569,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
33326
33569
|
}
|
|
33327
33570
|
{
|
|
33328
33571
|
const DREAM_CYCLE_SWEEP_INTERVAL_MS = 36e5;
|
|
33329
|
-
const dreamPlatformRoot = process.env.MAXY_PLATFORM_ROOT ??
|
|
33572
|
+
const dreamPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join56(__dirname, "..");
|
|
33330
33573
|
const dreamSweepScript = resolve45(dreamPlatformRoot, "plugins/memory/mcp/dist/scripts/dream-cycle-sweep.js");
|
|
33331
33574
|
registerLoop({
|
|
33332
33575
|
name: "dream-cycle-sweep",
|
|
@@ -33334,7 +33577,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
33334
33577
|
firstRunDelayMs: 3e4,
|
|
33335
33578
|
detached: true,
|
|
33336
33579
|
run: (ctx) => {
|
|
33337
|
-
if (!
|
|
33580
|
+
if (!existsSync52(dreamSweepScript)) return;
|
|
33338
33581
|
try {
|
|
33339
33582
|
const child = spawn3(process.execPath, [dreamSweepScript], {
|
|
33340
33583
|
env: { ...process.env, PLATFORM_ROOT: dreamPlatformRoot },
|
|
@@ -33350,11 +33593,11 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
33350
33593
|
});
|
|
33351
33594
|
}
|
|
33352
33595
|
{
|
|
33353
|
-
const outlookPlatformRoot = process.env.MAXY_PLATFORM_ROOT ??
|
|
33596
|
+
const outlookPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join56(__dirname, "..");
|
|
33354
33597
|
const outlookScript = resolve45(outlookPlatformRoot, "plugins/outlook/mcp/dist/scripts/complete-registration.js");
|
|
33355
33598
|
const OUTLOOK_COMPLETE_INTERVAL_MS = 3e4;
|
|
33356
33599
|
const runOutlookComplete = (ctx) => {
|
|
33357
|
-
if (!
|
|
33600
|
+
if (!existsSync52(outlookScript)) return;
|
|
33358
33601
|
try {
|
|
33359
33602
|
const child = spawn3(process.execPath, [outlookScript], {
|
|
33360
33603
|
env: { ...process.env, PLATFORM_ROOT: outlookPlatformRoot },
|
|
@@ -33376,19 +33619,19 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
33376
33619
|
});
|
|
33377
33620
|
}
|
|
33378
33621
|
{
|
|
33379
|
-
const auditRoot = process.env.MAXY_PLATFORM_ROOT ??
|
|
33622
|
+
const auditRoot = process.env.MAXY_PLATFORM_ROOT ?? join56(__dirname, "..");
|
|
33380
33623
|
const strandedAccountsDir = resolve45(auditRoot, "..", "data/accounts");
|
|
33381
33624
|
const STRANDED_AUDIT_INTERVAL_MS = 3e5;
|
|
33382
33625
|
const STRANDED_AGE_MS = 16 * 6e4;
|
|
33383
33626
|
const STRANDED_UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
33384
33627
|
const runStrandedAudit = () => {
|
|
33385
33628
|
try {
|
|
33386
|
-
if (!
|
|
33629
|
+
if (!existsSync52(strandedAccountsDir)) return;
|
|
33387
33630
|
const now = Date.now();
|
|
33388
33631
|
for (const name of readdirSync30(strandedAccountsDir)) {
|
|
33389
33632
|
if (!STRANDED_UUID_RE.test(name)) continue;
|
|
33390
33633
|
const pendingPath2 = resolve45(strandedAccountsDir, name, "secrets/outlook/pending-devicecode.enc");
|
|
33391
|
-
if (!
|
|
33634
|
+
if (!existsSync52(pendingPath2)) continue;
|
|
33392
33635
|
const ageMs2 = now - statSync27(pendingPath2).mtimeMs;
|
|
33393
33636
|
if (ageMs2 > STRANDED_AGE_MS) {
|
|
33394
33637
|
console.error(`[outlook-mcp] devicecode-stranded account=${name} ageSec=${Math.floor(ageMs2 / 1e3)}`);
|
|
@@ -33406,7 +33649,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
33406
33649
|
});
|
|
33407
33650
|
}
|
|
33408
33651
|
{
|
|
33409
|
-
const googleRoot = process.env.MAXY_PLATFORM_ROOT ??
|
|
33652
|
+
const googleRoot = process.env.MAXY_PLATFORM_ROOT ?? join56(__dirname, "..");
|
|
33410
33653
|
const googleAccountsDir = resolve45(googleRoot, "..", "data/accounts");
|
|
33411
33654
|
const GOOGLE_PENDING_AUDIT_INTERVAL_MS = 3e5;
|
|
33412
33655
|
const runGooglePendingAuditSafe = () => {
|
|
@@ -33425,7 +33668,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
33425
33668
|
const googleAuditScript = resolve45(googleRoot, "plugins/google/mcp/dist/scripts/account-audit.js");
|
|
33426
33669
|
const GOOGLE_ACCOUNT_AUDIT_INTERVAL_MS = 36e5;
|
|
33427
33670
|
const runGoogleAccountAudit = (ctx) => {
|
|
33428
|
-
if (!
|
|
33671
|
+
if (!existsSync52(googleAuditScript)) return;
|
|
33429
33672
|
try {
|
|
33430
33673
|
const child = spawn3(process.execPath, [googleAuditScript], {
|
|
33431
33674
|
env: { ...process.env, PLATFORM_ROOT: googleRoot },
|
|
@@ -33450,7 +33693,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
33450
33693
|
});
|
|
33451
33694
|
}
|
|
33452
33695
|
{
|
|
33453
|
-
const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ??
|
|
33696
|
+
const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join56(__dirname, "..");
|
|
33454
33697
|
const STORAGE_AUDIT_INTERVAL_MS = 3e5;
|
|
33455
33698
|
const runStorageAuditSafe = () => runStorageAudit(auditPlatformRoot).catch((err) => {
|
|
33456
33699
|
console.error(`[storage-audit] error="${err.message}"`);
|
|
@@ -33482,15 +33725,42 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
33482
33725
|
firstRunDelayMs: 5e4,
|
|
33483
33726
|
run: runDataPortalAuditSafe
|
|
33484
33727
|
});
|
|
33728
|
+
for (const o of migrateTelegramTwoSlot({
|
|
33729
|
+
accounts: listValidAccounts().map((a) => ({ accountId: a.accountId, accountDir: a.accountDir })),
|
|
33730
|
+
secretPathFor: secretPath,
|
|
33731
|
+
legacySecretPathFor: (botType, accountId) => resolve45(
|
|
33732
|
+
homedir4(),
|
|
33733
|
+
configDirName(),
|
|
33734
|
+
`${botType === "admin" ? ".telegram-admin-webhook-secret" : ".telegram-webhook-secret"}.${accountId}`
|
|
33735
|
+
),
|
|
33736
|
+
// Counted here and nowhere else: the session-key re-key strands these and no
|
|
33737
|
+
// later event reveals it, because the new key never resolves to the old JSONL.
|
|
33738
|
+
countOrphanSessions: countTelegramSessionsForAccount
|
|
33739
|
+
})) {
|
|
33740
|
+
console.error(
|
|
33741
|
+
`[telegram-audit] op=migrate-session-orphans accountId=${o.accountId} count=${o.orphanSessions} converted=${o.converted}${o.skippedAdmin ? ` skippedAdmin=${o.skippedAdmin}` : ""}${o.skippedPublic ? ` skippedPublic=${o.skippedPublic}` : ""}`
|
|
33742
|
+
);
|
|
33743
|
+
}
|
|
33485
33744
|
const runTelegramAudit = async () => {
|
|
33486
|
-
const accounts = listValidAccounts().map((a) => ({
|
|
33487
|
-
|
|
33745
|
+
const accounts = listValidAccounts().map((a) => ({
|
|
33746
|
+
accountId: a.accountId,
|
|
33747
|
+
accountDir: a.accountDir,
|
|
33748
|
+
telegram: a.config.telegram
|
|
33749
|
+
}));
|
|
33750
|
+
const configured = accounts.some((a) => listBotEntries(a.telegram).length > 0);
|
|
33488
33751
|
if (!configured) return;
|
|
33489
|
-
const
|
|
33752
|
+
const lastByBot = newestTelegramSessionByBot();
|
|
33753
|
+
let secretFilesOnDisk = [];
|
|
33754
|
+
try {
|
|
33755
|
+
secretFilesOnDisk = readdirSync30(telegramSecretsDir());
|
|
33756
|
+
} catch {
|
|
33757
|
+
}
|
|
33490
33758
|
const lines = buildTelegramAuditLines({
|
|
33491
33759
|
accounts,
|
|
33492
|
-
secretFileExists: (
|
|
33493
|
-
|
|
33760
|
+
secretFileExists: (botId) => existsSync52(secretPath(botId)),
|
|
33761
|
+
agentActive: isActiveAgentSlug,
|
|
33762
|
+
secretFilesOnDisk,
|
|
33763
|
+
lastInboundByBot: (botId) => lastByBot.get(botId) ?? null,
|
|
33494
33764
|
now: Date.now()
|
|
33495
33765
|
});
|
|
33496
33766
|
for (const line of lines) console.error(line);
|
|
@@ -33566,7 +33836,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
33566
33836
|
});
|
|
33567
33837
|
}
|
|
33568
33838
|
{
|
|
33569
|
-
const publishPlatformRoot = process.env.MAXY_PLATFORM_ROOT ??
|
|
33839
|
+
const publishPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join56(__dirname, "..");
|
|
33570
33840
|
const publishScript = resolve45(publishPlatformRoot, "plugins/scheduling/mcp/dist/scripts/publish-availability.js");
|
|
33571
33841
|
const PUBLISH_INTERVAL_MS = 3e5;
|
|
33572
33842
|
const bookingAccounts = () => {
|
|
@@ -33578,7 +33848,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
33578
33848
|
}
|
|
33579
33849
|
};
|
|
33580
33850
|
const spawnPublish = (account, ctx) => {
|
|
33581
|
-
if (!
|
|
33851
|
+
if (!existsSync52(publishScript)) return;
|
|
33582
33852
|
try {
|
|
33583
33853
|
const child = spawn3(process.execPath, [publishScript], {
|
|
33584
33854
|
env: {
|
|
@@ -33600,8 +33870,8 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
33600
33870
|
const availPath = resolve45(account.accountDir, "calendar-availability.json");
|
|
33601
33871
|
let hasBookingSite = false;
|
|
33602
33872
|
try {
|
|
33603
|
-
if (
|
|
33604
|
-
const cfg = JSON.parse(
|
|
33873
|
+
if (existsSync52(availPath)) {
|
|
33874
|
+
const cfg = JSON.parse(readFileSync48(availPath, "utf-8"));
|
|
33605
33875
|
hasBookingSite = typeof cfg.bookingDbName === "string" && cfg.bookingDbName.length > 0;
|
|
33606
33876
|
}
|
|
33607
33877
|
} catch {
|
|
@@ -33609,9 +33879,9 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
33609
33879
|
if (!hasBookingSite) return;
|
|
33610
33880
|
const statePath = resolve45(account.accountDir, "state", "booking-availability", "last-publish.json");
|
|
33611
33881
|
let lastSuccessAt = null;
|
|
33612
|
-
if (
|
|
33882
|
+
if (existsSync52(statePath)) {
|
|
33613
33883
|
try {
|
|
33614
|
-
const rec = JSON.parse(
|
|
33884
|
+
const rec = JSON.parse(readFileSync48(statePath, "utf-8"));
|
|
33615
33885
|
lastSuccessAt = rec.lastSuccessAt ?? null;
|
|
33616
33886
|
} catch {
|
|
33617
33887
|
console.error(`[calendar-availability] op=audit accountId=${account.accountId} snapshotAgeMs=na reason=bad-state-file`);
|
|
@@ -33643,12 +33913,12 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
33643
33913
|
startTimeEntryCensus(() => getSession());
|
|
33644
33914
|
startLedgerCensus(() => getSession());
|
|
33645
33915
|
{
|
|
33646
|
-
const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ??
|
|
33916
|
+
const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join56(__dirname, "..");
|
|
33647
33917
|
const auditScript = resolve45(auditPlatformRoot, "plugins/connector/mcp/dist/scripts/audit-connectors.js");
|
|
33648
33918
|
const auditLogDir = process.env.LOG_DIR ?? LOG_DIR;
|
|
33649
33919
|
const CONNECTOR_AUDIT_INTERVAL_MS = 3e5;
|
|
33650
33920
|
const runConnectorAudit = (ctx) => {
|
|
33651
|
-
if (!
|
|
33921
|
+
if (!existsSync52(auditScript)) return;
|
|
33652
33922
|
try {
|
|
33653
33923
|
const child = spawn3(process.execPath, [auditScript], {
|
|
33654
33924
|
env: { ...process.env, PLATFORM_ROOT: auditPlatformRoot, LOG_DIR: auditLogDir },
|
|
@@ -33768,11 +34038,11 @@ try {
|
|
|
33768
34038
|
var ADMINUSER_RECONCILE_INTERVAL_MS = 60 * 60 * 1e3;
|
|
33769
34039
|
async function runAdminUserReconcileTick() {
|
|
33770
34040
|
try {
|
|
33771
|
-
if (!
|
|
34041
|
+
if (!existsSync52(USERS_FILE)) {
|
|
33772
34042
|
console.error("[adminuser-self-heal] skip reason=no-users-file");
|
|
33773
34043
|
return;
|
|
33774
34044
|
}
|
|
33775
|
-
const usersRaw =
|
|
34045
|
+
const usersRaw = readFileSync48(USERS_FILE, "utf-8").trim();
|
|
33776
34046
|
if (!usersRaw) {
|
|
33777
34047
|
console.error("[adminuser-self-heal] skip reason=empty-users-file");
|
|
33778
34048
|
return;
|
|
@@ -33854,8 +34124,8 @@ var runIngestAuditTick = () => {
|
|
|
33854
34124
|
},
|
|
33855
34125
|
agentExists: (accountId, slug) => {
|
|
33856
34126
|
if (!house) return false;
|
|
33857
|
-
const dir = isHouseAccountKey(house.accountId, accountId) ? house.accountDir :
|
|
33858
|
-
return
|
|
34127
|
+
const dir = isHouseAccountKey(house.accountId, accountId) ? house.accountDir : join56(ACCOUNTS_DIR, accountId);
|
|
34128
|
+
return existsSync52(join56(dir, "agents", slug, "config.json"));
|
|
33859
34129
|
}
|
|
33860
34130
|
});
|
|
33861
34131
|
} catch (err) {
|
|
@@ -33903,8 +34173,8 @@ registerLoop({
|
|
|
33903
34173
|
runPublicAddressCollisionAudit({
|
|
33904
34174
|
houseAccountId: () => resolveAccount()?.accountId ?? null,
|
|
33905
34175
|
activeSlugs: () => listValidAccounts().map((a) => {
|
|
33906
|
-
const agentsRoot =
|
|
33907
|
-
if (!
|
|
34176
|
+
const agentsRoot = join56(a.accountDir, "agents");
|
|
34177
|
+
if (!existsSync52(agentsRoot)) return { accountId: a.accountId, slugs: [] };
|
|
33908
34178
|
const slugs = readdirSync30(agentsRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).filter((slug) => isActiveAgentSlug(a.accountDir, slug));
|
|
33909
34179
|
return { accountId: a.accountId, slugs };
|
|
33910
34180
|
})
|
|
@@ -33918,8 +34188,8 @@ registerLoop({
|
|
|
33918
34188
|
});
|
|
33919
34189
|
var USERS_RECONCILE_INTERVAL_MS = 60 * 60 * 1e3;
|
|
33920
34190
|
function countUsersRows() {
|
|
33921
|
-
if (!
|
|
33922
|
-
const raw =
|
|
34191
|
+
if (!existsSync52(USERS_FILE)) return 0;
|
|
34192
|
+
const raw = readFileSync48(USERS_FILE, "utf-8").trim();
|
|
33923
34193
|
if (!raw) return 0;
|
|
33924
34194
|
const users = JSON.parse(raw);
|
|
33925
34195
|
return users.filter((u) => typeof u.userId === "string").length;
|
|
@@ -34055,7 +34325,7 @@ if (bootAccountConfig?.whatsapp) {
|
|
|
34055
34325
|
}
|
|
34056
34326
|
init({
|
|
34057
34327
|
configDir: configDirForWhatsApp,
|
|
34058
|
-
platformRoot: resolve45(process.env.MAXY_PLATFORM_ROOT ??
|
|
34328
|
+
platformRoot: resolve45(process.env.MAXY_PLATFORM_ROOT ?? join56(__dirname, "..")),
|
|
34059
34329
|
accountConfig: bootAccountConfig,
|
|
34060
34330
|
// Task 2074 — enrich with the sub-account name and fan out to every open
|
|
34061
34331
|
// admin SSE stream. Fire-and-forget: the manager's persist path must not
|