@rubytech/create-realagent-code 0.1.578 → 0.1.579
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +9 -1
- package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +10 -0
- package/payload/platform/plugins/docs/references/admin-ui.md +6 -0
- package/payload/platform/plugins/docs/references/troubleshooting.md +2 -0
- package/payload/platform/scripts/check-no-esm-require.mjs +6 -0
- package/payload/platform/services/webchat-channel/dist/server.js +4 -4
- package/payload/platform/services/webchat-channel/dist/server.js.map +1 -1
- package/payload/server/public/activity.html +6 -6
- package/payload/server/public/agents.html +5 -5
- package/payload/server/public/assets/{AdminLoginScreens-CAZ3DSwA.js → AdminLoginScreens-Bus-Y-rG.js} +1 -1
- package/payload/server/public/assets/AdminLoginScreens-Bus-Y-rG.js.br +0 -0
- package/payload/server/public/assets/AdminLoginScreens-Bus-Y-rG.js.gz +0 -0
- package/payload/server/public/assets/AdminShell-Bsm7q3CW.js +3 -0
- package/payload/server/public/assets/AdminShell-Bsm7q3CW.js.br +0 -0
- package/payload/server/public/assets/AdminShell-Bsm7q3CW.js.gz +0 -0
- package/payload/server/public/assets/{activity-BwPck2yT.js → activity-BWqrCyYM.js} +1 -1
- package/payload/server/public/assets/activity-BWqrCyYM.js.br +0 -0
- package/payload/server/public/assets/activity-BWqrCyYM.js.gz +0 -0
- package/payload/server/public/assets/admin-zly7Y6EZ.js +1 -0
- package/payload/server/public/assets/admin-zly7Y6EZ.js.br +0 -0
- package/payload/server/public/assets/admin-zly7Y6EZ.js.gz +0 -0
- package/payload/server/public/assets/{agents-IQYaRvoh.js → agents-BsxGD1V1.js} +1 -1
- package/payload/server/public/assets/agents-BsxGD1V1.js.br +0 -0
- package/payload/server/public/assets/agents-BsxGD1V1.js.gz +0 -0
- package/payload/server/public/assets/{browser-CPG8w8rQ.js → browser-CRLxTaw2.js} +1 -1
- package/payload/server/public/assets/browser-CRLxTaw2.js.br +0 -0
- package/payload/server/public/assets/browser-CRLxTaw2.js.gz +0 -0
- package/payload/server/public/assets/{calendar-CRwrgNdq.js → calendar-Bmp6qI_P.js} +1 -1
- package/payload/server/public/assets/calendar-Bmp6qI_P.js.br +0 -0
- package/payload/server/public/assets/calendar-Bmp6qI_P.js.gz +0 -0
- package/payload/server/public/assets/chat-ChObDKHy.js +1 -0
- package/payload/server/public/assets/chat-ChObDKHy.js.br +0 -0
- package/payload/server/public/assets/chat-ChObDKHy.js.gz +0 -0
- package/payload/server/public/assets/chevron-left-D2l4jMcd.js +1 -0
- package/payload/server/public/assets/chevron-left-D2l4jMcd.js.br +0 -0
- package/payload/server/public/assets/clock-CMdfsroW.js +1 -0
- package/payload/server/public/assets/clock-CMdfsroW.js.br +0 -0
- package/payload/server/public/assets/clock-CMdfsroW.js.gz +0 -0
- package/payload/server/public/assets/{copy-JyiMTV0k.js → copy-Bcjpxqzn.js} +1 -1
- package/payload/server/public/assets/copy-Bcjpxqzn.js.br +0 -0
- package/payload/server/public/assets/copy-Bcjpxqzn.js.gz +0 -0
- package/payload/server/public/assets/data-CFB5Lgw-.js +1 -0
- package/payload/server/public/assets/data-CFB5Lgw-.js.br +0 -0
- package/payload/server/public/assets/data-CFB5Lgw-.js.gz +0 -0
- package/payload/server/public/assets/{field-B490-evJ.js → field-ChKB2l_d.js} +1 -1
- package/payload/server/public/assets/field-ChKB2l_d.js.br +0 -0
- package/payload/server/public/assets/field-ChKB2l_d.js.gz +0 -0
- package/payload/server/public/assets/{file-text-cnHv9ZS6.js → file-text-COZcf7mU.js} +1 -1
- package/payload/server/public/assets/file-text-COZcf7mU.js.br +0 -0
- package/payload/server/public/assets/file-text-COZcf7mU.js.gz +0 -0
- package/payload/server/public/assets/{graph-labels-C6qUA0vO.js → graph-labels-x-1MT-3c.js} +1 -1
- package/payload/server/public/assets/graph-labels-x-1MT-3c.js.br +0 -0
- package/payload/server/public/assets/graph-labels-x-1MT-3c.js.gz +0 -0
- package/payload/server/public/assets/{graph-lFwEUDSH.js → graph-nnV47iqM.js} +1 -1
- package/payload/server/public/assets/graph-nnV47iqM.js.br +0 -0
- package/payload/server/public/assets/graph-nnV47iqM.js.gz +0 -0
- package/payload/server/public/assets/{jsx-runtime-ChVh0xOm.css → jsx-runtime-CeNZasfC.css} +1 -1
- package/payload/server/public/assets/jsx-runtime-CeNZasfC.css.br +0 -0
- package/payload/server/public/assets/jsx-runtime-CeNZasfC.css.gz +0 -0
- package/payload/server/public/assets/{operator-BdoEy5mh.js → operator-BAO0-mjU.js} +1 -1
- package/payload/server/public/assets/operator-BAO0-mjU.js.br +0 -0
- package/payload/server/public/assets/operator-BAO0-mjU.js.gz +0 -0
- package/payload/server/public/assets/{page-DMrnnXNA.js → page-B-F08pod.js} +1 -1
- package/payload/server/public/assets/page-B-F08pod.js.br +0 -0
- package/payload/server/public/assets/page-B-F08pod.js.gz +0 -0
- package/payload/server/public/assets/page-BMOCy2aD.js +1 -0
- package/payload/server/public/assets/page-BMOCy2aD.js.br +0 -0
- package/payload/server/public/assets/page-BMOCy2aD.js.gz +0 -0
- package/payload/server/public/assets/play-DFtlk8Zs.js +1 -0
- package/payload/server/public/assets/play-DFtlk8Zs.js.br +0 -0
- package/payload/server/public/assets/play-DFtlk8Zs.js.gz +0 -0
- package/payload/server/public/assets/{public-B-X1Cxfw.js → public-_HJsUohA.js} +1 -1
- package/payload/server/public/assets/public-_HJsUohA.js.br +0 -0
- package/payload/server/public/assets/public-_HJsUohA.js.gz +0 -0
- package/payload/server/public/assets/{rotate-ccw-DVFvQAHG.js → rotate-ccw-DJeAnYLQ.js} +1 -1
- package/payload/server/public/assets/rotate-ccw-DJeAnYLQ.js.br +0 -0
- package/payload/server/public/assets/rotate-ccw-DJeAnYLQ.js.gz +0 -0
- package/payload/server/public/assets/{routines-CgkkGJaS.js → routines-CoIVXRCB.js} +2 -2
- package/payload/server/public/assets/routines-CoIVXRCB.js.br +0 -0
- package/payload/server/public/assets/routines-CoIVXRCB.js.gz +0 -0
- package/payload/server/public/assets/{skills-CW7gVcaG.js → skills-BjHt5lDG.js} +1 -1
- package/payload/server/public/assets/skills-BjHt5lDG.js.br +0 -0
- package/payload/server/public/assets/skills-BjHt5lDG.js.gz +0 -0
- package/payload/server/public/assets/{tasks-Ddjp0Rwh.js → tasks-iUFfeA9_.js} +2 -2
- package/payload/server/public/assets/tasks-iUFfeA9_.js.br +0 -0
- package/payload/server/public/assets/tasks-iUFfeA9_.js.gz +0 -0
- package/payload/server/public/assets/{triangle-alert-DUrUutX4.js → triangle-alert-Dwa986Gg.js} +1 -1
- package/payload/server/public/assets/triangle-alert-Dwa986Gg.js.br +0 -0
- package/payload/server/public/assets/triangle-alert-Dwa986Gg.js.gz +0 -0
- package/payload/server/public/assets/{useCopyFeedback-ClVINDrQ.js → useCopyFeedback-B1Jp6CsE.js} +1 -1
- package/payload/server/public/assets/useCopyFeedback-B1Jp6CsE.js.br +0 -0
- package/payload/server/public/assets/useCopyFeedback-B1Jp6CsE.js.gz +0 -0
- package/payload/server/public/assets/{useMediaQuery-BUFkZYRs.js → useMediaQuery-CVZYjRf6.js} +2 -2
- package/payload/server/public/assets/useMediaQuery-CVZYjRf6.js.br +0 -0
- package/payload/server/public/assets/useMediaQuery-CVZYjRf6.js.gz +0 -0
- package/payload/server/public/assets/useVoiceRecorder-D5b4r4z0.js +2 -0
- package/payload/server/public/assets/useVoiceRecorder-D5b4r4z0.js.br +0 -0
- package/payload/server/public/assets/useVoiceRecorder-D5b4r4z0.js.gz +0 -0
- package/payload/server/public/assets/{wrench-BW28s_d0.js → wrench-BD7xwbV1.js} +1 -1
- package/payload/server/public/assets/wrench-BD7xwbV1.js.br +0 -0
- package/payload/server/public/assets/wrench-BD7xwbV1.js.gz +0 -0
- package/payload/server/public/browser.html +5 -5
- package/payload/server/public/calendar.html +8 -8
- package/payload/server/public/chat.html +14 -14
- package/payload/server/public/data.html +12 -12
- package/payload/server/public/field.html +3 -3
- package/payload/server/public/graph.html +10 -10
- package/payload/server/public/index.html +14 -14
- package/payload/server/public/operator.html +16 -16
- package/payload/server/public/public.html +14 -14
- package/payload/server/public/routines.html +8 -8
- package/payload/server/public/skills.html +6 -6
- package/payload/server/public/tasks.html +7 -7
- package/payload/server/server.js +650 -248
- package/payload/server/public/assets/AdminLoginScreens-CAZ3DSwA.js.br +0 -0
- package/payload/server/public/assets/AdminLoginScreens-CAZ3DSwA.js.gz +0 -0
- package/payload/server/public/assets/AdminShell-BLs5v0qF.js +0 -3
- package/payload/server/public/assets/AdminShell-BLs5v0qF.js.br +0 -0
- package/payload/server/public/assets/AdminShell-BLs5v0qF.js.gz +0 -0
- package/payload/server/public/assets/activity-BwPck2yT.js.br +0 -0
- package/payload/server/public/assets/activity-BwPck2yT.js.gz +0 -0
- package/payload/server/public/assets/admin-DjtOoabA.js +0 -1
- package/payload/server/public/assets/admin-DjtOoabA.js.br +0 -0
- package/payload/server/public/assets/admin-DjtOoabA.js.gz +0 -0
- package/payload/server/public/assets/agents-IQYaRvoh.js.br +0 -0
- package/payload/server/public/assets/agents-IQYaRvoh.js.gz +0 -0
- package/payload/server/public/assets/browser-CPG8w8rQ.js.br +0 -0
- package/payload/server/public/assets/browser-CPG8w8rQ.js.gz +0 -0
- package/payload/server/public/assets/calendar-CRwrgNdq.js.br +0 -0
- package/payload/server/public/assets/calendar-CRwrgNdq.js.gz +0 -0
- package/payload/server/public/assets/chat-B7yZ4KLl.js +0 -1
- package/payload/server/public/assets/chat-B7yZ4KLl.js.br +0 -3
- package/payload/server/public/assets/chat-B7yZ4KLl.js.gz +0 -0
- package/payload/server/public/assets/chevron-left-BzKUQqLy.js +0 -1
- package/payload/server/public/assets/chevron-left-BzKUQqLy.js.br +0 -0
- package/payload/server/public/assets/clock-BafWFNGo.js +0 -1
- package/payload/server/public/assets/clock-BafWFNGo.js.br +0 -0
- package/payload/server/public/assets/copy-JyiMTV0k.js.br +0 -0
- package/payload/server/public/assets/copy-JyiMTV0k.js.gz +0 -0
- package/payload/server/public/assets/data-DHtGCVrl.js +0 -1
- package/payload/server/public/assets/data-DHtGCVrl.js.br +0 -0
- package/payload/server/public/assets/data-DHtGCVrl.js.gz +0 -0
- package/payload/server/public/assets/field-B490-evJ.js.br +0 -0
- package/payload/server/public/assets/field-B490-evJ.js.gz +0 -0
- package/payload/server/public/assets/file-text-cnHv9ZS6.js.br +0 -0
- package/payload/server/public/assets/file-text-cnHv9ZS6.js.gz +0 -0
- package/payload/server/public/assets/graph-lFwEUDSH.js.br +0 -0
- package/payload/server/public/assets/graph-lFwEUDSH.js.gz +0 -0
- package/payload/server/public/assets/graph-labels-C6qUA0vO.js.br +0 -0
- package/payload/server/public/assets/graph-labels-C6qUA0vO.js.gz +0 -0
- package/payload/server/public/assets/jsx-runtime-ChVh0xOm.css.br +0 -0
- package/payload/server/public/assets/jsx-runtime-ChVh0xOm.css.gz +0 -0
- package/payload/server/public/assets/operator-BdoEy5mh.js.br +0 -0
- package/payload/server/public/assets/operator-BdoEy5mh.js.gz +0 -0
- package/payload/server/public/assets/page-DMrnnXNA.js.br +0 -0
- package/payload/server/public/assets/page-DMrnnXNA.js.gz +0 -0
- package/payload/server/public/assets/page-DeMikGYL.js +0 -1
- package/payload/server/public/assets/page-DeMikGYL.js.br +0 -0
- package/payload/server/public/assets/page-DeMikGYL.js.gz +0 -0
- package/payload/server/public/assets/play-D6OAQVJ_.js +0 -1
- package/payload/server/public/assets/play-D6OAQVJ_.js.br +0 -0
- package/payload/server/public/assets/play-D6OAQVJ_.js.gz +0 -0
- package/payload/server/public/assets/public-B-X1Cxfw.js.br +0 -0
- package/payload/server/public/assets/public-B-X1Cxfw.js.gz +0 -0
- package/payload/server/public/assets/rotate-ccw-DVFvQAHG.js.br +0 -0
- package/payload/server/public/assets/rotate-ccw-DVFvQAHG.js.gz +0 -0
- package/payload/server/public/assets/routines-CgkkGJaS.js.br +0 -0
- package/payload/server/public/assets/routines-CgkkGJaS.js.gz +0 -0
- package/payload/server/public/assets/skills-CW7gVcaG.js.br +0 -0
- package/payload/server/public/assets/skills-CW7gVcaG.js.gz +0 -0
- package/payload/server/public/assets/tasks-Ddjp0Rwh.js.br +0 -0
- package/payload/server/public/assets/tasks-Ddjp0Rwh.js.gz +0 -0
- package/payload/server/public/assets/triangle-alert-DUrUutX4.js.br +0 -0
- package/payload/server/public/assets/triangle-alert-DUrUutX4.js.gz +0 -0
- package/payload/server/public/assets/useCopyFeedback-ClVINDrQ.js.br +0 -0
- package/payload/server/public/assets/useCopyFeedback-ClVINDrQ.js.gz +0 -0
- package/payload/server/public/assets/useMediaQuery-BUFkZYRs.js.br +0 -0
- package/payload/server/public/assets/useMediaQuery-BUFkZYRs.js.gz +0 -0
- package/payload/server/public/assets/useVoiceRecorder-DnyqQFet.js +0 -2
- package/payload/server/public/assets/useVoiceRecorder-DnyqQFet.js.br +0 -0
- package/payload/server/public/assets/useVoiceRecorder-DnyqQFet.js.gz +0 -0
- package/payload/server/public/assets/wrench-BW28s_d0.js.br +0 -0
- package/payload/server/public/assets/wrench-BW28s_d0.js.gz +0 -0
- /package/payload/server/public/assets/{jsx-runtime-YcUntiud.js → jsx-runtime-CjJ246F7.js} +0 -0
- /package/payload/server/public/assets/{jsx-runtime-YcUntiud.js.br → jsx-runtime-CjJ246F7.js.br} +0 -0
- /package/payload/server/public/assets/{jsx-runtime-YcUntiud.js.gz → jsx-runtime-CjJ246F7.js.gz} +0 -0
package/payload/server/server.js
CHANGED
|
@@ -5517,11 +5517,11 @@ var require_dist7 = __commonJS({
|
|
|
5517
5517
|
|
|
5518
5518
|
// server/index.ts
|
|
5519
5519
|
init_dist();
|
|
5520
|
-
import { readFileSync as
|
|
5520
|
+
import { readFileSync as readFileSync57, existsSync as existsSync56, watchFile, readdirSync as readdirSync33, statSync as statSync30 } from "fs";
|
|
5521
5521
|
import { spawn as spawn3 } from "child_process";
|
|
5522
5522
|
import { createHash as createHash8 } from "crypto";
|
|
5523
|
-
import { resolve as resolve51, join as
|
|
5524
|
-
import { homedir as
|
|
5523
|
+
import { resolve as resolve51, join as join65, basename as basename17 } from "path";
|
|
5524
|
+
import { homedir as homedir6 } from "os";
|
|
5525
5525
|
|
|
5526
5526
|
// server/lib/route-cache-watchers.ts
|
|
5527
5527
|
init_route_cache();
|
|
@@ -6777,7 +6777,16 @@ function readCredentials() {
|
|
|
6777
6777
|
expiresAt
|
|
6778
6778
|
};
|
|
6779
6779
|
}
|
|
6780
|
-
function
|
|
6780
|
+
function logCredentialWrite(site, refreshTokenLen) {
|
|
6781
|
+
console.log(`[claude-auth] op=cred-write site=${site} refreshTokenLen=${refreshTokenLen}`);
|
|
6782
|
+
}
|
|
6783
|
+
function writeHostCredsFile(credentialsFile, blob, refreshTokenLen) {
|
|
6784
|
+
const tmp = `${credentialsFile}.${process.pid}.tmp`;
|
|
6785
|
+
writeFileSync2(tmp, blob, { mode: 384 });
|
|
6786
|
+
renameSync(tmp, credentialsFile);
|
|
6787
|
+
logCredentialWrite("login-writeback", refreshTokenLen);
|
|
6788
|
+
}
|
|
6789
|
+
function writeOAuthCredentials(tokens, site) {
|
|
6781
6790
|
const newExpiresAt = typeof tokens.expiresIn === "number" && tokens.expiresIn > 0 ? Date.now() + tokens.expiresIn * 1e3 : Date.now() + 3600 * 1e3;
|
|
6782
6791
|
try {
|
|
6783
6792
|
let fileData = {};
|
|
@@ -6787,10 +6796,13 @@ function writeOAuthCredentials(tokens) {
|
|
|
6787
6796
|
} catch {
|
|
6788
6797
|
}
|
|
6789
6798
|
const existingOauth = fileData.claudeAiOauth;
|
|
6799
|
+
const existingRefresh = typeof existingOauth?.refreshToken === "string" ? existingOauth.refreshToken : "";
|
|
6800
|
+
const incomingRefresh = typeof tokens.refreshToken === "string" ? tokens.refreshToken : "";
|
|
6801
|
+
const blankingRefused = incomingRefresh.length === 0 && existingRefresh.length > 0;
|
|
6790
6802
|
fileData.claudeAiOauth = {
|
|
6791
6803
|
...existingOauth,
|
|
6792
6804
|
accessToken: tokens.accessToken,
|
|
6793
|
-
...
|
|
6805
|
+
...incomingRefresh.length > 0 ? { refreshToken: incomingRefresh } : {},
|
|
6794
6806
|
expiresAt: newExpiresAt
|
|
6795
6807
|
};
|
|
6796
6808
|
const tmpPath = `${CLAUDE_CREDENTIALS_FILE}.${process.pid}.tmp`;
|
|
@@ -6804,6 +6816,13 @@ function writeOAuthCredentials(tokens) {
|
|
|
6804
6816
|
}
|
|
6805
6817
|
throw writeErr;
|
|
6806
6818
|
}
|
|
6819
|
+
if (blankingRefused) {
|
|
6820
|
+
console.warn(
|
|
6821
|
+
`[claude-auth] op=cred-write-refused site=${site} reason=blank-refresh-token storedLen=${existingRefresh.length}`
|
|
6822
|
+
);
|
|
6823
|
+
}
|
|
6824
|
+
const landed = fileData.claudeAiOauth.refreshToken;
|
|
6825
|
+
logCredentialWrite(site, typeof landed === "string" ? landed.length : 0);
|
|
6807
6826
|
return newExpiresAt;
|
|
6808
6827
|
} catch (err) {
|
|
6809
6828
|
console.error(`[claude-auth] credential write failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -6933,9 +6952,11 @@ async function doRefreshLocked() {
|
|
|
6933
6952
|
}
|
|
6934
6953
|
const newExpiresAt = writeOAuthCredentials({
|
|
6935
6954
|
accessToken: newAccessToken,
|
|
6936
|
-
|
|
6955
|
+
// Task 2572 — `??` does not fall back on the empty string, so a response
|
|
6956
|
+
// carrying `refresh_token: ""` used to overwrite a good stored token.
|
|
6957
|
+
refreshToken: typeof newRefreshToken === "string" && newRefreshToken.length > 0 ? newRefreshToken : creds.refreshToken,
|
|
6937
6958
|
expiresIn: typeof expiresIn === "number" && expiresIn > 0 ? expiresIn : void 0
|
|
6938
|
-
});
|
|
6959
|
+
}, "refresh");
|
|
6939
6960
|
if (newExpiresAt) {
|
|
6940
6961
|
console.log(`[auth-refresh] op=renewed expiresAt=${new Date(newExpiresAt).toISOString()}`);
|
|
6941
6962
|
} else {
|
|
@@ -7240,13 +7261,30 @@ function killChromium() {
|
|
|
7240
7261
|
var AUTH_TARGET_ID_FILE = resolve4(LOG_DIR, "claude-auth-target.id");
|
|
7241
7262
|
function readAuthTargetId() {
|
|
7242
7263
|
try {
|
|
7243
|
-
const
|
|
7264
|
+
const parts = readFileSync4(AUTH_TARGET_ID_FILE, "utf8").trim().split(/\s+/);
|
|
7265
|
+
const id = parts[parts.length - 1];
|
|
7244
7266
|
return id.length > 0 ? id : null;
|
|
7245
7267
|
} catch {
|
|
7246
7268
|
return null;
|
|
7247
7269
|
}
|
|
7248
7270
|
}
|
|
7249
|
-
function
|
|
7271
|
+
function readAuthAttemptId() {
|
|
7272
|
+
try {
|
|
7273
|
+
const parts = readFileSync4(AUTH_TARGET_ID_FILE, "utf8").trim().split(/\s+/);
|
|
7274
|
+
return parts.length === 2 && parts[0].length > 0 ? parts[0] : null;
|
|
7275
|
+
} catch {
|
|
7276
|
+
return null;
|
|
7277
|
+
}
|
|
7278
|
+
}
|
|
7279
|
+
async function closeAuthTarget(targetId) {
|
|
7280
|
+
try {
|
|
7281
|
+
const res = await fetch(`http://127.0.0.1:${CDP_PORT}/json/close/${encodeURIComponent(targetId)}`);
|
|
7282
|
+
return res.ok;
|
|
7283
|
+
} catch {
|
|
7284
|
+
return false;
|
|
7285
|
+
}
|
|
7286
|
+
}
|
|
7287
|
+
function writeChromiumWrapper(attemptId) {
|
|
7250
7288
|
mkdirSync2(BIN_DIR, { recursive: true });
|
|
7251
7289
|
rmSync2(AUTH_TARGET_ID_FILE, { force: true });
|
|
7252
7290
|
const wrapperPath = resolve4(BIN_DIR, "chromium");
|
|
@@ -7257,6 +7295,9 @@ LOG_DIR="${LOG_DIR}"
|
|
|
7257
7295
|
LOG="$LOG_DIR/chromium.log"
|
|
7258
7296
|
TARGET_ID_FILE="$LOG_DIR/claude-auth-target.id"
|
|
7259
7297
|
CDP="http://127.0.0.1:${CDP_PORT}/json/new"
|
|
7298
|
+
# The sign-in this wrapper was generated for. Server-minted hex from
|
|
7299
|
+
# randomUUID().slice(0, 8), never caller-supplied, so it carries no shell.
|
|
7300
|
+
ATTEMPT_ID="${attemptId}"
|
|
7260
7301
|
|
|
7261
7302
|
mkdir -p "$LOG_DIR"
|
|
7262
7303
|
echo "==== [$(date)] chromium wrapper (cdp-handover) ====" >> "$LOG"
|
|
@@ -7288,9 +7329,9 @@ if [ -z "$TARGET_ID" ]; then
|
|
|
7288
7329
|
exit 65
|
|
7289
7330
|
fi
|
|
7290
7331
|
|
|
7291
|
-
printf '%s' "$TARGET_ID" > "$TARGET_ID_FILE"
|
|
7292
|
-
echo " opened target $TARGET_ID" >> "$LOG"
|
|
7293
|
-
echo "[chromium-wrapper] op=cdp-handover outcome=ready targetId=$TARGET_ID" >> "$LOG"
|
|
7332
|
+
printf '%s %s' "$ATTEMPT_ID" "$TARGET_ID" > "$TARGET_ID_FILE"
|
|
7333
|
+
echo " opened target $TARGET_ID for attempt $ATTEMPT_ID" >> "$LOG"
|
|
7334
|
+
echo "[chromium-wrapper] op=cdp-handover outcome=ready attempt=$ATTEMPT_ID targetId=$TARGET_ID" >> "$LOG"
|
|
7294
7335
|
`, { mode: 493 });
|
|
7295
7336
|
return wrapperPath;
|
|
7296
7337
|
}
|
|
@@ -18842,7 +18883,7 @@ var quickbooks_default = app12;
|
|
|
18842
18883
|
// server/routes/onboarding.ts
|
|
18843
18884
|
init_dist();
|
|
18844
18885
|
import { spawn, spawnSync as spawnSync2, execFileSync as execFileSync4 } from "child_process";
|
|
18845
|
-
import { openSync as openSync8, closeSync as closeSync8, writeFileSync as writeFileSync14, writeSync, existsSync as existsSync26, readFileSync as readFileSync32, unlinkSync as unlinkSync3
|
|
18886
|
+
import { openSync as openSync8, closeSync as closeSync8, appendFileSync as appendFileSync3, writeFileSync as writeFileSync14, writeSync, existsSync as existsSync26, readFileSync as readFileSync32, unlinkSync as unlinkSync3 } from "fs";
|
|
18846
18887
|
import { createHash as createHash4, randomUUID as randomUUID4 } from "crypto";
|
|
18847
18888
|
init_claude_agent();
|
|
18848
18889
|
init_neo4j_store();
|
|
@@ -18915,8 +18956,10 @@ function bridgeKeychainToFile(deps) {
|
|
|
18915
18956
|
deps.log(`[onboarding] op=login-writeback result=no-claudeAiOauth service=${JSON.stringify(service)}`);
|
|
18916
18957
|
return { wrote: false, reason: "no-claudeAiOauth", bytes: 0, service };
|
|
18917
18958
|
}
|
|
18959
|
+
const oauth = parsed.claudeAiOauth;
|
|
18960
|
+
const refreshTokenLen = typeof oauth.refreshToken === "string" ? oauth.refreshToken.length : 0;
|
|
18918
18961
|
try {
|
|
18919
|
-
deps.writeFile(blob);
|
|
18962
|
+
deps.writeFile(blob, refreshTokenLen);
|
|
18920
18963
|
} catch (err) {
|
|
18921
18964
|
deps.log(`[onboarding] op=login-writeback result=write-failed service=${JSON.stringify(service)} err=${err instanceof Error ? err.message : String(err)}`);
|
|
18922
18965
|
return { wrote: false, reason: "write-failed", bytes: 0, service };
|
|
@@ -19180,7 +19223,35 @@ function readUsersFile2() {
|
|
|
19180
19223
|
if (!raw) return [];
|
|
19181
19224
|
return JSON.parse(raw);
|
|
19182
19225
|
}
|
|
19183
|
-
var
|
|
19226
|
+
var pendingAuthLogin = null;
|
|
19227
|
+
async function endLogin(pending) {
|
|
19228
|
+
if (!pending) return { priorId: null, priorPid: null, tabClosed: null };
|
|
19229
|
+
let tabClosed = null;
|
|
19230
|
+
const targetId = readAuthTargetId();
|
|
19231
|
+
if (targetId && readAuthAttemptId() === pending.attemptId) {
|
|
19232
|
+
tabClosed = await closeAuthTarget(targetId);
|
|
19233
|
+
}
|
|
19234
|
+
const priorPid = pending.child?.pid ?? null;
|
|
19235
|
+
try {
|
|
19236
|
+
pending.child?.kill("SIGTERM");
|
|
19237
|
+
} catch {
|
|
19238
|
+
}
|
|
19239
|
+
return { priorId: pending.attemptId, priorPid, tabClosed };
|
|
19240
|
+
}
|
|
19241
|
+
function adoptOrStopChild(attemptId, child) {
|
|
19242
|
+
if (pendingAuthLogin?.attemptId === attemptId) {
|
|
19243
|
+
pendingAuthLogin.child = child;
|
|
19244
|
+
return true;
|
|
19245
|
+
}
|
|
19246
|
+
try {
|
|
19247
|
+
child.kill("SIGTERM");
|
|
19248
|
+
} catch {
|
|
19249
|
+
}
|
|
19250
|
+
return false;
|
|
19251
|
+
}
|
|
19252
|
+
function formatEnded(ended) {
|
|
19253
|
+
return `superseded=${ended.priorId ?? "none"} priorPid=${ended.priorPid ?? "none"} tabClosed=${ended.tabClosed === null ? "na" : ended.tabClosed}`;
|
|
19254
|
+
}
|
|
19184
19255
|
var darwinCredsBefore = {};
|
|
19185
19256
|
async function checkAuthStatus() {
|
|
19186
19257
|
let status;
|
|
@@ -19221,7 +19292,11 @@ app13.post("/claude-auth", async (c) => {
|
|
|
19221
19292
|
const remoteAddress = c.env?.incoming?.socket?.remoteAddress;
|
|
19222
19293
|
const transport = resolveBrowserTransport(c.req.raw, remoteAddress);
|
|
19223
19294
|
if (body.action === "stop") {
|
|
19224
|
-
|
|
19295
|
+
const superseded2 = pendingAuthLogin;
|
|
19296
|
+
pendingAuthLogin = null;
|
|
19297
|
+
const ended2 = await endLogin(superseded2);
|
|
19298
|
+
console.log(`[onboarding] op=auth-login-stop attempt=${ended2.priorId ?? "none"} outcome=${ended2.priorId ? "stopped" : "no-pending"} priorPid=${ended2.priorPid ?? "none"} tabClosed=${ended2.tabClosed === null ? "na" : ended2.tabClosed}`);
|
|
19299
|
+
return c.json({ stopped: ended2.priorId !== null });
|
|
19225
19300
|
}
|
|
19226
19301
|
if (body.action === "logout") {
|
|
19227
19302
|
const start = Date.now();
|
|
@@ -19246,7 +19321,7 @@ app13.post("/claude-auth", async (c) => {
|
|
|
19246
19321
|
}
|
|
19247
19322
|
if (body.action === "paste-code") {
|
|
19248
19323
|
const code = typeof body.code === "string" ? body.code : "";
|
|
19249
|
-
const child =
|
|
19324
|
+
const child = pendingAuthLogin?.child ?? null;
|
|
19250
19325
|
if (!child || !child.stdin || child.stdin.destroyed) {
|
|
19251
19326
|
console.log("[onboarding] op=claude-paste-code accepted=false reason=no-active-login");
|
|
19252
19327
|
return c.json({ accepted: false, error: "No active login to paste into. Start sign-in first." }, 400);
|
|
@@ -19268,8 +19343,13 @@ app13.post("/claude-auth", async (c) => {
|
|
|
19268
19343
|
const cdpReady = await ensureCdp(transport);
|
|
19269
19344
|
return c.json({ launched: cdpReady });
|
|
19270
19345
|
}
|
|
19346
|
+
const attemptId = randomUUID4().slice(0, 8);
|
|
19347
|
+
const superseded = pendingAuthLogin;
|
|
19348
|
+
pendingAuthLogin = { child: null, attemptId, startedAt: Date.now() };
|
|
19349
|
+
const ended = await endLogin(superseded);
|
|
19271
19350
|
ensureLogDir();
|
|
19272
|
-
|
|
19351
|
+
appendFileSync3(logPath("claude-auth"), `==== attempt ${attemptId} ${(/* @__PURE__ */ new Date()).toISOString()} ====
|
|
19352
|
+
`);
|
|
19273
19353
|
const claudeAuthLogFd = openSync8(logPath("claude-auth"), "a");
|
|
19274
19354
|
const onClaudeOutput = (chunk) => writeSync(claudeAuthLogFd, chunk);
|
|
19275
19355
|
if (process.platform === "darwin") {
|
|
@@ -19280,7 +19360,8 @@ app13.post("/claude-auth", async (c) => {
|
|
|
19280
19360
|
stdio: ["pipe", "pipe", "pipe"]
|
|
19281
19361
|
});
|
|
19282
19362
|
claudeProc2.unref();
|
|
19283
|
-
|
|
19363
|
+
const adopted2 = adoptOrStopChild(attemptId, claudeProc2);
|
|
19364
|
+
console.log(`[onboarding] op=auth-login-start attempt=${attemptId} platform=darwin transport=native outcome=${adopted2 ? "started" : "self-superseded"} ${formatEnded(ended)}`);
|
|
19284
19365
|
claudeProc2.stdin?.on("error", () => {
|
|
19285
19366
|
});
|
|
19286
19367
|
claudeProc2.stdout?.on("data", onClaudeOutput);
|
|
@@ -19292,11 +19373,9 @@ app13.post("/claude-auth", async (c) => {
|
|
|
19292
19373
|
before: darwinCredsBefore,
|
|
19293
19374
|
listMdats: listHostCredsMdats,
|
|
19294
19375
|
readBlob: readHostCredsBlob,
|
|
19295
|
-
|
|
19296
|
-
|
|
19297
|
-
|
|
19298
|
-
renameSync8(tmp, CLAUDE_CREDENTIALS_FILE);
|
|
19299
|
-
},
|
|
19376
|
+
// Task 2572 — the write itself lives in claude-auth.ts beside the other
|
|
19377
|
+
// writer of this file, so both emit provenance and both are testable.
|
|
19378
|
+
writeFile: (blob, refreshTokenLen) => writeHostCredsFile(CLAUDE_CREDENTIALS_FILE, blob, refreshTokenLen),
|
|
19300
19379
|
log: (line) => console.log(line)
|
|
19301
19380
|
});
|
|
19302
19381
|
persistKeychainService(
|
|
@@ -19304,7 +19383,7 @@ app13.post("/claude-auth", async (c) => {
|
|
|
19304
19383
|
(service) => writeFileSync14(join37(dirname13(CLAUDE_CREDENTIALS_FILE), ".keychain-service"), service, { mode: 384 }),
|
|
19305
19384
|
(line) => console.log(line)
|
|
19306
19385
|
);
|
|
19307
|
-
if (
|
|
19386
|
+
if (pendingAuthLogin?.child === claudeProc2) pendingAuthLogin = null;
|
|
19308
19387
|
});
|
|
19309
19388
|
return c.json({ started: true, transport: "native" });
|
|
19310
19389
|
}
|
|
@@ -19313,7 +19392,7 @@ app13.post("/claude-auth", async (c) => {
|
|
|
19313
19392
|
if (!vncReady) return c.json({ error: "VNC display failed to start" }, 500);
|
|
19314
19393
|
}
|
|
19315
19394
|
await ensureCdp(transport);
|
|
19316
|
-
const chromiumWrapper = writeChromiumWrapper();
|
|
19395
|
+
const chromiumWrapper = writeChromiumWrapper(attemptId);
|
|
19317
19396
|
const x11Env = buildX11Env(chromiumWrapper, transport);
|
|
19318
19397
|
vncLog("claude-auth", { action: "start", transport });
|
|
19319
19398
|
const claudeProc = spawn("claude", ["auth", "login"], {
|
|
@@ -19321,9 +19400,14 @@ app13.post("/claude-auth", async (c) => {
|
|
|
19321
19400
|
stdio: ["ignore", "pipe", "pipe"]
|
|
19322
19401
|
});
|
|
19323
19402
|
claudeProc.unref();
|
|
19403
|
+
const adopted = adoptOrStopChild(attemptId, claudeProc);
|
|
19404
|
+
console.log(`[onboarding] op=auth-login-start attempt=${attemptId} platform=${process.platform} transport=${transport} outcome=${adopted ? "started" : "self-superseded"} ${formatEnded(ended)}`);
|
|
19324
19405
|
claudeProc.stdout?.on("data", onClaudeOutput);
|
|
19325
19406
|
claudeProc.stderr?.on("data", onClaudeOutput);
|
|
19326
|
-
claudeProc.once("close", () =>
|
|
19407
|
+
claudeProc.once("close", () => {
|
|
19408
|
+
closeSync8(claudeAuthLogFd);
|
|
19409
|
+
if (pendingAuthLogin?.child === claudeProc) pendingAuthLogin = null;
|
|
19410
|
+
});
|
|
19327
19411
|
await waitForAuthPage(2e4);
|
|
19328
19412
|
return c.json({ started: true, transport });
|
|
19329
19413
|
});
|
|
@@ -20003,7 +20087,7 @@ init_dist();
|
|
|
20003
20087
|
init_paths();
|
|
20004
20088
|
init_remote_auth();
|
|
20005
20089
|
init_loop_registry();
|
|
20006
|
-
import { appendFileSync as
|
|
20090
|
+
import { appendFileSync as appendFileSync4, existsSync as existsSync27, renameSync as renameSync9, statSync as statSync19 } from "fs";
|
|
20007
20091
|
import { join as join38 } from "path";
|
|
20008
20092
|
var CLIENT_ERRORS_LOG = join38(LOG_DIR, "client-errors.log");
|
|
20009
20093
|
var MAX_LOG_SIZE = 10 * 1024 * 1024;
|
|
@@ -20149,7 +20233,7 @@ app15.post("/", async (c) => {
|
|
|
20149
20233
|
const safe = typeof v === "string" ? truncate(v, 200) : typeof v === "number" || typeof v === "boolean" ? String(v) : JSON.stringify(v).slice(0, 200);
|
|
20150
20234
|
return `${k}=${safe}`;
|
|
20151
20235
|
}).join(" ");
|
|
20152
|
-
const TAGGED_PREFIX_SOURCES = /* @__PURE__ */ new Set(["stream-log-click", "pwa", "graph-settle", "dashboard-load", "routines", "calendar-legibility"]);
|
|
20236
|
+
const TAGGED_PREFIX_SOURCES = /* @__PURE__ */ new Set(["stream-log-click", "pwa", "graph-settle", "dashboard-load", "routines", "calendar-legibility", "channels-card"]);
|
|
20153
20237
|
if (TAGGED_PREFIX_SOURCES.has(source)) {
|
|
20154
20238
|
console.log(
|
|
20155
20239
|
`[${source}] ts=${ts} ip=${ip} version=${version || "unknown"}${extra ? " " + extra : ""}`
|
|
@@ -20182,7 +20266,7 @@ app15.post("/", async (c) => {
|
|
|
20182
20266
|
tag: typeof body.tag === "string" ? truncate(body.tag, 32) : void 0,
|
|
20183
20267
|
status: typeof body.status === "number" ? body.status : void 0
|
|
20184
20268
|
};
|
|
20185
|
-
|
|
20269
|
+
appendFileSync4(CLIENT_ERRORS_LOG, JSON.stringify(payload) + "\n", "utf-8");
|
|
20186
20270
|
} catch (err) {
|
|
20187
20271
|
console.error(`[client-error] append failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
20188
20272
|
}
|
|
@@ -23275,18 +23359,18 @@ var ALLOWED_EVENTS = /* @__PURE__ */ new Set([
|
|
|
23275
23359
|
]);
|
|
23276
23360
|
var app27 = new Hono();
|
|
23277
23361
|
app27.post("/", async (c) => {
|
|
23278
|
-
const
|
|
23362
|
+
const TAG68 = "[admin:events]";
|
|
23279
23363
|
let body;
|
|
23280
23364
|
try {
|
|
23281
23365
|
body = await c.req.json();
|
|
23282
23366
|
} catch (err) {
|
|
23283
23367
|
const detail = err instanceof Error ? err.message : String(err);
|
|
23284
|
-
console.error(`${
|
|
23368
|
+
console.error(`${TAG68} reject reason=body-not-json detail=${detail}`);
|
|
23285
23369
|
return c.json({ ok: false, detail: "Request body was not valid JSON" }, 400);
|
|
23286
23370
|
}
|
|
23287
23371
|
const event = typeof body.event === "string" ? body.event : "";
|
|
23288
23372
|
if (!ALLOWED_EVENTS.has(event)) {
|
|
23289
|
-
console.error(`${
|
|
23373
|
+
console.error(`${TAG68} reject reason=event-not-allowed event=${JSON.stringify(event)}`);
|
|
23290
23374
|
return c.json({ ok: false, detail: `Event "${event}" is not allowed` }, 400);
|
|
23291
23375
|
}
|
|
23292
23376
|
const rawFields = body.fields && typeof body.fields === "object" ? body.fields : {};
|
|
@@ -34985,6 +35069,68 @@ function startSharedFolderCensus() {
|
|
|
34985
35069
|
});
|
|
34986
35070
|
}
|
|
34987
35071
|
|
|
35072
|
+
// app/lib/cred-refresh-census.ts
|
|
35073
|
+
init_loop_registry();
|
|
35074
|
+
import { readdirSync as readdirSync31, readFileSync as readFileSync53 } from "fs";
|
|
35075
|
+
import { homedir as homedir5 } from "os";
|
|
35076
|
+
import { join as join59 } from "path";
|
|
35077
|
+
var TAG60 = "[claude-auth]";
|
|
35078
|
+
function refreshTokenLength(raw) {
|
|
35079
|
+
let data;
|
|
35080
|
+
try {
|
|
35081
|
+
data = JSON.parse(raw);
|
|
35082
|
+
} catch {
|
|
35083
|
+
return null;
|
|
35084
|
+
}
|
|
35085
|
+
const oauth = data.claudeAiOauth;
|
|
35086
|
+
if (!oauth || typeof oauth !== "object") return null;
|
|
35087
|
+
return typeof oauth.refreshToken === "string" ? oauth.refreshToken.length : null;
|
|
35088
|
+
}
|
|
35089
|
+
function surveyBrandCredentials(home) {
|
|
35090
|
+
let entries2;
|
|
35091
|
+
try {
|
|
35092
|
+
entries2 = readdirSync31(home);
|
|
35093
|
+
} catch {
|
|
35094
|
+
return [];
|
|
35095
|
+
}
|
|
35096
|
+
const rows = [];
|
|
35097
|
+
for (const entry2 of entries2.sort()) {
|
|
35098
|
+
if (!entry2.startsWith(".")) continue;
|
|
35099
|
+
let raw;
|
|
35100
|
+
try {
|
|
35101
|
+
raw = readFileSync53(join59(home, entry2, ".claude", ".credentials.json"), "utf-8");
|
|
35102
|
+
} catch {
|
|
35103
|
+
continue;
|
|
35104
|
+
}
|
|
35105
|
+
rows.push({ brand: entry2.slice(1), refreshTokenLen: refreshTokenLength(raw) });
|
|
35106
|
+
}
|
|
35107
|
+
return rows;
|
|
35108
|
+
}
|
|
35109
|
+
function isBlank(row) {
|
|
35110
|
+
return row.refreshTokenLen === null || row.refreshTokenLen === 0;
|
|
35111
|
+
}
|
|
35112
|
+
function censusResult(rows) {
|
|
35113
|
+
if (rows.length === 0) return "no-brands-found";
|
|
35114
|
+
return rows.some(isBlank) ? "blank-tokens" : "ok";
|
|
35115
|
+
}
|
|
35116
|
+
function runCredRefreshCensus(home = homedir5()) {
|
|
35117
|
+
const rows = surveyBrandCredentials(home);
|
|
35118
|
+
const blank = rows.filter(isBlank).length;
|
|
35119
|
+
const detail = rows.map((r) => `${r.brand}:${r.refreshTokenLen === null ? "unreadable" : r.refreshTokenLen}`).join(",");
|
|
35120
|
+
console.log(
|
|
35121
|
+
`${TAG60} op=cred-refresh-census result=${censusResult(rows)} brands=${rows.length} blank=${blank} detail=${detail}`
|
|
35122
|
+
);
|
|
35123
|
+
return `${rows.length} brand(s), ${blank} blank`;
|
|
35124
|
+
}
|
|
35125
|
+
function startCredRefreshCensus() {
|
|
35126
|
+
registerLoop({
|
|
35127
|
+
name: "cred-refresh-census",
|
|
35128
|
+
intervalMs: 60 * 60 * 1e3,
|
|
35129
|
+
firstRunDelayMs: 0,
|
|
35130
|
+
run: () => runCredRefreshCensus()
|
|
35131
|
+
});
|
|
35132
|
+
}
|
|
35133
|
+
|
|
34988
35134
|
// app/lib/file-watcher.ts
|
|
34989
35135
|
init_data_path();
|
|
34990
35136
|
import * as fsp2 from "fs/promises";
|
|
@@ -35222,7 +35368,7 @@ init_account();
|
|
|
35222
35368
|
init_neo4j_store();
|
|
35223
35369
|
import { readdir as readdir7, readFile as readFile12, rename as rename5, writeFile as writeFile5, unlink as unlink4 } from "fs/promises";
|
|
35224
35370
|
import { existsSync as existsSync50 } from "fs";
|
|
35225
|
-
import { join as
|
|
35371
|
+
import { join as join60 } from "path";
|
|
35226
35372
|
var ADMIN_ROLE2 = "admin";
|
|
35227
35373
|
var WEBCHAT_CHANNEL2 = "webchat";
|
|
35228
35374
|
var SESSION_META_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.meta\.json$/i;
|
|
@@ -35267,7 +35413,7 @@ async function collectLiveSidecars(projectsRoot) {
|
|
|
35267
35413
|
}
|
|
35268
35414
|
for (const slug of slugs) {
|
|
35269
35415
|
if (!slug.isDirectory()) continue;
|
|
35270
|
-
const slugDir =
|
|
35416
|
+
const slugDir = join60(projectsRoot, slug.name);
|
|
35271
35417
|
let entries2;
|
|
35272
35418
|
try {
|
|
35273
35419
|
entries2 = await readdir7(slugDir, { withFileTypes: true });
|
|
@@ -35276,9 +35422,9 @@ async function collectLiveSidecars(projectsRoot) {
|
|
|
35276
35422
|
}
|
|
35277
35423
|
for (const entry2 of entries2) {
|
|
35278
35424
|
if (entry2.isFile() && SESSION_META_RE.test(entry2.name)) {
|
|
35279
|
-
out.push(
|
|
35425
|
+
out.push(join60(slugDir, entry2.name));
|
|
35280
35426
|
} else if (entry2.isDirectory() && entry2.name === "subagents") {
|
|
35281
|
-
const subDir =
|
|
35427
|
+
const subDir = join60(slugDir, entry2.name);
|
|
35282
35428
|
let subs;
|
|
35283
35429
|
try {
|
|
35284
35430
|
subs = await readdir7(subDir, { withFileTypes: true });
|
|
@@ -35286,7 +35432,7 @@ async function collectLiveSidecars(projectsRoot) {
|
|
|
35286
35432
|
continue;
|
|
35287
35433
|
}
|
|
35288
35434
|
for (const s of subs) {
|
|
35289
|
-
if (s.isFile() && SESSION_META_RE.test(s.name)) out.push(
|
|
35435
|
+
if (s.isFile() && SESSION_META_RE.test(s.name)) out.push(join60(subDir, s.name));
|
|
35290
35436
|
}
|
|
35291
35437
|
}
|
|
35292
35438
|
}
|
|
@@ -35298,7 +35444,7 @@ function shortId(path) {
|
|
|
35298
35444
|
return base.slice(0, 8);
|
|
35299
35445
|
}
|
|
35300
35446
|
async function migrateAdminWebchatSidecars(opts = {}) {
|
|
35301
|
-
const projectsRoot = opts.projectsRoot ?? (process.env.CLAUDE_CONFIG_DIR ?
|
|
35447
|
+
const projectsRoot = opts.projectsRoot ?? (process.env.CLAUDE_CONFIG_DIR ? join60(process.env.CLAUDE_CONFIG_DIR, "projects") : null) ?? "";
|
|
35302
35448
|
const usersFile = opts.usersFile ?? USERS_FILE;
|
|
35303
35449
|
const accountId = opts.accountId ?? resolveAccount()?.accountId ?? null;
|
|
35304
35450
|
const resolveName = opts.resolveName ?? defaultResolveName;
|
|
@@ -35376,7 +35522,7 @@ async function migrateAdminWebchatSidecars(opts = {}) {
|
|
|
35376
35522
|
init_account();
|
|
35377
35523
|
import { readdir as readdir8, readFile as readFile13, rename as rename6, writeFile as writeFile6, unlink as unlink5 } from "fs/promises";
|
|
35378
35524
|
import { existsSync as existsSync51 } from "fs";
|
|
35379
|
-
import { join as
|
|
35525
|
+
import { join as join61 } from "path";
|
|
35380
35526
|
var SESSION_META_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.meta\.json$/i;
|
|
35381
35527
|
function projectSlugForCwd(cwd) {
|
|
35382
35528
|
return cwd.replace(/\//g, "-");
|
|
@@ -35422,7 +35568,7 @@ async function collectSidecars(projectsRoot) {
|
|
|
35422
35568
|
}
|
|
35423
35569
|
for (const slug of slugs) {
|
|
35424
35570
|
if (!slug.isDirectory()) continue;
|
|
35425
|
-
const slugDir =
|
|
35571
|
+
const slugDir = join61(projectsRoot, slug.name);
|
|
35426
35572
|
let entries2;
|
|
35427
35573
|
try {
|
|
35428
35574
|
entries2 = await readdir8(slugDir, { withFileTypes: true });
|
|
@@ -35431,9 +35577,9 @@ async function collectSidecars(projectsRoot) {
|
|
|
35431
35577
|
}
|
|
35432
35578
|
for (const entry2 of entries2) {
|
|
35433
35579
|
if (entry2.isFile() && SESSION_META_RE2.test(entry2.name)) {
|
|
35434
|
-
out.push({ path:
|
|
35580
|
+
out.push({ path: join61(slugDir, entry2.name), slug: slug.name });
|
|
35435
35581
|
} else if (entry2.isDirectory() && (entry2.name === "subagents" || entry2.name === "archive")) {
|
|
35436
|
-
const subDir =
|
|
35582
|
+
const subDir = join61(slugDir, entry2.name);
|
|
35437
35583
|
let subs;
|
|
35438
35584
|
try {
|
|
35439
35585
|
subs = await readdir8(subDir, { withFileTypes: true });
|
|
@@ -35442,7 +35588,7 @@ async function collectSidecars(projectsRoot) {
|
|
|
35442
35588
|
}
|
|
35443
35589
|
for (const s of subs) {
|
|
35444
35590
|
if (s.isFile() && SESSION_META_RE2.test(s.name)) {
|
|
35445
|
-
out.push({ path:
|
|
35591
|
+
out.push({ path: join61(subDir, s.name), slug: slug.name });
|
|
35446
35592
|
}
|
|
35447
35593
|
}
|
|
35448
35594
|
}
|
|
@@ -35451,7 +35597,7 @@ async function collectSidecars(projectsRoot) {
|
|
|
35451
35597
|
return out;
|
|
35452
35598
|
}
|
|
35453
35599
|
async function migrateSidecarAccountIds(opts = {}) {
|
|
35454
|
-
const projectsRoot = opts.projectsRoot ?? (process.env.CLAUDE_CONFIG_DIR ?
|
|
35600
|
+
const projectsRoot = opts.projectsRoot ?? (process.env.CLAUDE_CONFIG_DIR ? join61(process.env.CLAUDE_CONFIG_DIR, "projects") : null) ?? "";
|
|
35455
35601
|
const accounts = opts.accounts ?? listValidAccounts();
|
|
35456
35602
|
const result = {
|
|
35457
35603
|
scanned: 0,
|
|
@@ -35546,13 +35692,13 @@ var InboundHub = class {
|
|
|
35546
35692
|
if (s.subscriber && s.ready) {
|
|
35547
35693
|
if (payload.accountId !== s.accountId) this.crossAccountDelivered++;
|
|
35548
35694
|
s.subscriber(payload);
|
|
35549
|
-
s.inFlight.push({ payload, state: "drained", enqueuedAt: now, drainedAt: now });
|
|
35695
|
+
s.inFlight.push({ payload, state: "drained", enqueuedAt: now, drainedAt: now, spawnUndeliveredAt: null });
|
|
35550
35696
|
return;
|
|
35551
35697
|
}
|
|
35552
35698
|
if (this.subscriberAccounts(payload.senderId).some((a) => a !== payload.accountId)) {
|
|
35553
35699
|
this.crossAccountQueued++;
|
|
35554
35700
|
}
|
|
35555
|
-
s.inFlight.push({ payload, state: "queued", enqueuedAt: now, drainedAt: null });
|
|
35701
|
+
s.inFlight.push({ payload, state: "queued", enqueuedAt: now, drainedAt: null, spawnUndeliveredAt: null });
|
|
35556
35702
|
}
|
|
35557
35703
|
/** This account's channel server for the sender connected. Register the
|
|
35558
35704
|
* subscriber but do NOT drain yet — the drain waits for markReady so nothing
|
|
@@ -35611,13 +35757,16 @@ var InboundHub = class {
|
|
|
35611
35757
|
* is attached -> inbound-undelivered (the readiness handoff broke);
|
|
35612
35758
|
* - a still-queued message older than queuedThresholdMs with NO subscriber
|
|
35613
35759
|
* attached -> spawn-undelivered (the channel server never attached: spawn
|
|
35614
|
-
* failed, or the session ended and never resumed)
|
|
35760
|
+
* failed, or the session ended and never resumed). Task 2575 — reported
|
|
35761
|
+
* ONCE and RETAINED, because the gateway retries the spawn for it;
|
|
35762
|
+
* - a still-queued message with NO subscriber older than giveUpMs ->
|
|
35763
|
+
* spawn-given-up (Task 2575: the retry window closed), removed;
|
|
35615
35764
|
* - a drained message unacked past ackThresholdMs -> drain-undelivered (the
|
|
35616
35765
|
* channel server never confirmed receipt — the silent cold-start drop).
|
|
35617
35766
|
* Task 2290 — "a subscriber" means one attached for the SAME account, so a
|
|
35618
35767
|
* message waiting on its own account's cold start is not masked by another
|
|
35619
35768
|
* account's live subscriber on the same phone. Each message is emitted at
|
|
35620
|
-
* most once
|
|
35769
|
+
* most once per kind; every kind but spawn-undelivered removes it. */
|
|
35621
35770
|
sweep(now, t) {
|
|
35622
35771
|
const events = [];
|
|
35623
35772
|
for (const s of this.senders.values()) {
|
|
@@ -35627,9 +35776,17 @@ var InboundHub = class {
|
|
|
35627
35776
|
events.push({ kind: "inbound-undelivered", accountId, senderId, waMessageId: e.payload.waMessageId, ageMs: now - e.enqueuedAt });
|
|
35628
35777
|
return false;
|
|
35629
35778
|
}
|
|
35630
|
-
if (e.state === "queued" && !s.subscriber
|
|
35631
|
-
|
|
35632
|
-
|
|
35779
|
+
if (e.state === "queued" && !s.subscriber) {
|
|
35780
|
+
const age = now - e.enqueuedAt;
|
|
35781
|
+
if (age > t.giveUpMs) {
|
|
35782
|
+
events.push({ kind: "spawn-given-up", accountId, senderId, waMessageId: e.payload.waMessageId, ageMs: age });
|
|
35783
|
+
return false;
|
|
35784
|
+
}
|
|
35785
|
+
if (age > t.queuedThresholdMs && e.spawnUndeliveredAt == null) {
|
|
35786
|
+
e.spawnUndeliveredAt = now;
|
|
35787
|
+
events.push({ kind: "spawn-undelivered", accountId, senderId, waMessageId: e.payload.waMessageId, ageMs: age });
|
|
35788
|
+
}
|
|
35789
|
+
return true;
|
|
35633
35790
|
}
|
|
35634
35791
|
if (e.state === "drained" && e.drainedAt != null && now - e.drainedAt > t.ackThresholdMs) {
|
|
35635
35792
|
events.push({ kind: "drain-undelivered", accountId, senderId, waMessageId: e.payload.waMessageId, ageMs: now - e.drainedAt });
|
|
@@ -35640,6 +35797,23 @@ var InboundHub = class {
|
|
|
35640
35797
|
}
|
|
35641
35798
|
return events;
|
|
35642
35799
|
}
|
|
35800
|
+
/** Task 2575 — every key holding queued work with NO subscriber whose oldest
|
|
35801
|
+
* message is older than `thresholdMs`. This is the population a refused spawn
|
|
35802
|
+
* leaves behind: the gateway's sweep re-invokes the retained spawn arguments
|
|
35803
|
+
* for each one. Read-only — it emits nothing and mutates nothing, so calling
|
|
35804
|
+
* it cannot consume a terminal event the sweep is about to report. */
|
|
35805
|
+
heldSpawns(now, thresholdMs) {
|
|
35806
|
+
const out = [];
|
|
35807
|
+
for (const s of this.senders.values()) {
|
|
35808
|
+
if (s.subscriber) continue;
|
|
35809
|
+
const queued = s.inFlight.filter((e) => e.state === "queued");
|
|
35810
|
+
if (queued.length === 0) continue;
|
|
35811
|
+
const oldestAgeMs = Math.max(...queued.map((e) => now - e.enqueuedAt));
|
|
35812
|
+
if (oldestAgeMs <= thresholdMs) continue;
|
|
35813
|
+
out.push({ accountId: s.accountId, senderId: s.senderId, oldestAgeMs, count: queued.length });
|
|
35814
|
+
}
|
|
35815
|
+
return out;
|
|
35816
|
+
}
|
|
35643
35817
|
/** Task 2290 — every account holding a live subscriber for this phone, sorted.
|
|
35644
35818
|
* Read-only. Feeds the cross-account field on the inbound log line and the
|
|
35645
35819
|
* queued census, so a phone shared across accounts is greppable at the moment
|
|
@@ -36142,6 +36316,7 @@ init_loop_registry();
|
|
|
36142
36316
|
var QUEUED_THRESHOLD_MS = 3e4;
|
|
36143
36317
|
var ACK_THRESHOLD_MS = 3e4;
|
|
36144
36318
|
var SWEEP_INTERVAL_MS = 15e3;
|
|
36319
|
+
var GIVE_UP_MS = 3e5;
|
|
36145
36320
|
var MEDIA_STANZA_STALE_MS = 12e4;
|
|
36146
36321
|
var WaGateway = class {
|
|
36147
36322
|
constructor(deps) {
|
|
@@ -36163,6 +36338,14 @@ var WaGateway = class {
|
|
|
36163
36338
|
// effective session account — the file-path validation scope.
|
|
36164
36339
|
docContexts = /* @__PURE__ */ new Map();
|
|
36165
36340
|
spawning = /* @__PURE__ */ new Set();
|
|
36341
|
+
/** Task 2575 — the arguments of the last `ensureChannelSession` call per hub
|
|
36342
|
+
* key, so the sweep can re-invoke a spawn that was refused. The refusal is not
|
|
36343
|
+
* visible here — `ensureChannelSession` returns void and server/index.ts logs
|
|
36344
|
+
* and returns on the `{ error }` envelope — so the retry keys off queue state
|
|
36345
|
+
* (a queued message with no subscriber), which is the observable consequence
|
|
36346
|
+
* of the refusal. `attempts` is the per-key retry counter that rides the log
|
|
36347
|
+
* line, reset whenever a fresh inbound takes the spawn path. */
|
|
36348
|
+
heldSpawnArgs = /* @__PURE__ */ new Map();
|
|
36166
36349
|
seq = 0;
|
|
36167
36350
|
// Task 2110 — senderId -> the arrival ms of every still-unemitted non-audio
|
|
36168
36351
|
// media stanza for that sender, oldest first. This is the race window: a reply
|
|
@@ -36299,7 +36482,8 @@ var WaGateway = class {
|
|
|
36299
36482
|
reconcile(now) {
|
|
36300
36483
|
const events = this.hub.sweep(now, {
|
|
36301
36484
|
queuedThresholdMs: QUEUED_THRESHOLD_MS,
|
|
36302
|
-
ackThresholdMs: ACK_THRESHOLD_MS
|
|
36485
|
+
ackThresholdMs: ACK_THRESHOLD_MS,
|
|
36486
|
+
giveUpMs: GIVE_UP_MS
|
|
36303
36487
|
});
|
|
36304
36488
|
for (const e of events) {
|
|
36305
36489
|
console.error(
|
|
@@ -36321,6 +36505,53 @@ var WaGateway = class {
|
|
|
36321
36505
|
);
|
|
36322
36506
|
return events;
|
|
36323
36507
|
}
|
|
36508
|
+
/** Task 2575 — re-invoke the retained spawn for every key holding queued work
|
|
36509
|
+
* with no channel server attached. Driven by startSweeper() beside reconcile();
|
|
36510
|
+
* called directly with an injected clock in tests. Returns the number of
|
|
36511
|
+
* ensureChannelSession calls made.
|
|
36512
|
+
*
|
|
36513
|
+
* This is the missing action behind `op=spawn-undelivered`. Before it, that
|
|
36514
|
+
* line was emitted and nothing consumed it: on 2026-08-08 it fired for wa-1 at
|
|
36515
|
+
* ageMs=37743 and the message was never delivered.
|
|
36516
|
+
*
|
|
36517
|
+
* A retry while auth is still dead re-enters the same refusal and returns; the
|
|
36518
|
+
* cost is one refusal line plus one op=spawn-retry line per key per sweep,
|
|
36519
|
+
* bounded by the hub's 5-minute give-up. */
|
|
36520
|
+
async retryHeldSpawns(now) {
|
|
36521
|
+
const held = this.hub.heldSpawns(now, QUEUED_THRESHOLD_MS);
|
|
36522
|
+
let calls = 0;
|
|
36523
|
+
for (const h of held) {
|
|
36524
|
+
const key2 = hubKey(h.accountId, h.senderId);
|
|
36525
|
+
if (this.spawning.has(key2)) continue;
|
|
36526
|
+
const entry2 = this.heldSpawnArgs.get(key2);
|
|
36527
|
+
if (!entry2) {
|
|
36528
|
+
console.error(
|
|
36529
|
+
`[whatsapp-native] op=spawn-retry-skipped senderId=${h.senderId} accountId=${h.accountId} ageMs=${h.oldestAgeMs} queued=${h.count} reason=no-args`
|
|
36530
|
+
);
|
|
36531
|
+
continue;
|
|
36532
|
+
}
|
|
36533
|
+
entry2.attempts++;
|
|
36534
|
+
this.spawning.add(key2);
|
|
36535
|
+
calls++;
|
|
36536
|
+
try {
|
|
36537
|
+
await this.deps.ensureChannelSession(entry2.args);
|
|
36538
|
+
console.error(
|
|
36539
|
+
`[whatsapp-native] op=spawn-retry senderId=${h.senderId} accountId=${h.accountId} attempt=${entry2.attempts} ageMs=${h.oldestAgeMs} queued=${h.count} ok=true`
|
|
36540
|
+
);
|
|
36541
|
+
} catch (err) {
|
|
36542
|
+
console.error(
|
|
36543
|
+
`[whatsapp-native] op=spawn-retry senderId=${h.senderId} accountId=${h.accountId} attempt=${entry2.attempts} ageMs=${h.oldestAgeMs} queued=${h.count} ok=false reason=${err instanceof Error ? err.message : String(err)}`
|
|
36544
|
+
);
|
|
36545
|
+
} finally {
|
|
36546
|
+
this.spawning.delete(key2);
|
|
36547
|
+
}
|
|
36548
|
+
}
|
|
36549
|
+
const heldKeys = new Set(held.map((h) => hubKey(h.accountId, h.senderId)));
|
|
36550
|
+
for (const k of [...this.heldSpawnArgs.keys()]) {
|
|
36551
|
+
if (!heldKeys.has(k) && !this.spawning.has(k)) this.heldSpawnArgs.delete(k);
|
|
36552
|
+
}
|
|
36553
|
+
return calls;
|
|
36554
|
+
}
|
|
36324
36555
|
/** Arm the periodic reconciliation sweep in the long-lived UI process.
|
|
36325
36556
|
*
|
|
36326
36557
|
* Task 1814 — armed through the registry, so it appears on /activity. This
|
|
@@ -36338,9 +36569,14 @@ var WaGateway = class {
|
|
|
36338
36569
|
return registerLoop({
|
|
36339
36570
|
name: "whatsapp-native-sweep",
|
|
36340
36571
|
intervalMs,
|
|
36341
|
-
run: () => {
|
|
36342
|
-
const
|
|
36343
|
-
|
|
36572
|
+
run: async () => {
|
|
36573
|
+
const now = Date.now();
|
|
36574
|
+
const events = this.reconcile(now);
|
|
36575
|
+
const retried = await this.retryHeldSpawns(now);
|
|
36576
|
+
const parts = [];
|
|
36577
|
+
if (events.length > 0) parts.push(`${events.length} terminal events`);
|
|
36578
|
+
if (retried > 0) parts.push(`${retried} spawn retries`);
|
|
36579
|
+
return parts.length > 0 ? parts.join(", ") : void 0;
|
|
36344
36580
|
}
|
|
36345
36581
|
});
|
|
36346
36582
|
}
|
|
@@ -36448,33 +36684,35 @@ var WaGateway = class {
|
|
|
36448
36684
|
console.error(
|
|
36449
36685
|
`[whatsapp-native] op=spawn-trigger senderId=${senderId} accountId=${input.effectiveAccountId}`
|
|
36450
36686
|
);
|
|
36687
|
+
const spawnArgs = {
|
|
36688
|
+
accountId: input.accountId,
|
|
36689
|
+
effectiveAccountId: input.effectiveAccountId,
|
|
36690
|
+
senderId,
|
|
36691
|
+
role: input.role ?? "admin",
|
|
36692
|
+
personId: input.personId ?? null,
|
|
36693
|
+
gatewayUrl: this.deps.gatewayUrl,
|
|
36694
|
+
serverPath: this.deps.serverPath,
|
|
36695
|
+
// Task 1575 — the gate's owner-self verdict; drives owner attribution
|
|
36696
|
+
// for an unbound owner self-phone at the spawn site.
|
|
36697
|
+
ownerSelfPhone: input.ownerSelfPhone,
|
|
36698
|
+
// Task 2262 — resolved once in onMessage; its presence routes this
|
|
36699
|
+
// spawn to /public-spawn under the named public agent.
|
|
36700
|
+
publicAgentSlug: input.publicAgentSlug,
|
|
36701
|
+
// Task 2281 — the correlation id, so the spawn outcome lines join the
|
|
36702
|
+
// rest of this inbound's lifeline. Only this call site takes it: the
|
|
36703
|
+
// passive branch above never reaches the public spawn, which is where
|
|
36704
|
+
// op=spawn-ok and op=spawn-failed are emitted.
|
|
36705
|
+
inboundId: input.inboundId,
|
|
36706
|
+
// Task 2234 — the registered-party label, forwarded verbatim so the
|
|
36707
|
+
// spawn env carries WA_SENDER_PARTY. Undefined for every other sender.
|
|
36708
|
+
// Only this call site takes it: the passive branch above hardcodes
|
|
36709
|
+
// role:'admin', and a registered party is public, so the combination
|
|
36710
|
+
// cannot arise there.
|
|
36711
|
+
party: input.party
|
|
36712
|
+
};
|
|
36713
|
+
this.heldSpawnArgs.set(spawnKey, { args: spawnArgs, attempts: 0 });
|
|
36451
36714
|
try {
|
|
36452
|
-
await this.deps.ensureChannelSession(
|
|
36453
|
-
accountId: input.accountId,
|
|
36454
|
-
effectiveAccountId: input.effectiveAccountId,
|
|
36455
|
-
senderId,
|
|
36456
|
-
role: input.role ?? "admin",
|
|
36457
|
-
personId: input.personId ?? null,
|
|
36458
|
-
gatewayUrl: this.deps.gatewayUrl,
|
|
36459
|
-
serverPath: this.deps.serverPath,
|
|
36460
|
-
// Task 1575 — the gate's owner-self verdict; drives owner attribution
|
|
36461
|
-
// for an unbound owner self-phone at the spawn site.
|
|
36462
|
-
ownerSelfPhone: input.ownerSelfPhone,
|
|
36463
|
-
// Task 2262 — resolved once in onMessage; its presence routes this
|
|
36464
|
-
// spawn to /public-spawn under the named public agent.
|
|
36465
|
-
publicAgentSlug: input.publicAgentSlug,
|
|
36466
|
-
// Task 2281 — the correlation id, so the spawn outcome lines join the
|
|
36467
|
-
// rest of this inbound's lifeline. Only this call site takes it: the
|
|
36468
|
-
// passive branch above never reaches the public spawn, which is where
|
|
36469
|
-
// op=spawn-ok and op=spawn-failed are emitted.
|
|
36470
|
-
inboundId: input.inboundId,
|
|
36471
|
-
// Task 2234 — the registered-party label, forwarded verbatim so the
|
|
36472
|
-
// spawn env carries WA_SENDER_PARTY. Undefined for every other sender.
|
|
36473
|
-
// Only this call site takes it: the passive branch above hardcodes
|
|
36474
|
-
// role:'admin', and a registered party is public, so the combination
|
|
36475
|
-
// cannot arise there.
|
|
36476
|
-
party: input.party
|
|
36477
|
-
});
|
|
36715
|
+
await this.deps.ensureChannelSession(spawnArgs);
|
|
36478
36716
|
} finally {
|
|
36479
36717
|
this.spawning.delete(spawnKey);
|
|
36480
36718
|
}
|
|
@@ -37002,8 +37240,41 @@ function createWebchatChannelRoutes(deps) {
|
|
|
37002
37240
|
}
|
|
37003
37241
|
|
|
37004
37242
|
// app/lib/webchat/gateway/resolve-turn-answer.ts
|
|
37005
|
-
import { existsSync as existsSync52, openSync as openSync9, readSync as readSync8, closeSync as closeSync9, readdirSync as
|
|
37006
|
-
import { join as
|
|
37243
|
+
import { existsSync as existsSync52, openSync as openSync9, readSync as readSync8, closeSync as closeSync9, readdirSync as readdirSync32 } from "fs";
|
|
37244
|
+
import { join as join62 } from "path";
|
|
37245
|
+
|
|
37246
|
+
// app/lib/channel-delivery/outbound-guard.ts
|
|
37247
|
+
var INTERNAL_LEAK_PATTERNS = [
|
|
37248
|
+
{ name: "task-id", re: /\bTasks?[\s-]+\d{2,4}\b/g },
|
|
37249
|
+
{ name: "media-path", re: /\/tmp\/maxy-media[A-Za-z0-9_./-]*/g },
|
|
37250
|
+
{ name: "repo-path", re: /\bplatform\/[A-Za-z0-9_./-]+/g },
|
|
37251
|
+
{ name: "bundle", re: /\bserver\.(?:js|cjs|mjs)\b/g },
|
|
37252
|
+
{ name: "source-file", re: /\b[A-Za-z0-9_-]+\.tsx?\b/g },
|
|
37253
|
+
{ name: "log-tag", re: /\[[a-z]+(?:-[a-z]+)+\]/g },
|
|
37254
|
+
{ name: "op-tag", re: /\bop=[a-z][a-z-]*\b/g }
|
|
37255
|
+
];
|
|
37256
|
+
function redactInternalLeaks(text) {
|
|
37257
|
+
const redactions = [];
|
|
37258
|
+
let clean = text;
|
|
37259
|
+
for (const { name, re } of INTERNAL_LEAK_PATTERNS) {
|
|
37260
|
+
clean = clean.replace(re, (m) => {
|
|
37261
|
+
redactions.push({ pattern: name, match: m });
|
|
37262
|
+
return "[redacted]";
|
|
37263
|
+
});
|
|
37264
|
+
}
|
|
37265
|
+
return { clean, redactions };
|
|
37266
|
+
}
|
|
37267
|
+
function guardOutbound(senderId, text) {
|
|
37268
|
+
const { clean, redactions } = redactInternalLeaks(text);
|
|
37269
|
+
for (const r of redactions) {
|
|
37270
|
+
console.error(
|
|
37271
|
+
`[channel-guard] op=redact senderId=${senderId} pattern=${r.pattern} bytes=${Buffer.byteLength(r.match, "utf8")}`
|
|
37272
|
+
);
|
|
37273
|
+
}
|
|
37274
|
+
return clean;
|
|
37275
|
+
}
|
|
37276
|
+
|
|
37277
|
+
// app/lib/webchat/gateway/resolve-turn-answer.ts
|
|
37007
37278
|
function resolveTurnAnswer(p) {
|
|
37008
37279
|
if (!SESSION_ID_RE2.test(p.sessionId)) return "";
|
|
37009
37280
|
const path = findJsonl(p.projectsRoot, p.sessionId);
|
|
@@ -37028,18 +37299,18 @@ function resolveTurnAnswer(p) {
|
|
|
37028
37299
|
for (const turn of turns) {
|
|
37029
37300
|
if (turn.kind === "agent-webchat-final" || turn.kind === "agent-reply") parts.push(turn.text);
|
|
37030
37301
|
}
|
|
37031
|
-
return segmentText(parts.join("\n\n")).join("\n\n");
|
|
37302
|
+
return segmentText(guardOutbound(p.senderId, parts.join("\n\n"))).join("\n\n");
|
|
37032
37303
|
}
|
|
37033
37304
|
function findJsonl(projectsRoot, sessionId) {
|
|
37034
37305
|
if (!existsSync52(projectsRoot)) return null;
|
|
37035
37306
|
let slugs;
|
|
37036
37307
|
try {
|
|
37037
|
-
slugs =
|
|
37308
|
+
slugs = readdirSync32(projectsRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
37038
37309
|
} catch {
|
|
37039
37310
|
return null;
|
|
37040
37311
|
}
|
|
37041
37312
|
for (const slug of slugs) {
|
|
37042
|
-
const live =
|
|
37313
|
+
const live = join62(projectsRoot, slug, `${sessionId}.jsonl`);
|
|
37043
37314
|
if (existsSync52(live)) return live;
|
|
37044
37315
|
}
|
|
37045
37316
|
return null;
|
|
@@ -37187,7 +37458,7 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
37187
37458
|
if (rec) rec.lastActivityAt = Date.now();
|
|
37188
37459
|
const awaiter = this.replyAwaiters.get(key2);
|
|
37189
37460
|
const projectsRoot = this.deps.publicProjectsRoot?.() ?? "";
|
|
37190
|
-
const text = projectsRoot ? resolveTurnAnswer({ ...turn, projectsRoot }) : "";
|
|
37461
|
+
const text = projectsRoot ? resolveTurnAnswer({ ...turn, senderId: keyDisplay(key2), projectsRoot }) : "";
|
|
37191
37462
|
const bytes = Buffer.byteLength(text, "utf8");
|
|
37192
37463
|
const deliveredAt = this.lastDeliveredAt.get(key2);
|
|
37193
37464
|
const latency = deliveredAt != null ? Date.now() - deliveredAt : -1;
|
|
@@ -37682,37 +37953,6 @@ var AnswerAccumulator = class {
|
|
|
37682
37953
|
}
|
|
37683
37954
|
};
|
|
37684
37955
|
|
|
37685
|
-
// app/lib/channel-delivery/outbound-guard.ts
|
|
37686
|
-
var INTERNAL_LEAK_PATTERNS = [
|
|
37687
|
-
{ name: "task-id", re: /\bTasks?[\s-]+\d{2,4}\b/g },
|
|
37688
|
-
{ name: "media-path", re: /\/tmp\/maxy-media[A-Za-z0-9_./-]*/g },
|
|
37689
|
-
{ name: "repo-path", re: /\bplatform\/[A-Za-z0-9_./-]+/g },
|
|
37690
|
-
{ name: "bundle", re: /\bserver\.(?:js|cjs|mjs)\b/g },
|
|
37691
|
-
{ name: "source-file", re: /\b[A-Za-z0-9_-]+\.tsx?\b/g },
|
|
37692
|
-
{ name: "log-tag", re: /\[[a-z]+(?:-[a-z]+)+\]/g },
|
|
37693
|
-
{ name: "op-tag", re: /\bop=[a-z][a-z-]*\b/g }
|
|
37694
|
-
];
|
|
37695
|
-
function redactInternalLeaks(text) {
|
|
37696
|
-
const redactions = [];
|
|
37697
|
-
let clean = text;
|
|
37698
|
-
for (const { name, re } of INTERNAL_LEAK_PATTERNS) {
|
|
37699
|
-
clean = clean.replace(re, (m) => {
|
|
37700
|
-
redactions.push({ pattern: name, match: m });
|
|
37701
|
-
return "[redacted]";
|
|
37702
|
-
});
|
|
37703
|
-
}
|
|
37704
|
-
return { clean, redactions };
|
|
37705
|
-
}
|
|
37706
|
-
function guardOutbound(senderId, text) {
|
|
37707
|
-
const { clean, redactions } = redactInternalLeaks(text);
|
|
37708
|
-
for (const r of redactions) {
|
|
37709
|
-
console.error(
|
|
37710
|
-
`[channel-guard] op=redact senderId=${senderId} pattern=${r.pattern} bytes=${Buffer.byteLength(r.match, "utf8")}`
|
|
37711
|
-
);
|
|
37712
|
-
}
|
|
37713
|
-
return clean;
|
|
37714
|
-
}
|
|
37715
|
-
|
|
37716
37956
|
// app/lib/channel-delivery/emitter.ts
|
|
37717
37957
|
function followerPendingMaxMs() {
|
|
37718
37958
|
return Number(process.env.CHANNEL_PTY_FOLLOWER_PENDING_MAX_MS ?? String(3e5));
|
|
@@ -38054,14 +38294,14 @@ function makeFileDelivery(opts) {
|
|
|
38054
38294
|
}
|
|
38055
38295
|
|
|
38056
38296
|
// app/lib/webchat/file-delivery.ts
|
|
38057
|
-
var
|
|
38297
|
+
var TAG61 = "[webchat-adaptor]";
|
|
38058
38298
|
function platformRoot2() {
|
|
38059
38299
|
return process.env.MAXY_PLATFORM_ROOT || "";
|
|
38060
38300
|
}
|
|
38061
38301
|
function makeWebchatSendFile(entry2) {
|
|
38062
38302
|
return async (filePath) => {
|
|
38063
38303
|
if (!entry2.accountId) {
|
|
38064
|
-
console.error(`${
|
|
38304
|
+
console.error(`${TAG61} file-delivery reject reason=no-account sender=${entry2.senderId}`);
|
|
38065
38305
|
return { ok: false, error: "no-account" };
|
|
38066
38306
|
}
|
|
38067
38307
|
const accountDir = resolve47(platformRoot2(), "..", "data/accounts", entry2.accountId);
|
|
@@ -38069,14 +38309,14 @@ function makeWebchatSendFile(entry2) {
|
|
|
38069
38309
|
const resolved = realpathSync9(filePath);
|
|
38070
38310
|
const accountResolved = realpathSync9(accountDir);
|
|
38071
38311
|
if (!resolved.startsWith(accountResolved + "/")) {
|
|
38072
|
-
console.error(`${
|
|
38312
|
+
console.error(`${TAG61} file-delivery reject reason=outside_account_directory sender=${entry2.senderId}`);
|
|
38073
38313
|
return { ok: false, error: "outside-account" };
|
|
38074
38314
|
}
|
|
38075
38315
|
return { ok: true };
|
|
38076
38316
|
} catch (err) {
|
|
38077
38317
|
const code = err.code;
|
|
38078
38318
|
console.error(
|
|
38079
|
-
`${
|
|
38319
|
+
`${TAG61} file-delivery reject reason=${code === "ENOENT" ? "not-found" : "path-error"} sender=${entry2.senderId}`
|
|
38080
38320
|
);
|
|
38081
38321
|
return { ok: false, error: code === "ENOENT" ? "not-found" : "path-error" };
|
|
38082
38322
|
}
|
|
@@ -38085,7 +38325,7 @@ function makeWebchatSendFile(entry2) {
|
|
|
38085
38325
|
function makeWebchatFileDelivery(entry2) {
|
|
38086
38326
|
return makeFileDelivery({
|
|
38087
38327
|
entry: entry2,
|
|
38088
|
-
tag:
|
|
38328
|
+
tag: TAG61,
|
|
38089
38329
|
channel: "webchat",
|
|
38090
38330
|
sendFile: makeWebchatSendFile(entry2),
|
|
38091
38331
|
deferUntilVerdict: true
|
|
@@ -38165,7 +38405,7 @@ function buildPublicWebchatSpawnRequest(input) {
|
|
|
38165
38405
|
}
|
|
38166
38406
|
|
|
38167
38407
|
// app/lib/whatsapp/inbound/file-delivery-bridge.ts
|
|
38168
|
-
var
|
|
38408
|
+
var TAG62 = "[whatsapp-adaptor]";
|
|
38169
38409
|
var SEND_USER_FILE2 = "SendUserFile";
|
|
38170
38410
|
var WHATSAPP_SEND_DOCUMENT = "whatsapp-send-document";
|
|
38171
38411
|
function platformRoot3() {
|
|
@@ -38183,7 +38423,7 @@ function makeWhatsAppSendFile(entry2, maxyAccountId) {
|
|
|
38183
38423
|
});
|
|
38184
38424
|
if (result.ok) return { ok: true };
|
|
38185
38425
|
console.error(
|
|
38186
|
-
`${
|
|
38426
|
+
`${TAG62} file-delivery reject reason=send-failed sender=${entry2.senderId} status=${result.status} message=${result.error}`
|
|
38187
38427
|
);
|
|
38188
38428
|
return { ok: false, error: result.error };
|
|
38189
38429
|
};
|
|
@@ -38191,7 +38431,7 @@ function makeWhatsAppSendFile(entry2, maxyAccountId) {
|
|
|
38191
38431
|
function makeWhatsAppFileDelivery(entry2, maxyAccountId) {
|
|
38192
38432
|
const shared = makeFileDelivery({
|
|
38193
38433
|
entry: entry2,
|
|
38194
|
-
tag:
|
|
38434
|
+
tag: TAG62,
|
|
38195
38435
|
channel: "whatsapp",
|
|
38196
38436
|
sendFile: makeWhatsAppSendFile(entry2, maxyAccountId)
|
|
38197
38437
|
});
|
|
@@ -38226,7 +38466,7 @@ function makeWhatsAppFileDelivery(entry2, maxyAccountId) {
|
|
|
38226
38466
|
if (!delivered) {
|
|
38227
38467
|
const fileField = call.filePath !== void 0 ? ` file=${call.filePath}` : "";
|
|
38228
38468
|
console.error(
|
|
38229
|
-
`${
|
|
38469
|
+
`${TAG62} file-delivery-unreconciled sender=${entry2.senderId} sessionId=${sid} tool=${WHATSAPP_SEND_DOCUMENT}${fileField}`
|
|
38230
38470
|
);
|
|
38231
38471
|
}
|
|
38232
38472
|
}
|
|
@@ -38391,6 +38631,34 @@ function whatsappDoorLabel(doorId) {
|
|
|
38391
38631
|
return decoded ? `${id82(decoded.accountId)}:${id82(decoded.senderId)}` : id82(doorId);
|
|
38392
38632
|
}
|
|
38393
38633
|
|
|
38634
|
+
// app/lib/channel-delivery/refusal-notice.ts
|
|
38635
|
+
var TAG63 = "[channel-delivery]";
|
|
38636
|
+
var notified = /* @__PURE__ */ new Set();
|
|
38637
|
+
function refusalNoticeText(reason) {
|
|
38638
|
+
return reason === "claude-auth-dead" ? "Your message arrived but no assistant could be started: the Claude sign-in on this install is not active. An operator has to sign in again; send this again once they have." : `Your message arrived but no assistant could be started (${reason}). Nothing was lost; try again shortly.`;
|
|
38639
|
+
}
|
|
38640
|
+
async function sendRefusalNotice(input) {
|
|
38641
|
+
if (notified.has(input.doorId)) return;
|
|
38642
|
+
const fields = `channel=${input.channel} sender=${input.sender} reason=${input.reason}`;
|
|
38643
|
+
if (!input.send) {
|
|
38644
|
+
console.error(`${TAG63} op=refusal-notice-failed ${fields} error=no-target`);
|
|
38645
|
+
return;
|
|
38646
|
+
}
|
|
38647
|
+
try {
|
|
38648
|
+
await input.send(guardOutbound(input.sender, refusalNoticeText(input.reason)));
|
|
38649
|
+
} catch (e) {
|
|
38650
|
+
console.error(
|
|
38651
|
+
`${TAG63} op=refusal-notice-failed ${fields} error=${e instanceof Error ? e.message : String(e)}`
|
|
38652
|
+
);
|
|
38653
|
+
return;
|
|
38654
|
+
}
|
|
38655
|
+
notified.add(input.doorId);
|
|
38656
|
+
console.error(`${TAG63} op=refusal-notice ${fields} status=${input.status}`);
|
|
38657
|
+
}
|
|
38658
|
+
function clearRefusalNotice(doorId) {
|
|
38659
|
+
notified.delete(doorId);
|
|
38660
|
+
}
|
|
38661
|
+
|
|
38394
38662
|
// app/lib/telegram/gateway/inbound-hub.ts
|
|
38395
38663
|
var InboundHub3 = class {
|
|
38396
38664
|
keys = /* @__PURE__ */ new Map();
|
|
@@ -38411,9 +38679,9 @@ var InboundHub3 = class {
|
|
|
38411
38679
|
const s = this.state(payload.key);
|
|
38412
38680
|
if (s.subscriber && s.ready) {
|
|
38413
38681
|
s.subscriber(payload);
|
|
38414
|
-
s.inFlight.push({ payload, state: "drained", enqueuedAt: now, drainedAt: now });
|
|
38682
|
+
s.inFlight.push({ payload, state: "drained", enqueuedAt: now, drainedAt: now, spawnUndeliveredAt: null });
|
|
38415
38683
|
} else {
|
|
38416
|
-
s.inFlight.push({ payload, state: "queued", enqueuedAt: now, drainedAt: null });
|
|
38684
|
+
s.inFlight.push({ payload, state: "queued", enqueuedAt: now, drainedAt: null, spawnUndeliveredAt: null });
|
|
38417
38685
|
}
|
|
38418
38686
|
}
|
|
38419
38687
|
/** A key's channel server connected. Register the subscriber but do NOT
|
|
@@ -38473,9 +38741,18 @@ var InboundHub3 = class {
|
|
|
38473
38741
|
* that never reached the agent:
|
|
38474
38742
|
* - a still-queued message older than queuedThresholdMs WHILE a subscriber
|
|
38475
38743
|
* is attached -> inbound-undelivered (the readiness handoff broke);
|
|
38744
|
+
* - a still-queued message older than queuedThresholdMs with NO subscriber
|
|
38745
|
+
* attached -> spawn-undelivered (the channel server never attached: the
|
|
38746
|
+
* spawn was refused, or the session ended and never resumed). Task 2575 —
|
|
38747
|
+
* this case did not exist on this door at all before, which is why tg-1
|
|
38748
|
+
* survived the 2026-08-08 outage unreported. Reported ONCE and RETAINED,
|
|
38749
|
+
* because the gateway retries the spawn for it;
|
|
38750
|
+
* - a still-queued message with NO subscriber older than giveUpMs ->
|
|
38751
|
+
* spawn-given-up (Task 2575: the retry window closed), removed;
|
|
38476
38752
|
* - a drained message unacked past ackThresholdMs -> drain-undelivered (the
|
|
38477
38753
|
* channel server never confirmed receipt — the silent cold-start drop).
|
|
38478
|
-
* Each message is emitted at most once
|
|
38754
|
+
* Each message is emitted at most once per kind; every kind but
|
|
38755
|
+
* spawn-undelivered removes it. */
|
|
38479
38756
|
sweep(now, t) {
|
|
38480
38757
|
const events = [];
|
|
38481
38758
|
for (const [key2, s] of this.keys) {
|
|
@@ -38484,6 +38761,18 @@ var InboundHub3 = class {
|
|
|
38484
38761
|
events.push({ kind: "inbound-undelivered", key: key2, messageId: e.payload.messageId, ageMs: now - e.enqueuedAt });
|
|
38485
38762
|
return false;
|
|
38486
38763
|
}
|
|
38764
|
+
if (e.state === "queued" && !s.subscriber) {
|
|
38765
|
+
const age = now - e.enqueuedAt;
|
|
38766
|
+
if (age > t.giveUpMs) {
|
|
38767
|
+
events.push({ kind: "spawn-given-up", key: key2, messageId: e.payload.messageId, ageMs: age });
|
|
38768
|
+
return false;
|
|
38769
|
+
}
|
|
38770
|
+
if (age > t.queuedThresholdMs && e.spawnUndeliveredAt == null) {
|
|
38771
|
+
e.spawnUndeliveredAt = now;
|
|
38772
|
+
events.push({ kind: "spawn-undelivered", key: key2, messageId: e.payload.messageId, ageMs: age });
|
|
38773
|
+
}
|
|
38774
|
+
return true;
|
|
38775
|
+
}
|
|
38487
38776
|
if (e.state === "drained" && e.drainedAt != null && now - e.drainedAt > t.ackThresholdMs) {
|
|
38488
38777
|
events.push({ kind: "drain-undelivered", key: key2, messageId: e.payload.messageId, ageMs: now - e.drainedAt });
|
|
38489
38778
|
return false;
|
|
@@ -38493,6 +38782,23 @@ var InboundHub3 = class {
|
|
|
38493
38782
|
}
|
|
38494
38783
|
return events;
|
|
38495
38784
|
}
|
|
38785
|
+
/** Task 2575 — every key holding queued work with NO subscriber whose oldest
|
|
38786
|
+
* message is older than `thresholdMs`. The gateway's sweep re-invokes the
|
|
38787
|
+
* retained spawn arguments for each one. Read-only — it emits nothing and
|
|
38788
|
+
* mutates nothing, so calling it cannot consume a terminal event the sweep is
|
|
38789
|
+
* about to report. */
|
|
38790
|
+
heldSpawns(now, thresholdMs) {
|
|
38791
|
+
const out = [];
|
|
38792
|
+
for (const [key2, s] of this.keys) {
|
|
38793
|
+
if (s.subscriber) continue;
|
|
38794
|
+
const queued = s.inFlight.filter((e) => e.state === "queued");
|
|
38795
|
+
if (queued.length === 0) continue;
|
|
38796
|
+
const oldestAgeMs = Math.max(...queued.map((e) => now - e.enqueuedAt));
|
|
38797
|
+
if (oldestAgeMs <= thresholdMs) continue;
|
|
38798
|
+
out.push({ key: key2, oldestAgeMs, count: queued.length });
|
|
38799
|
+
}
|
|
38800
|
+
return out;
|
|
38801
|
+
}
|
|
38496
38802
|
/** Whether a key currently has a live channel server attached. The gateway
|
|
38497
38803
|
* uses this to decide whether an inbound needs a cold-start spawn/resume. */
|
|
38498
38804
|
hasSubscriber(key2) {
|
|
@@ -38666,6 +38972,7 @@ init_loop_registry();
|
|
|
38666
38972
|
var QUEUED_THRESHOLD_MS3 = 3e4;
|
|
38667
38973
|
var ACK_THRESHOLD_MS3 = 3e4;
|
|
38668
38974
|
var SWEEP_INTERVAL_MS3 = 15e3;
|
|
38975
|
+
var GIVE_UP_MS2 = 3e5;
|
|
38669
38976
|
function telegramSubjectSessionId(input) {
|
|
38670
38977
|
if (input.role === "admin") {
|
|
38671
38978
|
const adminUserId = input.adminUserId ?? void 0;
|
|
@@ -38679,16 +38986,6 @@ var TelegramGateway = class {
|
|
|
38679
38986
|
}
|
|
38680
38987
|
hub = new InboundHub3();
|
|
38681
38988
|
replies = /* @__PURE__ */ new Map();
|
|
38682
|
-
/** Task 2559 — the keys whose last inbound was `role:'admin'`. The follower
|
|
38683
|
-
* census's bound set is the doors that SHOULD have a reader, and a public
|
|
38684
|
-
* Telegram session is given none by design: the public branch of
|
|
38685
|
-
* `buildTelegramEnsureSession` returns before the attach block, because the
|
|
38686
|
-
* file follower is an admin surface. Including a public key would report it
|
|
38687
|
-
* missing on every tick for ever and stop `missing=` ever reaching zero,
|
|
38688
|
-
* which is the one signal the census exists to give. Removed on a public
|
|
38689
|
-
* inbound, because the role is read from account config on every inbound and
|
|
38690
|
-
* a bot can be flipped between the two. */
|
|
38691
|
-
adminKeys = /* @__PURE__ */ new Set();
|
|
38692
38989
|
spawning = /* @__PURE__ */ new Set();
|
|
38693
38990
|
/** Task 2428 — keys whose channel server signalled ready while their spawn was
|
|
38694
38991
|
* still in flight. The harness discards a channel notification that arrives
|
|
@@ -38699,6 +38996,14 @@ var TelegramGateway = class {
|
|
|
38699
38996
|
* `resume-bound`. Holding the signal here rather than draining leaves
|
|
38700
38997
|
* `hub.ready` false, so every message in the window queues and none is lost. */
|
|
38701
38998
|
readyWhileSpawning = /* @__PURE__ */ new Set();
|
|
38999
|
+
/** Task 2575 — the arguments of the last `ensureChannelSession` call per key,
|
|
39000
|
+
* so the sweep can re-invoke a spawn that was refused. The refusal is not
|
|
39001
|
+
* visible here — `ensureChannelSession` returns void and the refusal sites in
|
|
39002
|
+
* server/telegram-ensure-session.ts log and return — so the retry keys off
|
|
39003
|
+
* queue state, the observable consequence of the refusal. `attempts` is the
|
|
39004
|
+
* per-key retry counter that rides the log line, reset whenever a fresh
|
|
39005
|
+
* inbound takes the spawn path. */
|
|
39006
|
+
heldSpawnArgs = /* @__PURE__ */ new Map();
|
|
38702
39007
|
seq = 0;
|
|
38703
39008
|
/** Hono sub-app exposing the loopback channel routes; mount on the UI app. */
|
|
38704
39009
|
routes() {
|
|
@@ -38745,7 +39050,8 @@ var TelegramGateway = class {
|
|
|
38745
39050
|
reconcile(now) {
|
|
38746
39051
|
const events = this.hub.sweep(now, {
|
|
38747
39052
|
queuedThresholdMs: QUEUED_THRESHOLD_MS3,
|
|
38748
|
-
ackThresholdMs: ACK_THRESHOLD_MS3
|
|
39053
|
+
ackThresholdMs: ACK_THRESHOLD_MS3,
|
|
39054
|
+
giveUpMs: GIVE_UP_MS2
|
|
38749
39055
|
});
|
|
38750
39056
|
for (const e of events) {
|
|
38751
39057
|
console.error(
|
|
@@ -38754,6 +39060,49 @@ var TelegramGateway = class {
|
|
|
38754
39060
|
}
|
|
38755
39061
|
return events;
|
|
38756
39062
|
}
|
|
39063
|
+
/** Task 2575 — re-invoke the retained spawn for every key holding queued work
|
|
39064
|
+
* with no channel server attached. Driven by startSweeper() beside reconcile();
|
|
39065
|
+
* called directly with an injected clock in tests. Returns the number of
|
|
39066
|
+
* ensureChannelSession calls made.
|
|
39067
|
+
*
|
|
39068
|
+
* This door kept its held message across the 2026-08-08 outage but still
|
|
39069
|
+
* waited 4m41s for it, because the drain fires only when a later inbound
|
|
39070
|
+
* spawns successfully. This is the trigger that removes that wait. */
|
|
39071
|
+
async retryHeldSpawns(now) {
|
|
39072
|
+
const held = this.hub.heldSpawns(now, QUEUED_THRESHOLD_MS3);
|
|
39073
|
+
let calls = 0;
|
|
39074
|
+
for (const h of held) {
|
|
39075
|
+
if (this.spawning.has(h.key)) continue;
|
|
39076
|
+
const entry2 = this.heldSpawnArgs.get(h.key);
|
|
39077
|
+
if (!entry2) {
|
|
39078
|
+
console.error(
|
|
39079
|
+
`[telegram-native] op=spawn-retry-skipped key=${h.key} ageMs=${h.oldestAgeMs} queued=${h.count} reason=no-args`
|
|
39080
|
+
);
|
|
39081
|
+
continue;
|
|
39082
|
+
}
|
|
39083
|
+
entry2.attempts++;
|
|
39084
|
+
this.spawning.add(h.key);
|
|
39085
|
+
calls++;
|
|
39086
|
+
try {
|
|
39087
|
+
await this.deps.ensureChannelSession(entry2.args);
|
|
39088
|
+
console.error(
|
|
39089
|
+
`[telegram-native] op=spawn-retry key=${h.key} attempt=${entry2.attempts} ageMs=${h.oldestAgeMs} queued=${h.count} ok=true`
|
|
39090
|
+
);
|
|
39091
|
+
} catch (err) {
|
|
39092
|
+
console.error(
|
|
39093
|
+
`[telegram-native] op=spawn-retry key=${h.key} attempt=${entry2.attempts} ageMs=${h.oldestAgeMs} queued=${h.count} ok=false reason=${err instanceof Error ? err.message : String(err)}`
|
|
39094
|
+
);
|
|
39095
|
+
} finally {
|
|
39096
|
+
this.spawning.delete(h.key);
|
|
39097
|
+
if (this.readyWhileSpawning.delete(h.key)) this.drain(h.key);
|
|
39098
|
+
}
|
|
39099
|
+
}
|
|
39100
|
+
const heldKeys = new Set(held.map((h) => h.key));
|
|
39101
|
+
for (const k of [...this.heldSpawnArgs.keys()]) {
|
|
39102
|
+
if (!heldKeys.has(k) && !this.spawning.has(k)) this.heldSpawnArgs.delete(k);
|
|
39103
|
+
}
|
|
39104
|
+
return calls;
|
|
39105
|
+
}
|
|
38757
39106
|
/** Arm the periodic reconciliation sweep in the long-lived UI process.
|
|
38758
39107
|
*
|
|
38759
39108
|
* Task 1814 — armed through the registry, so it appears on /activity. This
|
|
@@ -38771,9 +39120,14 @@ var TelegramGateway = class {
|
|
|
38771
39120
|
return registerLoop({
|
|
38772
39121
|
name: "telegram-native-sweep",
|
|
38773
39122
|
intervalMs,
|
|
38774
|
-
run: () => {
|
|
38775
|
-
const
|
|
38776
|
-
|
|
39123
|
+
run: async () => {
|
|
39124
|
+
const now = Date.now();
|
|
39125
|
+
const events = this.reconcile(now);
|
|
39126
|
+
const retried = await this.retryHeldSpawns(now);
|
|
39127
|
+
const parts = [];
|
|
39128
|
+
if (events.length > 0) parts.push(`${events.length} terminal events`);
|
|
39129
|
+
if (retried > 0) parts.push(`${retried} spawn retries`);
|
|
39130
|
+
return parts.length > 0 ? parts.join(", ") : void 0;
|
|
38777
39131
|
}
|
|
38778
39132
|
});
|
|
38779
39133
|
}
|
|
@@ -38800,8 +39154,6 @@ var TelegramGateway = class {
|
|
|
38800
39154
|
const willDeliverNow = this.hub.isReady(key2);
|
|
38801
39155
|
const source = input.source ?? "user";
|
|
38802
39156
|
this.replies.set(key2, input.reply);
|
|
38803
|
-
if (input.role === "admin") this.adminKeys.add(key2);
|
|
38804
|
-
else this.adminKeys.delete(key2);
|
|
38805
39157
|
let standingRules;
|
|
38806
39158
|
if (input.role === "admin" && this.deps.fetchStandingRules) {
|
|
38807
39159
|
const res = await this.deps.fetchStandingRules(input.accountId);
|
|
@@ -38847,21 +39199,23 @@ var TelegramGateway = class {
|
|
|
38847
39199
|
this.spawning.add(key2);
|
|
38848
39200
|
console.error(`[telegram-native] op=spawn-trigger key=${key2} botId=${input.botId} role=${input.role}`);
|
|
38849
39201
|
noteSpawnAttempt2(input.botId);
|
|
39202
|
+
const spawnArgs = {
|
|
39203
|
+
accountId: input.accountId,
|
|
39204
|
+
accountDir: input.accountDir,
|
|
39205
|
+
botId: input.botId,
|
|
39206
|
+
botToken: input.botToken,
|
|
39207
|
+
agentSlug: input.agentSlug,
|
|
39208
|
+
channelKey: key2,
|
|
39209
|
+
senderId: input.senderId,
|
|
39210
|
+
role: input.role,
|
|
39211
|
+
personId: input.personId ?? null,
|
|
39212
|
+
chatId: input.chatId,
|
|
39213
|
+
gatewayUrl: this.deps.gatewayUrl,
|
|
39214
|
+
serverPath: this.deps.serverPath
|
|
39215
|
+
};
|
|
39216
|
+
this.heldSpawnArgs.set(key2, { args: spawnArgs, attempts: 0 });
|
|
38850
39217
|
try {
|
|
38851
|
-
await this.deps.ensureChannelSession(
|
|
38852
|
-
accountId: input.accountId,
|
|
38853
|
-
accountDir: input.accountDir,
|
|
38854
|
-
botId: input.botId,
|
|
38855
|
-
botToken: input.botToken,
|
|
38856
|
-
agentSlug: input.agentSlug,
|
|
38857
|
-
channelKey: key2,
|
|
38858
|
-
senderId: input.senderId,
|
|
38859
|
-
role: input.role,
|
|
38860
|
-
personId: input.personId ?? null,
|
|
38861
|
-
chatId: input.chatId,
|
|
38862
|
-
gatewayUrl: this.deps.gatewayUrl,
|
|
38863
|
-
serverPath: this.deps.serverPath
|
|
38864
|
-
});
|
|
39218
|
+
await this.deps.ensureChannelSession(spawnArgs);
|
|
38865
39219
|
} finally {
|
|
38866
39220
|
this.spawning.delete(key2);
|
|
38867
39221
|
if (this.readyWhileSpawning.delete(key2)) this.drain(key2);
|
|
@@ -38876,17 +39230,21 @@ var TelegramGateway = class {
|
|
|
38876
39230
|
liveReplyTarget(key2) {
|
|
38877
39231
|
return this.replies.get(key2) ?? null;
|
|
38878
39232
|
}
|
|
38879
|
-
/** Task 2559 — every
|
|
38880
|
-
*
|
|
38881
|
-
*
|
|
38882
|
-
*
|
|
38883
|
-
*
|
|
38884
|
-
*
|
|
38885
|
-
* offset, which would yield a door id nothing keys on.
|
|
39233
|
+
/** Task 2559 — every door this gateway currently holds a live delivery closure
|
|
39234
|
+
* for, as the `<botId>:<senderId>` channel key the follower registry keys on.
|
|
39235
|
+
* The twin of `WaGateway.boundTargets()`: the census reconciles against
|
|
39236
|
+
* exactly this set, so "should have a reader" and "can be sent to" are one
|
|
39237
|
+
* fact and cannot drift. A key `parseTelegramChannelKey` rejects is the
|
|
39238
|
+
* pre-2368 bare-sender shape, skipped rather than halved at an arbitrary
|
|
39239
|
+
* offset, which would yield a door id nothing keys on.
|
|
39240
|
+
*
|
|
39241
|
+
* Task 2541 — every role, not admin only. 2559 filtered public keys out of
|
|
39242
|
+
* this set because a public session was given no reader; it has one now, so
|
|
39243
|
+
* the filter would hide the door instead of the absence. That is what it did:
|
|
39244
|
+
* `bound=1 attached=1 missing=0` printed on every tick while a public
|
|
39245
|
+
* visitor's answer reached no chat at all. */
|
|
38886
39246
|
boundTargets() {
|
|
38887
|
-
return [...this.replies.keys()].filter(
|
|
38888
|
-
(key2) => this.adminKeys.has(key2) && parseTelegramChannelKey(key2) !== null
|
|
38889
|
-
);
|
|
39247
|
+
return [...this.replies.keys()].filter((key2) => parseTelegramChannelKey(key2) !== null);
|
|
38890
39248
|
}
|
|
38891
39249
|
async sendOutbound(key2, text) {
|
|
38892
39250
|
const reply = this.replies.get(key2);
|
|
@@ -38928,7 +39286,7 @@ function buildTelegramSpawnRequest(input) {
|
|
|
38928
39286
|
}
|
|
38929
39287
|
|
|
38930
39288
|
// server/telegram-ensure-session.ts
|
|
38931
|
-
var
|
|
39289
|
+
var TAG64 = "[telegram-inbound]";
|
|
38932
39290
|
function buildTelegramEnsureSession(deps) {
|
|
38933
39291
|
return async (input) => {
|
|
38934
39292
|
const req = buildTelegramSpawnRequest({
|
|
@@ -38942,6 +39300,18 @@ function buildTelegramEnsureSession(deps) {
|
|
|
38942
39300
|
// Task 1789 — admin rows only; see the WhatsApp twin.
|
|
38943
39301
|
overrideSessionId: input.role === "admin" ? deps.resolveSessionOverride(input.accountId, input.channelKey) : null
|
|
38944
39302
|
});
|
|
39303
|
+
const notifySend = async (text) => {
|
|
39304
|
+
const sent = await deps.sendText(input.botToken, Number(input.chatId), text);
|
|
39305
|
+
if (!sent.ok) throw new Error(sent.error ?? "send failed");
|
|
39306
|
+
};
|
|
39307
|
+
const notifyRefused = (result2) => sendRefusalNotice({
|
|
39308
|
+
channel: "telegram",
|
|
39309
|
+
doorId: req.channelKey,
|
|
39310
|
+
sender: req.channelKey,
|
|
39311
|
+
reason: result2.error,
|
|
39312
|
+
status: result2.status,
|
|
39313
|
+
send: notifySend
|
|
39314
|
+
});
|
|
38945
39315
|
if (input.role === "public") {
|
|
38946
39316
|
const result2 = await deps.managerRcSpawn({
|
|
38947
39317
|
senderId: req.channelKey,
|
|
@@ -38959,15 +39329,24 @@ function buildTelegramEnsureSession(deps) {
|
|
|
38959
39329
|
`[telegram-native] op=spawn-failed botId=${input.botId} key=${req.channelKey} role=public agent=${input.agentSlug} error=${result2.error} status=${result2.status}`
|
|
38960
39330
|
);
|
|
38961
39331
|
noteSpawnFailed2(input.botId);
|
|
39332
|
+
await notifyRefused(result2);
|
|
38962
39333
|
return;
|
|
38963
39334
|
}
|
|
38964
39335
|
console.error(
|
|
38965
|
-
`${
|
|
39336
|
+
`${TAG64} op=telegram-spawn-door botId=${input.botId} role=public door=rc agent=${input.agentSlug}`
|
|
38966
39337
|
);
|
|
38967
39338
|
console.error(
|
|
38968
|
-
`${
|
|
39339
|
+
`${TAG64} op=spawn botId=${input.botId} sessionId=${result2.sessionId} targetAccountId=${input.accountId} role=public`
|
|
38969
39340
|
);
|
|
38970
39341
|
noteSpawnOk2(input.botId);
|
|
39342
|
+
clearRefusalNotice(req.channelKey);
|
|
39343
|
+
deps.followers.attach({
|
|
39344
|
+
sessionId: result2.sessionId,
|
|
39345
|
+
senderId: req.channelKey,
|
|
39346
|
+
accountId: input.accountId,
|
|
39347
|
+
chatId: input.chatId,
|
|
39348
|
+
botToken: input.botToken
|
|
39349
|
+
});
|
|
38971
39350
|
return;
|
|
38972
39351
|
}
|
|
38973
39352
|
const result = await deps.managerRcSpawn(req);
|
|
@@ -38976,12 +39355,14 @@ function buildTelegramEnsureSession(deps) {
|
|
|
38976
39355
|
`[telegram-native] op=spawn-failed botId=${input.botId} key=${req.channelKey} role=admin error=${result.error} status=${result.status}`
|
|
38977
39356
|
);
|
|
38978
39357
|
noteSpawnFailed2(input.botId);
|
|
39358
|
+
await notifyRefused(result);
|
|
38979
39359
|
return;
|
|
38980
39360
|
}
|
|
38981
39361
|
console.error(
|
|
38982
|
-
`${
|
|
39362
|
+
`${TAG64} op=spawn botId=${input.botId} sessionId=${req.sessionId} targetAccountId=${input.accountId} role=admin`
|
|
38983
39363
|
);
|
|
38984
39364
|
noteSpawnOk2(input.botId);
|
|
39365
|
+
clearRefusalNotice(req.channelKey);
|
|
38985
39366
|
deps.followers.attach({
|
|
38986
39367
|
sessionId: req.sessionId,
|
|
38987
39368
|
senderId: req.channelKey,
|
|
@@ -38997,7 +39378,7 @@ init_attachments();
|
|
|
38997
39378
|
import { realpathSync as realpathSync10 } from "fs";
|
|
38998
39379
|
import { readFile as readFile14, stat as stat10 } from "fs/promises";
|
|
38999
39380
|
import { resolve as resolve48, basename as basename16 } from "path";
|
|
39000
|
-
var
|
|
39381
|
+
var TAG65 = "[telegram:outbound]";
|
|
39001
39382
|
var TELEGRAM_DOCUMENT_MAX_BYTES = 50 * 1024 * 1024;
|
|
39002
39383
|
async function sendTelegramDocument(input) {
|
|
39003
39384
|
const { botToken, chatId, filePath, caption, maxyAccountId, platformRoot: platformRoot5 } = input;
|
|
@@ -39014,10 +39395,10 @@ async function sendTelegramDocument(input) {
|
|
|
39014
39395
|
} catch (err) {
|
|
39015
39396
|
const code = err.code;
|
|
39016
39397
|
if (code === "ENOENT") {
|
|
39017
|
-
console.error(`${
|
|
39398
|
+
console.error(`${TAG65} document ENOENT scope=account-dir accountDir=${accountDir} maxyAccountId=${maxyAccountId}`);
|
|
39018
39399
|
return { ok: false, status: 500, error: `Account directory not resolvable for ${maxyAccountId}` };
|
|
39019
39400
|
}
|
|
39020
|
-
console.error(`${
|
|
39401
|
+
console.error(`${TAG65} document path error scope=account-dir maxyAccountId=${maxyAccountId}: ${String(err)}`);
|
|
39021
39402
|
return { ok: false, status: 500, error: String(err) };
|
|
39022
39403
|
}
|
|
39023
39404
|
let resolvedPath;
|
|
@@ -39026,14 +39407,14 @@ async function sendTelegramDocument(input) {
|
|
|
39026
39407
|
} catch (err) {
|
|
39027
39408
|
const code = err.code;
|
|
39028
39409
|
if (code === "ENOENT") {
|
|
39029
|
-
console.error(`${
|
|
39410
|
+
console.error(`${TAG65} document ENOENT scope=file path=${filePath} maxyAccountId=${maxyAccountId}`);
|
|
39030
39411
|
return { ok: false, status: 404, error: `File not found: ${filePath}` };
|
|
39031
39412
|
}
|
|
39032
|
-
console.error(`${
|
|
39413
|
+
console.error(`${TAG65} document path error scope=file maxyAccountId=${maxyAccountId}: ${String(err)}`);
|
|
39033
39414
|
return { ok: false, status: 500, error: String(err) };
|
|
39034
39415
|
}
|
|
39035
39416
|
if (!resolvedPath.startsWith(accountResolved + "/")) {
|
|
39036
|
-
console.error(`${
|
|
39417
|
+
console.error(`${TAG65} document REJECTED reason=outside_account_directory maxyAccountId=${maxyAccountId}`);
|
|
39037
39418
|
return { ok: false, status: 403, error: "Access denied: file is outside the account directory" };
|
|
39038
39419
|
}
|
|
39039
39420
|
const fileStat = await stat10(resolvedPath);
|
|
@@ -39066,24 +39447,24 @@ async function sendTelegramDocument(input) {
|
|
|
39066
39447
|
error = e instanceof Error ? e.message : String(e);
|
|
39067
39448
|
}
|
|
39068
39449
|
console.error(
|
|
39069
|
-
`${
|
|
39450
|
+
`${TAG65} sent document to=${chatId} file=${filename} bytes=${fileStat.size} ok=${ok2}` + (messageId !== void 0 ? ` id=${messageId}` : "")
|
|
39070
39451
|
);
|
|
39071
39452
|
return ok2 ? { ok: true, messageId } : { ok: false, status: 500, error };
|
|
39072
39453
|
}
|
|
39073
39454
|
|
|
39074
39455
|
// app/lib/telegram/outbound/file-delivery.ts
|
|
39075
|
-
var
|
|
39456
|
+
var TAG66 = "[telegram:outbound]";
|
|
39076
39457
|
function platformRoot4() {
|
|
39077
39458
|
return process.env.MAXY_PLATFORM_ROOT || "";
|
|
39078
39459
|
}
|
|
39079
39460
|
function makeTelegramSendFile(entry2, botToken) {
|
|
39080
39461
|
return async (filePath, caption) => {
|
|
39081
39462
|
if (!botToken) {
|
|
39082
|
-
console.error(`${
|
|
39463
|
+
console.error(`${TAG66} file-delivery reject reason=no-bot-token sender=${entry2.senderId} role=${entry2.role}`);
|
|
39083
39464
|
return { ok: false, error: "no-bot-token" };
|
|
39084
39465
|
}
|
|
39085
39466
|
if (entry2.replyTarget == null) {
|
|
39086
|
-
console.error(`${
|
|
39467
|
+
console.error(`${TAG66} file-delivery reject reason=no-reply-target sender=${entry2.senderId} role=${entry2.role}`);
|
|
39087
39468
|
return { ok: false, error: "no-reply-target" };
|
|
39088
39469
|
}
|
|
39089
39470
|
const result = await sendTelegramDocument({
|
|
@@ -39100,7 +39481,7 @@ function makeTelegramSendFile(entry2, botToken) {
|
|
|
39100
39481
|
function makeTelegramFileDelivery(entry2, botToken) {
|
|
39101
39482
|
return makeFileDelivery({
|
|
39102
39483
|
entry: entry2,
|
|
39103
|
-
tag:
|
|
39484
|
+
tag: TAG66,
|
|
39104
39485
|
channel: "telegram",
|
|
39105
39486
|
sendFile: makeTelegramSendFile(entry2, botToken)
|
|
39106
39487
|
});
|
|
@@ -39190,7 +39571,7 @@ init_webchat();
|
|
|
39190
39571
|
// app/lib/channel-pty-bridge/public-session-end-review.ts
|
|
39191
39572
|
init_src3();
|
|
39192
39573
|
import { randomUUID as randomUUID14 } from "crypto";
|
|
39193
|
-
var
|
|
39574
|
+
var TAG67 = "[public-session-review]";
|
|
39194
39575
|
var CONSUMED_CAP = 500;
|
|
39195
39576
|
var consumed = /* @__PURE__ */ new Set();
|
|
39196
39577
|
function consumeOnce(sessionId) {
|
|
@@ -39217,7 +39598,7 @@ async function fetchJsonl(sessionId) {
|
|
|
39217
39598
|
return await res.text();
|
|
39218
39599
|
} catch (err) {
|
|
39219
39600
|
console.error(
|
|
39220
|
-
`${
|
|
39601
|
+
`${TAG67} jsonl-fetch-failed sessionId=${sessionId} err="${err instanceof Error ? err.message : String(err)}"`
|
|
39221
39602
|
);
|
|
39222
39603
|
return null;
|
|
39223
39604
|
}
|
|
@@ -39241,7 +39622,7 @@ async function fetchPriorWrites(sliceToken, accountId) {
|
|
|
39241
39622
|
const res = await fetch(url);
|
|
39242
39623
|
if (!res.ok) {
|
|
39243
39624
|
console.error(
|
|
39244
|
-
`${
|
|
39625
|
+
`${TAG67} prior-writes-fetch-failed sliceToken=${sliceToken.slice(0, 8)} status=${res.status}`
|
|
39245
39626
|
);
|
|
39246
39627
|
return [];
|
|
39247
39628
|
}
|
|
@@ -39249,7 +39630,7 @@ async function fetchPriorWrites(sliceToken, accountId) {
|
|
|
39249
39630
|
return Array.isArray(body.writes) ? body.writes : [];
|
|
39250
39631
|
} catch (err) {
|
|
39251
39632
|
console.error(
|
|
39252
|
-
`${
|
|
39633
|
+
`${TAG67} prior-writes-fetch-threw sliceToken=${sliceToken.slice(0, 8)} err="${err instanceof Error ? err.message : String(err)}"`
|
|
39253
39634
|
);
|
|
39254
39635
|
return [];
|
|
39255
39636
|
}
|
|
@@ -39278,7 +39659,7 @@ function composeInitialMessage(input) {
|
|
|
39278
39659
|
}
|
|
39279
39660
|
async function dispatchReviewer(input, initialMessage) {
|
|
39280
39661
|
const sessionId = randomUUID14();
|
|
39281
|
-
console.log(`${
|
|
39662
|
+
console.log(`${TAG67} route target=rc-spawn sessionId=${sessionId.slice(0, 8)} sourceSession=${input.sessionId.slice(0, 8)}`);
|
|
39282
39663
|
const spawned = await managerRcSpawn({
|
|
39283
39664
|
sessionId,
|
|
39284
39665
|
initialMessage,
|
|
@@ -39302,21 +39683,21 @@ async function firePublicSessionEndReview(input) {
|
|
|
39302
39683
|
const dispatchedAt = Date.now();
|
|
39303
39684
|
if (consumed.has(input.sessionId)) {
|
|
39304
39685
|
console.log(
|
|
39305
|
-
`${
|
|
39686
|
+
`${TAG67} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-already-reviewed`
|
|
39306
39687
|
);
|
|
39307
39688
|
return;
|
|
39308
39689
|
}
|
|
39309
39690
|
const jsonl = await fetchJsonl(input.sessionId);
|
|
39310
39691
|
if (!jsonl) {
|
|
39311
39692
|
console.log(
|
|
39312
|
-
`${
|
|
39693
|
+
`${TAG67} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-jsonl-missing`
|
|
39313
39694
|
);
|
|
39314
39695
|
return;
|
|
39315
39696
|
}
|
|
39316
39697
|
const operatorTurns = countOperatorTurns(jsonl);
|
|
39317
39698
|
if (operatorTurns === 0) {
|
|
39318
39699
|
console.log(
|
|
39319
|
-
`${
|
|
39700
|
+
`${TAG67} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-no-turns`
|
|
39320
39701
|
);
|
|
39321
39702
|
return;
|
|
39322
39703
|
}
|
|
@@ -39330,19 +39711,19 @@ async function firePublicSessionEndReview(input) {
|
|
|
39330
39711
|
});
|
|
39331
39712
|
if (!consumeOnce(input.sessionId)) {
|
|
39332
39713
|
console.log(
|
|
39333
|
-
`${
|
|
39714
|
+
`${TAG67} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-already-reviewed`
|
|
39334
39715
|
);
|
|
39335
39716
|
return;
|
|
39336
39717
|
}
|
|
39337
39718
|
const dispatched = await dispatchReviewer(input, initialMessage);
|
|
39338
39719
|
if (!dispatched.ok) {
|
|
39339
39720
|
console.error(
|
|
39340
|
-
`${
|
|
39721
|
+
`${TAG67} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-spawn-failed reason=${dispatched.reason}`
|
|
39341
39722
|
);
|
|
39342
39723
|
return;
|
|
39343
39724
|
}
|
|
39344
39725
|
console.log(
|
|
39345
|
-
`${
|
|
39726
|
+
`${TAG67} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=dispatched managerSessionId=${dispatched.managerSessionId} operatorTurns=${operatorTurns} priorWrites=${priorWrites.length} ms=${Date.now() - dispatchedAt}`
|
|
39346
39727
|
);
|
|
39347
39728
|
}
|
|
39348
39729
|
|
|
@@ -39494,7 +39875,7 @@ init_account();
|
|
|
39494
39875
|
|
|
39495
39876
|
// server/telegram-audit.ts
|
|
39496
39877
|
import { statSync as statSync28 } from "fs";
|
|
39497
|
-
import { join as
|
|
39878
|
+
import { join as join63 } from "path";
|
|
39498
39879
|
init_channel_key();
|
|
39499
39880
|
init_sidebar_sessions();
|
|
39500
39881
|
function buildTelegramAuditLines(input) {
|
|
@@ -39560,7 +39941,7 @@ function countTelegramSessionsForAccount(accountId) {
|
|
|
39560
39941
|
function* walkTelegramSidecars() {
|
|
39561
39942
|
const cfg = claudeConfigDir();
|
|
39562
39943
|
if (!cfg) return;
|
|
39563
|
-
const projectsRoot =
|
|
39944
|
+
const projectsRoot = join63(cfg, "projects");
|
|
39564
39945
|
for (const { path } of enumerateSessionSidecars(projectsRoot)) {
|
|
39565
39946
|
const meta = readSidecarMeta(path);
|
|
39566
39947
|
if (meta.channel !== "telegram" || !meta.accountId) continue;
|
|
@@ -39595,15 +39976,15 @@ function answeringOriginTarget(send, key2, noteAnswered3) {
|
|
|
39595
39976
|
}
|
|
39596
39977
|
|
|
39597
39978
|
// server/telegram-migrate.ts
|
|
39598
|
-
import { readFileSync as
|
|
39599
|
-
import { dirname as dirname18, join as
|
|
39979
|
+
import { readFileSync as readFileSync54, writeFileSync as writeFileSync19, existsSync as existsSync53, renameSync as renameSync11, mkdirSync as mkdirSync11 } from "fs";
|
|
39980
|
+
import { dirname as dirname18, join as join64 } from "path";
|
|
39600
39981
|
function migrateTelegramTwoSlot(input) {
|
|
39601
39982
|
const out = [];
|
|
39602
39983
|
for (const account of input.accounts) {
|
|
39603
|
-
const configPath3 =
|
|
39984
|
+
const configPath3 = join64(account.accountDir, "account.json");
|
|
39604
39985
|
let config;
|
|
39605
39986
|
try {
|
|
39606
|
-
config = JSON.parse(
|
|
39987
|
+
config = JSON.parse(readFileSync54(configPath3, "utf-8"));
|
|
39607
39988
|
} catch {
|
|
39608
39989
|
console.error(`[telegram-audit] op=migrate-unreadable accountId=${account.accountId}`);
|
|
39609
39990
|
continue;
|
|
@@ -39705,14 +40086,14 @@ function backfillMisfiledRecords(params) {
|
|
|
39705
40086
|
init_message_store();
|
|
39706
40087
|
|
|
39707
40088
|
// server/lib/booking-site-accounts.ts
|
|
39708
|
-
import { existsSync as existsSync54, readFileSync as
|
|
40089
|
+
import { existsSync as existsSync54, readFileSync as readFileSync55 } from "fs";
|
|
39709
40090
|
import { resolve as resolve49 } from "path";
|
|
39710
40091
|
function listBookingSiteAccounts(accounts) {
|
|
39711
40092
|
return accounts.filter((a) => {
|
|
39712
40093
|
const availPath = resolve49(a.accountDir, "calendar-availability.json");
|
|
39713
40094
|
if (!existsSync54(availPath)) return false;
|
|
39714
40095
|
try {
|
|
39715
|
-
const cfg = JSON.parse(
|
|
40096
|
+
const cfg = JSON.parse(readFileSync55(availPath, "utf-8"));
|
|
39716
40097
|
return typeof cfg.bookingDbName === "string" && cfg.bookingDbName.length > 0;
|
|
39717
40098
|
} catch {
|
|
39718
40099
|
return false;
|
|
@@ -39757,7 +40138,7 @@ init_paths();
|
|
|
39757
40138
|
|
|
39758
40139
|
// ../lib/entitlement/src/index.ts
|
|
39759
40140
|
import { createPublicKey, createHash as createHash7, verify as cryptoVerify } from "crypto";
|
|
39760
|
-
import { existsSync as existsSync55, readFileSync as
|
|
40141
|
+
import { existsSync as existsSync55, readFileSync as readFileSync56, statSync as statSync29 } from "fs";
|
|
39761
40142
|
import { resolve as resolve50 } from "path";
|
|
39762
40143
|
|
|
39763
40144
|
// ../lib/entitlement/src/canonicalize.ts
|
|
@@ -39821,7 +40202,7 @@ function resolveEntitlement(brand, account) {
|
|
|
39821
40202
|
function verifyAndResolve(brand, entitlementPath, account) {
|
|
39822
40203
|
let pubkeyPem;
|
|
39823
40204
|
try {
|
|
39824
|
-
pubkeyPem =
|
|
40205
|
+
pubkeyPem = readFileSync56(pubkeyPath(brand), "utf-8");
|
|
39825
40206
|
} catch (err) {
|
|
39826
40207
|
return logResolved(anonymousFallback("pubkey-missing"), {
|
|
39827
40208
|
reason: "pubkey-missing"
|
|
@@ -39835,7 +40216,7 @@ function verifyAndResolve(brand, entitlementPath, account) {
|
|
|
39835
40216
|
}
|
|
39836
40217
|
let envelope;
|
|
39837
40218
|
try {
|
|
39838
|
-
envelope = JSON.parse(
|
|
40219
|
+
envelope = JSON.parse(readFileSync56(entitlementPath, "utf-8"));
|
|
39839
40220
|
} catch {
|
|
39840
40221
|
return logResolved(anonymousFallback("malformed"), { reason: "malformed" });
|
|
39841
40222
|
}
|
|
@@ -39997,14 +40378,14 @@ function clientFrom(c) {
|
|
|
39997
40378
|
}
|
|
39998
40379
|
installMediaDownloadGuard();
|
|
39999
40380
|
var PLATFORM_ROOT6 = process.env.MAXY_PLATFORM_ROOT || "";
|
|
40000
|
-
var BRAND_JSON_PATH = PLATFORM_ROOT6 ?
|
|
40381
|
+
var BRAND_JSON_PATH = PLATFORM_ROOT6 ? join65(PLATFORM_ROOT6, "config", "brand.json") : "";
|
|
40001
40382
|
var BRAND = { productName: "Maxy", hostname: "maxy", configDir: ".maxy", marketingUrl: "https://getmaxy.com" };
|
|
40002
40383
|
if (BRAND_JSON_PATH && !existsSync56(BRAND_JSON_PATH)) {
|
|
40003
40384
|
console.error(`[brand] WARNING: brand.json not found at ${BRAND_JSON_PATH} \u2014 using Maxy defaults`);
|
|
40004
40385
|
}
|
|
40005
40386
|
if (BRAND_JSON_PATH && existsSync56(BRAND_JSON_PATH)) {
|
|
40006
40387
|
try {
|
|
40007
|
-
const parsed = JSON.parse(
|
|
40388
|
+
const parsed = JSON.parse(readFileSync57(BRAND_JSON_PATH, "utf-8"));
|
|
40008
40389
|
BRAND = { ...BRAND, ...parsed };
|
|
40009
40390
|
} catch (err) {
|
|
40010
40391
|
console.error(`[brand] Failed to parse brand.json: ${err.message}`);
|
|
@@ -40036,11 +40417,11 @@ var brandLoginOpts = {
|
|
|
40036
40417
|
appleTouchIconPath: brandAppIcon512Path,
|
|
40037
40418
|
themeColor: BRAND.defaultColors?.primary
|
|
40038
40419
|
};
|
|
40039
|
-
var ALIAS_DOMAINS_PATH =
|
|
40420
|
+
var ALIAS_DOMAINS_PATH = join65(homedir6(), BRAND.configDir, "alias-domains.json");
|
|
40040
40421
|
function loadAliasDomains() {
|
|
40041
40422
|
try {
|
|
40042
40423
|
if (!existsSync56(ALIAS_DOMAINS_PATH)) return null;
|
|
40043
|
-
const parsed = JSON.parse(
|
|
40424
|
+
const parsed = JSON.parse(readFileSync57(ALIAS_DOMAINS_PATH, "utf-8"));
|
|
40044
40425
|
if (!Array.isArray(parsed)) {
|
|
40045
40426
|
console.error("[alias-domains] malformed alias-domains.json \u2014 expected array");
|
|
40046
40427
|
return null;
|
|
@@ -40181,7 +40562,7 @@ var waGateway = new WaGateway({
|
|
|
40181
40562
|
// resolver and `fanTargetsFor` is a constructor dep, not a callable method.
|
|
40182
40563
|
fanTargetsFor: whatsappFanTargets,
|
|
40183
40564
|
gatewayUrl: `http://127.0.0.1:${process.env.MAXY_UI_INTERNAL_PORT ?? ""}`,
|
|
40184
|
-
serverPath: process.env.MAXY_WA_CHANNEL_SERVER_PATH ?? resolve51(process.env.MAXY_PLATFORM_ROOT ??
|
|
40565
|
+
serverPath: process.env.MAXY_WA_CHANNEL_SERVER_PATH ?? resolve51(process.env.MAXY_PLATFORM_ROOT ?? join65(__dirname, ".."), "services/whatsapp-channel/dist/server.js"),
|
|
40185
40566
|
// Task 751 / 1390 — file delivery on the native channel. `maxyAccountId` (the
|
|
40186
40567
|
// path-validation scope) is the sender's effective SESSION account, resolved
|
|
40187
40568
|
// once at the inbound gate and threaded here via the gateway's per-sender doc
|
|
@@ -40196,7 +40577,7 @@ var waGateway = new WaGateway({
|
|
|
40196
40577
|
caption,
|
|
40197
40578
|
accountId,
|
|
40198
40579
|
maxyAccountId,
|
|
40199
|
-
platformRoot: resolve51(process.env.MAXY_PLATFORM_ROOT ??
|
|
40580
|
+
platformRoot: resolve51(process.env.MAXY_PLATFORM_ROOT ?? join65(__dirname, ".."))
|
|
40200
40581
|
});
|
|
40201
40582
|
return result.ok ? { ok: true, messageId: result.messageId } : { ok: false, error: result.error };
|
|
40202
40583
|
},
|
|
@@ -40286,11 +40667,20 @@ var waGateway = new WaGateway({
|
|
|
40286
40667
|
`[whatsapp:public-agent] op=spawn-failed inboundId=${inboundId} senderId=${senderId} slug=${publicAgentSlug} error=${publicResult.error}`
|
|
40287
40668
|
);
|
|
40288
40669
|
noteSpawnFailed();
|
|
40670
|
+
await sendRefusalNotice({
|
|
40671
|
+
channel: "whatsapp",
|
|
40672
|
+
doorId: whatsappDoorId({ effectiveAccountId, senderId }),
|
|
40673
|
+
sender: senderId,
|
|
40674
|
+
reason: publicResult.error,
|
|
40675
|
+
status: publicResult.status,
|
|
40676
|
+
send: waGateway.liveReplyTarget(effectiveAccountId, senderId)
|
|
40677
|
+
});
|
|
40289
40678
|
} else {
|
|
40290
40679
|
console.error(
|
|
40291
40680
|
`[whatsapp:public-agent] op=spawn-ok inboundId=${inboundId} senderId=${senderId} slug=${publicAgentSlug} ms=${Date.now() - publicSpawnStartedAt}`
|
|
40292
40681
|
);
|
|
40293
40682
|
noteSpawnOk();
|
|
40683
|
+
clearRefusalNotice(whatsappDoorId({ effectiveAccountId, senderId }));
|
|
40294
40684
|
}
|
|
40295
40685
|
return;
|
|
40296
40686
|
}
|
|
@@ -40329,8 +40719,17 @@ var waGateway = new WaGateway({
|
|
|
40329
40719
|
const result = await managerRcSpawn({ ...req, userId, party });
|
|
40330
40720
|
if ("error" in result) {
|
|
40331
40721
|
console.error(`[whatsapp-native] spawn-failed senderId=${senderId} role=${role} error=${result.error} status=${result.status}`);
|
|
40722
|
+
await sendRefusalNotice({
|
|
40723
|
+
channel: "whatsapp",
|
|
40724
|
+
doorId: whatsappDoorId({ effectiveAccountId, senderId }),
|
|
40725
|
+
sender: senderId,
|
|
40726
|
+
reason: result.error,
|
|
40727
|
+
status: result.status,
|
|
40728
|
+
send: waGateway.liveReplyTarget(effectiveAccountId, senderId)
|
|
40729
|
+
});
|
|
40332
40730
|
return;
|
|
40333
40731
|
}
|
|
40732
|
+
clearRefusalNotice(whatsappDoorId({ effectiveAccountId, senderId }));
|
|
40334
40733
|
nativeFileFollowers.attach({
|
|
40335
40734
|
sessionId: req.sessionId,
|
|
40336
40735
|
senderId,
|
|
@@ -40353,7 +40752,7 @@ function runDuplicateSenderAudit() {
|
|
|
40353
40752
|
try {
|
|
40354
40753
|
const configDir = process.env.CLAUDE_CONFIG_DIR;
|
|
40355
40754
|
if (!configDir) return;
|
|
40356
|
-
const projectsRoot =
|
|
40755
|
+
const projectsRoot = join65(configDir, "projects");
|
|
40357
40756
|
const records2 = [];
|
|
40358
40757
|
for (const { path: jsonlPath, isSubagent, archived } of enumerateJsonls(projectsRoot)) {
|
|
40359
40758
|
if (isSubagent || archived) continue;
|
|
@@ -40405,7 +40804,7 @@ var webchatGateway = new WebchatGateway({
|
|
|
40405
40804
|
// rather than hanging the POST.
|
|
40406
40805
|
publicProjectsRoot: () => {
|
|
40407
40806
|
const cfg = claudeConfigDir();
|
|
40408
|
-
return cfg ?
|
|
40807
|
+
return cfg ? join65(cfg, "projects") : "";
|
|
40409
40808
|
},
|
|
40410
40809
|
gatewayUrl: webchatGatewayUrl(),
|
|
40411
40810
|
serverPath: webchatServerPath(),
|
|
@@ -40562,6 +40961,8 @@ var telegramGateway = new TelegramGateway({
|
|
|
40562
40961
|
managerRcSpawn,
|
|
40563
40962
|
managerSpawn,
|
|
40564
40963
|
resolveSessionOverride: (accountId, channelKey) => resolveChannelOverride(accountId, "telegram", channelKey),
|
|
40964
|
+
// Task 2573 — the send the refusal notice goes out on.
|
|
40965
|
+
sendText: sendTelegramText,
|
|
40565
40966
|
followers: telegramFileFollowers
|
|
40566
40967
|
})
|
|
40567
40968
|
});
|
|
@@ -41047,7 +41448,7 @@ app80.get("/agent-assets/:slug/:filename", (c) => {
|
|
|
41047
41448
|
const ext = "." + filename.split(".").pop()?.toLowerCase();
|
|
41048
41449
|
const contentType = IMAGE_MIME[ext] || "application/octet-stream";
|
|
41049
41450
|
console.log(`[agent-assets] serve slug=${slug} file=${filename} status=200`);
|
|
41050
|
-
const body =
|
|
41451
|
+
const body = readFileSync57(filePath);
|
|
41051
41452
|
return c.body(body, 200, {
|
|
41052
41453
|
"Content-Type": contentType,
|
|
41053
41454
|
"Cache-Control": "public, max-age=3600"
|
|
@@ -41077,7 +41478,7 @@ app80.get("/generated/:filename", (c) => {
|
|
|
41077
41478
|
const ext = "." + filename.split(".").pop()?.toLowerCase();
|
|
41078
41479
|
const contentType = IMAGE_MIME[ext] || "application/octet-stream";
|
|
41079
41480
|
console.log(`[generated] serve file=${filename} status=200`);
|
|
41080
|
-
const body =
|
|
41481
|
+
const body = readFileSync57(filePath);
|
|
41081
41482
|
return c.body(body, 200, {
|
|
41082
41483
|
"Content-Type": contentType,
|
|
41083
41484
|
"Cache-Control": "public, max-age=86400"
|
|
@@ -41092,7 +41493,7 @@ var brandLogoPath = "/brand/maxy-monochrome.png";
|
|
|
41092
41493
|
var brandIconPath = "/brand/maxy-monochrome.png";
|
|
41093
41494
|
if (BRAND_JSON_PATH && existsSync56(BRAND_JSON_PATH)) {
|
|
41094
41495
|
try {
|
|
41095
|
-
const fullBrand = JSON.parse(
|
|
41496
|
+
const fullBrand = JSON.parse(readFileSync57(BRAND_JSON_PATH, "utf-8"));
|
|
41096
41497
|
if (fullBrand.assets?.logo) brandLogoPath = `/brand/${fullBrand.assets.logo}`;
|
|
41097
41498
|
brandIconPath = fullBrand.assets?.icon ? `/brand/${fullBrand.assets.icon}` : brandLogoPath;
|
|
41098
41499
|
} catch {
|
|
@@ -41123,7 +41524,7 @@ var brandAppIconsExist = [brandAppIcon192Path, brandAppIcon512Path, brandMaskabl
|
|
|
41123
41524
|
);
|
|
41124
41525
|
var SW_SOURCE = (() => {
|
|
41125
41526
|
try {
|
|
41126
|
-
return
|
|
41527
|
+
return readFileSync57(resolve51(process.cwd(), "public", "sw.js"), "utf-8");
|
|
41127
41528
|
} catch {
|
|
41128
41529
|
return null;
|
|
41129
41530
|
}
|
|
@@ -41131,9 +41532,9 @@ var SW_SOURCE = (() => {
|
|
|
41131
41532
|
function readInstalledVersion() {
|
|
41132
41533
|
try {
|
|
41133
41534
|
if (!PLATFORM_ROOT6) return "unknown";
|
|
41134
|
-
const versionFile =
|
|
41535
|
+
const versionFile = join65(PLATFORM_ROOT6, "config", `.${BRAND.hostname}-version`);
|
|
41135
41536
|
if (!existsSync56(versionFile)) return "unknown";
|
|
41136
|
-
const content =
|
|
41537
|
+
const content = readFileSync57(versionFile, "utf-8").trim();
|
|
41137
41538
|
return content || "unknown";
|
|
41138
41539
|
} catch {
|
|
41139
41540
|
return "unknown";
|
|
@@ -41144,7 +41545,7 @@ var versionScript = `<script>window.__MAXY_VERSION__=${JSON.stringify(INSTALLED_
|
|
|
41144
41545
|
function cachedHtml(file) {
|
|
41145
41546
|
let html = htmlCache.get(file);
|
|
41146
41547
|
if (!html) {
|
|
41147
|
-
html =
|
|
41548
|
+
html = readFileSync57(resolve51(process.cwd(), "public", file), "utf-8");
|
|
41148
41549
|
const productNameEsc = escapeHtml(BRAND.productName);
|
|
41149
41550
|
html = html.replace(/<title>([^<]*)<\/title>/, (_match, inner) => `<title>${escapeHtml(inner).replace(/Maxy/g, productNameEsc)}</title>`);
|
|
41150
41551
|
html = html.replace('href="/favicon.ico"', `href="${escapeHtml(brandFaviconPath)}"`);
|
|
@@ -41161,14 +41562,14 @@ ${clientErrorReporterScript}
|
|
|
41161
41562
|
return html;
|
|
41162
41563
|
}
|
|
41163
41564
|
function loadBrandingCache(agentSlug) {
|
|
41164
|
-
const configDir =
|
|
41565
|
+
const configDir = join65(homedir6(), BRAND.configDir);
|
|
41165
41566
|
try {
|
|
41166
41567
|
const verdict = resolvePublicAddressFromDisk(agentSlug);
|
|
41167
41568
|
const accountId = verdict.kind === "served" ? verdict.owner.accountId : getDefaultAccountId();
|
|
41168
41569
|
if (!accountId) return null;
|
|
41169
|
-
const cachePath =
|
|
41570
|
+
const cachePath = join65(configDir, "branding-cache", accountId, `${agentSlug}.json`);
|
|
41170
41571
|
if (!existsSync56(cachePath)) return null;
|
|
41171
|
-
return JSON.parse(
|
|
41572
|
+
return JSON.parse(readFileSync57(cachePath, "utf-8"));
|
|
41172
41573
|
} catch {
|
|
41173
41574
|
return null;
|
|
41174
41575
|
}
|
|
@@ -41263,7 +41664,7 @@ app80.use("/vnc-popout.html", logViewerFetch);
|
|
|
41263
41664
|
app80.get("/vnc-popout.html", (c) => {
|
|
41264
41665
|
let html = htmlCache.get("vnc-popout.html");
|
|
41265
41666
|
if (!html) {
|
|
41266
|
-
html =
|
|
41667
|
+
html = readFileSync57(resolve51(process.cwd(), "public", "vnc-popout.html"), "utf-8");
|
|
41267
41668
|
const name = escapeHtml(BRAND.productName);
|
|
41268
41669
|
html = html.replace("<title>Browser \u2014 Maxy</title>", `<title>${name}</title>`);
|
|
41269
41670
|
html = html.replace("</head>", ` ${brandScript}
|
|
@@ -41417,7 +41818,7 @@ var hostname = process.env.HOSTNAME ?? "127.0.0.1";
|
|
|
41417
41818
|
var httpServer = serve({ fetch: app80.fetch, port, hostname });
|
|
41418
41819
|
console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
41419
41820
|
{
|
|
41420
|
-
const reconcilePlatformRoot = process.env.MAXY_PLATFORM_ROOT ??
|
|
41821
|
+
const reconcilePlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join65(__dirname, "..");
|
|
41421
41822
|
const reconcileScript = resolve51(reconcilePlatformRoot, "plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js");
|
|
41422
41823
|
const runReconcile = (ctx) => {
|
|
41423
41824
|
if (!existsSync56(reconcileScript)) return;
|
|
@@ -41440,7 +41841,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
41440
41841
|
detached: true,
|
|
41441
41842
|
run: runReconcile
|
|
41442
41843
|
});
|
|
41443
|
-
const reconcileStatePath =
|
|
41844
|
+
const reconcileStatePath = join65(MAXY_DIR, "booking-reconcile-state.json");
|
|
41444
41845
|
registerLoop({
|
|
41445
41846
|
name: "calendar-reconcile-liveness",
|
|
41446
41847
|
intervalMs: 6e5,
|
|
@@ -41458,7 +41859,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
41458
41859
|
}
|
|
41459
41860
|
{
|
|
41460
41861
|
const DREAM_CYCLE_SWEEP_INTERVAL_MS = 36e5;
|
|
41461
|
-
const dreamPlatformRoot = process.env.MAXY_PLATFORM_ROOT ??
|
|
41862
|
+
const dreamPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join65(__dirname, "..");
|
|
41462
41863
|
const dreamSweepScript = resolve51(dreamPlatformRoot, "plugins/memory/mcp/dist/scripts/dream-cycle-sweep.js");
|
|
41463
41864
|
registerLoop({
|
|
41464
41865
|
name: "dream-cycle-sweep",
|
|
@@ -41482,7 +41883,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
41482
41883
|
});
|
|
41483
41884
|
}
|
|
41484
41885
|
{
|
|
41485
|
-
const outlookPlatformRoot = process.env.MAXY_PLATFORM_ROOT ??
|
|
41886
|
+
const outlookPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join65(__dirname, "..");
|
|
41486
41887
|
const outlookScript = resolve51(outlookPlatformRoot, "plugins/outlook/mcp/dist/scripts/complete-registration.js");
|
|
41487
41888
|
const OUTLOOK_COMPLETE_INTERVAL_MS = 3e4;
|
|
41488
41889
|
const runOutlookComplete = (ctx) => {
|
|
@@ -41508,7 +41909,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
41508
41909
|
});
|
|
41509
41910
|
}
|
|
41510
41911
|
{
|
|
41511
|
-
const auditRoot = process.env.MAXY_PLATFORM_ROOT ??
|
|
41912
|
+
const auditRoot = process.env.MAXY_PLATFORM_ROOT ?? join65(__dirname, "..");
|
|
41512
41913
|
const strandedAccountsDir = resolve51(auditRoot, "..", "data/accounts");
|
|
41513
41914
|
const STRANDED_AUDIT_INTERVAL_MS = 3e5;
|
|
41514
41915
|
const STRANDED_AGE_MS = 16 * 6e4;
|
|
@@ -41517,7 +41918,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
41517
41918
|
try {
|
|
41518
41919
|
if (!existsSync56(strandedAccountsDir)) return;
|
|
41519
41920
|
const now = Date.now();
|
|
41520
|
-
for (const name of
|
|
41921
|
+
for (const name of readdirSync33(strandedAccountsDir)) {
|
|
41521
41922
|
if (!STRANDED_UUID_RE.test(name)) continue;
|
|
41522
41923
|
const pendingPath2 = resolve51(strandedAccountsDir, name, "secrets/outlook/pending-devicecode.enc");
|
|
41523
41924
|
if (!existsSync56(pendingPath2)) continue;
|
|
@@ -41538,7 +41939,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
41538
41939
|
});
|
|
41539
41940
|
}
|
|
41540
41941
|
{
|
|
41541
|
-
const googleRoot = process.env.MAXY_PLATFORM_ROOT ??
|
|
41942
|
+
const googleRoot = process.env.MAXY_PLATFORM_ROOT ?? join65(__dirname, "..");
|
|
41542
41943
|
const googleAccountsDir = resolve51(googleRoot, "..", "data/accounts");
|
|
41543
41944
|
const GOOGLE_PENDING_AUDIT_INTERVAL_MS = 3e5;
|
|
41544
41945
|
const runGooglePendingAuditSafe = () => {
|
|
@@ -41582,7 +41983,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
41582
41983
|
});
|
|
41583
41984
|
}
|
|
41584
41985
|
{
|
|
41585
|
-
const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ??
|
|
41986
|
+
const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join65(__dirname, "..");
|
|
41586
41987
|
const STORAGE_AUDIT_INTERVAL_MS = 3e5;
|
|
41587
41988
|
const runStorageAuditSafe = () => runStorageAudit(auditPlatformRoot).catch((err) => {
|
|
41588
41989
|
console.error(`[storage-audit] error="${err.message}"`);
|
|
@@ -41618,7 +42019,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
41618
42019
|
accounts: listValidAccounts().map((a) => ({ accountId: a.accountId, accountDir: a.accountDir })),
|
|
41619
42020
|
secretPathFor: secretPath,
|
|
41620
42021
|
legacySecretPathFor: (botType, accountId) => resolve51(
|
|
41621
|
-
|
|
42022
|
+
homedir6(),
|
|
41622
42023
|
configDirName2(),
|
|
41623
42024
|
`${botType === "admin" ? ".telegram-admin-webhook-secret" : ".telegram-webhook-secret"}.${accountId}`
|
|
41624
42025
|
),
|
|
@@ -41641,7 +42042,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
41641
42042
|
const lastByBot = newestTelegramSessionByBot();
|
|
41642
42043
|
let secretFilesOnDisk = [];
|
|
41643
42044
|
try {
|
|
41644
|
-
secretFilesOnDisk =
|
|
42045
|
+
secretFilesOnDisk = readdirSync33(telegramSecretsDir());
|
|
41645
42046
|
} catch {
|
|
41646
42047
|
}
|
|
41647
42048
|
const lines = buildTelegramAuditLines({
|
|
@@ -41784,7 +42185,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
41784
42185
|
});
|
|
41785
42186
|
}
|
|
41786
42187
|
{
|
|
41787
|
-
const publishPlatformRoot = process.env.MAXY_PLATFORM_ROOT ??
|
|
42188
|
+
const publishPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join65(__dirname, "..");
|
|
41788
42189
|
const publishScript = resolve51(publishPlatformRoot, "plugins/scheduling/mcp/dist/scripts/publish-availability.js");
|
|
41789
42190
|
const PUBLISH_INTERVAL_MS = 3e5;
|
|
41790
42191
|
const bookingAccounts = () => {
|
|
@@ -41819,7 +42220,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
41819
42220
|
let hasBookingSite = false;
|
|
41820
42221
|
try {
|
|
41821
42222
|
if (existsSync56(availPath)) {
|
|
41822
|
-
const cfg = JSON.parse(
|
|
42223
|
+
const cfg = JSON.parse(readFileSync57(availPath, "utf-8"));
|
|
41823
42224
|
hasBookingSite = typeof cfg.bookingDbName === "string" && cfg.bookingDbName.length > 0;
|
|
41824
42225
|
}
|
|
41825
42226
|
} catch {
|
|
@@ -41829,7 +42230,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
|
41829
42230
|
let lastSuccessAt = null;
|
|
41830
42231
|
if (existsSync56(statePath)) {
|
|
41831
42232
|
try {
|
|
41832
|
-
const rec = JSON.parse(
|
|
42233
|
+
const rec = JSON.parse(readFileSync57(statePath, "utf-8"));
|
|
41833
42234
|
lastSuccessAt = rec.lastSuccessAt ?? null;
|
|
41834
42235
|
} catch {
|
|
41835
42236
|
console.error(`[calendar-availability] op=audit accountId=${account.accountId} snapshotAgeMs=na reason=bad-state-file`);
|
|
@@ -41864,7 +42265,7 @@ startDispatchCensus(() => getSession());
|
|
|
41864
42265
|
startDispatchFieldCensus(() => getSession());
|
|
41865
42266
|
startDispatchTgCensus(() => getSession());
|
|
41866
42267
|
{
|
|
41867
|
-
const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ??
|
|
42268
|
+
const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join65(__dirname, "..");
|
|
41868
42269
|
const auditScript = resolve51(auditPlatformRoot, "plugins/connector/mcp/dist/scripts/audit-connectors.js");
|
|
41869
42270
|
const auditLogDir = process.env.LOG_DIR ?? LOG_DIR;
|
|
41870
42271
|
const CONNECTOR_AUDIT_INTERVAL_MS = 3e5;
|
|
@@ -41995,7 +42396,7 @@ async function runAdminUserReconcileTick() {
|
|
|
41995
42396
|
console.error("[adminuser-self-heal] skip reason=no-users-file");
|
|
41996
42397
|
return;
|
|
41997
42398
|
}
|
|
41998
|
-
const usersRaw =
|
|
42399
|
+
const usersRaw = readFileSync57(USERS_FILE, "utf-8").trim();
|
|
41999
42400
|
if (!usersRaw) {
|
|
42000
42401
|
console.error("[adminuser-self-heal] skip reason=empty-users-file");
|
|
42001
42402
|
return;
|
|
@@ -42077,8 +42478,8 @@ var runIngestAuditTick = () => {
|
|
|
42077
42478
|
},
|
|
42078
42479
|
agentExists: (accountId, slug) => {
|
|
42079
42480
|
if (!house) return false;
|
|
42080
|
-
const dir = isHouseAccountKey(house.accountId, accountId) ? house.accountDir :
|
|
42081
|
-
return existsSync56(
|
|
42481
|
+
const dir = isHouseAccountKey(house.accountId, accountId) ? house.accountDir : join65(ACCOUNTS_DIR, accountId);
|
|
42482
|
+
return existsSync56(join65(dir, "agents", slug, "config.json"));
|
|
42082
42483
|
}
|
|
42083
42484
|
});
|
|
42084
42485
|
} catch (err) {
|
|
@@ -42126,9 +42527,9 @@ registerLoop({
|
|
|
42126
42527
|
runPublicAddressCollisionAudit({
|
|
42127
42528
|
houseAccountId: () => resolveAccount()?.accountId ?? null,
|
|
42128
42529
|
activeSlugs: () => listValidAccounts().map((a) => {
|
|
42129
|
-
const agentsRoot =
|
|
42530
|
+
const agentsRoot = join65(a.accountDir, "agents");
|
|
42130
42531
|
if (!existsSync56(agentsRoot)) return { accountId: a.accountId, slugs: [] };
|
|
42131
|
-
const slugs =
|
|
42532
|
+
const slugs = readdirSync33(agentsRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).filter((slug) => isActiveAgentSlug(a.accountDir, slug));
|
|
42132
42533
|
return { accountId: a.accountId, slugs };
|
|
42133
42534
|
})
|
|
42134
42535
|
});
|
|
@@ -42142,7 +42543,7 @@ registerLoop({
|
|
|
42142
42543
|
var USERS_RECONCILE_INTERVAL_MS = 60 * 60 * 1e3;
|
|
42143
42544
|
function countUsersRows() {
|
|
42144
42545
|
if (!existsSync56(USERS_FILE)) return 0;
|
|
42145
|
-
const raw =
|
|
42546
|
+
const raw = readFileSync57(USERS_FILE, "utf-8").trim();
|
|
42146
42547
|
if (!raw) return 0;
|
|
42147
42548
|
const users = JSON.parse(raw);
|
|
42148
42549
|
return users.filter((u) => typeof u.userId === "string").length;
|
|
@@ -42173,6 +42574,7 @@ registerLoop({
|
|
|
42173
42574
|
});
|
|
42174
42575
|
startGraphHealthTimer();
|
|
42175
42576
|
startSharedFolderCensus();
|
|
42577
|
+
startCredRefreshCensus();
|
|
42176
42578
|
void migrateUploads().catch((err) => console.error(`[uploads-migrate] failed err="${err instanceof Error ? err.message : String(err)}"`)).finally(() => {
|
|
42177
42579
|
void startFileWatcher().catch((err) => {
|
|
42178
42580
|
console.error(`[file-watcher] start-failed err="${err instanceof Error ? err.message : String(err)}"`);
|
|
@@ -42295,7 +42697,7 @@ if (bootAccountConfig?.whatsapp) {
|
|
|
42295
42697
|
}
|
|
42296
42698
|
init({
|
|
42297
42699
|
configDir: configDirForWhatsApp,
|
|
42298
|
-
platformRoot: resolve51(process.env.MAXY_PLATFORM_ROOT ??
|
|
42700
|
+
platformRoot: resolve51(process.env.MAXY_PLATFORM_ROOT ?? join65(__dirname, "..")),
|
|
42299
42701
|
accountConfig: bootAccountConfig,
|
|
42300
42702
|
// Task 2074 — enrich with the sub-account name and fan out to every open
|
|
42301
42703
|
// admin SSE stream. Fire-and-forget: the manager's persist path must not
|